Operation lifecycle — a Tier-2 op, dispatch to commit [Flow]
Source path: knowledge-base/diagrams/flows/operation-lifecycle.md
# Operation lifecycle — a Tier-2 op, dispatch to commit `[Flow]`
A **Tier-2 operation** is the async, streamed machinery that *precedes* a mutation: it does the I/O (LLM calls, Firecrawl, media render), streams progress, and ends by dispatching reducer actions. **111 ops** across 35 handler modules, each a first-class Swagger-style contract in one catalog. This is what the engine-pipeline's "AI" box expands into.
```mermaid
flowchart TB
Caller(["caller — UI GeneratePanel · MCP run_op · Pilot · scheduler"])
Caller -->|"dispatch(op, cls, id, params)"| Control
Control["**control** — resolve spec by op id<br/>gate on cls/id + status"]
Control -->|"status: planned"| P501(["501 — declared, not yet built"])
Control -->|"status: built"| Handler
subgraph Handler["handler runs — emits an OpEvent stream"]
direction TB
Engine["engine work<br/>callGenerate · firecrawl · render"]
Engine -->|"emit"| Ev
Ev["**OpEvent**: token · reasoning ·<br/>progress · warning"]
end
Handler -->|"ctx.commit(action)"| Commit["**commit** — one or more reducer actions<br/>via applyLocalMutation"]
Commit -->|"OpEvent: commit {action · cls · id · version}"| Stream
Handler -->|"terminal"| Done["OpEvent: **done** {version · result}<br/>— or **error** {message · code}"]
Stream(["SSE echo → cache adopts"]) --> UI(["UI updates live"])
Done --> Next(["**nextMoves** — the spec suggests<br/>the next affordance(s)"])
```
**Invariants**
- **The op is a caller, not a writer.** Every state change still goes through `applyLocalMutation` — the op's terminus is `ctx.commit(action)`, which fires the same reducer + echo as the UI. `commits: [...]` in the spec declares exactly which actions it may land.
- **The event stream is a small closed union.** `token · reasoning · progress · commit · warning · done · error`. A consumer branches on the **`code`** of an error (structured), never on the prose — the MCP keys recovery guidance off it.
- **`planned` vs `built` is the surface contract.** Specs are declared UP FRONT so the manifest, docs, and OpenAPI advertise the whole Tier-2 surface; a `planned` op 501s with a clear message until its handler is bound. This keeps the headless surface honest about what exists.
- **One catalog, many transports.** UI, MCP, Pilot, and the scheduler all dispatch the *same* `OPERATION_SPECS`. `nextMoves` on each spec encode the practice (orient-first, escalate at consequential boundaries) — they teach an AI how to navigate, not a flat op→op chain.
**Example** — `research-run` commits `START/FINISH_DOMAIN_PROGRAM_RUN`, `UPSERT_STREAM`, `UPDATE_PROJECTION`, `LOG_RESEARCH_RUN`, `UPSERT_ARTICLE`, `ADD_RESEARCH_BRIEF`, `ADD_DOMAIN_PROGRAM_EVENTS`; engine `runResearchRun` + `firecrawl`; nextMove → run the Opinion pass.
**Where it lives:** `src/lib/server/ops/{types,catalog,control,commit,next-move}.ts`, `src/lib/server/ops/handlers/` (35 modules). MCP surface: [mcp-runtime](mcp-runtime.md) · [mcp-chains](mcp-chains.md). Regenerate the MCP manual after any spec change (`npm run gen:mcp-manual`).
Open on GitHubRaw Markdown source