← back to AbramsEgo
memos/a2a-activation-runbook.md
86 lines
# A2A activation runbook — how to turn the external-consult client ON (TK-10381)
The A2A client is BUILT and ships **OFF**: the allowlist `data/a2a-agents.json` is `[]`, so
every `/api/a2a/consult` is refused and **zero outbound connection is ever attempted**.
Turning it on is a single Steve-gated edit. This is the turnkey procedure.
> Gate reminder: activation is Steve's call. Only add a peer you TRUST — every response it
> returns is treated as hostile input (rendered escaped + UNTRUSTED, never fed to an agent),
> but the whole point of an allowlist is that you vouch for the host you're talking to.
## 1. Add ONE peer to the allowlist
Edit `~/Projects/AbramsEgo/data/a2a-agents.json` (starts `[]`). Each entry:
```json
[
{
"name": "acme-advisor",
"url": "https://agent.acme.example.com",
"purpose": "monetization/affiliate best-practice consults",
"auth_ref": "env:A2A_ACME_TOKEN"
}
]
```
Field rules (enforced by the client):
- **name** — the lookup key you pass as `{ "agent": "<name>" }`. Keep it short/unique.
- **url** — the peer's BASE url. **MUST be `https://` on port 443** (no http, no custom port,
no embedded `user:pass@`). The client fetches `<url>/.well-known/agent-card.json` and pins
every RPC call to THIS host (a hostile card can't redirect it elsewhere).
- **purpose** — free text; shows in the dashboard picker + `/api/a2a/agents`.
- **auth_ref** — OPTIONAL. Prefer **`env:VARNAME`** — the token is read from the process env
(populated by secrets-manager), NOT stored in this JSON file. A bare literal is accepted
but discouraged (don't put a live key in a repo file). Omit entirely for no-auth peers.
If you used `env:A2A_ACME_TOKEN`, add that secret via the `secrets` skill so it lands in the
AbramsEgo `.env` / process env — never paste the raw token into `a2a-agents.json`.
## 2. Load it
```sh
pm2 restart abramsego --update-env # picks up the new allowlist + any new env token
```
## 3. Verify
```sh
cd ~/Projects/AbramsEgo && bash scripts/smoke.sh
```
Note: the smoke test guards the CLOSED gate by default (asserts an empty allowlist to catch an
*unintended* opening). Once you've intentionally added a peer, run it with **`A2A_EXPECT_PEERS=1`**
so that check becomes an informational NOTE instead of a failure:
```sh
A2A_EXPECT_PEERS=1 bash scripts/smoke.sh
```
The two gate-MECHANISM assertions (unlisted-agent refused, payload-linter armed) always run and
must still pass.
Then confirm the peer is live:
```sh
curl -s -u admin:DW2024! http://127.0.0.1:9773/api/a2a/agents # lists your peer
curl -s -u admin:DW2024! -X POST -H 'Content-Type: application/json' \
-d '{"agent":"acme-advisor","q":"what is a good affiliate cookie window?"}' \
http://127.0.0.1:9773/api/a2a/consult # returns answer_untrusted
```
The dashboard "🛰️ A2A Consult" panel (at :9773) will now show the peer in its dropdown and
render answers tagged **UNTRUSTED · EXTERNAL**.
## 4. Rollback (turn it back OFF)
Set `data/a2a-agents.json` back to `[]` and `pm2 restart abramsego`. Every consult refuses
again; no code change needed.
## What stays gated / out of scope
- **A2A SERVER mode** (publishing our own Agent Card, opening an inbound port) is a SEPARATE,
default-BLOCK decision — it would make :9773 a public attack surface. Not built.
- Acting on a consult's advice: any recommended ACTION drafts to
`~/.claude/yolo-queue/pending-approval/` for officer/Steve review — A2A advice never
auto-executes and never enters `/api/chat` or an agent prompt.
Security rails reference: `memos/a2a-egress-rails.md`. Client: `lib/a2a-client.js`.