← 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`.