Operating Autonomous Agent Swarms
Operating Autonomous Agent Swarms#
A swarm is not a chat session with extra tabs. It is a fleet of bounded processes whose writes are illegal on trunk, whose tokens are a capability, and whose phase transitions are refused by exit code 1.
Nomos inverts control: the compiled Go runtime owns TRIAGE → SPEC → PLAN → EDIT → REVIEW → SYNC → LEARN. The model is a stochastic ALU invoked with an ephemeral CapBAC envelope. Operators who treat the ALU as the scheduler reintroduce the 2024–2026 failure class: context saturation, prompt drift, hallucinated agreement, silent test regression.
This chapter is the operational contract for that inversion. It specifies who may plan, who may mutate, what the machine proves, and which four failure modes you will actually hit in production.
1. Dual-tier operations, not dual personalities#
Two cognitive tiers exist because planning and mutation have incompatible blast radii. Mixing them in one process is how you get a frontier model rewriting main while “just fixing a test.”
| Tier 1 — Frontier Orchestrator | Tier 2 — Sandboxed Swarm | |
|---|---|---|
| Job | Strategic planning, DAG decomposition, human negotiation, release signoff | Turn-bounded TDD mutation inside a transient worktree |
| Phases | TRIAGE, SPEC, PLAN, REVIEW (and HITL gates on SYNC) |
EDIT, adversarial AUDIT |
| Medium | Interactive IDE canvas; signed intent artifacts | Headless daemon: nomos code / ncode |
| Write surface | Specs, plans, review notes. Code only via --override=surgical (≤ 3 staged lines) |
Isolated worktrees/<repo>-<task> |
| Model class | Frontier reasoning (deep topology, contract locking) | High-throughput local or cheap cloud (tool-call + AST diff) |
| Proof | Plan DAG acyclicity, schema lock, human/cryptographic approval | Compile, tests green, cyclomatic complexity < 15, docstring density ≥ 10%, import boundary closure |
| On gate fail | Do not mutate. Re-plan or reject. | Retry ≤ N, then escalate. Never widen the worktree. |
The substrate, not the model, chooses the tier. Domain routing is declarative:
# ~/.nomos/data/config.yaml — cognitive domain routing
domains:
triage: { tier: 1, model: "gemini-3.7-flash", fallback: "local" }
spec: { tier: 1, model: "gemini-3.7-flash", fallback: "local" }
planning: { tier: 1, model: "gemini-3.7-flash", fallback: "workhorse" }
code_generation: { tier: 2, model: "local", fallback: "workhorse" }
adversarial_audit: { tier: 2, model: "gemini-3.7-flash", fallback: "workhorse" }
review_walkthrough: { tier: 1, model: "gemini-3.7-flash", fallback: "local" }
escalation:
max_t2_retry_attempts: 3
circuit_breaker_on_dod_fail: true
auto_escalate_to_tier1: trueIf a Tier-2 worker needs “just a bit more architecture,” it does not get it. The circuit breaker fires. Architecture is a Tier-1 artifact.
Topological slicing (non-negotiable)#
A task must not span conflicting planes:
- Substrate — compiled Go engine, AST gates, state machine.
- Intent — Markdown playbooks, specs, swarm prompts.
- Membrane — SvelteKit cockpit, CSS, spectator feed.
- Infra — NixOS, daemons, secrets.
Slice on domain boundaries, not story-point fiction. A swarm that “also tweaks the HUD while fixing the parser” is a contamination vector, not velocity.
YAGNI is a gate, not a slogan: stdlib → native framework → third-party last. No single-implementation interfaces. No factory archaeology. No magic strings. Delete before wrapping.
Surgical override exists for live pair-programming only:
--override=tier1/--override=surgical- Pre-commit measures
git diff --staged. If line count > 3, the Cognitive Firewall rejects the commit and forcesnomos code <KEY>.
That is the entire exception. Do not grow it.
2. The runtime is the scheduler#
Human workflow is non-deterministic intent. The engine is a rigid binary. Prevention is cheaper than sweeping.
Go engine runs the loop.
LLM is called as a bounded heuristic with token/step budgets.
Verification is non-verbal: AST + tests + signatures.
Failed proof => exit 1. No negotiation.Lifecycle (operator-facing)#
stateDiagram-v2
[*] --> IDLE
IDLE --> TRIAGE : ingest intent
TRIAGE --> SPEC : size and route
SPEC --> PLAN : lock schema contract
PLAN --> EDIT : signed DAG (nplan)
EDIT --> AUDIT : TDD worker complete
AUDIT --> REVIEW : red-team pass
REVIEW --> SYNC : DoD gates green
SYNC --> LEARN : 2PC release + receipts
LEARN --> IDLE : GitBrain notes indexed
EDIT --> PLAN : T2 circuit breaker / escalate
REVIEW --> EDIT : gate fail, bounded rework
SYNC --> REVIEW : 2PC abort| Phase | Who runs | What is allowed | What is locked |
|---|---|---|---|
IDLE |
Daemon | Read | Entire workspace write |
TRIAGE |
T1 | Dedup, size (cli:low/high), priority |
Code |
SPEC |
T1 | Requirements, OpenAPI/Protobuf lock, acceptance | Code |
PLAN |
T1 (nplan) |
LATS/MCTS decomposition → topological DAG | Code |
EDIT |
T2 (ncode) |
Mutate only the claimed worktree | Trunk, other worktrees |
AUDIT |
T2 red-team | Adversarial tests, maker-checker | Trunk |
REVIEW |
T1 + machine DoD | Walkthrough, 39 binary gates | Mutations |
SYNC |
Substrate | 2PC merge, prune worktrees, Git Notes | Partial merges |
LEARN |
GitBrain | Distill telemetry into refs/notes/agent |
Speculative rewrite of history |
Repository files are read-only in PLAN, SPEC, and REVIEW. Pre-commit hooks physically block trunk edits. All execution lives in worktrees/<repo>-<task>.
Non-verbal proof#
Polite prompts are not a control plane. The following are mechanical:
- Cyclomatic complexity per function
< 15. - Docstring density
≥ 10%. - Import boundary closure (no illegal plane crossings).
- 100% green test traces for the claimed package set.
- Compile / typecheck exit 0.
Fail any one: transition blocked. The agent cannot “agree” the tests were flaky. It refactors or the task escalates.
CapBAC envelope#
Every LLM invocation is issued a capability, not an open tab:
- token budget — hard ceiling on prompt + completion for this call.
- step budget — max tool turns in this phase.
- path allowlist — worktree prefix only.
- phase lock — the ALU cannot self-promote
EDIT→SYNC. - TTL — capability expires; renewal is the engine’s job.
Exhaustion is a first-class terminal state, not a retry-forever condition. See §4.2.
Release: two-phase commit and receipts#
SYNC is not git merge with extra logging.
- Prepare: worktree is frozen; DoD re-run; merge-base checked; notes payload built.
- Commit: fast-forward or explicit merge onto the release branch; attach Ed25519-signed receipt to
refs/notes/agent. - Abort: any prepare failure leaves trunk untouched; worktree retained for forensics until operator GC.
Receipt fields (minimum): task key, DAG node IDs, model IDs, CapBAC remaining, AST gate hashes, test trace hash, worktree commit, operator/signoff identity, timestamp.
SHA-256 byte parity is required when SSoT docs are projected to public membranes. If the membrane diverges, the projection job fails closed.
3. Operating the two tiers#
3.1 Tier-1 orchestrator runbook#
Inputs. Backlog item or playbook fragment. GitBrain retrieval (subconscious notes), not a paste of the entire repo.
Actions.
- Deduplicate against open DAG nodes (
TRIAGE). Semantic clones are merged, not double-dispatched. - Lock contracts in
SPEC: schemas, acceptance predicates, out-of-scope list. Ambiguity that survives this phase becomes swarm thrash. - Emit a topological DAG, not a paragraph plan. Nodes are single-plane, single-package where possible. Edges are data/contract dependencies, not “then also.”
- Sign the plan. Unsigned DAGs are not schedulable. Signoff is a capability grant to Tier 2, not a vibe.
- After workers return:
REVIEWis maker-checker against the spec, not a second implementation. Dual-layer walkthrough is drafted here; the 39 DoD gates are evaluated by the engine, not by the model’s self-grade. - Release signoff is a HITL cryptographic gate on
nomos release ship. One-click in the cockpit is still a signature.
Forbidden. Direct multi-file mutation. Expanding scope mid-flight without a new PLAN. Feeding a worker the orchestrator’s full conversation.
Failover. If the frontier model is unavailable, fallback in config.yaml applies. Planning quality may drop; do not compensate by giving Tier 2 a longer leash.
3.2 Tier-2 swarm runbook#
Inputs. A single DAG node, the spec slice, the allowlisted paths, a failing test or a test to write first.
Protocol (TDD, non-optional).
- Claim worktree
worktrees/<repo>-<task>. If claim fails, do not create a second tree. Exit. - Write or restore a reproducer. Red before green.
- Mutate the minimum AST that turns the trace green without violating complexity/doc/import gates.
- Run the package tests and the DoD subset for touched files.
- Emit a patch + telemetry. Do not merge. Do not delete the worktree. Do not “clean up” unrelated files.
Retry. max_t2_retry_attempts: 3. Each attempt must change the hypothesis (different failing assertion, different function), not repeat the same patch. After N, escalate with structured failure: last diffs, gate IDs, token/step remaining, worktree path.
Forbidden. Touching trunk. Spawning child swarms. Reading sibling worktrees. Pulling extra files “for context.” Raising own CapBAC.
Local silicon (e.g. Gemma/Qwen-class on a single 4080-class GPU) is the economic default for code_generation. Frontier flash models may be used for adversarial_audit because red-teaming is cheaper than a bad merge. That is an inference-cost decision encoded in YAML, not an operator whim at 02:00.
3.3 Escalation ladder#
T2 EDIT
→ DoD fail × N
→ circuit_breaker_on_dod_fail
→ auto_escalate_to_tier1
→ T1 re-PLAN or reject
→ Human PO if contract is wrongEscalation payload is telemetry, not a chat apology. If the PO changes the contract, the DAG is rewritten and workers are re-dispatched. In-place “keep going” is how runaway loops start.
4. Failure modes (the ones that page you)#
These are not hypothetical. They are the residual of inverted control when operators or bugs re-open a hole.
4.1 Runaway swarm loops#
Symptom. Token burn without DAG progress. Same worktree commit hash oscillating. Test file rewritten, production file reverted, repeat. Cockpit phase stuck in EDIT with rising turn count.
Mechanism.
- Retry policy coupled to “model said it needs one more try” instead of gate IDs.
- Plan node too coarse: one worker owns parser + UI + migration.
- Hidden cycle in the DAG (A waits on B’s type; B patches A’s fixture).
- Worker treats flaky tests as a license to mutate timing, sleeps, or global config.
Detection.
- Step budget monotonic decrease with unchanged
git rev-parse HEADin the worktree across ≥ 2 turns. - Gate failure signature (tuple of rule IDs) repeats.
- Cyclomatic complexity increases while tests stay red.
- Fan-out: worker process count > claimed DAG nodes.
Containment.
- Engine kills the capability (TTL / step = 0). SIGTERM the worker; SIGKILL after grace.
- Freeze the worktree. Do not GC yet.
- Circuit-break the node; mark
ESCALATED. Sibling nodes that do not depend on it may continue. - T1 inspects the frozen tree. If the node was mis-sliced, split the DAG. If the contract is wrong, revert to
SPEC. - Ban “continue from last prompt.” New invocation, new CapBAC, same worktree or a replacement claim—never both.
Prevention. Acyclicity check on nplan output. Node size caps (files, packages, planes). Flaky tests are infrastructure bugs: quarantine the test, do not give the swarm a longer timeout.
4.2 CapBAC budget exhaustion#
Symptom. Worker exits BUDGET_EXHAUSTED mid-file. Partial writes. Orchestrator “helpfully” reissues a larger envelope. Monthly inference cliff.
Mechanism.
- Unbounded retrieval: GitBrain dump + full-file reads + tool traces stuffed back into the next prompt.
- Tier mismatch: T2 using a frontier model with chat-sized context “because quality.”
- Missing step accounting on tool failures (retries that do not debit).
- Human override of budgets without recording a new capability grant.
Detection.
- Envelope remaining ≤ 0 with phase ≠ terminal.
- Prompt tokens / unique AST nodes touched → ∞ (context bloat ratio).
- Repeated
fallbackpromotions in one task. - Cost counter (not just tokens) exceeding the task’s declared class (
cli:lowvscli:high).
Containment.
- Exhaustion is success of the sandbox, not a bug to bypass. Leave partial state in the worktree.
- Do not auto-raise budgets. T1 may grant a new envelope with a written reason in the task log. Same ceiling class unless the sizing in
TRIAGEwas wrong—then re-triage. - Strip context: workers receive the spec slice, the failing test, and the allowlisted files. Not the orchestrator transcript.
- If exhaustion occurs before a red test exists, the node was not ready for
EDIT. Return toPLAN.
Prevention. Ephemeral prompts. CapBAC debit on every tool call including failures. Sizing in TRIAGE is an economic act: cli:low maps to local T2; cli:high still does not mean unlimited.
4.3 Worktree leaks#
Symptom. Disk fills with worktrees/<repo>-*. Nested worktrees. Workers writing into a stale tree after SYNC. Trunk dirty because a hook was --no-verify’d. Ghost processes holding locks on pruned paths.
Mechanism.
SYNCmerge succeeded; prune skipped on error and never retried.- Double claim: two workers, one task key, two trees; the loser keeps running.
- Operator
git worktree addby hand “to peek,” then a swarm claims the same branch. - Crash between prepare and abort in 2PC; tree left dirty, lock file lost.
- Pre-commit hook not installed on a clone (laptop, CI sidecar).
Detection.
git worktree listentries whose task keys areSYNC’d orIDLE.- Age > TTL with no live PID in the substrate process table.
- Worktree branch still present on origin after receipt notes exist.
- Divergence: worktree
HEADnot an ancestor of the claimed task commit innomos_global.db.
Containment.
- Stop dispatch globally if leak count exceeds a hard cap (disk is a shared-memory analog here).
- Reconcile: DB claim vs
git worktree listvs process table. Unclaimed trees → quarantine dir, notrm -rfuntil hashed for forensics. - Reinstall hooks (
nomos doctor). RefuseEDITon clones that fail hook presence. - 2PC abort path must be idempotent: prune is allowed after crash recovery.
Prevention. Single claim table in SQLite (nomos_global.db) as SSoT for trees. Workers cannot worktree add. GC is a substrate daemon job, not a Friday script. Releases attach receipts before prune; prune failure pages, merge does not roll back if already committed—repair forward.
4.4 Context contamination#
Symptom. Worker implements a discarded alternative from a PLAN brainstorm. Secrets from a sibling task appear in a patch. Membrane CSS “fixes” land in a Go parser change. Tests assert yesterday’s schema. Reviewer model praises a plan that is not the signed one.
Mechanism.
- Shared conversation memory across tiers and tasks.
- GitBrain retrieval without task-scoped filters (subconscious is not a dump).
- Copying
.envornomos_global.dbinto the worktree “so the agent can see state.” - Prompt that includes the full DAG, all specs, and prior failures “for completeness.”
- Membrane/Intent/Substrate files in one allowlist because the repo is a monorepo.
Detection.
- Diff touches files outside the node allowlist (engine must treat this as firewall, not a warning).
- String match of other task keys, secrets patterns, or discarded plan headings in the patch.
- Import boundary gate fail (the useful case of contamination).
- Review text cites node IDs not in the signed DAG.
Containment.
- Drop the invocation. Invalidate the prompt cache for that task.
- Rotate any secret that entered a prompt or a patch. Assume LLM logs are hostile storage.
- Re-dispatch with a sterile envelope: signed spec hash, allowlist, reproducer. No chat history.
- If contamination crossed planes, split the repo-facing task and add a gate test that would have caught the import.
Prevention.
- Ephemeral ALU: no durable session across phases.
- Retrieval is query-shaped and hashed into the receipt (what was retrieved, not the raw blob in the merge).
- Allowlists are plane-aware. Monorepo ≠ mono-context.
- Vault (conscious contract) vs GitBrain (subconscious index): workers do not get the Vault; orchestrators do not dump GitBrain wholesale.
- Spectator / cockpit streams are outbound telemetry. Do not pipe them back into the next prompt.
Contamination is the quietest of the four. Loops and leaks page you. Contamination ships.
5. Cognitive Firewall and hermetic claims#
The firewall is the union of Git hooks, phase locks, and CapBAC:
- Trunk is not a workspace.
- Phase determines write rights, not the model’s self-description.
git commiton a protected branch from a swarm identity is rejected at hook, not at code review.--no-verifyis an operator-level incident if used to land swarm output.
Claim protocol:
CLAIM(task) → insert worktree row (unique task key)
→ git worktree add worktrees/<repo>-<task>
→ issue CapBAC(path=that prefix, steps, tokens, ttl, phase=EDIT)
→ run T2
→ RECORD(diff, gates, budgets)
→ RELEASE claim only after SYNC prune or operator quarantineDouble-claim is a unique-index failure, not a retry.
6. Observability that is not entertainment#
A single terminal cannot serialize T1 planning, N T2 loops, and 39 gates. Operators who try will approve the wrong thing.
Minimum operational picture:
- Phase lock per task (
IDLE…LEARN), not a chat status. - Worktree path + HEAD and whether it matches the claim table.
- CapBAC remaining (tokens, steps, TTL).
- Last gate vector (which DoD IDs failed).
- DAG: runnable, blocked, escalated, signed.
- Glass-box trace: tool calls and AST diagnostics, stored as telemetry, not automatically re-injected as prompt.
HITL steering is cryptographic command: approve plan, approve ship, abort 2PC, raise (or refuse) a new envelope. It is not “send another message to the swarm.”
Public spectator feeds are projections with SHA-256 parity. They are not control surfaces.
7. Economics without folklore#
Inference cost is dominated by retries and context, not by “which logo is on the model.”
- Default
EDITto local T2. Escalate models only on circuit breaker, not on anxiety. cli:low/cli:highinTRIAGEis a budget class. Changing class mid-task is a re-triage.- A failed DoD after three local attempts is cheaper than one unbounded frontier session that merges.
- GitBrain exists so you do not pay to re-read the repository. Abuse of GitBrain (dump) inverts that.
Track per-task: envelopes issued, tokens, wall time, gate-fail signatures, worktree-hours (disk × time). If worktree-hours grow faster than merged receipts, you have a leak or a loop—usually both.
8. Operator checklist (pre-dispatch)#
- Task is single-plane and allowlisted.
- Spec hash recorded; acceptance is executable (tests or schema).
- DAG acyclic; each node has a package/path bound.
- Hooks present; trunk dirty check is clean.
- Claim table has no stale row for this key.
- CapBAC class matches
TRIAGEsize. - No shared session memory from another task.
- GC/TTL daemons alive.
Post-SYNC:
- Receipt note present, signature verifies.
- Worktree gone or quarantined with reason.
- Membrane projection parity if docs changed.
-
LEARNindexed; failure signatures filed (the next loop’s detector).
9. What you do not do#
- Do not let the model choose its tier, budget, or phase.
- Do not merge from a worktree that failed any DoD ID.
- Do not keep a “debug” swarm on trunk.
- Do not feed cockpit transcripts back into workers.
- Do not treat escalation as a personality conflict. It is a scheduler event.
- Do not write exploits, bypass hooks, or disable gates “temporarily” to land a demo. Temporary is how contamination becomes architecture.
The substrate is an OS for agents: isolation, capabilities, a kernel that does not take advice from user space about whether the trap handler should fire. Operate it that way. The ALU is fast. The proof is the job.