Problem
v2 spec is silent on how agents within a flow talk to each other while running. v1 had this natively via the broker. Sequential handoff (output-to-input) and `f.dispatch` (child flow) cover most cases, but true async mid-step chatter is unsupported — a fan-out of parallel agent steps that need to consult each other cannot express that in the current spec.
Options considered + rejected
- New in-process channel primitive. Duplicates the messaging layer we've built in `../relay` (DMs, channels, inbox, threads, reactions, dead-letter). Not going to reinvent a year of pty/message plumbing.
- Free-for-all broker access. Agents get workspace credentials and post/subscribe freely. Violates journal-as-truth (RFC-0001 covenant 1): broker delivery is at-least-once with dedup-by-message-id, and any broker DM that influenced an agent's output is invisible to `step.completed`. Replay lies.
Chosen design — broker-as-effect-transport with journaled boundaries
- Each flow-run gets an ephemeral broker workspace, short-lived, workspace-key seeded from `run_id` (deterministic).
- Agents get workspace credentials as part of their step setup.
- Every message send/receive is journaled as an effect step via the existing effect record/confirm claim path (`docs/RFC-0001` Appendix A).
- Idempotency key = message id, so replay's effect-claim mechanism dedupes automatically. No message is delivered twice on resume.
- Broker is transport, journal is truth.
New covenant clause (proposed for SURFACE.md §6 / §7)
Broker interactions with a v2 flow are legal only as journaled effect steps. Any bypass of that journaling makes the run non-replayable and must set `step.completed.human_intervention: true`.
Author-facing surface
New primitive on `Ctx`:
```ts
const channel = f.channel('review-panel');
await channel.post('security-lens', 'this looks off at line 42'); // journaled effect
const msg = await channel.recv('maintainability-lens', { timeout: '30s' }); // journaled effect
```
Both `post` and `recv` compile to the kernel `effect` primitive (closed-vocabulary preserved).
Ephemeral workspace lifecycle
- Created on first `f.channel` use in a run; workspace-key seeded deterministically from `run_id`.
- Cleaned up when run reaches terminal state.
- If run parks, workspace persists until resume or lease expiry.
- Broker crash: workspace state can be reconstructed from journal (effect records are the truth).
Acceptance
- Two agents in one flow post/recv on a shared channel; journal contains exactly one effect step per message boundary (send + receive).
- SIGKILL mid-flow after post-but-before-recv: resume replays effect record, receiver sees the same message once (idempotent by message id).
- `flows check` refuses `f.channel` calls with unresolved participants (agents referenced but not spawned).
- Concurrent posts serialize deterministically per channel (journal ordering).
- Workspace credentials never leak into step outputs (redacted at effect boundary).
Files to touch
- `packages/surface/src/channel.ts` (new — `ChannelHandle`, `ChannelParticipant` types)
- `packages/surface/src/context.ts` (extend `Ctx` with `channel(name)`)
- `packages/sdk/src/authored-flow-executor.ts` (route `f.channel` calls to effect steps)
- `packages/sdk/src/effect-channel.ts` (new — broker-adapter with per-run workspace lifecycle + idempotency key management)
- `packages/sdk/src/preflight.ts` (channel participants must resolve to declared agents)
- `kernel/relayflowd-core/**` (`effect` step accepts `channel` kind; small extension)
- `docs/SURFACE.md` (subsection under §2 rule 3 for `f.channel` + new §7 covenant clause "Broker as effect transport")
- Integration tests (packages/sdk/tests/) using a local agent-relay broker fixture
Not-in-scope this slice
- Persistent workspaces across runs
- Cross-flow channels (a channel spans one run only)
- Admin/audit UI for channel history
Depends on
Follow-ups
- Cross-flow persistent channels (future spec slice)
- Per-channel policy (rate limits, participant allow-lists)
- Auditor UI reading channel history from journal
Problem
v2 spec is silent on how agents within a flow talk to each other while running. v1 had this natively via the broker. Sequential handoff (output-to-input) and `f.dispatch` (child flow) cover most cases, but true async mid-step chatter is unsupported — a fan-out of parallel agent steps that need to consult each other cannot express that in the current spec.
Options considered + rejected
Chosen design — broker-as-effect-transport with journaled boundaries
New covenant clause (proposed for SURFACE.md §6 / §7)
Author-facing surface
New primitive on `Ctx`:
```ts
const channel = f.channel('review-panel');
await channel.post('security-lens', 'this looks off at line 42'); // journaled effect
const msg = await channel.recv('maintainability-lens', { timeout: '30s' }); // journaled effect
```
Both `post` and `recv` compile to the kernel `effect` primitive (closed-vocabulary preserved).
Ephemeral workspace lifecycle
Acceptance
Files to touch
Not-in-scope this slice
Depends on
Follow-ups