🦞🎬 Architecture & Protocol Specification
flowchart TD subgraph CLIENTS["Surfaces"] SPA["Web SPA (SvelteKit)"] EXT["Chrome Extension (WXT)"] PWA["Mobile PWA"] end subgraph GATEWAY["StoryFlow Gateway (Bun 1.4 :7540)"] WSS["Bun.serve WebSocket Server"] REST["REST Endpoints (/snapshot, /api)"] DB[(bun:sqlite SSOT)] JQ["Job Queue & Scheduler"] REG["Registry (capabilities.yml)"] end subgraph OMP["omp Runtime (omp --mode rpc)"] RPC["RPC v2 stdio Bridge"] PROD["Production Session"] SUB["Subagents Swarm"] end SPA <-->|WebSocket: deltas / ui.request| WSS EXT <-->|WebSocket in SW| WSS PWA <-->|WebSocket| WSS SPA -->|GET /snapshot?since=seq| REST WSS <--> JQ JQ <--> DB JQ <--> REG JQ <--> RPC RPC <--> PROD PROD <--> SUB
1. Repository Layout (§2.3 SSOT)
The repository follows a clean monorepo structure:
storyflow/
apps/
gateway/ # Bun daemon: omp rpc bridge, WS/REST, SQLite SSOT, job queue, registry
face/ # SvelteKit SPA (adapter-static + fallback), PWA manifest
panel/ # WXT: sidepanel, background SW (WebSocket), flow content scripts
packages/
protocol/ # Zod envelopes: events, jobs, shots, contributions (written first)
ui/ # Svelte 5 + daisyUI 5 shared component library (Vite lib mode)
plugins/ # First-party departments: film, game, app medium adapters
evidence/ # Audit evidence, receipts, screenshots
CHEATSHEET.md # Human bible
sum.yml # Machine SSOT twin (wins on conflict)
old-flow-files/ # Frozen archive (Flow2API, FlowTurbine, Arcade Cabinet)2. Gateway ↔ omp RPC Specification (§2.4 SSOT)
The gateway interacts with omp using the documented RPC v2 protocol over JSONL stdio:
| Concern | Implementation Rule |
|---|---|
| Spawn | omp --mode rpc --profile storyflow, lazy per production, idle-disposed. |
| Frames | Send negotiate_protocol v2 on receiving the ready frame. Reassemble rpc_chunk frames (maxFrameBytes 1 MiB, reassembled ceiling 64 MiB) with RpcFrameDecoder. |
| Resume | Respawn process + switch_session { sessionPath } + get_messages_page replay. Never trust process liveness. On session_busy or stale_cursor, discard partial page and retry. |
| Turn Completion | A turn is complete only on agent_end with isTerminal !== false. The prompt response is merely an acknowledgement. |
| Extension UI | extension_ui_request (select, confirm, input, editor) renders as a <dialog> on connected clients. Answered with extension_ui_response. |
| Agent Writes | Register storyflow:// via set_host_uri_schemes (writable) so agent shot edits route through the gateway daemon, eliminating two-writer drift. |
| Todos & Mirroring | Mirror the production plan into omp using set_todos. Subscribe to subagent events using set_subagent_subscription: "events" to feed the Agents crew tree. |
| SSOT Boundary | SQLite owns productions, shots, jobs, asset_index. External files like LEDGER.md, vault bible, and session JSONL are agent-owned and imported as snapshots. |
| Spend Tracking | The Wall’s $ metric is calculated from our own job ledger. Broker usage is labelled “≤5 min stale”. |
| Job Queue | Concurrency controlled per-provider from the registry; exponential retry backoff; idempotent resume; provenance sidecar per asset; preflight cost calculation required. |
3. Protocol Envelopes (§2.5 SSOT)
Communication across the single WebSocket connection uses strictly typed JSON envelopes:
Envelope Schema
{
"v": 2,
"t": "shot.update",
"prod": "prod_01a0be9f",
"seq": 42,
"data": { ... }
}v(number): Protocol version (currently2).t(string): Envelope kind / topic.prod(string): Target production ID.seq(number): Monotonically increasing sequence number for state synchronization.data(object): Typed payload.
Envelope Kinds
snapshot: Full state transfer for client catch-up.shot.*: Shot CRUD and state transitions (shot.created,shot.updated,shot.deleted).job.*: Queue execution states (job.queued,job.started,job.completed,job.failed).asset.*: Produced media and provenance sidecars (asset.created,asset.promoted).agent.*: Swarm status, plans, and subagent tree updates (agent.event,agent.todo).ui.request: Server-to-client dialog prompts (e.g., bible veto, approval modals).ui.response: Client-to-server responses to UI prompts.ping/pong: Heartbeat keepalive.
Keepalive & Reconnect
- Daemon Ping: The gateway pings every 20 seconds. This is critical for Chrome MV3 service workers which terminate on idle sockets even on Chrome 116+.
- Reconnect Handshake:
- Client detects disconnect or
visibilitychangeevent. - Client issues REST
GET /snapshot?since=<seq>. - Client reconnects WebSocket and resumes receiving deltas from
seq.
- Client detects disconnect or
- Optimism Principle: Optimistic UI is permitted only when local and reversible (e.g. text entry, ordering). Run actions, cost deductions, and approvals must wait for the server echo.