MeridiansMeridians

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 GitHub

Raw Markdown source