Skip to content

Repository files navigation

Envoy

Your negotiating agent. Companies deployed AI agents to talk to you — support bots, billing bots, retention bots. Customers walk into those negotiations unarmed. Envoy is your seat at the table.

Envoy is an autonomous consumer-advocacy agent built with the Strands Agents SDK. Hand it a billing dispute, a refund claim, a cancellation fight, or a price hike. Envoy builds the case (a Strands multi-agent Graph: intake → research → strategy), then negotiates live against the company's own agent while you do nothing.

You see exactly one thing: a Decision Card when there is a real decision to make. Everything else is quiet.

Architecture


The problem

Disputing a charge is one of the most repetitive, judgment-heavy chores in life: hold music, re-explaining, being offered vouchers, repeating yourself, giving up. Companies automated their side of this conversation. Envoy automates yours — and keeps a human only where the human matters: the decision to accept money.

How it works

you ──▶ dispute (paste text / forward email)
          │
          ▼
   ┌────────────────────────────────┐
   │  STRATEGY GRAPH (Strands Graph)│  intake ─▶ research ─▶ strategy
   │  claim value · reserve · BATNA │  (Tavily web research when available)
   └───────────────┬────────────────┘
                   ▼
   ┌────────────────────────────────┐      A2A over the wire      ┌──────────────────────────────┐
   │  ENVOY — advocate agent        │◀───────────────────────────▶│  COMPANY AGENT               │
   │  tool-using, session-persisted │        Move protocol        │  runs on Bedrock AgentCore   │
   │  knows your reserve & BATNA    │                             │  frontline→supervisor→       │
   └───────────────┬────────────────┘                             │  director policy ladder      │
                   │                                              └──────────────────────────────┘
                   ▼
         settlement or impasse?
                   │
                   ▼
        ┌────────────────────┐
        │   DECISION CARD    │  ◀── the only time you are interrupted
        │  [Approve] [Decline]│     (a real Strands interrupt)
        └─────────┬──────────┘
                  ▼
     settlement executed → savings ledger

What's inside (Strands capabilities used)

Strands capability Where
Multi-agent Graph (GraphBuilder, deterministic DAG) strategy phase: intake → research → strategy
Interrupts + hooks (BeforeToolCallEvent, event.interrupt) every human decision: the agent halts its event loop, the engine surfaces a Decision Card, and the agent resumes with an interruptResponse block
Session persistence (FileSessionManager) interrupted negotiations survive a process restart and resume where they stopped
Structured output (Pydantic) every negotiation turn is a typed Move; settlements are typed contracts
Tools (@tool(context=True)) send_move, settle, ask_owner; the last two are interrupt-gated
LiteLLM model provider + fallback chain a custom FallbackModel falls through on rate limits, so a live run never stalls
A2A protocol the counterparty is a real remote agent reachable over A2A
Bedrock AgentCore Runtime the counterparty is deployed and serving live (see below)
Bedrock model provider the deployed agent runs on Bedrock models, no external API keys

The negotiation protocol

Each turn is a structured Move:

  • intent: open | counter | firm | escalate | final | accept | consult_owner
  • offer: typed (full_refund, partial_refund, credit, discount_months, fee_waiver, voucher, price_lock) with amount, currency, months, and binding conditions
  • message: 1–3 sentences, in character

Human gating flows through exactly one path — the Strands interrupt system:

  1. Settlement approval — money moves only if you tap Approve.
  2. Consult — the agent genuinely needs your judgment.
  3. Impasse review — Envoy hit its ceiling: take their best offer, or decline and get the escalation pack, with the whole negotiation kept as evidence.

Live deployment

The counterparty agent (the company's policy-bound billing agent) runs on Amazon Bedrock AgentCore Runtime and is reachable over A2A:

Runtime ARN   arn:aws:bedrock-agentcore:us-east-1:084164017195:runtime/envoyprovider_envoyprovider-1sz1uA9ww6
Region        us-east-1
Protocol      A2A (JSON-RPC over AgentCore Runtime)

Set the ARN and the local engine negotiates against the real remote agent instead of the in-process simulator:

ENVOY_PROVIDER_RUNTIME_ARN=arn:aws:bedrock-agentcore:us-east-1:084164017195:runtime/envoyprovider_envoyprovider-1sz1uA9ww6
AWS_PROFILE=envoy
AWS_REGION=us-east-1

If the remote call fails for any reason, the engine falls back to the local simulator so a demo never stalls.

Deploying it yourself

npm install -g @aws/agentcore
cd envoyprovider
agentcore deploy -y          # builds CDK, provisions the runtime, reports the ARN
agentcore status             # READY + invocation URL
agentcore invoke --prompt-file invoke_payload.json

Run it locally

# backend (Python 3.10+)
cd backend
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt      # Windows (bin/pip on macOS/Linux)
cp .env.example .env                               # set ENVOY_MODEL + one provider key

.venv/Scripts/python -m uvicorn app.main:app --port 8000
# frontend (Node 20+)
cd frontend
npm install
npm run dev            # http://localhost:5173

Model configuration (any one)

ENVOY_MODEL=groq/openai/gpt-oss-120b               + GROQ_API_KEY=***
ENVOY_MODEL=openrouter/anthropic/claude-sonnet-4.5  + OPENROUTER_API_KEY=***
ENVOY_MODEL=gemini/gemini-3.6-flash                 + GEMINI_API_KEY=***
ENVOY_MODEL=bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0  + AWS credentials

The advocate and the provider start on different models (separate rate-limit buckets), each with an ordered fallback chain (ENVOY_MODEL, ENVOY_PROVIDER_MODEL).

Demo disputes included

Four scenarios, each with a realistic confidential provider policy the company agent must obey: a gym charge after cancellation, a subscription cancellation fee, a UK261 flight-delay claim, and a mid-contract price hike. Paste any dispute as free text and Envoy infers the class, falling back to a generic consumer-policy agent.

What a run looks like

  1. Paste: "FitLife Gym charged my card ₹2,499 on Sep 1 even though I cancelled in January…"
  2. The strategy graph classifies it, researches leverage, and sets reserve = full refund, BATNA = chargeback.
  3. Envoy opens hard. The company's frontline offers a ₹500 goodwill credit.
  4. Envoy holds firm and escalates. The company's supervisor tier authorizes the refund.
  5. You get one Decision Card: "Settlement reached: full refund of ₹2,499 — approve?"
  6. You tap Approve. The savings ledger ticks up. You never spoke to anyone.

Repo layout

backend/app/
  main.py                 FastAPI: disputes in, negotiations out, decisions answered
  store.py                SQLite: negotiations, transcript turns, decisions, settlements, ledger
  scenarios.py            dispute scenarios + confidential provider policies + inference
  agent/
    protocol.py           the shared Move/Offer/Settlement contract (A2A-compatible)
    advocate.py           Envoy: strategy Graph + tool-using, interrupt-gated negotiator
    gates.py              DecisionGateHook: BeforeToolCallEvent -> Strands interrupt -> Decision Card
    tools.py              send_move · settle · ask_owner (last two gated)
    provider.py           the company's agent: policy-bound, tiered authority
    remote_provider.py    calls the AgentCore-deployed counterparty over A2A
    llm.py                FallbackModel: ordered provider chain, survives rate limits
    engine.py             negotiation loop: rounds, interrupts, settlements, impasse review
  agentcore_provider/     the deployed counterparty (A2A entrypoint + policy core)
frontend/src/App.tsx      Negotiation Room: live transcript, offer chips, Decision Cards, ledger
envoyprovider/            AgentCore CLI project (CDK infrastructure)

Honest scope notes

  • Settlements write a verifiable artifact (reference ID, terms) into the ledger; they do not move real money. All negotiation behavior — strategy, tactics, offers, acceptances, impasses — is real agent behavior, not scripted.
  • The company-side policy documents are fictional but realistic. The counterparty's authority ladder (frontline cannot refund, supervisor can, director is hidden) mirrors how these desks actually work.
  • The four seeded disputes ship with both a matching policy document and a remote A2A counterparty; free-text disputes fall back to a generic consumer-policy agent.

Roadmap

  • Email-forward ingestion (fwd@envoy.app) and payment-provider webhooks
  • Real provider integrations: point the engine at a company's public agent over A2A
  • Multi-dispute campaigns: negotiate with five providers in parallel, approve one bundle
  • Background watcher for renewal dates and price hikes, surfacing only real decisions

License

MIT — see LICENSE.

About

Envoy your negotiating agent. The first customer-side AI agent for agent-to-agent dispute resolution.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages