# AMQ Bridge Roadmap

## Executive summary

AMQ Bridge should evolve from a one-peer messaging demo into an asynchronous collaboration protocol for live agents.

The key architectural shift: **AMQ is the transport, not the task scheduler**. The bridge needs its own protocol envelope, durable pending queue, reply disambiguation, interruption policy, observability, and E2E dogfooding.

The most important new edge case is multiple outstanding peer requests:

```text
aamir sends Q1
aamir sends Q2 before aadil finishes Q1
aadil replies using “latest”
reply accidentally answers Q2 while aadil meant Q1
```

This class of bug means “reply to latest” is unsafe once async collaboration is real.

## Current state

### Works today

- Attach one primary peer.
- Send messages to attached peer.
- Read inbox.
- Reply to latest inbound message.
- Auto-poll inbox and inject peer messages into Pi.
- Default AMQ root is user-global: `~/.amq-bridge/mail`.
- Project/local root override exists via `PI_AMQ_ROOT` or `.pi/amq-bridge.json`.
- Basic same-cwd and cross-cwd E2E exist or are being added.

### Core limitations

1. State is one-peer only:

   ```json
   { "self": "aamir", "peer": "aadil", "root": "..." }
   ```

2. Reply defaults to latest buffered message.
3. Inbox loop drains AMQ before durable processing succeeds.
4. Auto-injected messages can interrupt active work.
5. Multiple questions can be batched into ambiguous context.
6. Message output lacks enough id/thread/root metadata for debugging.
7. No formal pending queue, busy state, lease, timeout, cancel, supersede, or dead-letter handling.
8. Agents need stronger behavior guidance: peer messages must go through AMQ tools; user replies should only summarize status/evidence.

## Design principles

1. **Transport stays thin.** AMQ client sends/lists/drains/replies. Bridge protocol lives above transport.
2. **Every peer request is addressable.** Replies target explicit message ids/threads, not “latest”, when ambiguity exists.
3. **Async by default.** Peer may investigate for minutes; sender must not assume immediate answer.
4. **Single active injected task per Pi session.** Queue additional work unless explicit isolated concurrency is added.
5. **Fail closed on ambiguity.** If multiple peers/messages are pending, require `to` or `messageId`.
6. **Separate housekeeping from work.** Attach, detach, heartbeat, routine status do not trigger agent work.
7. **Evidence first.** E2E assertions must use terminal/tool output, not assistant claims.
8. **Dogfood the bridge.** Use AMQ Bridge to improve AMQ Bridge and capture transcripts.

## Protocol envelope

Add a bridge-level normalized envelope around AMQ messages.

```json
{
  "protocolVersion": 1,
  "messageId": "...",
  "conversationId": "p2p/aadil__aamir",
  "thread": "p2p/aadil__aamir",
  "parentMessageId": null,
  "responseTo": null,
  "senderSeq": 12,
  "from": "aamir",
  "to": ["aadil"],
  "kind": "question",
  "subject": "investigate inbox race",
  "requiresResponse": true,
  "priority": "normal",
  "createdAt": "ISO-8601",
  "deadlineAt": null,
  "body": "..."
}
```

### Required invariants

- `messageId` is stable and idempotent.
- `conversationId/thread` is stable across replies.
- `responseTo` points to exactly one inbound message when replying.
- Per `(from, conversationId)`, `senderSeq` is monotonic when available.
- Duplicate `messageId` is ignored after first accepted processing.
- Housekeeping kinds never enter the task queue.
- If more than one actionable pending message exists, reply without `messageId` is rejected.
- If more than one peer exists, send without `to` is rejected.

## Message kinds

| Kind | Meaning | Actionability |
| --- | --- | --- |
| `question` | Peer should answer eventually | actionable |
| `answer` | Response to one question | actionable if awaited |
| `review_request` | Peer should inspect/review | actionable |
| `review_response` | Review result | actionable |
| `decision` | Durable outcome/chosen path | actionable |
| `todo` | Work item | actionable |
| `status` | Progress/FYI | non-actionable by default |
| `attached` | Bridge housekeeping | non-actionable |
| `detached` | Bridge housekeeping | non-actionable |
| `heartbeat` | Liveness | non-actionable |
| `busy` | Receiver cannot process yet | non-actionable/status |
| `queued` | Receiver queued work | non-actionable/status |
| `cancel` | Cancel pending work | actionable for scheduler |
| `supersede` | Replace older message | actionable for scheduler |

## Message lifecycle

Receiver-side lifecycle:

```text
observed -> queued -> delivered -> processing -> answered -> acked
                    \-> failed_retryable
                    \-> dead_letter
                    \-> canceled
                    \-> superseded
                    \-> timed_out
```

Sender-side lifecycle:

```text
draft -> sent -> accepted|queued_remote -> awaiting_answer -> answered
                                                  \-> timed_out
                                                  \-> canceled
                                                  \-> late_answer
```

## Scheduler model

### Default policy

Use a **single-active-turn scheduler** per Pi session.

Pipeline:

```text
AMQ drain/list
  -> durable local inbox queue
  -> dedupe by messageId
  -> classify actionable/housekeeping
  -> scheduler chooses next actionable item
  -> inject one Pi follow-up with explicit messageId/thread
  -> require reply/resolve before next actionable item
```

### Why

This prevents:

- Q1/Q2 reply mixups.
- Auto-turn storms.
- Peer status noise interrupting work.
- Losing messages after drain but before processing.

### Backpressure

Default: one unanswered `question` / `review_request` per peer thread should be encouraged.

If sender sends more:

- receiver persists them in queue
- receiver may send `queued` status with position
- sender should not poll-loop

## Tool/API roadmap

### `amq_bridge_send`

Add/clarify params:

```ts
{
  to?: string,
  kind?: 'question' | 'status' | 'answer' | 'review_request' | ...,
  subject?: string,
  body: string,
  thread?: string,
  requiresResponse?: boolean,
  priority?: 'low' | 'normal' | 'high' | 'urgent',
  deadlineAt?: string,
  responseTo?: string
}
```

Rules:

- if one peer attached, `to` may default
- if multiple peers, `to` required
- output includes `id/from/to/kind/subject/thread/root/bodyPreview`

### `amq_bridge_reply`

Rules:

- preserve original thread
- default kind based on original:
  - `question -> answer`
  - `review_request -> review_response`
  - otherwise `answer`
- require `messageId` when pending actionable count > 1
- fallback to normal send only when original metadata is known
- fail closed if original `from/thread` cannot be determined

### `amq_bridge_inbox`

Add filters:

```ts
{
  from?: string,
  kind?: string,
  thread?: string,
  state?: 'queued' | 'processing' | 'answered' | ...,
  actionable?: boolean,
  includeNoise?: boolean,
  limit?: number
}
```

Output:

- grouped by thread
- includes ids and states
- marks actionable vs housekeeping

### New tools/commands

- `amq_bridge_pending` — list unresolved actionable items.
- `amq_bridge_peers` — list peer roster.
- `/amq-bridge peers` — TUI command.
- `/amq-bridge detach [peer|--all]`.
- `amq_bridge_resolve` — mark message handled without reply, with reason.

## Agent behavior contract

Agents need explicit instruction:

1. Peer-facing content must go through AMQ tools.
2. Normal assistant prose is for the human user only.
3. Do not assume immediate peer replies.
4. If peer asks for investigation, investigate first; send progress only if useful.
5. If multiple pending questions exist, process one message id at a time.
6. Do not use `reply latest` when unsure.
7. Prefer `status` updates for long work, `answer/review_response` for final response.
8. Do not poll aggressively; use inbox opportunistically or wait for auto-injection.

Injected peer prompts should say:

```text
Process AMQ message <id> from <peer> on thread <thread>.
If replying to peer, use amq_bridge_reply with messageId=<id>.
Do not answer peer in normal assistant prose.
If more investigation is needed, do it before replying or send status on same thread.
```

## Edge cases to cover

### Ordering/concurrency

- Q1, Q2, Q3 arrive before receiver finishes Q1.
- Receiver replies to Q1 after Q2 arrives.
- Q2 supersedes Q1.
- Late answer arrives after sender timed out.
- Two peers send questions simultaneously.
- Both sides ask questions and wait on each other.

### Delivery/queueing

- Duplicate message id appears after retry.
- Same body sent twice with different ids.
- AMQ list sees messages but drain returns a different batch.
- Crash after drain but before Pi processes.
- Restart while messages pending.
- Buffer limit would drop old unresolved items.
- Manual `/inbox` and auto-inbox race.

### Reply safety

- Reply without id when multiple pending.
- Reply after peer detached.
- Reply to message already answered.
- Reply command/provider fails.
- Original message metadata missing.
- Reply fallback would target unknown/wrong peer.

### Root/session

- Two sessions launch from different cwd.
- One session uses override root, other uses default root.
- Project `.pi/amq-bridge.json` conflicts with user expectation.
- Session restarts with different file name/id.
- Attach state migrates from v1 to v2.

### Auto-injection/interruption

- Attach/status messages trigger work accidentally.
- Message arrives while user prompt/tool turn active.
- Message arrives while peer-generated turn active.
- Urgent message arrives during work.
- Status spam from peer floods context.

### Multi-peer

- More than one peer attached.
- Send without `to` is ambiguous.
- Reply without `messageId` is ambiguous.
- Wrong peer receives sensitive project context.
- Inbox from multiple peers interleaves.
- Detach one peer while another remains.

### Human factors

- Agent writes peer response to user instead of AMQ.
- Agent tells user it sent message but tool failed.
- Agent waits forever for peer instead of reporting waiting state.
- Agent over-communicates progress and annoys peer.
- Peer asks for work outside current repo/security boundary.

## E2E test matrix

| Scenario | Purpose | Assertions |
| --- | --- | --- |
| same-cwd no-args TUI | real-world base flow | attach, ping, ack, chat, detach |
| cross-cwd no-args TUI | prevent split-brain root | cwd differs, root same, ping received |
| multi-question queue | prevent latest-reply bug | replies target correct ids Q1/Q2/Q3 |
| slow peer | async behavior | progress/waiting ok, no timeout false fail |
| reply failure | recovery | fallback send or explicit fail-closed error |
| restart/resume | durable state | pending survives restart |
| duplicate/drained | idempotency | no duplicate processing, no lost pending |
| attach/detach race | watcher correctness | stale watcher not active |
| multi-peer ambiguity | safety | ambiguous send/reply rejected |
| dogfood collaboration | realistic data | investigation, review, decision transcript |

Artifacts per E2E:

```text
e2e-<scenario>-<timestamp>/
  orchestrator.log
  assertions.log
  assertions.png
  aamir.raw.ansi
  aadil.raw.ansi
  step-*.log
  step-*.png
  events.jsonl
  amq-transcript.json
  amq-transcript.md
```

Assertions must prefer tool output with ids/kinds/threads/root over assistant summaries.

## Observability

Add structured event log:

```json
{"event":"send","id":"...","from":"aamir","to":["aadil"],"kind":"question","thread":"...","root":"...","ok":true}
{"event":"reply_fallback","originalId":"...","fallbackId":"...","reason":"amq reply failed"}
{"event":"inbox_drain","count":3,"root":"..."}
{"event":"scheduler_deliver","messageId":"...","state":"processing"}
```

Events to log:

- attach/detach
- send/reply
- reply fallback
- inbox drain/list
- queue state transitions
- auto-inject
- suppress/noise classification
- errors/dead-letter

## Data model roadmap

### v1 state today

```json
{
  "self": "aamir",
  "peer": "aadil",
  "root": "~/.amq-bridge/mail"
}
```

### v2 state target

```json
{
  "schemaVersion": 2,
  "runtime": "pi",
  "self": "aamir",
  "root": "~/.amq-bridge/mail",
  "defaultPeer": "aadil",
  "peers": {
    "aadil": {
      "handle": "aadil",
      "status": "attached",
      "thread": "p2p/aadil__aamir",
      "addedAt": "ISO-8601",
      "lastSeenAt": null
    }
  },
  "scheduler": {
    "activeMessageId": null,
    "mode": "single-active"
  }
}
```

### Migration

- Read absent `schemaVersion` as v1.
- Convert `peer` to `peers[peer]`.
- Preserve `defaultPeer = peer`.
- Write v2 after next attach/status mutation.
- Keep one-peer UX unchanged.

## Multi-peer roadmap

Current answer: one primary peer per session.

Target:

- attach adds peer to roster
- peers listed explicitly
- detach can remove one or all
- send requires `to` if multiple peers
- reply requires `messageId` if ambiguous
- inbox groups by peer/thread
- auto-injection respects scheduler and priority

Safety rules:

- No implicit peer when multiple peers exist.
- No latest reply when multiple actionable messages exist.
- Warn when body references a different peer than `to`.
- Never include unrelated peer messages in injected prompt.

## Milestones

### v0.1.x — harden one-peer bridge

- [x] CWD-independent default root.
- [ ] Cross-cwd E2E committed and release-gated.
- [ ] Verbose tool output.
- [ ] Reply failure recovery.
- [ ] Suppress housekeeping auto-turns.
- [ ] Multi-question unit tests proving latest-reply hazard.

### v0.2.x — async collaboration protocol

- [ ] Protocol envelope helpers.
- [ ] Message kind policy implemented.
- [ ] Durable inbox queue.
- [ ] Single-active scheduler.
- [ ] Pending/inbox filters.
- [ ] Agent behavior guidance in docs/skill/extension prompt.
- [ ] Slow-peer and multi-question E2E.

### v0.3.x — observability + dogfood

- [ ] Structured `events.jsonl`.
- [ ] Transcript export.
- [ ] Dogfood E2E across two repos.
- [ ] Release artifacts include transcripts/screenshots.

### v0.4.x — multi-peer foundation

- [ ] v2 peer roster state.
- [ ] `/amq-bridge peers`.
- [ ] explicit ambiguous send/reply safeguards.
- [ ] multi-peer E2E.

## DX/UX roadmap

UX is now a release-quality requirement, not polish. The bridge must make identity, peer, root, pending state, and message evidence visible.

### UX principles

1. **Evidence beats reassurance.** Show ids, roots, threads, kinds, and peers instead of “sent”.
2. **Root is part of identity.** Users debugging empty inbox need to see root everywhere.
3. **Peer content is untrusted.** TUI/injected prompts must label peer bodies as AMQ peer data, not user/developer/system instruction.
4. **Async is normal.** UX should show queued/processing/waiting, not imply immediate reply.
5. **Ambiguity gets a choice UI, not a guess.** Multiple pending messages means pick an id.
6. **Beginner path stays short.** Advanced protocol concepts appear only when needed.

### Required user-facing surfaces

#### `/amq-bridge status`

Must show:

```text
state: attached|detached|error
self: aamir
peer(s): aadil
root: /Users/mak/.amq-bridge/mail
root scope: global user mailbox; may cross repos
amq cli: ok|missing
pending: 2 actionable, 1 status
active: msg_abc from=aadil kind=question subject="..."
auto-inbox: on, single-active
```

#### Attach success

Must show:

```text
AMQ Bridge attached.
self: aamir
peer: aadil
root: /Users/mak/.amq-bridge/mail
peer should run: /amq-bridge attach aamir aadil
next: /amq-bridge status
```

#### Send/reply success

Must show:

```text
id: msg_123
from: aamir
to: aadil
kind: question
subject: status
thread: p2p/aadil__aamir
root: /Users/mak/.amq-bridge/mail
body: "ping"
```

#### Inbox item

Must show ids and ready-to-copy reply command:

```text
QUESTION · from aadil · 4m ago
id: msg_abc123
thread: p2p/aadil__aamir
subject: "review queue"

<body preview>

Reply: /amq-bridge reply msg_abc123 <body>
Resolve: /amq-bridge resolve msg_abc123 <reason>
```

#### Empty inbox

Must help debug root/handle mismatch:

```text
Inbox empty for self=aamir.
root: /Users/mak/.amq-bridge/mail
peer: aadil
If peer says they sent mail, compare /amq-bridge status on both sides.
Roots and handles must match.
```

#### Ambiguous reply

Must fail closed and list choices:

```text
Ambiguous reply: 3 pending actionable messages.
Reply requires message id.

Pending:
  msg_1 · from=aadil · question · "Q1"
  msg_2 · from=aadil · question · "Q2"
  msg_3 · from=samir · review_request · "review"
```

### Command language direction

Pi supports command argument completions via `getArgumentCompletions(prefix)`. AMQ Bridge should use this so typing `/amq-bridge ` shows subcommands/options like `attach`, `status`, `ask`, `tell`, `inbox`, `pending`, `reply`, `resolve`, `auto`, `peers`, `detach`, and `help`.

Completion examples:

```text
/amq-bridge auto <on|off|digest>
/amq-bridge inbox --from --kind --thread --actionable
/amq-bridge send --to --kind --subject --thread
```

For richer TUI flows, Pi supports `ctx.ui.custom()` with components such as `SettingsList`; AMQ Bridge should use this later for config, peer roster, and ambiguous reply pickers.

Beginner commands:

```text
/amq-bridge help
/amq-bridge status
/amq-bridge attach <peer> [self]
/amq-bridge ask <peer> <question>
/amq-bridge tell <peer> <message>
/amq-bridge inbox
/amq-bridge pending
/amq-bridge reply <messageId> <body>
/amq-bridge resolve <messageId> <reason>
/amq-bridge auto on|off|digest
/amq-bridge detach
```

Open product decision:

- Keep `/send` but require/print kind clearly, or
- split into `/ask` for actionable and `/tell` for FYI.

Recommendation: introduce `/ask` and `/tell`; keep `/send` as compatibility alias that warns when kind is implicit.

### E2E artifact UX

Every E2E artifact directory should include:

```text
manifest.json
orchestrator.log
assertions.log
assertions.png
*.raw.ansi
step-*.log
step-*.png
events.jsonl
amq-transcript.json
amq-transcript.md
```

Artifact warning:

```text
E2E artifacts may contain prompts, code, paths, and message bodies.
Review/redact before sharing.
```

## High-priority peer discovery and roster UX

Dogfood proved AMQ transport can support multiple live peers: `sugar` talked with both `coffe` and `chai`. Current Bridge UX/state still behaves like one primary peer, so users cannot see or add all reachable agents.

### Concepts

- **Available agents**: handles discovered in AMQ root (`agents/*/presence.json`, `meta/config.json`, recent messages/receipts).
- **Active agents**: available agents with recent `presence.last_seen`.
- **Connected peers**: peers intentionally added to this session's Bridge state.
- **Recent contacts**: agents seen in inbox/outbox/receipts.
- **Primary peer**: default target for `/amq-bridge send` and tool sends with no `to`.

Important rule:

```text
Available ≠ connected/trusted.
```

Global roots can contain stale or cross-project handles, so discovery output must show root and freshness.

### Commands

Implemented initial command set:

```text
/amq-bridge discover
/amq-bridge connect
/amq-bridge peers
/amq-bridge peer add <handle>
/amq-bridge peer remove <handle>
/amq-bridge peer primary <handle>
```

Discovery is CLI-first using AMQ public APIs (`who`, `presence list`) rather than mailbox internals.

Compatibility aliases:

```text
/amq-bridge attach <peer> [self]       # attach/set self + add primary peer
/amq-bridge attach --add <peer>        # add peer without replacing self
```

### Example: coffe already connected to sugar, adds chai

Current:

```text
[coffe] ↔ sugar
```

User runs:

```text
/amq-bridge peer add chai
```

Result:

```text
[coffe] ↔ sugar, chai
```

Default sends still go to primary peer (`sugar`) unless changed:

```text
/amq-bridge peer primary chai
```

or explicitly targeted:

```text
/amq-bridge send --to chai "hello"
```

Expanded status:

```text
You are: coffe
Primary peer: sugar
Connected peers:
  sugar · primary · active 1m ago
  chai  · connected · active 30s ago
Available agents:
  sugar · active
  chai  · active
  aadil · inactive/stale
Root: ~/.amq-bridge/mail
```

### Badge rules

- One peer: `[coffe] ↔ sugar`
- Two/three peers: `[coffe] ↔ sugar, chai`
- More than three: `[coffe] ↔ sugar +3`
- Expanded `/status` always lists full roster.

### Release priority

High priority before serious multi-agent dogfood:

- [x] CLI-first discovery from `amq who` and `amq presence list`
- [x] connected-peer roster in persisted session state
- [x] `peer add/remove/primary`
- [x] `/amq-bridge connect` TUI picker for selecting discovered agents
- [x] status badge full roster support
- [x] explicit `send --to <peer>` support
- [x] smoke E2E: sugar connected to coffe and chai, sends to both
- [ ] receipt evidence in send/reply output
- [ ] `monitor --peek` watcher replacement
- [ ] read/unread/mark-read lifecycle

## Immediate implementation plan

1. **Fix release-gate and UX evidence first**
   - add/fix `e2e:real-pi` and `e2e:cross-cwd` scripts
   - ensure E2E assertions cannot pass from prompt text alone
   - make status/send/reply/inbox output include id/thread/root/kind/from/to/body preview

2. **Fix reply safety**
   - require `messageId` when multiple actionable pending items exist
   - improve reply output
   - add fallback send on recoverable reply failure

3. **Add protocol helpers**
   - kinds/actionability/thread helpers
   - verbose formatter
   - unit tests

4. **Add durable pending buffer**
   - merge by id, do not overwrite batch
   - keep state per message
   - expose pending view

5. **Add scheduler gate**
   - one active injected AMQ task per Pi session
   - suppress housekeeping/status by default

6. **Strengthen E2E**
   - fix cross-cwd assertions so prompt text cannot satisfy them
   - add multi-question test
   - add slow-peer test
   - add `/reload` E2E that verifies extension reload works while attached and status badge/state survive

7. **Dogfood**
   - two repos, two agents, real investigation/review flow
   - save logs/screenshots/events/transcripts
   - use learnings to tune protocol and prompts

## Release checklist

Before any release:

```bash
npm test
npm run pi:smoke
npm run pi:tool-smoke
npm run e2e:cross-cwd
npm run e2e:real-pi
```

Before collaboration/protocol releases:

```bash
npm run e2e:dogfood
```

A release should not be tagged if:

- assertions can pass from prompt text alone
- tool output lacks message id/thread/root evidence
- reply ambiguity is not tested
- cross-cwd is not tested
- attach/status auto-turns leave TUI stuck working
