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.
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.
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
| 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 |
Each turn is a structured Move:
intent:open | counter | firm | escalate | final | accept | consult_owneroffer: typed (full_refund,partial_refund,credit,discount_months,fee_waiver,voucher,price_lock) with amount, currency, months, and binding conditionsmessage: 1–3 sentences, in character
Human gating flows through exactly one path — the Strands interrupt system:
- Settlement approval — money moves only if you tap Approve.
- Consult — the agent genuinely needs your judgment.
- Impasse review — Envoy hit its ceiling: take their best offer, or decline and get the escalation pack, with the whole negotiation kept as evidence.
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-1If the remote call fails for any reason, the engine falls back to the local simulator so a demo never stalls.
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# 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:5173ENVOY_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 credentialsThe advocate and the provider start on different models (separate rate-limit buckets), each with an ordered fallback chain (ENVOY_MODEL, ENVOY_PROVIDER_MODEL).
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.
- Paste: "FitLife Gym charged my card ₹2,499 on Sep 1 even though I cancelled in January…"
- The strategy graph classifies it, researches leverage, and sets reserve = full refund, BATNA = chargeback.
- Envoy opens hard. The company's frontline offers a ₹500 goodwill credit.
- Envoy holds firm and escalates. The company's supervisor tier authorizes the refund.
- You get one Decision Card: "Settlement reached: full refund of ₹2,499 — approve?"
- You tap Approve. The savings ledger ticks up. You never spoke to anyone.
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)
- 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.
- 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
MIT — see LICENSE.