# Spec: agent-scale architecture — many agents, one main ``` Type: spec Status: IMPLEMENTED Status note: slices A–K are held as of 2026-07-10: 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; and read-only environment doctor. 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 ``` ## Dependency notes 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). ## The problem this solves 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. ## Decided shape (summary) | 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](../engineering/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 | ## 1. Dispatch activity (advisory overlap) ### Behavior 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/.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. ```bash 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 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. ## 2. Verification phases (fast vs land) ### Behavior | 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 | fmt, tests, terminal + Bevy clippy/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](../engineering/crate-workspace.md). Tangled runs two contracts. `.tangled/workflows/corpus.yml` is the fast always-on corpus job and explicitly installs `python3`; it runs classifier and corpus fixtures before the real corpus/wiki/index checks. `check.yml` uses a two-commit clone and `tools/ci-rust-changed.sh` to stop after classification when a push cannot affect Rust. Pull-request and manual triggers deliberately run the complete Rust/Bevy safety gate. Tangled has no scheduled workflow trigger or native path filter, so the manual trigger is the supported full safety run instead of pretending a scheduler exists. ### Acceptance criteria (slice B) 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](../engineering/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. ## 3. Append-only ledgers and generated indexes ### Behavior 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. ### Behavior (landed) ```bash # 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 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 ### Behavior (landed helpers) ```bash tools/worktree-new.sh --class frontend --key crates/misaligned-bevy/src/main.rs # -> .Codex/worktrees/ on worktree-, seed-cargo-target, activity tools/worktree-done.sh # 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. 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) 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. ## 5. Corpus / wiki engine ### Behavior (landed) `tools/corpus_engine.py` is a one-pass Python engine (indexes pages and headings once). Entry points stay stable: ```bash 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. ### 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 wording — 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. ## 6. Headless Bevy land evidence ### Behavior 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 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). ## 7. Agent heartbeats ### Behavior (landed) ```bash tools/heartbeat.sh start --worktree path --phase boot tools/heartbeat.sh phase 'check-land' tools/heartbeat.sh end --status ok|fail ``` Writes gitignored `.agents/runs//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) 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 ### Behavior Every non-IMPLEMENTED spec is a dispatchable or intentionally staged work order and declares the structured fields owned by [meta.md](meta.md#work-order-metadata): 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 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. ## 9. Dispatch consistency gate ### Behavior 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. 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 1. The local docs gate and fast corpus CI call the same consistency command. 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. ## 10. Deterministic scenario evidence ### Behavior 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 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. ## 11. Task lifecycle and environment doctor ### Behavior `tools/task.sh` composes the existing helpers; it does not replace their contracts: ```bash 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 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. ## Relationship to the crate workspace [crate-workspace.md](../engineering/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) 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 ## Rejected alternatives - **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.