Skip to content

Spec: agent-scale architecture — many agents, one main

SPEC Binding system contract
Status: IMPLEMENTED
Status note: slices A–L are held as of 2026-07-11: advisory activity and shared
worktree/run state; package-aware fast/land verification; generated ledgers;
worktree bootstrap/prune; corpus engine; hidden-window Bevy evidence;
heartbeats; spec-owned work-order metadata plus generated ROADMAP status;
human/JSON project status; dispatch-consistency fixtures; deterministic
agent scenarios; safe task lifecycle wrapper with serialized final landing;
read-only environment doctor; and tick intake memory/briefing.
Stage: Process
Work order: project-operations
Work priority: 5
Work class: process
Blocked by: none
Exclusive keys:
- tools/
- wiki/process/
- crates/misaligned-bevy/
Design:
- wiki/vision/simulation-laws.md#justification-and-legibility
- wiki/process/living-spec.md#the-corpus-rule
- wiki/process/meta.md#change-rule
Depends on:
- wiki/engineering/crate-workspace.md#spec-crate-workspace-core-terminal-bevy-assets
- wiki/engineering/env.md#spec-the-environment-variable-registry-every-switch-documented
- wiki/process/workflows.md#workflows
- wiki/process/ROADMAP.md#roadmap-the-dispatch-board

The structured references above identify the contracts to re-verify. Relationship context:

engineering/crate-workspace.md (target package shape; implement as its own work order), engineering/env.md (new tool env vars register there), process/workflows.md (knowledge — keep in sync when gates change), process/ROADMAP.md (knowledge — dispatch board and conflict flags).

Misaligned is built by many agents in parallel. The corpus-first rule and worktrees are right; the coordination architecture was thin, so agents collided on the same files, ran maximum gates at once, union-merged the same ledgers, and filled the disk with private Bevy target/ trees. Speed of check.sh matters; who may work on what and what “done” means matter more.

This spec is the standing process law for that scale. Implement criteria as independent landings unless a criterion names another as a hard prereq.

Layer Decision
Task visibility Work orders advertise likely paths; overlap warns but never blocks isolated worktrees
CPU mutex At most one full Rust gate on the machine (lock — already in check.sh)
Landing mutex One short task-finish lock covers final rebase, land gate, merge, and push
Verification Mid-loop narrow tier; once land gate; CI fast corpus plus change-aware Rust
Ledgers Append-only uniquely named files; generated indexes, not hand-edited tips
Disk Private package outputs per worktree; shared dependency cache only
Packages Workspace shape in crate-workspace.md (deferred)
Docs gates Fast, testable Python corpus/wiki engine with fixture contract
Bevy evidence Headless/offscreen land proof, not only interactive windows
Observability Heartbeat files so “is it stuck?” is answerable
Dispatch truth Specs own structured work metadata; ROADMAP receives a generated live index
Project status One human/JSON command combines work orders, activity, runs, worktrees, issues, and freshness
Scenario evidence Deterministic agent scripts emit transcripts and machine-readable evidence
Task lifecycle One safe wrapper starts, checks, lands, or abandons a metadata-backed work order
Environment One doctor command reports missing required and optional local capabilities

Before an agent starts repository work that can edit code or binding wiki pages, it records work-order activity:

  • id — worktree/task name (e.g. dark-frame)
  • class — docs | frontend | sim | save | process
  • exclusive_keys — legacy metadata name for likely edit surfaces (for example crates/misaligned-bevy/src/main.rs, crates/misaligned-core/src/save.rs, or a binding wiki page). These paths are collision information, not ownership.
  • status — claimed → checking → landing → done, with blocked and abandoned available when the task is not progressing.

A second activity record whose paths intersect an active task warns and proceeds. Worktrees isolate edits; Git exposes textual collisions; the corpus, tests, and landing agent resolve semantic collisions against current main. Hard path exclusion was retired after it created a wait cycle on 2026-07-10: action-contract held main.rs while waiting for camera-exposure work that needed the same file. A blocked task must never stop its own prerequisite.

Only scarce phases are serialized: the machine-wide Rust gate and the short task.sh finish landing phase. The ROADMAP rule of at most one sim+save-heavy work order remains an explicit dispatch constraint rather than a path lock.

Storage keeps the historical machine-local gitignored path .agents/claims/<id>.claim (override with MISALIGNED_CLAIMS_DIR). The claim.sh filename and claim verb are compatibility interfaces for older worktrees; activity is the current verb. Discover with tools/claim.sh list. Holder pid is the calling agent/shell (PPID, or MISALIGNED_CLAIM_PID). Dead holders are reaped on the next claim/list. No Tangled auth required.

Terminal window
tools/claim.sh activity dark-frame --class frontend --key crates/misaligned-bevy/src/main.rs \
--key wiki/interface/material-dark-frame.md
tools/claim.sh list
tools/claim.sh set-status dark-frame blocked # visible, never a path lock
tools/claim.sh set-status dark-frame checking # or landing
tools/claim.sh release dark-frame # done (or --status abandoned)
# bundled with worktree create/remove:
tools/worktree-new.sh dark-frame --class frontend --key crates/misaligned-bevy/src/main.rs
tools/worktree-done.sh dark-frame

ROADMAP conflict flags (🟥 sim+save / 🟧 sim / 🟩 isolated) remain the dispatch index. Path activity predicts reconciliation cost; it does not prohibit work.

Acceptance criteria (slice A) — HELD 2026-07-09

Section titled “Acceptance criteria (slice A) — HELD 2026-07-09”
  1. A documented activity procedure exists (tool or script + AGENT.md / prompts pointer) that records class + likely paths + status.
  2. Attempting a second overlapping activity succeeds with a warning naming the other task and paths; it never creates a wait dependency.
  3. blocked is representable, and completing or abandoning work clears its activity record.
  4. ROADMAP or workflows.md states that agents inspect overlaps, work in isolated worktrees, and reconcile at landing.
Phase When What
Fast / mid-loop After an edit Narrowest ./tools/check.sh mode that can catch the edit (--docs / --lib / --frontend or auto)
Land Once before merge to main One green auto or --full under the rust lock when Rust is involved
CI: corpus Every push / pull request Python fixtures, corpus/wiki gates, generated-index freshness
CI: Rust Rust-impacting pushes; every pull request / manual run four timeout-bounded shards: core/terminal + scenarios, Bevy tests, Bevy Clippy, Bevy build

Already held (2026-07-09): path auto-classification, --docs|--lib|--frontend|--full|--land, rust gate lock, parallel docs gates, collapsed agent smoke. Package-aware classification waits on crate-workspace.md.

Tangled runs two contracts. .tangled/workflows/corpus.yml is the fast always-on corpus job and explicitly installs its fixture dependencies, including python3, GNU grep, and GNU sed; it runs workflow-manifest, design-amendment, corpus, and project-operations fixtures before the real corpus/wiki/index checks. The Rust contract is four workflows with the same native push paths inventory over the knot-provided changed-file set for the complete ref update: check.yml owns format, core/terminal tests and Clippy, and executable agent scenarios; the three check-bevy-*.yml shards own Bevy tests, Clippy, and the normal build separately. That avoids both the former shallow HEAD^ classifier and the later failure mode where a cold cargo test --workspace consumed Spindle’s entire workflow deadline before Clippy or the build started. Pull-request and manual triggers deliberately run all four shards. Tangled has no scheduled trigger, so the manual trigger is the supported full safety run instead of pretending a scheduler exists.

Each Bevy shard establishes identical Linux pkg-config shims through tools/ci-pkg-config.sh before its one expensive Cargo command. Linked Bevy tests and the binary build use the explicit ci Cargo profile: the same targets, features, debug assertions, and overflow checks as development, but without the interactive profile’s dependency optimization, debug symbols, or incremental-cache overhead. This keeps cold code generation inside the hosted deadline; Bevy Clippy continues to use the development profile because it does not perform that linked codegen and is independently deadline-green. The local hook and server-side design gate call one tools/design_amendment_gate.sh; the local path reads binding page types from the staged snapshot, and both paths treat source deletions as source changes. Workflow fixtures pin the shard inventory, CI profile, ordering, dependencies, path filters, commands, and shared-policy contracts.

  1. AGENT.md and prompts/README state mid-loop narrow vs one land gate — HELD.
  2. ./tools/check.sh --land is an alias for the land policy (auto) documented in workflows.md — HELD.
  3. After crate-workspace.md lands, classification keys off packages, not only path prefixes — HELD (check.sh classifies core/terminal vs Bevy/assets package paths).
  4. Docs failures run in a small Python-enabled workflow before expensive Rust work; non-Rust pushes skip the Rust wall, while pull-request/manual runs are full — HELD.
  5. No hosted Rust workflow contains more than one cold Bevy Cargo command; core/terminal and each Bevy proof complete in independently bounded shards, with linked test/build codegen using the cold-start ci profile — HELD.

Defense: the native ref-update path filter implements the proportional-gate contract without trusting a two-commit clone to describe an arbitrary push. The shared amendment gate implements the living-spec same-commit rule from the actual candidate tree (the index locally, checked-out commit in CI), while the timeout-bounded Bevy shards, cold-start profile, shared pkg-config setup, and executable scenarios make the advertised Rust wall an honest product gate rather than a guaranteed deadline/setup failure or syntax-only smoke.

3. Append-only ledgers and generated indexes

Section titled “3. Append-only ledgers and generated indexes”

Shared tip-of-file ledgers (wiki/log/DEVLOG.md hand edits, hand-maintained rows in wiki/process/specs.md when avoidable) cause every parallel land to rebase-union the same lines.

Target:

  • Session / devlog bodies remain uniquely named files (wiki/log/YYYY-MM-DD-topic.md) — already the habit.
  • DEVLOG ledger becomes a generated index (or a strict append-only convention with one tool that inserts a block without rewriting others’ entries). Preferred: generate DEVLOG.md from session log headers / frontmatter so agents never edit the index by hand.
  • specs.md status table is generated from each spec page’s Status: frontmatter (and title), or updated only by a tool that rewrites the whole table from the tree. Agents do not hand-merge table rows.
  • Decision volumes stay dated append-only files (already).

Union-merge rules in AGENT.md remain the safety net until generation lands; after generation, the generator is the source of truth for the index file.

Terminal window
# after adding wiki/log/YYYY-MM-DD-topic.md and/or amending a Type: spec:
tools/ledger_index.sh # rewrite DEVLOG.md + specs.md
tools/ledger_index.sh --check # fail if indexes stale (wired into check.sh)

Session bodies stay uniquely named. Indexes are fully generated; last writer wins on the index files only. ./tools/check.sh docs path runs --check. Both projections declare Generated: tools/ledger_index.sh in their metadata, making their replaceable status machine-readable instead of relying on prose.

Acceptance criteria (slice C) — HELD 2026-07-10

Section titled “Acceptance criteria (slice C) — HELD 2026-07-10”
  1. A tool can regenerate the DEVLOG index and specs board from the tree without manual row editing — HELD (tools/ledger_index.sh).
  2. AGENT.md / prompts say: add a uniquely named log file; run the generator; do not hand-edit generated indexes — HELD.
  3. Parallel landings no longer require hand union of DEVLOG rows — HELD (unique session files + regenerate).

4. Worktree bootstrap, dependency cache, prune

Section titled “4. Worktree bootstrap, dependency cache, prune”
Terminal window
tools/worktree-new.sh <task> --class frontend --key crates/misaligned-bevy/src/main.rs
# -> .Codex/worktrees/<task> on worktree-<task>, seed-cargo-target, activity
tools/worktree-done.sh <task> # clear activity, rm target/, remove worktree

.Codex/worktrees is the one repository default for every agent surface. Operators that need another location set MISALIGNED_WORKTREE_ROOT for both helpers; tool brands do not define separate roots. Activity reaping also checks Git’s registered worktree basenames, so a task created by a harness-native Letta or Claude worktree remains durable even when its short-lived creator PID has exited and it lives outside .Codex/worktrees.

Shared dependency compilation cache (sccache or cargo cache) remains recommended; local package artifacts stay per-worktree. Never share one CARGO_TARGET_DIR across worktrees for local packages.

Acceptance criteria (slice D) — HELD 2026-07-09 (helpers; sccache optional)

Section titled “Acceptance criteria (slice D) — HELD 2026-07-09 (helpers; sccache optional)”
  1. A single documented command creates a seeded worktree — HELD (tools/worktree-new.sh).
  2. workflows.md documents private-target rule; sccache is optional install guidance — HELD (private target); sccache install remains operator choice.
  3. Removing a worktree prunes target/ by default via tools/worktree-done.sh — HELD.

tools/corpus_engine.py is a one-pass Python engine (indexes pages and headings once). Entry points stay stable:

Terminal window
tools/wiki_gate.sh # python3 tools/corpus_engine.py --wiki
tools/corpus_gate.sh # python3 tools/corpus_engine.py --corpus
tools/test_corpus_engine.sh # fixture suite (also in check.sh docs path)

Requires python3 (same as ledger_index.sh). Any page with a valid Generated: metadata owner is skipped for link checks so a derived, truncated blurb cannot false-fail; the corpus gate verifies that the generator target exists. The same pass rejects the retired live-runtime symbols submit_ops_job, OperationsState, PendingOpsJob, and OpsJobKind from Rust source and comments. Binding law/spec pages may name those exact symbols only inside a paragraph that explicitly marks the reference former, retired, legacy, superseded, historical, migrated, no longer live, or removed. Legacy- prefixed save migration types remain valid because they are distinct symbols.

Acceptance criteria (slice E) — HELD 2026-07-10

Section titled “Acceptance criteria (slice E) — HELD 2026-07-10”
  1. Docs gates complete in well under a few seconds on a quiet machine — HELD (~0.3s each for wiki + corpus on ~200 pages).
  2. Fixture tests cover reachability, links, Design and dependency anchors, exact roles/status, generated owners, doorway bounds/structure, and retired authority/runtime wording, including explicit historical and migration exemptions — HELD (tools/test_corpus_engine.sh).
  3. Existing hooks and the fast CI workflow still call tools/wiki_gate.sh and tools/corpus_gate.sh — HELD.

Frontend land confidence must not require a human watching a window. Extend the shot harness (already env-driven) so land-tier frontend work can assert:

  • process exit 0
  • fog-audit (or successor) pass
  • optional frame metric / hash stability where deterministic

Document the required MISALIGNED_SHOT=… (or successor) for “seen running” on Bevy-impacting landings.

Acceptance criteria (slice F) — HELD 2026-07-10

Section titled “Acceptance criteria (slice F) — HELD 2026-07-10”
  1. A documented headless/offscreen path produces pass/fail without manual window interaction.
  2. workflows.md “definition of done” for Bevy cites that path.
  3. --frontend gate optionally invokes a cheap harness smoke when Bevy sources change (time-bounded; not a full art review).
Terminal window
tools/heartbeat.sh start <id> --worktree path --phase boot
tools/heartbeat.sh phase <id> 'check-land'
tools/heartbeat.sh end <id> --status ok|fail

Writes gitignored .agents/runs/<id>/status.json + events.ndjson (override MISALIGNED_RUNS_DIR). Humans and other agents inspect status files instead of empty stdout pipes. Dispatch prompts should call start/ phase/end around long runs (implement-gap contract).

Acceptance criteria (slice G) — HELD 2026-07-09 (tool + docs)

Section titled “Acceptance criteria (slice G) — HELD 2026-07-09 (tool + docs)”
  1. Documented convention + helper to start/update a run record — HELD.
  2. implement-gap / prompts path documents heartbeat use — HELD (prompts README + implement-gap pointer; agents still must call the tool).
  3. Paths are gitignored; convention is written in process docs — HELD.

8. Structured work orders and project status

Section titled “8. Structured work orders and project status”

Every non-IMPLEMENTED spec is a dispatchable or intentionally staged work order and declares the structured fields owned by meta.md: task slug, priority, work class, hard blockers, and likely edit surfaces (the legacy Exclusive keys field). Status: remains the lifecycle source of truth; the work-order fields must not duplicate it.

tools/work_orders.py validates that metadata and generates the live work-order region of ROADMAP. Human rationale and historical dispatch prose remain hand-authored outside the generated region. The generated projection is replaceable and may never become a second owner of status or dependency law.

tools/project-status.py is the one answer to “what is happening now?” It combines, without mutating the repository:

  • ready, active, blocked, and staged work orders in priority order;
  • advisory activity and heartbeat/run state;
  • every worktree’s dirty, ahead/behind, branch, and last-commit age;
  • Tangled decision issues when the local CLI is available/authenticated;
  • generated-index freshness and a short recommended-next-lane read.

Human output is concise. --json emits the same facts for agents and other tools. Network/auth failure degrades the issue field to unavailable; it does not make local project status unusable.

Acceptance criteria (slice H) — HELD 2026-07-10

Section titled “Acceptance criteria (slice H) — HELD 2026-07-10”
  1. Every non-IMPLEMENTED spec carries valid work-order metadata, and malformed class, priority, blocker reference, task slug, or advertised path fails the docs gate.
  2. One generator writes/checks a clearly marked live region in ROADMAP from those fields; changing a spec status or blocker without regeneration fails.
  3. tools/project-status.py reports work orders, activity, runs, worktrees, optional decision issues, and generated freshness in both human and JSON forms without changing repository state.
  4. At least one fixture proves a blocked spec cannot be recommended as the next lane and a later-stage READY spec remains staged rather than becoming an accidental B1 dispatch.

The docs path runs a fast project-operations consistency check alongside the corpus and wiki checks. It enforces facts that are mechanical enough to prove:

  • work-order metadata and anchored blocker targets are valid;
  • generated dispatch output is fresh;
  • explicit status labels in ROADMAP agree with the owning spec;
  • an [OPEN] section does not retain entries explicitly marked resolved;
  • an acceptance criterion cannot remain locally marked blocked on an issue that the same current page declares closed;
  • advertised path keys exist, so retired workspace paths cannot silently remain dispatch locks.
  • fresh activity, heartbeat/run, and registered-worktree records agree: an active run cannot lose its activity record or point at no worktree, and a claimed/checking/landing activity must name a registered worktree. Stale historical runs degrade to warnings so one abandoned heartbeat cannot brick the repository forever.

Semantic design disagreements still belong to ticks; the gate must not guess at meaning from ordinary prose. Network-only issue truth is shown by project status and is never required by CI.

Acceptance criteria (slice I) — HELD 2026-07-10

Section titled “Acceptance criteria (slice I) — HELD 2026-07-10”
  1. The local docs gate and fast corpus CI call the same consistency command (tools/project-status.py --check --offline); JSON and human status expose the identical consistency errors and warnings.
  2. Fixtures pin every rule above, including useful file/line diagnostics.
  3. Existing current-corpus violations are repaired before the gate is enabled; dated logs remain untouched even when they preserve superseded language.

Checked-in scenarios/*.agent files are agent-protocol command scripts with declarative EXPECT / REJECT assertions. tools/scenario.py builds or uses the terminal binary, runs a scenario twice with the same seed, rejects output drift or ANSI, checks its assertions, and writes an optional evidence bundle: the input, transcript, SHA-256 digest, and JSON result.

The initial suite covers the opening read, machine delegation/routing, and the general project smoke path. Scenarios prove semantic state and narration; they do not use pixel-perfect screenshots as game-law assertions. A scenario may name a Bevy shot kind as additional visual evidence, routed through the headless harness in slice F.

Acceptance criteria (slice J) — HELD 2026-07-10

Section titled “Acceptance criteria (slice J) — HELD 2026-07-10”
  1. Scenario definitions are validated by the docs gate without compiling Rust; malformed directives or missing quit fail fast.
  2. A normal run is deterministic for the same seed, checks every declared assertion, and exits nonzero with a focused diff/error on failure.
  3. --output DIR writes a transcript, copied command source, digest, and JSON summary; a no-output run leaves the tree clean.
  4. At least three checked-in scenarios cover project smoke, opening/senses, and machine-work delegation.

tools/task.sh composes the existing helpers; it does not replace their contracts:

Terminal window
tools/task.sh start wiki/process/agent-scale.md
tools/task.sh status
tools/task.sh check project-operations
tools/task.sh finish project-operations
tools/task.sh abandon project-operations

start resolves task/class/likely paths from the spec and creates the seeded worktree with an activity record. check moves activity/heartbeat to checking and runs the land gate. finish requires a clean committed branch, rebases onto current origin/main, reruns the land gate, fast-forwards a clean current primary checkout, pushes main, ends the heartbeat, and removes the task worktree. One machine-local landing lock serializes that final rebase/check/merge/push window. A failed rebase, check, relationship check, or push stops before cleanup. It never commits, stashes, resets, force-pushes, or guesses through a conflict. abandon uses the existing clean-worktree refusal unless explicitly handled by the operator through the lower-level helper.

tools/doctor.sh performs read-only checks for required commands, Cargo workspace health, Tangled context/auth availability, worktree/activity state, private-target safety, and optional accelerators such as sccache and the headless display path. Required failures exit nonzero; optional absences are plain warnings.

Acceptance criteria (slice K) — HELD 2026-07-10

Section titled “Acceptance criteria (slice K) — HELD 2026-07-10”
  1. The wrapper starts a task using only its spec path and metadata and exposes the same project status command.
  2. Check/finish update activity and heartbeat phases, and finish refuses dirty, uncommitted, divergent-primary, failed-gate, or rejected-push states before deleting anything.
  3. Abandon preserves dirty work by default.
  4. The doctor distinguishes required failures from optional warnings and has a no-network mode suitable for CI or offline work.
  5. Shell syntax/fixture tests exercise argument handling and the destructive refusal paths without creating or pushing a real task branch.
  6. The final landing lock refuses a live holder, reaps a dead holder, and cannot be confused with advisory path activity.

Ticks keep persistent state between beats so each one stops re-deriving context, re-picking slices blind, and discarding surplus discovery. The state lives in wiki/process/tick-ledger.md (a coverage table and a findings queue, hand-edited by ticks per wiki/process/tick.md); the intake command is tools/tick-brief.sh, which emits recent commits, activity, project status, Tangled decision labels, the findings queue, and the stalest coverage rows in one shot. Intake order is fixed: harvest decision-made issues, then the findings queue, then a fresh audit of the stalest slice. Recurring mechanical finding classes are promoted into tools/corpus_engine.py checkers rather than re-found by ticks.

Acceptance criteria (slice L) — HELD 2026-07-11

Section titled “Acceptance criteria (slice L) — HELD 2026-07-11”
  1. tools/tick-brief.sh runs from a task worktree and degrades each unavailable section (missing tang, missing helper) to a note instead of failing the brief.
  2. The coverage table renders stalest-first in the brief, and a quiet tick can record a clean verdict as its trace.
  3. The findings queue holds one-line surplus findings that a later tick can take, re-verify, and act on; taking an entry deletes the line.
  4. wiki/process/tick.md step 0 and the tick skill shims reference the brief and the queues, so a fresh audit is the fallback rather than the default.

crate-workspace.md is the package work order. This page is the coordination and tooling work order. They reinforce each other but land separately. The crate workspace has landed; project-operations metadata and checks therefore use current crates/ package paths and reject retired monorepo activity paths.

Implementation order (suggested, not binding)

Section titled “Implementation order (suggested, not binding)”
  1. Slice A (activity) — makes likely collisions visible without blocking work
  2. Slice C (ledgers) — stops landing union theater
  3. Slice D (worktree bootstrap/prune) — disk
  4. Slice B leftovers + G (land alias, heartbeats) — agent behavior
  5. Slice E (corpus engine) — docs gate latency
  6. Slice H + I (structured work/status and consistency) — dispatch truth
  7. Slice J (scenarios) + F (headless Bevy) — reproducible evidence
  8. Slice K (task lifecycle + doctor) — one safe operator doorway
  9. Slice L (tick intake memory/briefing) — tick economics: queues before fresh discovery
  • Only faster check.sh — necessary but insufficient without visible activity, landing serialization, and ledger shape.
  • Shared CARGO_TARGET_DIR across worktrees — false-green risk.
  • CI as the only gate — local loop must stay honest.
  • One mega-agent that owns main — rejects the multi-agent premise of the repo.