Introduction
Castellan
Self-evolving agent coordination runtime — the harness is the castellan.
Terminal demo — run → episode → multiplexer
Offline / no CDN — static transcript
$ castellan plugin list
$ castellan run --goal "reach target" --plugin gridworld
episode satisfied
$ castellan run --json | head -3
Download castellan-demo.cast ·
Run live from repo root: ./docs/demo/castellan-demo.sh
Castellan is a Rust coordination runtime: agents wake from a decaying pressure field, verify work through typed harness recursion (RAH), and improve topology from episode logs.
What makes Castellan different
| Dimension | Typical harness | Castellan |
|---|---|---|
| Coordination | Scripts / chat | Stigmergic pressure field + scheduler |
| Topology | Fixed | Evolved from .castellan/episodes/ |
| Recursion | API subagents | RAH verified depth in multiplexer panes |
| Execution | Vendor PTY | In-tree multiplexer + castellan dashboard |
Castellan is not a meta-harness wrapper or workflow DSL. Default castellan run uses deterministic plugins (gridworld, shell) — wire your LLM via MCP, ACP, or remote panes. See What Castellan is / is not.
Conventions
| Term | Meaning |
|---|---|
| Binary | castellan — single CLI entrypoint |
| Config | Layered castellan.toml → castellan.toml reference |
| Workspace | .castellan/ — episodes, archive, traces |
| MCP tools | castellan_* prefix — MCP tools |
| Socket API | NDJSON on multiplexer socket — Socket API |
Episode flywheel
flowchart LR
RUN[castellan run] --> LOG[.castellan/episodes]
LOG --> EVOLVE[castellan evolve]
EVOLVE --> GATE[castellan drift-check]
GATE --> RUN
classDef runtime stroke:#0969da,stroke-width:2px
classDef evolve stroke:#d97706,stroke-width:2px
classDef gate stroke:#1a7f37,stroke-width:2px
class RUN,LOG runtime
class EVOLVE evolve
class GATE gate
- Run —
castellan runorcastellan run --rlm - Log — episode JSON in
.castellan/episodes/ - Evolve — topology mutations from corpus
- Gate —
castellan drift-checkmanifest guardrails
castellan run --goal "reach target" --plugin gridworld
Full walkthrough: Quickstart.
Quick paths
| Goal | Page |
|---|---|
| First episode | Quickstart |
| Daily driver loop | Daily driver |
| Agent in panes | Agent guide |
| CLI surface | CLI reference |
Common commands
Build this book locally
mdbook build book && mdbook serve book
See Deployment. Live site: castellan-docs.pages.dev.
Install
Get the castellan binary on your machine and confirm the verify trio passes.
Install methods
| Method | Command | Best for |
|---|---|---|
| Install script | curl -fsSL https://raw.githubusercontent.com/Alphabetsoup16/Castellan/main/install.sh | bash | Quick try / operators |
| From clone | git clone … && ./install.sh | Development |
| Cargo path | cargo install --path crates/castellan-cli | Rust contributors |
| Cargo release build | cargo build --release -p castellan-cli | CI / local dev |
Requires stable Rust from rustup.rs; toolchain pinned in rust-toolchain.toml.
After install script or release build, ensure castellan is on PATH:
export PATH="$PWD/target/release:$PATH" # from repo root
Verify trio
Run these three commands after install — all should succeed:
castellan --version
castellan run --goal "reach target" --plugin gridworld
castellan doctor
Episode JSON appears under .castellan/episodes/. For full CI parity, see Verify and ./scripts/verify.sh.
Optional: mdbook (docs contributors)
cargo install mdbook mdbook-mermaid --locked
mdbook build book
Next steps
| Goal | Page |
|---|---|
| First episode walkthrough | Quickstart |
| Multiplexer daily loop | Daily driver |
| Agent onboarding | Agent guide |
Quickstart
Clone → gridworld episode in under two minutes.
Install
curl -fsSL https://raw.githubusercontent.com/Alphabetsoup16/Castellan/main/install.sh | bash
From source: Install.
First success
castellan run --goal "reach target" --plugin gridworld
Episode JSON lands in .castellan/episodes/.
Terminal demo
Offline / no CDN — static transcript
$ castellan plugin list
$ castellan run --goal "reach target" --plugin gridworld
episode satisfied
Common commands
Next steps
| Goal | Page |
|---|---|
| Daily driver loop | Daily driver |
| MCP tool catalog | MCP tools |
| Remote multiplexer | CLI — remote |
| Verify before PR | Verify |
./scripts/verify.sh
Daily driver loop
The recommended local workflow for developing and operating Castellan.
Loop
castellan multiplexer ensure → castellan dashboard → castellan run → castellan evolve → ./scripts/verify.sh
- Multiplexer —
castellan multiplexer ensurestarts the in-tree PTY server (~/.config/castellan/castellan.sock). - Dashboard —
castellan dashboardopens the ratatui control tower (agents, pressure, pane tree). Writes~/.config/castellan/statusline.jsonfor MCP observability. - Run —
castellan run --goal "…" --plugin gridworld(scheduler) or--rlm(verify/act loop with mux pane topology). - Evolve —
castellan evolve --episodes 16 --workspace .castellan/episodesmutates topology from episode corpus. - Verify —
./scripts/verify.shbefore every PR (fmt, clippy, hack, tests, smoke benches, dashboard + socket e2e).
Headless CI parity
CI runs the same smoke surface without a TTY:
Remote / SSH
Dashboard
castellan dashboard is the ratatui control tower over the in-tree multiplexer — the daily-driver view of agents, pressure, pane topology, and evolution archive.
Launch
castellan multiplexer ensure
castellan dashboard
Defaults:
- Socket:
~/.config/castellan/castellan.sock(override with--socket) - Episodes:
.castellan/episodes - Archive:
.castellan/archive
Keybindings
| Key | Action |
|---|---|
j / k | Select agent up/down |
Enter | Attach to selected pane (Ctrl+Q to detach) |
r | Refresh agents + layout snapshot |
q | Quit |
Headless observability
The dashboard writes ~/.config/castellan/statusline.json on each refresh. MCP tool castellan_read_statusline (see MCP tools) reads this snapshot for external monitors.
CI smoke
Dashboard client code is tested against a mock Unix socket:
cargo test -p castellan-dashboard --test integration
Included in ./scripts/verify.sh.
What Castellan is / is not
Castellan is
- A coordination runtime — pressure-field scheduler, typed blackboard, episode logs
- A plugin harness boundary —
CastellanPlugin::verify_goaldrives the defaultcastellan runloop - An in-tree multiplexer — HerdR-shaped NDJSON socket API + PTY panes
- A topology evolution loop —
castellan evolvemutates wake maps from episodes - Governance-aware —
castellan.tomlhooks, tool pipeline, drift-check manifest
Default castellan run uses deterministic plugins (gridworld, shell). The scheduler wakes agents from substrate signals — not a manager LLM.
Castellan is not
- An OpenCode / Claude Code / claw-code clone — no drop-in coding agent with dozens of providers on the hot path
- An OpenClaw wrapper — Castellan does not host or wrap external harness binaries; it is the coordination runtime
- A meta-harness composition layer — unlike Omnigent-style wrappers, Castellan does not orchestrate Claude Code + Codex as black boxes
- A workflow DSL — coordination is the pressure field, not hand-drawn graphs
- A chat router — agent-to-agent NL handoffs are intentionally forbidden
Honest scope: claw-code and OpenClaw optimize single-agent coding loops. Castellan optimizes multi-agent coordination geometry — scheduler ticks, pheromone deposits, topology evolution, and multiplexer pane farms.
Comparison snapshot
| Need | Castellan | claw-code / OpenClaw |
|---|---|---|
| Daily coding agent | Use castellan acp or external editor + MCP | Primary product |
| Multi-pane agent farm | In-tree multiplexer + dashboard | External tmux scripts |
| Topology evolution | castellan evolve + archive | Not in scope |
| Stigmergy / pressure field | Core scheduler | Not in scope |
| Provider matrix | Optional verify-path LLM only | Hot-path feature |
Honest adoption path
| Need | Use |
|---|---|
| Prove coordination thesis | castellan run, swarm-demo, Govcraft bench in verify.sh |
| Editor integration | castellan acp (stdio + prompt → engine) |
| External tool bridge | castellan mcp or castellan run --mcp |
| Remote pane farm | castellan remote --plugin <name> with governance |
| Cursor / Claude daily driver | Pair Castellan multiplexer with agent hooks |
Castellan deliberately does not chase OpenCode/Claude Code/claw-code feature parity — coordination runtime and honest scope are the product.
Related
Verify
The single verification entry point is ./scripts/verify.sh:
cargo fmt --checkcargo clippy --workspace --all-targets -- -D warningscargo hack check --workspace --each-feature --no-dev-depscargo test --workspace- Release build + CLI smoke tests (run, json, rah-demo, acp, swarm-demo)
- Benchmarks: stigmergy ablation, govcraft acceptance, topology ablation
- Documentation smoke:
./scripts/smoke-docs.sh
./scripts/verify.sh
castellan drift-check
CI runs the same script on every PR (.github/workflows/ci.yml).
Troubleshooting
Common first-run failures and fixes.
castellan: command not found
cargo install --path crates/castellan-cli
# or
cargo build -p castellan-cli && export PATH="$PWD/target/debug:$PATH"
Multiplexer socket missing
castellan multiplexer ensure
# or
castellan herd server
Override socket path: export CASTELLAN_SOCKET=~/.config/castellan/castellan.sock
drift-check fails locally
./scripts/drift-guard.sh
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
Guardrail edits require manifest updates — see governance policies.
castellan run exits immediately with plugin error
castellan plugin list
castellan plugin info gridworld
Confirm --plugin name matches registry entry. Shell plugin requires bounded argv — no shell injection.
Dashboard shows disconnected
- Start multiplexer:
castellan herd server - Attach session:
castellan herd attach - Check
~/.config/castellan/statusline.json— see statusline
--json output not parseable
castellan run --json emits one CastellanEvent per line (NDJSON). Do not mix with pretty-printed episode JSON — episode file is written to .castellan/episodes/ separately.
Docs build fails
cargo install mdbook mdbook-mermaid --locked
./scripts/install-mermaid-assets.sh
mdbook build book
Related
Coordination model
Agents coordinate through the environment — typed blackboard slots, decaying pheromone zones, and scheduler wake pressure. No conversation loops, router LLMs, or NL handoff chains.
Substrate primitives
| Primitive | Role |
|---|---|
| Blackboard | Typed JSON slots; plugins read/write via PluginContext |
| Pheromone zones | Signal maps with exponential decay (PheromoneField) |
| Perception radius | Bounds how far an agent reads field signals |
| Pressure field | PressureScheduler queue — wake when deps + pressure ≥ threshold |
| Completion | NextAction::Complete deposits completion signal; dependents wake |
Wake pressure = min(base_pressure, min(dep signals)), boosted by multiplexer deposits.
Allowed vs forbidden
| Pattern | Status |
|---|---|
| Deposit completion → dependent wakes | Allowed |
| Blackboard slot handoff (typed JSON) | Allowed |
NextAction::Delegate { task } | Allowed |
| Multiplexer pane deposits | Allowed |
| Agent-to-agent chat | Forbidden |
| Router LLM choosing next agent | Forbidden |
| Re-encoding substrate as NL | Forbidden |
Coordination flow
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:#8250df,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
Scheduler loop
CastellanEngine::run_goal:
- Encode initial task from plugin manifest
- Decay field → refresh pressure → dispatch ready batch
- Read host output → apply deposits → coordinate in zone
- Verify → deposit completion → enqueue retries/delegates
- Snapshot field, topology, wake stats into episode JSON
Multiplexer deposits
Remote observations may include pane deposits:
{"pheromone_deposits": [{"zone": "herdr", "signal": "activity", "amount": 0.5}]}
These feed herdr_wake_boost, waking stalled tasks when panes report activity.
See also
Memory architecture
Castellan memory is the coordination substrate plus durable episode artifacts — not a vector store or conversation archive.
Layers
| Layer | What | Where |
|---|---|---|
| Hot | Blackboard, PheromoneField, scheduler | In-process |
| Warm | Episodes, traces, checkpoints, topology archive | .castellan/ JSON or SQLite |
| Cold | Turso Sync (planned) | Multi-machine share |
flowchart TB
subgraph hot["Hot"]
BB[Blackboard]
FIELD[PheromoneField]
SCHED[Scheduler]
end
subgraph warm["Warm"]
EP[episodes]
TR[traces]
TA[topology archive]
end
RUN[castellan run] --> SCHED
SCHED --> FIELD
RUN --> BB
RUN --> EP
RUN --> TR
EVOLVE[castellan evolve] --> EP
EVOLVE --> TA
MCP[castellan mcp] --> warm
REPLAY[castellan replay] --> EP
classDef runtime stroke:#0969da,stroke-width:2px
classDef substrate stroke:#8250df,stroke-width:2px
classDef evolve stroke:#d97706,stroke-width:2px
class RUN,REPLAY runtime
class BB,FIELD,SCHED,MCP substrate
class EVOLVE,EP,TR,TA evolve
Compared to vector-RAG systems: Castellan memory is environment-mediated — typed slots and zone signals, not embedding similarity. See Coordination model.
Today (JSON default)
castellan run --goal "reach target" --plugin gridworld
castellan replay --episode .castellan/episodes/<id>.json
castellan memory status
| Artifact | Path |
|---|---|
| Episodes | .castellan/episodes/<goal-id>.json |
| Traces | .castellan/traces/<goal-id>.jsonl |
| Topology archive | .castellan/topology_archive.json |
Live episodes: castellan_read_blackboard, castellan_deposit_signal — MCP tools.
SQLite backend
Set [memory] backend = "sqlite" in castellan.toml:
castellan_recall— query episodes, checkpoints, observationscastellan_remember— persist agent observations (not live field deposits)castellan_query_field_history— sparse field snapshots at checkpoints
castellan evolve reads SQL aggregates when backend is sqlite. Full config: castellan.toml.
What never goes in the database
| Data | Why |
|---|---|
| Live pheromone ticks | In-process decay semantics |
| Conversation transcripts | Anti-coordination — see Coordination model |
| Vector embeddings | Out of scope v1 |
Engineering detail
SQL schema, MemoryStore trait, sync policy: MEMORY_ARCHITECTURE.md (maintainer spec).
Organism model
Castellan treats the harness as a living organism: genomes express coordination parameters, circulation vitals track field health, and an immune gate rejects harmful topology mutations.
Status: Phases 1–4 complete (circulation, DNA lineage, immune gate, per-zone expression).
Architecture
castellan-core/organism.rs DNA types + circulation vitals + zone expression
castellan-substrate/pheromone per-zone decay (fallback to global lambda)
castellan-runtime/circulation vitals + immune_fever tracking
castellan-runtime/engine.rs vitals + genome_id + fever + decay wire-through
castellan-evolve/genome.rs lineage + crossover + archive reconstruction
castellan-evolve/immune.rs corpus threat detection + auto_reject
castellan-evolve/mutator.rs crossover + cold-zone decay adjustment
castellan-cli/organism_cmd.rs castellan vitals | castellan genome list|lineage
Organism loop
flowchart LR
RUN[castellan run] --> VITALS[circulation vitals]
VITALS --> EP[episode JSON]
EP --> EV[castellan evolve]
EV --> IMM[immune_report]
IMM -->|accept| ARCH[genome archive]
IMM -->|reject| QUAR[quarantine]
classDef runtime stroke:#0969da,stroke-width:2px
classDef evolve stroke:#d97706,stroke-width:2px
classDef gate stroke:#1a7f37,stroke-width:2px
class RUN,VITALS,EP runtime
class EV,ARCH evolve
class IMM,QUAR gate
CastellanGenomeexpresses coordination + decay params at episode endPheromoneFieldcirculation →CirculationVitalsin episodecastellan evolvemutates or crossover (Pareto ≥ 2) →immune_report()gates accept- Quarantined genomes excluded from
archive.nearest()seed - Per-zone decay tuned from proposer cold-zone heatmap
CLI
castellan run --goal "reach target" --plugin gridworld
castellan vitals
castellan evolve --episodes 10
castellan genome list
castellan genome lineage <genome_id>
Phase completion
| Phase | Deliverable | Status |
|---|---|---|
| 1 Circulation | circulation_vitals, castellan vitals, fitness dimension | ✅ |
| 2 DNA | genome_id lineage, archive seed reconstruction, castellan genome | ✅ |
| 3 Immune | drift + fitness + immune gate, quarantine, fever deposit | ✅ |
| 4 Expression | per-zone decay, crossover, flux-grid test, fever unit test | ✅ |
flux-grid (optional)
- Feature:
castellan-substrate/flux-grid(vendored 64×64Stigmergygrid) - Not enabled in default builds — zone-keyed
PheromoneFieldremains default substrate - Enable for experiments:
cargo build -p castellan-substrate --features flux-grid
Known deferred
flux-gridin swarm-demo default path- Full
parent_idslineage CLI (crossover setsparent_ids; CLI shows primaryparent_genome_idonly) RAH_MAX_DEPTHincastellan-core(cycle avoidance withcastellan-evolve↔castellan-rah)
See also
- Coordination model — substrate and scheduler
- Quickstart — first episode
- Episode flywheel on Introduction
Topology evolution
Frontier bet: evolution that changes coordination geometry, not prompts.
What this proves
- Episode logs from real
castellan run/swarm-demoruns ground fitness viaplugin_grounded_fitness(). castellan evolve --episodes 10runs accept/reject over generations (drift + immune gates).- Drift gate: incompatible candidates are rejected on the production path;
drift_rejectedanddrift_violationsappear in CLI JSON when the gate fires. - Gen-0 vs gen-N fitness delta and genome lineage are observable in CLI output.
Quick run
cargo build --release -p castellan-cli
bash docs/demo/evolve-proof.sh
Or step by step:
castellan swarm-demo
castellan run --goal "reach target" --plugin gridworld # repeat 3×
castellan evolve --episodes 10 --workspace .castellan/episodes
castellan genome list
castellan genome lineage <genome_id>
Sample output shape
{
"generations": 10,
"accepted": 4,
"rejected": 6,
"drift_rejected": 1,
"drift_violations": [
{ "check_id": "topology_edge_refs", "message": "edge agent-1→agent-2 references missing node(s)" }
],
"gen0_fitness": 0.69,
"final_fitness": 0.74,
"fitness_delta": 0.05,
"seed_source": "episodes",
"pareto_front": []
}
CI gate
Fast smoke (no LLM, fixture episodes only):
cargo run -q -p evolve_proof
Fixture corpus: tests/fixtures/evolve-episodes/ (4 gridworld episodes).
Honest limits
- Fitness is plugin-grounded from in-tree gridworld episodes, not Govcraft 48.5% LLM benchmark.
- Mutations target wake threshold, topology nodes, and per-zone decay — not prompt strings.
- Crossover activates when Pareto front has ≥2 non-quarantined entries.
- Drift gate blocks structurally invalid / manifest-failing candidates before fitness accept.
Related
Evolve → run round-trip
Loop: episodes → evolve → archive → seed run → write-back fitness
Overview
Castellan closes the loop between topology evolution and production runs:
castellan evolve— mutates harness topology from episode corpus; writes.castellan/topology_archive.jsoncastellan run --seed-archive— seeds topology/coordination from nearest archive entry; logsgenome_id+genome_seedevent- Write-back — after a successful run, merges episode fitness into the archive entry (by
genome_id)
Write-back is enabled with --write-back or automatically when --seed-archive is used. Pass --no-write-back to disable the automatic path.
Quick reproduce
EVOLVE_FIX="tests/fixtures/evolve-episodes"
mkdir -p .castellan/episodes
cp "$EVOLVE_FIX"/*.json .castellan/episodes/
castellan evolve --episodes 10 --workspace .castellan/episodes
castellan run --goal "reach target" --plugin gridworld --seed-archive --json
Expected in episode JSON:
genome_id— seeded from archivefitness— plugin-grounded success scoreevents[]containinggenome_seedand (when write-back applies)archive_write_back
Write-back semantics
- Trigger:
GoalStatus::Satisfiedand bothgenome_id+fitnesspresent - Merge:
TopologyArchive::write_back_fitnessupdates matching entry by id; re-sorts by success score - Archive path:
--archive(default.castellan/topology_archive.json)
JSON events
With --json, archive write-back emits a typed archive_write_back CastellanEvent line before episode_end.
Related
RLM loop
In-tree verify/act loop: castellan run --rlm.
Sandbox seam (RlmSandboxHost)
Behind castellan-rlm feature sandbox — stub for future Modal/Daytona backends:
#![allow(unused)]
fn main() {
use castellan_rlm::{RlmSandboxHost, SandboxRequest, StubSandboxHost};
}
Not wired into run_until_satisfied yet.
Governance overview
Castellan ships two governance surfaces:
- Manifest drift-check —
castellan drift-checkenforces required CI checks, style files, and deprecation notices. See policies. - Runtime hooks —
castellan.tomlsession hooks and multiplexer agent lifecycle reporting. See hooks.
Neither surface uses a router LLM. Governance is deterministic policy + operator-configured shell hooks.
Quick commands
castellan drift-check # manifest guardrails
castellan doctor --json # staged runtime diagnostics
Related
Governance policies
Policies that keep Castellan’s codebase and guardrails from drifting apart.
Verification pipeline
.github/workflows/ci.yml
│
├── scripts/drift-guard.sh → castellan drift-check (manifest)
└── scripts/verify.sh → fmt, clippy, hack, test, build, smoke
CI must call ./scripts/drift-guard.sh then ./scripts/verify.sh only. Do not add duplicate lint or test steps to the workflow — that creates anti-drift debt.
Single source of truth
| Concern | Canonical location |
|---|---|
| Required checks list | crates/castellan-governance/src/manifest.rs |
| Check enforcement | castellan drift-check (also invoked by drift-guard.sh) |
| Full verification | scripts/verify.sh |
| Code style | Engineering guide |
| License / advisory policy | deny.toml |
When adding a new required check:
- Add a
ManifestCheckentry tomanifest.rs. - Implement validation in
run_drift_check. - If the check belongs in the full suite, add it to
verify.sh. - Note the change in your PR description (required).
PR requirements
PRs that touch any of the following must include a ## Guardrails section in the description explaining what changed and why:
scripts/verify.shscripts/drift-guard.sh.github/workflows/**crates/castellan-governance/src/manifest.rsrust-toolchain.toml,clippy.toml,rustfmt.toml,deny.toml
PRs that change Rust style conventions must update the engineering guide in the same PR.
Drift-check command
cargo run -p castellan-cli -- drift-check
# or, after install:
castellan drift-check
Exits non-zero when any VERIFICATION_MANIFEST expectation fails. Use locally before pushing when editing guardrail files.
Python deprecation
The python/ tree is deprecated and packages were removed. python/README.md must retain a deprecation notice — drift-check enforces this when the directory exists.
Review expectations
- Guardrail changes: one maintainer approval minimum.
- Manifest changes: confirm
castellan drift-checkand./scripts/verify.shboth pass in CI. - No
allow(dead_code)in crate sources without removing the check from manifest (not permitted).
Related
- Agent hooks — multiplexer lifecycle reporting
- CLI reference —
castellan drift-check
Agent hooks
Castellan supports two hook surfaces: governance lifecycle hooks (configured in castellan.toml) and multiplexer agent lifecycle reporting (socket API for editor agents).
Governance lifecycle hooks (castellan.toml)
Configure deterministic shell hooks in layered config (castellan.toml, ~/.castellan/config.toml, .castellan/local.toml):
[[hooks.hooks]]
event = "SessionStart"
command = "./scripts/hooks/session-start.sh"
[[hooks.hooks]]
event = "PreToolUse"
command = "./scripts/hooks/pre-tool.sh"
[[hooks.hooks]]
event = "PostToolUse"
command = "./scripts/hooks/post-tool.sh"
[[hooks.hooks]]
event = "SessionEnd"
command = "./scripts/hooks/session-end.sh"
Accepted event / kind values: SessionStart, SessionEnd, PreToolUse, PostToolUse (snake_case aliases also work).
Each command receives a single JSON payload line on stdin:
| Event | Payload fields |
|---|---|
SessionStart | goal_id, plugin |
SessionEnd | goal_id, status |
PreToolUse | tool, argv, agent_id |
PostToolUse | tool, argv, success |
Invocation paths
| Path | When hooks run |
|---|---|
castellan run | SessionStart/SessionEnd on episode boundaries; PreToolUse/PostToolUse on MCP tool calls and shell plugin execution via ToolGovernancePipeline |
castellan run --rlm | Same session hooks; mux delegate steps use nested sub_harness panes |
| Shell plugin | PreToolUse/PostToolUse with tool: "shell" and argv in payload |
Hook commands must exit 0; non-zero exits fail the surrounding operation.
Multiplexer agent lifecycle (socket API)
Agents (Claude Code, Codex, Cursor) can report lifecycle state to the multiplexer without relying on screen heuristics.
Socket API
{"id":"1","method":"pane.report_agent","params":{
"pane_id": "w1:p1",
"agent": "claude",
"state": "working",
"source": "hook"
}}
States: idle, working, blocked, done, unknown.
When source is hook, screen heuristics are ignored until the pane is reset.
Shell hook example
Install into your agent wrapper (~/.config/castellan/hooks/herdr-agent-state.sh):
#!/usr/bin/env bash
# Usage: herdr-agent-state.sh <pane_id> <agent> <state>
PANE_ID="${1:?pane_id}"
AGENT="${2:?agent}"
STATE="${3:?state}"
SOCKET="${CASTELLAN_SOCKET:-$HOME/.config/castellan/castellan.sock}"
printf '%s\n' "{\"id\":\"hook\",\"method\":\"pane.report_agent\",\"params\":{\"pane_id\":\"$PANE_ID\",\"agent\":\"$AGENT\",\"state\":\"$STATE\",\"source\":\"hook\"}}" \
| nc -U "$SOCKET"
Claude / Codex / Cursor integration
| Agent | Suggested hook point |
|---|---|
| Claude Code | Wrap claude in a shell function; call hook on tool-approval and turn-complete |
| Codex | Export CODEX_HOOK_CMD pointing to the script above |
| Cursor | Use cursor-agent lifecycle env callbacks if available; else poll is fallback |
CLI wait helper
castellan herd wait --pane w1:p1 --status idle --timeout-ms 60000
Equivalent to events.wait with pane_agent_status_changed.
Environment
| Variable | Purpose |
|---|---|
CASTELLAN_SOCKET | Override default ~/.config/castellan/castellan.sock |
CASTELLAN_ENV=1 | Set in spawned pane shells (detect castellan-managed sessions) |
Related
Multiplexer overview
Castellan ships an in-tree PTY multiplexer with a HerdR-shaped NDJSON socket API. One binary (castellan) serves daily-driver pane farms without an external herdr binary.
Architecture
castellan herd server → castellan-multiplexer (Unix socket)
│
├── pane.spawn / pane.attach (PTY proxy)
├── events.subscribe / events.wait
└── pane.report_agent (hook protocol)
Pane agent status feeds the pressure field: agent_status observations can deposit pheromones that boost scheduler wake on the herdr zone.
Quick start
castellan herd server # background socket
castellan swarm-demo # scheduler-led multi-pane demo
castellan herd attach # interactive session
Attach modes
| Mode | Command | Use |
|---|---|---|
| Poll | castellan herd status | Headless CI, scripts |
| Proxy | castellan herd attach | Daily-driver TTY |
Related
Plugin architecture
What Is a CastellanPlugin?
A plugin is a goal environment adapter: it encodes goals into runnable tasks, verifies observations against goals, and scores outcomes for evolution.
#![allow(unused)]
fn main() {
#[async_trait]
pub trait CastellanPlugin: Send + Sync {
fn name(&self) -> &'static str;
fn encode_task(&self, goal: &Goal) -> TaskDescriptor;
async fn verify_goal(&self, goal: &Goal, observation: &serde_json::Value) -> AgentReport;
fn fitness(&self, goal: &Goal, report: &AgentReport) -> FitnessVector;
fn tools(&self) -> Vec<ToolSpec> { vec![] }
}
}
Supporting traits:
GoalVerifier— verify-only surface for RLM loopsCrucibleAdapter— verify + automatic remediation hints (CruciblePluginAdapter)
Coordination hooks (optional, via context at runtime):
PluginContext— blackboard read/write, budget snapshotCoordinationContext— zone list, perception radius,deposit_signal/read_signal
Current plugins
| Plugin | Kind | LLM | Description |
|---|---|---|---|
| gridworld | Simulator | No | Deterministic 5×5 grid; agents navigate to target; boids coordination deposits |
| shell | Executor | No | Bounded subprocess (argv, cwd, timeout); exit code + output verification |
| crucible | Adapter | No | Wraps any CastellanPlugin with remediation on failure |
Lifecycle
register → encode → scheduler run → verify → episode snapshot → evolve fitness
- Register —
PluginRegistry::builtin()loads built-ins + manifest TOML - Encode —
encode_task(goal)produces initialTaskDescriptorpayload - Run — scheduler enqueues in plugin primary zone; host executes; observations flow back
- Verify —
verify_goalreturnsAgentReportwithNextAction - Episode — runtime writes
.castellan/episodes/<goal-id>.json - Evolve —
castellan evolvereads episodes; plugin name inenvironment_fingerprintseeds archive lookup
Plugin manifest (plugin.toml)
name = "gridworld"
description = "Deterministic 5x5 grid navigation simulator"
version = "0.1.0"
[zones]
primary = "grid"
default = ["grid", "goal"]
[blackboard]
slots = ["state", "last_observation"]
Manifests live at crates/castellan-plugins/<name>/plugin.toml. Discovery scans that tree at registry init.
Add a plugin in under 10 minutes
castellan plugin new myplugin
castellan plugin list
castellan run --goal "your goal" --plugin myplugin
Installed plugins register automatically via plugins.toml + build.rs — no registry.rs edit.
Extension points
| Extension | Location |
|---|---|
| Plugins | castellan-plugin-api, castellan-plugins |
| RunHost backends | castellan-runtime::RunHost, castellan-rah, castellan-herdr |
| Evolve hooks | castellan-evolve — GepaHook, EpisodeLogProposer |
| Governance | castellan-governance — VERIFICATION_MANIFEST |
| Multiplexer | castellan-multiplexer — pane deposits → observations |
Related
CLI overview
Castellan is a single binary (castellan) with subcommands for goal runs, evolution, multiplexer control, MCP, and diagnostics.
Precedence
Configuration resolves in this order (highest wins):
- CLI flags — e.g.
--goal,--plugin,--json - Environment variables — e.g.
CASTELLAN_MUX_AUTO,CASTELLAN_SESSION_SPEND_CAP_USD - Layered config files — see
castellan.toml
Output modes
| Mode | Flag / entrypoint | Consumer | Reference |
|---|---|---|---|
| Human text | default | Interactive terminal | run |
| NDJSON events | castellan run --json | CI, automation, log pipelines | run |
| MCP stdio | castellan mcp | Cursor, Claude Desktop, other MCP clients | mcp, MCP tools |
| ACP stdio | castellan acp | Editor-class agents (Zed, experimental) | ACP integration |
| Socket NDJSON | castellan multiplexer server | Dashboard, remote attach, HerdR parity | Socket API |
One runnable recipe per mode:
# text — human terminal
castellan run --goal "reach target" --plugin gridworld
# json — typed NDJSON events for CI (episode JSON also lands in .castellan/episodes/)
castellan run --goal "reach target" --plugin gridworld --json | grep episode_end
# mcp — stdio JSON-RPC (tools/list handshake)
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}\n' | castellan mcp
# socket — multiplexer NDJSON API
castellan multiplexer server &
castellan multiplexer status
# acp — agent client protocol over stdio
castellan acp doctor
Episode JSON is always written to .castellan/episodes/<goal-id>.json on successful runs (separate from --json stdout).
Top-level commands
| Command | Page |
|---|---|
run | run |
evolve | evolve |
remote | remote |
herd | herd |
plugin | plugin |
mcp | mcp |
telemetry | telemetry |
doctor | doctor |
replay | replay |
swarm-demo, rah-demo | demos |
Other commands: multiplexer, acp, skills, episodes, recipes, drift-check, memory, integration, vitals, genome, dashboard, completions, ipc (deprecated). Run castellan <cmd> --help for flags. Recipes are defined in castellan.toml [[recipes]].
Shell completions
Completions are generated from live clap metadata so they stay in sync with the CLI:
castellan completions bash # bash completion script
castellan completions zsh # zsh completion script
castellan completions fish # fish completion script
Install examples:
# bash (user-local)
mkdir -p ~/.local/share/bash-completion/completions
castellan completions bash > ~/.local/share/bash-completion/completions/castellan
# zsh
castellan completions zsh > "${fpath[1]}/_castellan"
# fish
mkdir -p ~/.config/fish/completions
castellan completions fish > ~/.config/fish/completions/castellan.fish
Re-run after upgrading castellan when subcommands or flags change.
Help snapshots
CLI help is snapshot-tested in CI. After intentional CLI changes:
UPDATE_SNAPSHOTS=1 cargo test -p castellan-cli cli_help_snapshot
See Deployment.
Recipes
castellan run
Scheduler-led goal loop with a plugin verifier. Writes episode JSON to .castellan/episodes/<goal-id>.json.
Usage
castellan run --goal <GOAL> [--plugin gridworld] [--resume [<GOAL_ID>]]
[--rlm] [--max-steps 8]
[--audit <PATH>] [--json] [--mcp] [--model <URL_OR_NAME>]
[--genome <ID>] [--seed-archive] [--archive .castellan/topology_archive.json]
[--write-back] [--no-write-back] [--mux] [--no-mux]
When resuming, --goal is optional (defaults to checkpoint description). Plugin comes from the checkpoint.
Key flags
| Flag | Default | Notes |
|---|---|---|
--goal | required unless --resume | Natural-language goal text |
--plugin | gridworld | Verifier plugin (castellan plugin list) |
--resume | off | Continue from .castellan/checkpoints/ (optional goal UUID; default latest) |
--rlm | off | In-tree RLM verify/act loop |
--max-steps | 8 | Max RLM steps when --rlm |
--json | off | NDJSON castellan-events on stdout |
--mcp | off | Process mcp_tools from observation payload |
--mux | off (explicit) | Force scheduler-led run via in-process multiplexer panes |
--no-mux | off | Rollback for mux-by-default; also CASTELLAN_MUX_AUTO=0 or CASTELLAN_MUX_DISABLE=1 |
--seed-archive | off | Seed topology from evolve archive before run |
--write-back | auto with --seed-archive | Merge fitness into archive after success |
--mux runs via MuxHarnessHost; episode JSON includes pane_tree. BL-020 (MOAT Phase 3): mux is the default when the multiplexer socket is healthy — no CASTELLAN_MUX_AUTO=1 required. Rollback with --no-mux, CASTELLAN_MUX_AUTO=0, or CASTELLAN_MUX_DISABLE=1.
--json events
| Event | Summary |
|---|---|
episode_start | goal_id, plugin, goal |
scheduler_tick | tick, pending |
task_dispatched | tick, task_id, zone |
verify_result | tick, zone, goal_met |
cost_rollup | per-tick usd, tokens, session total_usd, total_tokens |
deposit | zone signal deposit |
archive_write_back | optional genome merge |
episode_end | goal_id, status, fitness, episode_path |
See statusline for dashboard consumption.
Recipes
Gridworld smoke (verify.sh default):
castellan run --goal "reach target" --plugin gridworld
RLM verify/act:
castellan run --goal "reach target" --plugin gridworld --rlm --max-steps 4
Resume mid-goal:
castellan run --goal "reach target" --plugin gridworld
castellan run --resume
Mux topology in episode:
castellan run --goal "reach target" --plugin gridworld --mux
Evolve round-trip:
castellan evolve --episodes 5 --workspace .castellan/episodes
castellan run --goal "reach target" --plugin gridworld --seed-archive --write-back
Related
castellan evolve
Evolve harness topology from episode logs. Optional Python GEPA hook; drift gate rejects incompatible mutations.
Usage
castellan evolve [--episodes 10] [--workspace .castellan/episodes] [--hook rust|seed-only] [--summarize]
castellan evolve --plan
castellan evolve --dry-run
castellan evolve --apply-proposal <UUID>
Key flags
| Flag | Default | Notes |
|---|---|---|
--episodes | 10 | Generations to run |
--workspace | .castellan/episodes | Episode JSON corpus directory |
--hook | rust | Proposer hook (seed-only for fixture tests) |
--summarize | off | Write .castellan/episode_summary.json without evolving |
--plan | off | Read-only analysis: emit an evolution plan JSON, zero writes |
--dry-run | off | Persist proposals to .castellan/proposals/, no archive writes |
--apply-proposal | — | Resolve one persisted proposal (drift gate re-checked) |
--summarize compacts the corpus for the proposer (DRY with substrate heatmap). Drift-incompatible candidates are rejected and logged.
Plan vs dry-run vs apply
| Mode | Reads corpus | Writes proposals | Writes archive |
|---|---|---|---|
--plan | yes | no | no |
--dry-run | yes | yes | no |
--apply-proposal | yes | resolves one | on accept |
Use --plan for a pre-mutation analysis turn (incumbent, proposed coordination, pressure summary, drift preview, fitness estimate), then --dry-run + --apply-proposal (or the MCP castellan_propose_mutation / castellan_resolve_mutation pair) for the gated two-phase apply.
Recipes
Standard evolve from local episodes:
castellan run --goal "reach target" --plugin gridworld
castellan evolve --episodes 10 --workspace .castellan/episodes
Summarize only (no mutation):
castellan evolve --summarize --workspace .castellan/episodes
Seed archive then run:
castellan evolve --episodes 10
castellan run --goal "reach target" --plugin gridworld --seed-archive
Related
castellan herd
HerdR-shaped UX over the in-tree multiplexer — common attach, status, and tab operations without raw socket JSON.
Usage
castellan herd <SUBCOMMAND>
Subcommands include server, status, tabs, attach, wait, and related multiplexer helpers. Run castellan herd --help for the current list.
Recipes
Check multiplexer health:
castellan herd status
Attach to a running session:
castellan herd attach <session-id>
List tabs before attach:
castellan herd tabs
Daily-driver stack:
castellan multiplexer server &
castellan herd status
castellan dashboard
Related
castellan remote
Attach to the in-tree multiplexer and run a governed goal on remote panes.
Usage
castellan remote --goal <GOAL> [--plugin gridworld] [--socket <PATH>]
[--ssh user@host] [--stub] [--audit <PATH>]
Key flags
| Flag | Notes |
|---|---|
--goal | Required goal text |
--plugin | Verifier plugin (default gridworld) |
--socket | Multiplexer NDJSON socket path |
--ssh | Remote host for SSH attach |
--stub | Deterministic stub run (CI smoke) |
--audit | Optional governance audit JSONL |
Governance pipeline (with_governance) applies on the remote hot path.
Recipes
Local stub smoke (verify.sh):
castellan remote --stub --goal "reach target" --plugin gridworld
Remote with audit trail:
castellan remote --goal "refactor auth module" --plugin shell --audit .castellan/audit.jsonl
Related
castellan plugin
List, inspect, scaffold, install, link, and uninstall dynamic plugins.
Usage
castellan plugin list
castellan plugin info <NAME>
castellan plugin new <NAME>
castellan plugin install --from <DIR>
castellan plugin install --git <URL> [--name <NAME>]
castellan plugin link --from <DIR> [--name <NAME>]
castellan plugin uninstall <NAME>
Installed plugins register via plugins.toml and build.rs — no hand-editing registry.rs.
Link vs install
install | link | |
|---|---|---|
| Sources | copied into crates/castellan-plugins/<name> | symlinked — edits are live |
| Iterate | re-install after every change | rebuild only (cargo build) |
| Uninstall | deletes the copy | removes the symlink, sources intact |
castellan plugin list reports "source": "builtin" | "install" | "link" per plugin.
Recipes
List registered plugins:
castellan plugin list
Install from local directory:
castellan plugin install --from ./my-plugin
cargo build --release -p castellan-cli # rebuild to register
Iterate on a plugin without reinstall:
castellan plugin link --from ~/src/my-plugin
cargo build --release -p castellan-cli # rebuild to register
# edit ~/src/my-plugin/src/lib.rs, rebuild — no re-link needed
castellan plugin uninstall my-plugin # removes only the symlink
Install from git:
castellan plugin install --git https://github.com/example/castellan-gridworld-extra.git
Uninstall:
castellan plugin uninstall sample-plugin
Related
castellan mcp
MCP stdio JSON-RPC server exposing stigmergy and harness tools from the shared registry.
Usage
castellan mcp
Methods: initialize, tools/list, tools/call. Same registry as castellan run --mcp live episodes.
Recipes
List tools (stdio):
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | castellan mcp
Discovery flow:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"castellan_search_tools","arguments":{"query":"deposit"}}}' \
| castellan mcp
Wire Cursor: see Cursor setup.
Related
castellan telemetry
Query dashboard telemetry database — sessions, agent transitions, vitals snapshots, and per-tick cost rollups — without a TTY.
Usage
castellan telemetry [--limit 20] [--db <PATH>]
castellan dashboard --history [--limit 20]
Default DB: ~/.config/castellan/telemetry.db.
Key flags
| Flag | Default | Notes |
|---|---|---|
--limit | 20 | Max rows per query |
--db | user config path | Override telemetry database |
Summary JSON includes cost_rollup_count, session_cost_usd, session_tokens, and recent_costs (from governance cost_rollup wire events recorded while the dashboard is open).
Recipes
Headless session list:
castellan telemetry --limit 5
Dashboard history JSON:
castellan dashboard --history --limit 10
Related
castellan doctor
Staged runtime diagnostics: multiplexer socket, governance manifest, plugin registry, evolve archive, latest episode vitals.
Usage
castellan doctor [--workspace .] [--json]
Exits non-zero when any stage fails. --json for automation.
Stages
| Stage | Checks |
|---|---|
| Multiplexer | Default socket reachable |
| Governance | Manifest / config load |
| Plugins | Registry matches plugins.toml |
| Archive | Topology archive readable |
| Vitals | Latest episode circulation metrics |
Recipes
Local workspace check:
castellan doctor --workspace .
CI-friendly JSON:
castellan doctor --json | jq .
Related
castellan replay
Replay a saved episode JSON or audit JSONL file. --continue-run resumes from checkpoint when supported.
Usage
castellan replay <PATH> [--continue-run]
Recipes
Inspect last episode:
castellan replay .castellan/episodes/latest.json
Replay audit trail:
castellan replay .castellan/audit.jsonl
Related
Demo commands
Scheduler and RAH demos for verify.sh and local exploration.
castellan swarm-demo
Multi-pane scheduler-led swarm coordination demo.
castellan swarm-demo
Expects output with pane_count, mux_host, and sub_harness_depth (verify.sh smoke).
castellan rah-demo
Depth-3 RAH pane spawn demo — multiplexer + topology binding.
castellan rah-demo
Expects "pane_tree_depth": 3 and "episode_pane_tree": true.
Recipe: demo → evolve flywheel
castellan run --goal "reach target" --plugin gridworld --mux > .castellan/episodes/mux-run.json
castellan evolve --episodes 3 --workspace .castellan/episodes
Related
CLI reference
Per-subcommand pages with flags tables and recipes.
Start here: CLI overview
| Command | Page |
|---|---|
run | run |
evolve | evolve |
herd | herd |
remote | remote |
plugin | plugin |
mcp | mcp |
doctor | doctor |
replay | replay |
| Demos | demos |
Help snapshots: UPDATE_SNAPSHOTS=1 cargo test -p castellan-cli cli_help_snapshot
castellan.toml reference
Layered configuration for governance, shell policy, hooks, and MCP permissions. Schema source: crates/castellan-governance/src/config.rs.
File locations (merge order)
| Layer | Path | Precedence |
|---|---|---|
| User | ~/.castellan/config.toml | lowest |
| Project | castellan.toml (walk up from cwd) | middle |
| Local | .castellan/local.toml (same repo as project castellan.toml) | highest |
Later layers override earlier ones. CLI flags and environment variables override all files.
Example
[governance]
session_spend_cap_usd = 12.5
max_risk_score = 0.85
denied_paths = ["/secret", "~/.ssh"]
[shell]
allowed_commands = ["echo", "cargo", "git"]
denied_commands = ["rm", "curl"]
denied_patterns = ["sudo .*"]
max_output_bytes = 65536
[permissions]
default_mode = "ask"
[permissions.tools]
castellan_propose_mutation = "deny"
castellan_deposit_signal = "allow"
[[hooks.hooks]]
event = "PreToolUse"
command = "./scripts/hooks/pre-tool.sh"
Sections
[memory]
| Key | Type | Description |
|---|---|---|
backend | json | sqlite | Durable store backend (default: json) |
path | string | SQLite database path when backend = "sqlite" (default: .castellan/castellan.db) |
mirror_json | bool | Also write .castellan/episodes/*.json alongside SQL (default: true) |
auto_import | bool | Import existing JSON corpus on first SQLite open (default: true) |
See Memory architecture.
[governance]
| Key | Type | Description |
|---|---|---|
session_spend_cap_usd | float | Session spend ceiling |
max_risk_score | float | Risk score threshold |
denied_paths | string[] | Paths blocked for tool/file access |
[shell]
| Key | Type | Description |
|---|---|---|
allowed_commands | string[] | Allowlist for shell plugin |
denied_commands | string[] | Blocked command names |
denied_patterns | string[] | Regex patterns to block |
max_output_bytes | integer | Cap captured stdout/stderr |
[permissions]
| Key | Type | Description |
|---|---|---|
preset | cautious | balanced | permissive | Approval preset expanded into a base policy |
default_mode | allow | ask | deny | Default MCP tool policy (overrides preset base) |
[permissions.tools] | map | Per-tool overrides |
Permission modes filter tools/list before exposure to MCP clients. castellan doctor reports the active preset.
[models]
| Key | Type | Description |
|---|---|---|
default | string | Fallback model spec for all roles (default: mock) |
verify | string | Model for the verify client |
act | string | Model for the act client |
plan | string | Model for the plan client |
Per-role tiers; the scheduler never routes through an LLM. --model-tier on castellan run selects a role at the CLI.
[[recipes]]
| Key | Type | Description |
|---|---|---|
name | string | Recipe identifier for castellan recipes run <name> |
description | string | Shown by castellan recipes list |
command | string | Shell command executed via sh -c |
cwd | string | Optional working directory |
Recipes inherit [shell] denied_commands / denied_patterns — they are not a policy bypass. Later config layers override recipes by name.
[[recipes]]
name = "gridworld-smoke"
description = "Run gridworld reach-target and write an episode JSON"
command = "castellan run --goal 'reach target' --plugin gridworld --json"
[[hooks.hooks]]
| Key | Type | Description |
|---|---|---|
event | string | Hook event name |
command | string | Shell command to invoke |
See Agent hooks.
Environment overrides
| Variable | Maps to |
|---|---|
CASTELLAN_SESSION_SPEND_CAP_USD | governance.session_spend_cap_usd |
CASTELLAN_MAX_RISK_SCORE | governance.max_risk_score |
CASTELLAN_SHELL_ALLOWED | CSV → shell.allowed_commands |
CASTELLAN_SHELL_DENIED | CSV → shell.denied_commands |
CASTELLAN_DENIED_PATHS | CSV → governance.denied_paths |
CASTELLAN_PERMISSION_MODE | permissions.default_mode |
CASTELLAN_MEMORY_BACKEND | memory.backend |
CASTELLAN_MEMORY_DB | memory.path |
CASTELLAN_MEMORY_MIRROR_JSON | memory.mirror_json |
Full list: Environment variables.
Related
MCP tools
Castellan exposes a progressive-disclosure MCP surface: meta-tools for discovery, compact listings in tools/list, and full schemas on demand via castellan_describe_tool.
Registry: crates/castellan-runtime/src/mcp/registry.rs
Stdio server: castellan mcp
Live episodes: castellan run --mcp → CastellanEngine::call_mcp_tool
Discovery flow
flowchart LR
A[Agent starts] --> B[discover / search tools]
B --> C[describe_tool]
C --> D[Invoke tool]
D --> E[Governance + trace]
classDef runtime stroke:#0969da,stroke-width:2px
classDef substrate stroke:#8250df,stroke-width:2px
classDef gate stroke:#1a7f37,stroke-width:2px
class A,B runtime
class C,D substrate
class E gate
- Discover —
castellan_discover_toolsreturns domain-indexed tool cards (no full schemas). - Search —
castellan_search_toolswithquery(and optionaldomain) narrows by intent. Results are BM25-ranked over tool name, summary, description, and domain; when no whole term matches (partial words), substring matching kicks in. The response reports which via"ranking": "bm25" | "substring". - Describe —
castellan_describe_toolwithnamereturns full schema, preconditions, side effects, examples. - Invoke —
tools/callor livemcp_toolsobservation payload.
Meta-tools are always listed with full schemas. Operational tools in tools/list use compact cards (call castellan_describe_tool for parameters).
Error responses
Discovery tools return "ok": false with stable error codes. Stdio server maps to JSON-RPC -32601 (not found) / -32602 (invalid params). Search queries are capped at 256 characters.
Meta tools
| Tool | Domain | Purpose |
|---|---|---|
castellan_discover_tools | meta | Browse domains and tool names |
castellan_search_tools | meta | BM25-ranked search over catalog |
castellan_describe_tool | meta | Full ToolSpec for one tool |
Operational tools
| Tool | Domain | Summary | Live (--mcp) | Governance |
|---|---|---|---|---|
castellan_deposit_signal | substrate | Deposit pheromone into zone | Yes | pre/post hooks |
castellan_read_signal | substrate | Read signal strength | Yes | read-only |
castellan_read_blackboard | substrate | Read blackboard slot | Yes | read-only |
castellan_topology_snapshot | substrate | Harness topology JSON | Yes | read-only |
castellan_metrics | substrate | Session counters | Yes | read-only |
castellan_multiplexer_status | multiplexer | Dashboard statusline snapshot | Yes | read-only |
castellan_propose_mutation | evolve | Preview harness mutation (diff + drift); does not apply | Yes | preview-only |
castellan_resolve_mutation | evolve | Accept or reject a persisted proposal | Yes | high-impact gate |
castellan_read_resource | substrate | Resolve typed URI (episode://, genome://, pane://, blackboard://) | Yes | read-only |
castellan_recall | memory | Recall durable memory by key | Yes | read-only |
castellan_remember | memory | Store durable memory entry | Yes | pre/post hooks |
castellan_query_field_history | substrate | Query pressure-field history | Yes | read-only |
All operational tools are in LIVE_TOOL_NAMES for castellan run --mcp.
Start the server
castellan mcp
Wire your MCP client to stdin/stdout JSON-RPC. Cursor config example in Cursor setup.
Example: discovery sequence
{"name": "castellan_search_tools", "arguments": {"query": "deposit", "domain": "substrate"}}
{"name": "castellan_describe_tool", "arguments": {"name": "castellan_deposit_signal"}}
{"name": "castellan_deposit_signal", "arguments": {"zone": "grid", "signal": "coordination", "amount": 2.0}}
Example: live episode
Observation payload for castellan run --goal "..." --plugin gridworld --mcp:
{
"mcp_tools": [
{
"tool": "castellan_deposit_signal",
"args": { "zone": "grid", "signal": "coordination", "amount": 2.0 }
}
]
}
Trace JSONL emits mcp_discovery for meta-tools and mcp_tool for operational calls. Governance pre/post hooks apply on the hot path.
Design principles
| Principle | Implementation |
|---|---|
| Progressive discovery | Meta-tools before full schemas |
| Context efficiency | Compact tools/list for operational tools |
| Live parity | Same registry for castellan mcp and castellan run --mcp |
| Description quality | Summary + preconditions + side effects per tool |
Adding a tool
- Add
ToolSpecinregistry.rsand register inall_tool_specs(). - Add handler in
handlers.rsand route incall_tool_with_registry. - Add name to
LIVE_TOOL_NAMESif live episodes should expose it. - Update this page and run
cargo test -p castellan-runtime.
See also MCP overview and Agent guide.
Socket API
Castellan exposes a newline-delimited JSON (NDJSON) API over a Unix domain socket. The in-tree castellan-multiplexer server implements HerdR-compatible automation methods; castellan-herdr is the NDJSON client.
Source: crates/castellan-multiplexer/src/api.rs · HerdR-compatible NDJSON automation (external herdr binary not required)
Connect
| Setting | Default |
|---|---|
| Socket path | $CASTELLAN_SOCKET → ~/.config/castellan/castellan.sock → $XDG_RUNTIME_DIR/castellan.sock |
| Protocol | One JSON request per line; one JSON response per line |
| Streaming | events.subscribe and pane.attach may stream additional NDJSON events |
Ensure the server is running:
castellan multiplexer ensure
# or
castellan herd server
Example request:
{"id": 1, "method": "ping", "params": {}}
Example response:
{"id": 1, "result": {"type": "pong", "version": "...", "protocol": "..."}}
Errors use {"id": ..., "error": {"code": "...", "message": "..."}}.
Method catalog
| Method | Purpose |
|---|---|
ping | Health check; returns server and protocol version |
session.snapshot | Full session tree snapshot |
session.attach | Resolve named session → workspace + pane |
workspace.create | Create workspace with label, optional cwd |
workspace.list | List workspaces |
tab.create | Create tab in workspace |
tab.list | List tabs (optional workspace filter) |
tab.focus | Focus tab by id |
pane.split | BSP split (direction, ratio, optional cwd) |
pane.swap | Swap panes |
pane.zoom | Zoom pane (mode: toggle / in / out) |
pane.focus | Focus pane |
pane.list | List panes (optional workspace/tab filter) |
pane.read | Read visible screen text |
pane.send_text | Send text to pane PTY |
pane.send_keys | Send key sequence |
pane.send_input | Send text and/or keys |
pane.resize | Resize PTY rows/cols |
pane.write | Raw write to pane |
pane.attach | Stream pane output (subscription) |
pane.report_agent | Hook: report agent name + state |
pane.clear_agent_authority | Clear agent authority on pane |
agent.get | Agent info for target pane |
agent.send | Send text to agent pane |
agent.list | List agents (optional workspace filter) |
agent.start | Start registered agent in new/split pane |
agent.explain | Diagnose agent state heuristics |
events.subscribe | Stream filtered events |
events.wait | Block until matching event (timeout) |
worktree.create | Git worktree helper |
worktree.list | List worktrees |
worktree.remove | Remove worktree |
server.live_handoff | Graceful server restart (layout preserved) |
server.stop | Acknowledge stop (server may exit) |
Methods marked Stream return an initial result then additional NDJSON event lines on the same connection.
CLI mapping
| Automation need | CLI |
|---|---|
| Rich status dashboard | castellan herd status |
| Attach to pane | castellan herd attach --pane <id> |
| Wait for agent idle | castellan herd wait --pane <id> --status idle |
| Install agent hooks | castellan integration install <agent> |
| Remote over SSH | castellan remote --ssh user@host |
Agent detection
Screen heuristics detect Claude, Codex, and Cursor agent states (idle / working / blocked). Hooks via pane.report_agent provide authoritative state when integrations are installed.
Live handoff
server.live_handoff writes ~/.config/castellan/sessions/handoff.json, spawns a replacement daemon, and releases the socket. PTY processes are not preserved across handoff — layout and visible text restore only.
Testing
cargo test -p castellan-multiplexer
cargo test -p castellan-herdr
castellan multiplexer ensure
castellan herd status
See Daily driver loop for the operator workflow.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
CASTELLAN_SOCKET | ~/.config/castellan/castellan.sock | Multiplexer Unix socket path |
CASTELLAN_ENV | unset | Set to 1 in castellan-managed pane shells |
RUST_LOG | info | Tracing filter for castellan subcommands |
CASTELLAN_DOCS_URL | https://castellan-docs.pages.dev | llms.txt / sitemap base URL in CI scripts |
OLLAMA_URL | unset | Optional local LLM for ignored integration tests |
Session / remote
| Variable | Purpose |
|---|---|
SSH_AUTH_SOCK | Used by castellan remote --ssh for agent forwarding |
Hooks
See agent hooks for CASTELLAN_SOCKET in shell hook scripts.
Related
Agent guide
Onboarding for AI agents operating Castellan panes — Claude Code, Codex, Cursor, and Pi. Paste this page (or llms.txt) into your agent context before driving Castellan.
What you are operating
Castellan is a coordination runtime, not a chat router. Agents coordinate through:
- Blackboard — typed JSON slots
- Pheromone field — decaying zone signals that wake the scheduler
- Multiplexer panes — PTY sessions with NDJSON socket control
Do not coordinate via agent-to-agent natural language. Use substrate tools and pane deposits.
Install (operator machine)
curl -fsSL https://raw.githubusercontent.com/Alphabetsoup16/Castellan/main/install.sh | bash
export PATH="$HOME/.cargo/bin:$PATH" # if install.sh used cargo install
Verify:
castellan run --goal "reach target" --plugin gridworld
ls .castellan/episodes/
Daily-driver loop
castellan multiplexer ensure # start socket server
castellan dashboard # ratatui control tower (optional)
castellan herd status # agent states across panes
castellan run --goal "..." --plugin shell --mcp
Episode flywheel: castellan run → .castellan/episodes/*.json → castellan evolve → castellan drift-check.
Agent-specific pane setup
| Agent | Start in pane | Hook install |
|---|---|---|
| Claude Code | claude in pane | castellan integration install claude |
| Codex | codex in pane | castellan integration install codex |
| Cursor | Cursor terminal or cursor CLI | MCP via castellan mcp (see below) |
| Pi | pi in pane | Use Pi programmatic/RPC mode in pane |
Spawn via socket API:
{"id": 1, "method": "agent.start", "params": {"name": "claude", "focus": true}}
Or CLI: castellan herd attach --pane <id> after castellan herd status.
MCP workflow (Cursor and headless agents)
- Operator wires
castellan mcpin MCP settings (Cursor setup). - Agent calls
castellan_search_tools→castellan_describe_tool→ invoke. - For live substrate during episodes, operator runs
castellan run --mcp.
Key tools: castellan_deposit_signal, castellan_read_blackboard, castellan_topology_snapshot, castellan_multiplexer_status.
Full catalog: MCP tools.
Socket automation (HerdR-shaped)
Connect to ~/.config/castellan/castellan.sock. One JSON request per line.
{"id": 1, "method": "ping", "params": {}}
{"id": 2, "method": "agent.list", "params": {}}
{"id": 3, "method": "pane.read", "params": {"pane_id": "w1:p1", "lines": 40}}
{"id": 4, "method": "events.wait", "params": {"match_event": {"agent_status": "idle"}, "timeout_ms": 60000}}
Full method table: Socket API.
Diagnosis recipes
| Symptom | Check | Fix |
|---|---|---|
| No socket | castellan herd status fails | castellan multiplexer ensure |
| Agent stuck “working” | castellan herd agent explain --pane <id> | Wait or pane.report_agent hook |
| MCP tools missing | Cursor MCP panel | Restart Cursor; verify castellan mcp path |
| Episode not written | castellan run exit code | Run with --json; check plugin goal |
| Drift rejected | castellan drift-check output | Review castellan.toml governance section |
Flags agents should know
| Command | When |
|---|---|
castellan run --json | Headless NDJSON contract (episode_start … episode_end) |
castellan run --mcp | Live MCP tools on running engine |
castellan run --rlm | RLM verify/act loop |
castellan remote --ssh user@host | Remote pane farm |
castellan evolve --episodes N | Topology mutation from corpus |
Do not invent flags — verify with castellan --help or CLI reference.
Skills
Castellan scans .castellan/skills/ and ~/.cursor/skills/ on castellan run. Author skills as SKILL.md with frontmatter; install via Cursor skills UI or copy into project.
Honest limits
- Castellan is not OpenCode/Claude Code — no built-in 75-provider coding agent on the hot path.
- Default
castellan runuses deterministic plugins; wire your LLM via MCP or panes. - Agent-to-agent chat coordination is forbidden by design.
See What Castellan is / is not.
Cursor setup
Use Castellan in Cursor via MCP (castellan mcp). ACP is available for experiments but MCP is the primary editor path.
Install
git clone https://github.com/Alphabetsoup16/Castellan.git && cd Castellan
./install.sh
export PATH="$PWD/target/release:$PATH"
Wire MCP in Cursor
Add a server entry to Cursor MCP config (Settings → MCP, or ~/.cursor/mcp.json):
{
"mcpServers": {
"castellan": {
"command": "/path/to/Castellan/target/release/castellan",
"args": ["mcp"]
}
}
}
Set command to your built target/release/castellan path. Restart Cursor.
Discovery flow: castellan_search_tools → castellan_describe_tool → invoke. Full catalog: MCP tools.
First smoke
MCP vs ACP
| Path | Cursor use |
|---|---|
castellan mcp | Primary — tool discovery, substrate read/write |
castellan acp | Experimental — in-process prompt bridge only |
Skills and memory
Skills: .castellan/skills/ and ~/.cursor/skills/ are both scanned on castellan run.
Memory is the substrate (blackboard, field, episodes) — see Memory architecture. Vector RAG is deferred. Maintainer SQL schema and trait detail: MEMORY_ARCHITECTURE.md.
Agent onboarding
For paste-into-agent context: Agent guide.
ACP (Agent Client Protocol)
Castellan ships an ACP v1 stdio agent via castellan acp. Editors such as Zed can spawn Castellan as an external coordination agent — scheduler-primary runs that produce episodes, not chat transcripts.
MCP remains the primary Cursor integration path. See Cursor setup.
Capabilities
| Method | Status |
|---|---|
initialize | Protocol v1, camelCase agent capabilities |
session/new | Binds sessionId → castellan-memory acp_sessions |
session/fork | Fork session with parent lineage (parentSessionId persisted) |
session/prompt | Runs CastellanEngine + persist_episode_bundle; emits session/update stream |
session/resume | Restores checkpoint metadata (no chat replay) |
session/load | Replays episode metadata via session/update notifications |
session/cancel | Aborts in-flight prompt task |
session/close | Cancels + marks session closed |
permissions/request | Governance pipeline; set CASTELLAN_ACP_AUTO_DENY=1 in CI |
prompt | Deprecated alias for session/prompt |
Phase 3 (MOAT): mux-by-default when socket healthy; session/fork for parent-child lineage; EpisodeCheckpoint saved on every prompt.
Diagnostics
castellan acp doctor
Prints protocol version, agent capabilities, memory backend, mux health (mux_default_on, mux_effective, mux_socket_healthy), and env flags (CASTELLAN_MUX_AUTO, CASTELLAN_MUX_DISABLE, CASTELLAN_ACP_AUTO_DENY).
Zed smoke (5 steps)
- Build Castellan:
cargo build --release -p castellan-cli - Copy examples/zed-acp.json into your Zed settings agents list (adjust
commandpath). - Open a workspace with a
.castellan/directory (or let Castellan create one on first prompt). - Start the Castellan agent in Zed and send:
reach targetwith context plugingridworld. - Confirm the agent returns
stopReason: completed, ≥3session/updatenotifications, and.castellan/episodes/<goal_id>.jsonexists.
Mux episodes
BL-020: mux is the default when the multiplexer socket is healthy. Explicit "mux": true in prompt context forces mux; "mux": false opts out. Rollback env vars: CASTELLAN_MUX_AUTO=0 or CASTELLAN_MUX_DISABLE=1. ACP attaches pane_tree to persisted episodes for evolve flywheel compatibility.
Session fork
{"jsonrpc":"2.0","id":2,"method":"session/fork","params":{"sessionId":"<parent-id>"}}
Returns { "sessionId": "<child-id>", "parentSessionId": "<parent-id>" }. Child inherits cwd, plugin, and latest goal from parent.
Limitations
- No full chat transcript replay on
session/load— episode metadata only (MEMORY_ARCHITECTURE). - External editor MCP servers are bridged for
castellan_*tools only in Phase 2; shell/file tools remain editor-side. - crates.io publish for library crates is dry-run only until the API stabilizes.
Related
MCP overview
Castellan exposes stigmergy and harness tools via castellan mcp (stdio CLI) and live episodes (castellan run --mcp).
Start the server
castellan mcp
Wire your MCP client to stdin/stdout JSON-RPC (same surface as cargo run -p castellan-mcp).
Quick tool list
| Tool | Description |
|---|---|
castellan_discover_tools | Browse tool domains |
castellan_search_tools | Keyword search |
castellan_describe_tool | Full schema for one tool |
castellan_deposit_signal | Deposit stigmergic signal |
castellan_read_blackboard | Read blackboard slot |
castellan_topology_snapshot | Harness topology JSON |
castellan_multiplexer_status | Dashboard statusline |
castellan_propose_mutation | Topology mutation (governed) |
Full catalog with live-column and governance notes: MCP tools.
Operational tools in LIVE_TOOL_NAMES are available on castellan run --mcp (see crates/castellan-runtime/src/mcp/registry.rs).
Live episode example
{
"mcp_tools": [
{
"tool": "castellan_deposit_signal",
"args": { "zone": "grid", "signal": "coordination", "amount": 2.0 }
}
]
}
Governance pre/post hooks apply on the MCP hot path when --mcp is set.
Editor integration
- Cursor setup — wire MCP in Cursor settings
- Agent guide — onboarding for Claude/Codex/Cursor/Pi
Statusline and observability
Castellan exposes two complementary observability surfaces:
statusline.json— dashboard pane/agent snapshot (MCP-readable)castellan run --json— typedcastellan-eventsNDJSON stream
Both are produced by the same runtime; they serve different consumers.
statusline.json
Written by castellan dashboard to ~/.config/castellan/statusline.json (override with workspace config).
{
"connected": true,
"socket": "/Users/you/.config/castellan/castellan.sock",
"agents": { "total": 3, "idle": 1, "working": 1, "blocked": 0, "done": 1, "unknown": 0 },
"selected": { "pane_id": "w1:p1", "agent": "claude", "status": "working" },
"vitals": { "circulation_score": 0.82, "deferred_ratio": 0.1, "cold_zone_count": 0, "herdr_pulse": 0.4 },
"live_pressure": { "zone_intensity": {} },
"telemetry": { "agent_transitions": 42 }
}
MCP read path
Use castellan mcp tools or castellan dashboard --history for headless telemetry queries.
castellan-events schema (--json)
castellan run --json emits one serde-tagged event per line. Canonical variants:
| Event | When |
|---|---|
episode_start | Before scheduler loop |
scheduler_tick | Each tick |
wake_summary | After dispatch batch |
task_dispatched | Per dispatched task |
verify_result | Plugin verify |
tool_decision | MCP / governance tool path |
permission_denied | Governance deny |
budget_exhausted | Dispatch cap hit |
deposit | Pheromone deposit |
archive_write_back | Post-run archive merge |
episode_end | After episode JSON written |
Multiplexer events.subscribe can bridge the same schema via EmittedEvent::from_castellan_event.
Related
Engineering guide
Rust conventions for Castellan contributors. Guardrail enforcement lives in governance policies.
Toolchain
- Pin via
rust-toolchain.toml - Format:
cargo fmt --all - Lint:
cargo clippy --workspace --all-targets -- -D warnings
Verification
./scripts/verify.sh
castellan drift-check
Crate boundaries
| Crate | Responsibility |
|---|---|
castellan-core | Types: Goal, EpisodeLog, topology |
castellan-runtime | Scheduler, engine, MCP hot path |
castellan-events | Unified observability schema |
castellan-cli | Operator surface |
castellan-multiplexer | PTY + socket API |
Docs
- Published book (
book/src/) is canonical for operators — castellan-docs.sgoldbeg.workers.dev docs/holds engineering specs; see docs/INDEX.md- Do not link raw GitHub
docs/blobs from book pages — use relative book links
Related
Documentation style
Guidelines for Castellan book pages.
Voice
- Honest scope — say what Castellan is not early
- Operator-first — copy-paste commands that produce visible artifacts
- No placeholder social proof
Formatting
- One H1 per page (mdbook enforces)
- Use
> **Note**/> **Warning**admonitions - Mermaid diagrams: use
classDef+ semantic classes (runtime,substrate,evolve,gate) — see Design system - No inline
style fill:#...(breaks dark mode) - Max 3 columns in comparison tables
Links
- Relative book links only — never link to raw GitHub
docs/blobs from book pages - Cross-link concepts ↔ reference ↔ integrations
Brand tokens
See Design system for the full token reference, typography scale, layout, components, and Mermaid rules.
Quick palette (defined in book/theme/css/custom.css):
--castellan-coordination— teal links and active nav--castellan-evolve— amber evolution accents--castellan-runtime/--castellan-substrate/--castellan-gate— diagram roles
Harness capability docs
- Single source:
docs/harness-manifest.tomldrivesdocs/HARNESS_FEATURES.mdand the landscape table indocs/COMPETITIVE_MATRIX.md. - Update workflow: edit the manifest, then
cargo run -p harness-docs -- generate. - CI:
./scripts/check-harness-doc-parity.sh(viaverify.shsmoke-docs) fails on drift.
Related
Deployment
Audience: maintainers publishing docs and configuring CI.
Documentation site (mdbook → Cloudflare Pages)
Castellan docs are built with mdbook from book/ and deployed on every push to main that touches book/, docs/, or .github/workflows/docs.yml.
| Environment | URL |
|---|---|
| Production | https://castellan-docs.pages.dev |
| Workers subdomain | https://castellan-docs.sgoldbeg.workers.dev (may vary by account) |
Local build
cargo install mdbook mdbook-mermaid --locked # once
mdbook build book
open book/book/index.html
Live preview:
mdbook serve book --open
./scripts/verify.sh runs scripts/smoke-docs.sh, which includes mdbook build book.
CLI help snapshots
When CLI flags or subcommands change intentionally:
UPDATE_SNAPSHOTS=1 cargo test -p castellan-cli cli_help_snapshot
verify.sh runs cli_help_snapshot tests (BL-014). MCP tool names must appear in MCP tools — enforced by scripts/check-mcp-doc-parity.sh (BL-015).
CI workflow
Workflow: .github/workflows/docs.yml
- Install Rust toolchain and mdbook
mdbook build book→ output inbook/book/cloudflare/wrangler-actionuploadsbook/book/to Pages projectcastellan-docs
Model A (canonical): GitHub Actions direct upload — not Cloudflare Git integration. Keep CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID in GitHub secrets.
gh workflow run docs.yml # manual deploy
Required GitHub secrets
| Secret | Description |
|---|---|
CLOUDFLARE_API_TOKEN | Account Settings Read + Cloudflare Pages Edit |
CLOUDFLARE_ACCOUNT_ID | 32-char account ID from dashboard sidebar — not a zone ID |
Troubleshooting
| Symptom | Fix |
|---|---|
| Deploy 403 | Token lacks Pages Edit for target account |
| Wrong account ID | Copy from Workers & Pages sidebar, not zone overview |
| Dashboard build token error | Disconnect Git builds; use Model A CI upload only |
Application / CLI releases
The Castellan CLI is not yet published to crates.io (API still moving). Install from source:
cargo install --path crates/castellan-cli --locked
Semver tags and CI
| Step | Command / workflow |
|---|---|
| Local verify | ./scripts/verify.sh |
| Release prep (dry-run publish) | ./scripts/release-prep.sh [version] |
| Tag push | git tag -a v0.1.0 -m "Release v0.1.0" && git push origin v0.1.0 |
| GitHub Release | .github/workflows/release.yml — verify, artifact upload, changelog body |
MSRV: rust-version = "1.75" in root Cargo.toml. Release workflow pins toolchain 1.75.
Changelog: CHANGELOG.md (Keep a Changelog format).
CI runs ./scripts/verify.sh on every PR and push to main.
Related
Frontier positioning
Thesis: Castellan owns stigmergic runtime coordination, verified harness recursion (RAH), and episode-grounded topology evolution — not meta-harness wrapping or workflow DSLs.
What Castellan is not competing on
- claw-code / OpenClaw — single-agent coding loops and provider matrices
- Omnigent-style wrappers — composing external harnesses with policy YAML
- LangGraph / workflow DSLs — hand-drawn agent graphs
What Castellan owns
| Wedge | Evidence |
|---|---|
| Pressure-field scheduler | castellan run, stigmergy benches in verify.sh |
| In-tree multiplexer | HerdR-shaped socket API, no external binary |
| Topology evolution | castellan evolve, .castellan/topology_archive.json |
| Honest verification | castellan drift-check, plugin-grounded fitness |
Adoption framing
Use Castellan when coordination geometry must evolve from episode logs — not when you need a drop-in Claude Code replacement.
Related
Stigmergy ablation
Method: In-tree honest proxies — not upstream Govcraft LLM meeting-scheduling (48.5% paper benchmark).
Summary
| Bench | Stigmergy | Random / baseline | Notes |
|---|---|---|---|
Chain dispatch (stigmergy_ablation) | 5/5 zones | 0/5 zones | Dependency wake via pheromone deposits |
| Gridworld success rate (8 runs) | 100% | 100% | Both configs reach target; chain dispatch is the discriminant |
Govcraft proxy (govcraft_acceptance) | 41.7% booking rate | 8.3% booking rate | Discrete slot scheduling with PheromoneField |
| Upstream Govcraft | — | — | Requires API keys / Ollama; not bundled in-tree |
Raw JSON (CI reproduce)
{"stigmergy_chain_completed":5,"random_chain_completed":0,"stigmergy_success_rate":1.0,"random_success_rate":1.0}
{"bench":"govcraft_acceptance","mode":"in_tree_honest_equivalent","stigmergy_bookings":200,"random_bookings":40,"stigmergy_rate":0.4166666666666667,"random_rate":0.08333333333333333}
Honest labeling
- In-tree proxy:
benches/govcraft_acceptanceandbenches/stigmergy_ablationusePheromoneField+ scheduler semantics shaped after Govcraft pressure-field-experiment. - Not reproduced: LLM agent meeting scheduling at 48.5% vs 12.6% conversation baseline from the January 2026 paper.
- CI: Both benches run in
./scripts/verify.shon every PR.
Reproduce
cargo run -q -p stigmergy_ablation
cargo run -q -p govcraft_acceptance
bash docs/demo/run-ablation.sh
./scripts/verify.sh