Problem
Permission and question relays — RuntimeEvents that need user action and produce a RuntimeDecision once answered — currently have their request-lifetime state machine (pending → dispatched → resolved/timeout/cancelled) implemented inside src/app/channels/sessionBridge.ts (~578 LOC). Two callers wire their controller callbacks independently:
src/app/providers/useFeed.ts (interactive shell)
src/app/exec/runner.ts (batch exec mode)
Each maintains its own TTL/cancel/reconnect logic. Result: ~1.9k LOC across four modules with relay implicit in the design but absent as a named seam.
Why this matters (leverage)
- "Relay" is not in
CONTEXT.md, despite ~1.9k LOC of code dedicated to it. The missing word is the strongest signal that the seam is missing.
- Future features — relay rate limits, cross-session dedup, guaranteed-once delivery, retry-on-permission-deny — are scattered concerns today; would become localized after extraction.
- Both callers (interactive + exec) would consume the same coordinator, killing duplicated relay callback wiring.
Proposal sketch
Create `src/core/relay/relayCoordinator.ts` that:
- Owns the request-lifetime state machine for relay-shaped `RuntimeEvent`s.
- Sits between `controller.handleEvent()` and the transport (`SessionBridge`, harness `runtime.sendDecision`).
- Emits `RuntimeDecision`s back into the controller's intent pipeline when relays resolve / timeout / cancel.
- `SessionBridge` reduces to a thin transport wrapper.
- `useFeed` and `runner` both wire through the coordinator.
Add Relay to `CONTEXT.md` as a first-class term once the seam crystallizes.
Files involved
- `src/app/channels/sessionBridge.ts` (~578 LOC → ~300)
- `src/app/providers/useFeed.ts` (~593 LOC → ~500)
- `src/app/exec/runner.ts` (~600 LOC → ~450)
- `src/core/controller/runtimeController.ts` (~150 LOC, light changes)
- New: `src/core/relay/relayCoordinator.ts` (~250 LOC)
Open questions
- Does the coordinator live in `core/controller/` (relays are decision-shaped) or as a sibling `core/relay/`?
- One coordinator instance per session, or process-global with per-session keying?
- Does it need SQLite-backed state (relay survives restart) or is in-memory enough?
Blast radius
Medium. Gateway, harnesses, and `infra/sessions` are unaffected.
Provenance
Surfaced by `/improve-codebase-architecture` + `/zoom-out` analysis after landing the IndexedTimeline deepening. Ranked #1 of three candidates.
Problem
Permission and question relays —
RuntimeEvents that need user action and produce aRuntimeDecisiononce answered — currently have their request-lifetime state machine (pending → dispatched → resolved/timeout/cancelled) implemented insidesrc/app/channels/sessionBridge.ts(~578 LOC). Two callers wire their controller callbacks independently:src/app/providers/useFeed.ts(interactive shell)src/app/exec/runner.ts(batch exec mode)Each maintains its own TTL/cancel/reconnect logic. Result: ~1.9k LOC across four modules with relay implicit in the design but absent as a named seam.
Why this matters (leverage)
CONTEXT.md, despite ~1.9k LOC of code dedicated to it. The missing word is the strongest signal that the seam is missing.Proposal sketch
Create `src/core/relay/relayCoordinator.ts` that:
Add Relay to `CONTEXT.md` as a first-class term once the seam crystallizes.
Files involved
Open questions
Blast radius
Medium. Gateway, harnesses, and `infra/sessions` are unaffected.
Provenance
Surfaced by `/improve-codebase-architecture` + `/zoom-out` analysis after landing the IndexedTimeline deepening. Ranked #1 of three candidates.