Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Coordination model

What

Castellan agents coordinate through a shared coordination field, not through conversation. A typed blackboard, decaying pheromone zones, and a pressure-driven scheduler decide who wakes and when — there is no router LLM picking the next agent, no natural-language handoff, and no agent-to-agent chat.

Why

Coordination geometry (topology, zones, wake thresholds) is the thing that gets tuned and evolved, not prompt text. Vocabulary: Conventions.

If you are wiring up a plugin, debugging why an agent never wakes, or trying to understand what castellan run --mux actually does under the hood, this page is the mental model you need.

Note: "Coordination" here means task dependency resolution and signal propagation between concurrent panes/agents in one run — not multi-agent chat orchestration. See What Castellan is / is not.

Substrate primitives

PrimitiveTypeRole
BlackboardTyped JSON slotsStructured read/write state, scoped per plugin manifest ([blackboard] slots = [...])
Pheromone zonesPheromoneFieldNamed signal maps with exponential decay; agents perceive within a radius
Perception radiusScalar per zoneBounds how far an agent reads field signals — no global broadcast
Pressure fieldPressureScheduler queueWakes tasks when dependency signals + base pressure clear a threshold
Completion signalDepositNextAction::Complete deposits a completion signal into the zone; dependents wake
Quorum gate (H1)QuorumSensorPhase unlock when density ≥ Q and ≥ k distinct contributors deposit within window W — colony consensus without a router LLM
Trail corridors (H4/H9)CorridorFieldOptional (from,to,signal) edges with per-edge λ; Physarum conductance c ← c + α·flux − β·c prunes low-flow paths
Cement (H3)cement signalSuccessful verify deposits cement; morphogenesis may birth topology nodes under regulatory caps

Wake pressure is computed as min(base_pressure, min(dependency signals)), optionally boosted by multiplexer pane activity (pane_status_wake_boost). A task with unmet dependencies stays deferred no matter how high its own base pressure is — the field, not an individual agent, decides readiness.

Quorum = colony phase gate. Unlike task wake (deps ∩ pressure), quorum locks a phase transition until multi-contributor density clears — ablate to a single depositor and the phase stays locked (FalseQuorum immune when mass lacks diversity).

Response thresholds (H2). Topology nodes carry private θ maps; among ready tasks the scheduler can prefer the node with highest stimulus−θ surplus — specialization emerges without hardcoded roles.

Allowed vs forbidden coordination patterns

PatternStatusWhy
Deposit completion → dependent wakesAllowedCore substrate mechanism
Blackboard slot handoff (typed JSON)AllowedStructured, auditable, replayable
NextAction::Delegate { task }AllowedExplicit sub-task spawn, still substrate-mediated
Multiplexer pane deposits (pheromone_deposits)AllowedPane activity feeds the pane zone
Agent-to-agent chatForbiddenRe-introduces router-LLM coordination cost and non-determinism
Router LLM choosing next agentForbiddenDefeats the “coordination is geometry, not prompts” thesis
Re-encoding substrate state as natural language for another agent to parseForbiddenLoses structure, adds token cost, adds failure surface

Anatomy: one scheduler tick

flowchart TB
    G[Goal] --> P[encode_task]
    P --> Q[Pending queue]
    Q --> RP[refresh_pressure]
    RP --> DEP{deps met?}
    DEP -->|no| DEFER[deferred]
    DEP -->|yes| DISPATCH[dispatch]
    DISPATCH --> HOST[RunHost]
    HOST --> OBS[observation]
    OBS --> BB[Blackboard]
    OBS --> FIELD[PheromoneField]
    OBS --> V[verify_goal]
    V --> COMP[deposit completion]
    COMP --> FIELD
    FIELD --> RP

    classDef runtime stroke:#0969da,stroke-width:2px
    classDef substrate stroke:#5b6b8c,stroke-width:2px
    classDef gate stroke:#1a7f37,stroke-width:2px

    class G,P,Q,RP,DISPATCH,HOST runtime
    class OBS,BB,FIELD substrate
    class V,COMP gate

CastellanEngine::run_goal walks this loop until the goal is satisfied, exhausted, or a step budget expires:

  1. Encode — the plugin’s encode_task(goal) produces the initial TaskDescriptor.
  2. Decay + refresh — the field decays one tick; refresh_pressure recomputes wake pressure for every pending task.
  3. Dispatch — the ready batch (dependencies met, pressure over threshold) goes to the RunHost (in-process, mux panes, or remote).
  4. Observe — host output updates the blackboard and deposits into zone(s); coordinate_from_observation applies any explicit deposits from the plugin.
  5. Verifyverify_goal checks the observation against the goal; on success it deposits a completion signal so dependents wake next tick.
  6. Snapshot — field state, topology, and wake stats are written into the episode JSON under .castellan/episodes/.

How operators use it

You rarely touch the substrate directly — you drive it through castellan run and read its effects back out of episode JSON and live MCP tools.

1. Run a goal and let the scheduler coordinate:

castellan run --goal "reach target" --plugin gridworld

2. Force scheduler-led multi-pane coordination (topology + pane tree recorded in the episode):

castellan run --goal "reach target" --plugin gridworld --mux

3. Inspect live substrate state while a run is active (via MCP tools, from an editor or castellan mcp client):

castellan_read_blackboard
castellan_deposit_signal

4. Replay a past run to see the coordination trace tick-by-tick:

castellan replay --episode .castellan/episodes/<goal-id>.json

5. Watch multiplexer pane activity feed the field — pane observations can carry pheromone_deposits:

{"pheromone_deposits": [{"zone": "pane", "signal": "activity", "amount": 0.5}]}

These deposits feed pane_status_wake_boost, which nudges wake pressure for stalled tasks whenever panes report activity — useful when a long-running shell pane is making progress but hasn’t hit a dependency signal yet.

Recipes

When a plugin has independent sub-tasks that must merge before completion, use NextAction::Delegate to spawn them and let the blackboard carry results back — don’t have the parent poll or message the children in natural language.

When you want a run to react to external pane activity (e.g. a human working in another tab), enable --mux so pane deposits land in the pane zone and boost wake pressure for dependent tasks.

When debugging “my task never wakes,” dump the episode JSON and check pending/deferred tasks against their declared dependencies — the field will not dispatch a task early no matter how urgent it looks to a human reader.

When you need deterministic, replayable coordination for CI, avoid --rlm/LLM-driven verify and stick to plugin-native verify_goal — the scheduler loop itself never calls an LLM.

Failure paths / troubleshooting

SymptomLikely causeFix
Task stuck in deferred foreverDependency never deposits completion (upstream task failed or plugin didn’t verify)Check upstream task’s verify_goal result in episode JSON; fix the plugin’s completion condition
Pane activity doesn’t seem to speed anything up--mux not set, so pane deposits never reach the scheduler’s fieldRe-run with --mux, or confirm the multiplexer socket is up (castellan multiplexer ensure)
Two plugins fight over the same zoneZones overlap in plugin.toml [zones] without intentGive each plugin a distinct primary zone; share default zones deliberately
Wake pressure looks “stuck” below thresholdPerception radius too small to see the depositing zoneWiden the zone’s perception radius or deposit into a zone the waiting task actually watches
Coordination trace missing from episodeRun used --no-mux / in-process host without topology trackingRe-run with --mux if you need pane_tree and per-zone deposit history

See also