Generated flows
Every node below is read out of the source the flow runs through — the route table in packages/web/src/server/index.ts and the EnsoObservation union in packages/core/src/observation.ts. Nothing here is drawn by hand, so a route or a kind that moves changes this page or fails the docs gate.
Control plane
Section titled “Control plane”The 12 control routes: what the browser can ask the runtime to do. Observation routes are the next diagram’s first stage rather than a second copy of it.
flowchart LR Browser["Browser"] Browser -->|POST /api/projects| R_POST_api_projects["respondToProjectRegistration → ProjectRegistry.register"] Browser -->|DELETE /api/projects/:projectId| R_DELETE_api_projects_projectId["respondToProjectRemoval → ProjectRegistry.remove"] Browser -->|DELETE /api/prompts/:id| R_DELETE_api_prompts_id["respondToPromptRemoval → PromptLibrary.remove"] Browser -->|PUT /api/prompts/:id| R_PUT_api_prompts_id["respondToPromptWrite → PromptLibrary.write"] Browser -->|POST /api/threads/:threadId/abort| R_POST_api_threads_threadId_abort["respondToAbort → abortThread"] R_POST_api_threads_threadId_abort --> H_respondToAbort_abortThread["abortThread()"] H_respondToAbort_abortThread --> Runtime Browser -->|POST /api/threads/:threadId/command| R_POST_api_threads_threadId_command["respondToHostCommand → runHostCommand"] Browser -->|POST /api/threads/:threadId/configure| R_POST_api_threads_threadId_configure["respondToConfigure → configureThread"] Browser -->|POST /api/threads/:threadId/continue| R_POST_api_threads_threadId_continue["respondToContinue → continueThread → ThreadRuntime.continueRun"] R_POST_api_threads_threadId_continue --> H_respondToContinue_continueThread_ThreadRuntime_continueRun["continueThread()"] H_respondToContinue_continueThread_ThreadRuntime_continueRun --> Runtime Browser -->|POST /api/threads/:threadId/dialog| R_POST_api_threads_threadId_dialog["respondToDialog → answerThreadDialog"] R_POST_api_threads_threadId_dialog --> H_respondToDialog_answerThreadDialog["answerThreadDialog()"] H_respondToDialog_answerThreadDialog --> Runtime Browser -->|POST /api/threads/:threadId/fork| R_POST_api_threads_threadId_fork["respondToFork → forkThreadAt → AgentHost.forkThread"] R_POST_api_threads_threadId_fork --> H_respondToFork_forkThreadAt_AgentHost_forkThread["forkThreadAt()"] H_respondToFork_forkThreadAt_AgentHost_forkThread --> Storage Browser -->|POST /api/threads/:threadId/mode| R_POST_api_threads_threadId_mode["respondToModeChange → changeThreadMode"] R_POST_api_threads_threadId_mode --> H_respondToModeChange_changeThreadMode["changeThreadMode()"] H_respondToModeChange_changeThreadMode --> Runtime Browser -->|POST /api/threads/:threadId/prompt| R_POST_api_threads_threadId_prompt["respondToPrompt → promptThread"] R_POST_api_threads_threadId_prompt --> H_respondToPrompt_promptThread["promptThread()"] H_respondToPrompt_promptThread --> Runtime Storage["AgentHost (storage)"] Runtime["ThreadRuntime"] --> Observations["EnsoObservation stream"]
| Node | Contract | Input | Source |
|---|---|---|---|
respondToProjectRegistration → ProjectRegistry.register |
POST /api/projects |
body: EnsoProjectRegistration — an absolute path strictly under a root the ENSO_HOME config declares (projects.roots), optional title |
packages/web/src/server/index.ts:1337 |
respondToProjectRemoval → ProjectRegistry.remove |
DELETE /api/projects/:projectId |
path: projectId (sixteen hex digits) |
packages/web/src/server/index.ts:140 |
respondToPromptRemoval → PromptLibrary.remove |
DELETE /api/prompts/:id |
path: id; header: If-Match: <ETag> (the version read) or If-Match: * |
packages/web/src/server/index.ts:138 |
respondToPromptWrite → PromptLibrary.write |
PUT /api/prompts/:id |
path: id; headers: exactly one precondition — If-None-Match: * (create: no file of that name), If-Match: <ETag> (replace the version read), If-Match: * (replace whatever is there); body: EnsoLibraryPromptWrite — name, description, category (system, append or session) and the markdown body |
packages/web/src/server/index.ts:137 |
respondToAbort → abortThread |
POST /api/threads/:threadId/abort |
path: threadId; no body |
packages/web/src/server/index.ts:106packages/web/src/server/follow.ts:895 |
respondToHostCommand → runHostCommand |
POST /api/threads/:threadId/command |
path: threadId; body: EnsoHostCommandRun — model with provider/id, thinking with a level, login with provider/authType, logout with a provider, compact with nothing; a view row (context, fork) is the page’s to do, never run |
packages/web/src/server/index.ts:110 |
respondToConfigure → configureThread |
POST /api/threads/:threadId/configure |
path: threadId; body: EnsoSessionConfiguration — optional systemPrompt (a library prompt id, system or append, or null for pi’s default; absent leaves the session’s as it is), model (provider/id), thinking (a level that model supports) and mode; applied in that order under one lease, a value equal to the current one left alone |
packages/web/src/server/index.ts:112 |
respondToContinue → continueThread → ThreadRuntime.continueRun |
POST /api/threads/:threadId/continue |
path: threadId; no body |
packages/web/src/server/index.ts:127packages/web/src/server/follow.ts:440 |
respondToDialog → answerThreadDialog |
POST /api/threads/:threadId/dialog |
path: threadId; body: DialogAnswer |
packages/web/src/server/index.ts:105packages/web/src/server/follow.ts:518 |
respondToFork → forkThreadAt → AgentHost.forkThread |
POST /api/threads/:threadId/fork |
path: threadId; body: EnsoForkRequest — entryId, the pi entry id of one of the thread’s user messages; the fork is pi’s /fork at position before, taken from the session file |
packages/web/src/server/index.ts:125packages/web/src/server/follow.ts:851 |
respondToModeChange → changeThreadMode |
POST /api/threads/:threadId/mode |
path: threadId; body: EnsoPermissionModeChange |
packages/web/src/server/index.ts:108packages/web/src/server/follow.ts:551 |
respondToPrompt → promptThread |
POST /api/threads/:threadId/prompt |
path: threadId; body: EnsoPromptBody |
packages/web/src/server/index.ts:104packages/web/src/server/follow.ts:340 |
Observation pipeline
Section titled “Observation pipeline”One pi event becomes one observation and crosses six stages to reach the browser store. The union declares 26 kinds; the mapper produces 24 of them, and the rest are produced by the prompt route (see Observations for the per-kind producer, carrier and consumer).
flowchart LR Pi["pi runtime event"] Pi --> Mapper["mapPiEvent (24 kinds)"] Mapper --> Schema["EnsoObservation"] Schema --> Frame["observation frame"] Frame --> Connection["follow connection"] Connection --> Adapter["to-agui chunk"] Adapter --> Router["custom event router"] Router --> Store["browser store"]
| Stage | Contract | Source |
|---|---|---|
mapPiEvent (24 kinds) |
Observations | packages/web/src/host/map-events.ts:298 |
EnsoObservation |
Core exports | packages/core/src/observation.ts:633 |
observation frame |
Observations | packages/web/src/server/follow.ts:215 |
follow connection |
Routes | packages/web/src/follow-connection.ts:493 |
to-agui chunk |
Observations | packages/web/src/to-agui.ts:76 |
custom event router |
Observations | packages/web/src/custom-event-router.ts:212 |
Kinds, in the union’s order of declaration: text-delta, thinking-delta, tool-call, tool-result, custom-entry, permission-mode, permission-mode-rejected, model, message-start, message-end, session-usage, branch-compacted, login, turn-start, turn-end, provider-refused, provider-retry, provider-retry-ended, queue, prompt-rejected, extension-error, extension-ui-request, dialog-settled, run-ended, settled, unmapped.
