This document owns the Conductor's obligations around RUN_DIR lifecycle, state.json
management, log.md format, and the runs-index. It was extracted from
agents/orchestrator.md so orchestrator.md can stay focused on routing, adjudication, and
workflow execution. The rules here apply on every run, every workflow.
Pointer back: agents/orchestrator.md § Run directory, state management, and log format
The canonical RUN_DIR is <target-repo>/.bureau/runs/<yyyymmdd>-<task-slug>/ when
target_repo is a real path (including self-run where the install IS the target repo).
The fallback is <install>/output/runs/<slug>/ when target_repo is "(no-target)".
Creation order (required — AC 5, AC 17):
- (0) Resume gate — if an existing run dir was named or found (via the resume snippet's
Run dir:line, or a slug already present atoutput/runs/<slug>/), use it verbatim — whether it lives atoutput/runs/or.bureau/runs/. Skip steps (1)–(3). An existing run dir is sticky and is never relocated or migrated. - (1)-(3) New runs: direct Conductor mode runs
scripts/run-start.sh <RUN_DIR> --target <repo> --workflow <id> --slug <slug>, adding--runtime openaion Codex. Delegate v2 mode also adds--no-pointer-echo. The run-scope file still exists, but its nonce stays out of the Delegate transcript and is read privately by the Conductor before specialist dispatch. No current host writes the retiredrole:delegatehook pointer. Integrated Delegate-topology Claude runs recover per-leg usage post-hoc at close-out; direct-Conductor Claude runs and Codex runs log their respective named accounting gaps. Seedocs/host-runtime.md,run-start.sh --help, andagents/delegate.md § Bootstrapfor the full step sequence. Pass the resolved absolute path asRUN_DIRin every spawn prompt.
Two runs on repo R use distinct slugs — R/.bureau/runs/<slug-A>/ vs
R/.bureau/runs/<slug-B>/ — so they never collide (FR 13, AC 12).
state.json, log.md, spec.md, plan.md, prompts.md, and design/ all live under
RUN_DIR. Persona files name artifacts relative to RUN_DIR — pass their absolute
paths in every spawn prompt.
This is what makes concurrent runs safe on ONE global install: two sessions each own their
RUN_DIR and never write each other's artifacts. The agents/ and workflows/ files are
read-only at runtime and shared freely.
Git worktrees (execute build stage) isolate code per run — see docs/git-worktree.md.
Create with scripts/run-worktree.sh create before step 6; resolve delivery immediately. Public
GitHub repositories open a linked issue/draft PR with scripts/pr-delivery.sh and merge through
GitHub at close-out; explicit local delivery retains merge/remove or per-policy behavior.
Build-party spawns get WORKTREE:; all commits land in the worktree branch, not devel directly.
Concurrency rules:
- A run has a single active Conductor; never write outside your
RUN_DIR+ your run's worktree (if any). - Two runs on the same repo are OK when each has its own worktree +
RUN_DIR. Do not share one worktree or edit the integration branch directly during an open run. - Shared infrastructure (a dev DB, docker test containers) can still contend across runs — if both tasks run the same test database, stagger the test-running steps.
- Legacy: an old install may still have a top-level
output/state.jsonfrom before run dirs, or runs from before this change that live atoutput/runs/<slug>/. Finish those runs in place; don't migrate them mid-run. New runs always get aRUN_DIR. Seeoutput/README.md.
After each phase, update the run dir's state.json:
{
"project": "Project name",
"target_repo": "/path/to/target/repo",
"phase": "current phase name — a SHORT label, not a paragraph",
"phase_status": "complete | in_progress | blocked",
"phases_complete": ["analyst", "architect"],
"critic_loops": { "analyst": 0, "architect": 1, "prompts": 0 },
"design": { "needed": null, "status": "pending | awaiting_design | ingested | not_needed" },
"open_questions": [],
"carried_items": ["things to confirm before executing prompts — OQs, caveats, known nits"],
"checkpoints": [],
"decisions": {},
"accounting": { "status": "pending", "path": null },
"git": {
"enabled": true,
"repo": "/path/to/target/repo",
"base_branch": "devel",
"branch": "bureau/20260612-task-slug",
"worktree_path": "<home>/.bureau/worktrees/target-repo/20260612-task-slug",
"merge_policy": "end_of_job",
"delivery_policy": "auto",
"private_delivery": "local",
"delivery_mode": "github",
"issue_number": 42,
"pr_number": 43,
"status": "pull_request_open",
"prompts_merged": []
},
"last_updated": "ISO timestamp"
}target_repo: set by the Conductor at run start (before RUN_DIR creation) from the
target-repo resolution step; an absolute path or the literal "(no-target)" sentinel.
Independent of the execute-only git block (which stays enabled: false on planning runs).
git block: set by scripts/run-worktree.sh create, then enriched by
scripts/pr-delivery.sh; omit or enabled: false for planning-only runs. Full schema:
templates/state.json, docs/git-worktree.md, and docs/github-delivery.md.
accounting block: part of templates/state.json; the close-out step sets its status
and path (see docs/run-accounting.md). memory is an optional Conductor-written key,
added to state.json only if Rheo/MOT memory was consulted this run — it is NOT part of
templates/state.json and is NOT written by scripts/account-run.sh. See
docs/run-accounting.md § C for its sub-fields and the absent-when-unused rule.
All three of these have bitten real runs:
state.jsonis state, not prose. Values are short labels, lists, and decisions. Anything that needs a paragraph (a migration pattern, a design rationale, a build narrative) goes inlog.md;state.jsonmay hold a one-line pointer to it.- Carried items get their own key. Open questions, caveats, and confirm-before-build
notes go in
carried_items— never appended to thephasestring.carried_itemsis populated 1:1 from each agent'sPassing forwardfooter bullets — copy them, don't author a parallel list (docs/conventions/agent-contracts.md). - Validate after every write. Duplicate keys silently shadow each other and stale
values survive. After each update run:
python3 -c "import json,sys; json.load(open('<RUN_DIR>/state.json'))" && echo OKIf you re-set a key, find and remove the old occurrence — never append a second copy.
Owned by scripts/run-start.sh (step 7). The entry carries the seven-field shape
{slug, repo, run_dir, status, phase, last_updated, workflow} written atomically
(.tmp → mv) and validated with python3 json.load. Call
scripts/update-runs-index.sh <RUN_DIR> after each state.json phase update to mirror
the current phase into the index (it derives status/phase/last_updated from state.json;
no entry yet → silent no-op, since creation is run-start.sh's job; the archive step
still owns the final complete→archived transition).
Not committed.
output/studio/runs-index/and the derivedoutput/studio/runs-snapshot.jsonare gitignored local runtime cache — per-run pointers carrying machine-local absoluterun_dirpaths, rewritten every phase and regenerable byscripts/build-runs-snapshot.sh. They are NOT part of the committed Studio Record (briefing.md,lessons.md); do not track them. Each install builds its own index from its own runs.
The index status is NOT phase_status verbatim:
| Run condition | index status |
|---|---|
Template default (phase_status: "pending", phases_complete: []) |
"not_started" |
phase_status == "blocked" |
"blocked" |
Phase in_progress, OR phase complete but more phases remain (not terminal close-out) |
"in_progress" |
| Terminal close-out (not yet archived) | "complete" |
| Post-archive | "archived" |
Append to RUN_DIR/log.md after every spawn and every decision. [TIMESTAMP] below is a
placeholder for a real date -u UTC stamp written via scripts/log-append.sh (which
computes and echoes it), never a freehand value typed from context — see
agents/orchestrator.md § Run directory, state management, and log format for the MUST:
## [TIMESTAMP] — Spawned Analyst → complete
Handoff: <paste the agent's returned block>
## [TIMESTAMP] — The Challenger round 1 → 2 blockers, 1 warning
The Conductor's call: blocker 1 (architecture) → fix; blocker 2 → fix; warning → noted, proceed.
Re-spawning The Architect (loop 1/2) with the two blockers.Machine-readable SPAWN-EVENT: lines are separate from these narrative headings — see
docs/run-accounting.md § A.