MeridiansMeridians

MCP action chains — how an AI navigates the surface [Flow]

Source path: knowledge-base/diagrams/flows/mcp-chains.md

# MCP action chains — how an AI navigates the surface `[Flow]`

The MCP surface shares one bounded static catalog and materialises its first-class domain capabilities from the daemon manifest (built from the same `QUERY_SPECS` / `OPERATION_SPECS` / `ACTION_META` the docs use). Every result carries a uniform envelope `{ ok, working_set, result, delta?, affordances? }` — the **affordances** are the spec's declared `nextMoves`, the hypermedia that teaches the practice so an agent follows links rather than re-scanning the full generated tool list.

The chain enforces **orient-first → escalate-at-boundaries → stage-before-durable-action**:

```mermaid
flowchart TD
    orient["**orient** — describe / list_documents<br/>where am I"]
    orient --> whats["read_loop · whats_changed<br/>what's running, what moved"]
    orient --> when["read_hotspots → window_context<br/>WHERE in time it moved"]

    whats -->|quiet| run["run_domain_loop / research_run<br/>produce the next feed"]
    run -.->|"affordance: orient FIRST"| whats
    whats -->|Position proposal awaits| stage["**stage_loop_directive**<br/>arm a Position — does NOT apply"]
    stage -.->|affordance| apply["run_loop_project<br/>establishes the Position on next run"]
    whats -->|steer a fold| designate["**stage_loop_directive** (merge)<br/>designate a Stream's outcome BEFORE the window"]
    designate -.->|affordance| merge["run_loop_tutor<br/>autonomous group merge — folds now, no approval"]
    whats -->|question callable| project["create_position<br/>stake a forward read (POS id)"]
    project -.->|affordance| checks["research_run applies Checks →<br/>read_positions: the revision trail"]
```

## The central-plane chain — discover → drill down → adopt → share

The chain above moves work inside *one* record. The other half of the surface is **central** (Supabase behind the gateway): the shared shelf of expert models, and the account/admin planes. It has its own shape — *shelf altitude first, deep read second, and never guess at what a collection contains.*

```mermaid
flowchart TD
    browse["**exchange_browse**<br/>shelf cards · one page of 24"]
    browse -.->|"nextCursor ≠ null"| browse
    browse --> got["exchange_get_listing<br/>versions · ratings · full metadata"]
    browse -->|"kind: collection"| mem["**exchange_collection_members**<br/>the ROSTER + which are separately listed"]
    mem -->|"the listed subset"| sums["exchange_listing_summaries<br/>name them all in ONE call"]
    mem -.->|"listingId: null =<br/>only inside the bundle"| adopt
    sums --> got
    got --> adopt["**exchange_adopt**<br/>fork → verify → NEW records"]
    adopt -.->|affordance| focus["focus / list_documents<br/>→ rejoins the chain above"]
    got --> vis["exchange_set_visibility<br/>snapshot → granted → public"]
    vis --> grant["exchange_grant → list_grants → revoke<br/>the sharing progression"]
```

Two traps the affordances exist to prevent: a `nextCursor` ignored **silently truncates the shelf at 24**, and `members: []` on a four-domain collection reads as *empty* when it actually means *none separately adoptable* — which is why `collection-members` returns the roster and the listed subset together.

The **account and admin** planes hang off the same tier, gated by a declared `scope` (`director` → your own account and listings; `admin` → the cross-Director views; `owner` → revoking an admin, and the SQL console). A mis-scoped call is refused *locally*, before it leaves the process, so the agent gets the reason instead of a bare remote 403. See [platform model](../concepts/platform-model.md#three-authorities--dont-conflate-them).

- **`nextMoves` are static per-spec, hand-written** — on queries, ops, and control actions alike (a move names its destination by `query` / `op` / `action` id and the tool name is *derived*, so a rename can't strand it). A bare read/op with none falls back to a generic reconcile pair. The affordance says *when* ("IF belief moved…"); the agent judges.
- **The generated manual audits this graph**: `knowledge-base/generated/MCP_MANUAL.md` reports hubs (most-pointed-at tools) and **isolated** tools — a real read/op nothing routes to, i.e. a discovery gap. Regenerate and read that section after adding a spec; it is the cheapest way to find a capability an agent will never discover.
- **The whole surface is also reachable generically** via `meridians_query` / `meridians_run_op` / `meridians_ops` / `meridians_manifest` — planned (unbuilt) ops are reachable only this way.
- The practice this encodes (machine attends → human judges → model remembers → reality grades) is the same one [PRODUCT_BEHAVIOURS.md](../../knowledge/product/PRODUCT_BEHAVIOURS.md#the-same-practice-headless--one-loop-three-drivers) documents for humans — MCP is a 1:1 clone, not a second mechanism.

## Driving a Scenario game headless

One capability has its own headless shape distinct from the read→act→refine chain above: **running a live Scenario game over MCP**. The `Scenario`-group ops split the studio's one-click `advance()` into an on-rails player half (`game-play-round`), a synchronous non-generative driver (`game-command`), and the generative world half (`game-resolve`) — with a `needsStudio` boundary that stops a headless caller from running a generative step wrong. The full loop is mapped in [scenario-loop → *Driving a game headless (MCP)*](scenario-loop.md#driving-a-game-headless-mcp).
Open on GitHub

Raw Markdown source