space-architect 7.1.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -0
  3. data/README.md +58 -246
  4. data/lib/space_architect/architect_project.rb +40 -180
  5. data/lib/space_architect/bug_report.rb +2 -1
  6. data/lib/space_architect/cli/architect.rb +69 -103
  7. data/lib/space_architect/cli/research.rb +1 -1
  8. data/lib/space_architect/cli.rb +9 -14
  9. data/lib/space_architect/harness.rb +97 -344
  10. data/lib/space_architect/pi/builder-guard.ts +103 -0
  11. data/lib/space_architect/research/mux.rb +47 -32
  12. data/lib/space_architect/research/pi_events.rb +70 -0
  13. data/lib/space_architect/research/renderer.rb +39 -39
  14. data/lib/space_architect/research/supervisor.rb +35 -14
  15. data/lib/space_architect/research.rb +1 -6
  16. data/lib/space_architect/session_sync/bin_path.rb +1 -1
  17. data/lib/space_architect/session_sync/cursor.rb +1 -1
  18. data/lib/space_architect/session_sync/plist.rb +1 -1
  19. data/lib/{space_core → space_architect}/version.rb +2 -2
  20. data/lib/space_architect.rb +1 -2
  21. data/skill/architect/SKILL.md +30 -25
  22. data/skill/architect/dispatch.md +137 -115
  23. data/skill/architect/research.md +14 -8
  24. data/skill/architect-research/SKILL.md +18 -11
  25. data/skill/architect-vocabulary/SKILL.md +3 -3
  26. metadata +20 -178
  27. data/exe/space +0 -13
  28. data/exe/src +0 -13
  29. data/lib/space_architect/cli/space.rb +0 -29
  30. data/lib/space_architect/cli/src.rb +0 -38
  31. data/lib/space_architect/dispatcher.rb +0 -25
  32. data/lib/space_core/atomic_write.rb +0 -21
  33. data/lib/space_core/cli/base_command.rb +0 -19
  34. data/lib/space_core/cli/build.rb +0 -27
  35. data/lib/space_core/cli/config.rb +0 -49
  36. data/lib/space_core/cli/current.rb +0 -16
  37. data/lib/space_core/cli/help.rb +0 -195
  38. data/lib/space_core/cli/helpers.rb +0 -115
  39. data/lib/space_core/cli/init.rb +0 -29
  40. data/lib/space_core/cli/list.rb +0 -24
  41. data/lib/space_core/cli/loop_status.rb +0 -47
  42. data/lib/space_core/cli/new.rb +0 -38
  43. data/lib/space_core/cli/pack.rb +0 -29
  44. data/lib/space_core/cli/path.rb +0 -16
  45. data/lib/space_core/cli/repeatable_options.rb +0 -84
  46. data/lib/space_core/cli/repo.rb +0 -76
  47. data/lib/space_core/cli/run.rb +0 -44
  48. data/lib/space_core/cli/shell.rb +0 -125
  49. data/lib/space_core/cli/show.rb +0 -21
  50. data/lib/space_core/cli/status.rb +0 -74
  51. data/lib/space_core/cli/use.rb +0 -17
  52. data/lib/space_core/cli.rb +0 -177
  53. data/lib/space_core/commands.rb +0 -51
  54. data/lib/space_core/config.rb +0 -152
  55. data/lib/space_core/errors.rb +0 -14
  56. data/lib/space_core/git_client.rb +0 -49
  57. data/lib/space_core/mise_client.rb +0 -37
  58. data/lib/space_core/oci_builder.rb +0 -56
  59. data/lib/space_core/oci_packer.rb +0 -103
  60. data/lib/space_core/oci_runner.rb +0 -89
  61. data/lib/space_core/paths.rb +0 -58
  62. data/lib/space_core/repo_reference.rb +0 -19
  63. data/lib/space_core/repo_resolver.rb +0 -167
  64. data/lib/space_core/shell_integration.rb +0 -438
  65. data/lib/space_core/slugger.rb +0 -16
  66. data/lib/space_core/space.rb +0 -174
  67. data/lib/space_core/space_store.rb +0 -319
  68. data/lib/space_core/state.rb +0 -86
  69. data/lib/space_core/templates/oci/dockerfile.erb +0 -73
  70. data/lib/space_core/templates/oci/dockerignore.erb +0 -17
  71. data/lib/space_core/templates/oci/entrypoint.sh.erb +0 -16
  72. data/lib/space_core/terminal.rb +0 -161
  73. data/lib/space_core/warnings.rb +0 -13
  74. data/lib/space_core/xdg.rb +0 -33
  75. data/lib/space_core.rb +0 -26
  76. data/lib/space_src/cli/clone.rb +0 -54
  77. data/lib/space_src/cli/config.rb +0 -65
  78. data/lib/space_src/cli/daemon.rb +0 -362
  79. data/lib/space_src/cli/options.rb +0 -21
  80. data/lib/space_src/cli/org.rb +0 -199
  81. data/lib/space_src/cli/repo.rb +0 -169
  82. data/lib/space_src/cli/shell.rb +0 -121
  83. data/lib/space_src/cli/status.rb +0 -75
  84. data/lib/space_src/cli/sync.rb +0 -161
  85. data/lib/space_src/cli.rb +0 -159
  86. data/lib/space_src/cloner.rb +0 -77
  87. data/lib/space_src/config/contract.rb +0 -54
  88. data/lib/space_src/config/duration.rb +0 -79
  89. data/lib/space_src/config/model.rb +0 -49
  90. data/lib/space_src/config/store.rb +0 -156
  91. data/lib/space_src/forge/client.rb +0 -31
  92. data/lib/space_src/forge/github.rb +0 -98
  93. data/lib/space_src/launchd/agent.rb +0 -195
  94. data/lib/space_src/launchd/plist.rb +0 -129
  95. data/lib/space_src/log_rotator.rb +0 -46
  96. data/lib/space_src/migration.rb +0 -43
  97. data/lib/space_src/nav.rb +0 -99
  98. data/lib/space_src/paths.rb +0 -72
  99. data/lib/space_src/scm/client.rb +0 -87
  100. data/lib/space_src/scm/git.rb +0 -232
  101. data/lib/space_src/scm/status.rb +0 -24
  102. data/lib/space_src/shell.rb +0 -90
  103. data/lib/space_src/shell_integration.rb +0 -321
  104. data/lib/space_src/state/lock.rb +0 -59
  105. data/lib/space_src/state/store.rb +0 -140
  106. data/lib/space_src/sync/engine.rb +0 -503
  107. data/lib/space_src/sync/repo_plan.rb +0 -215
  108. data/lib/space_src/sync/report.rb +0 -18
  109. data/lib/space_src/ui/interactive_reporter.rb +0 -280
  110. data/lib/space_src/ui/json_reporter.rb +0 -39
  111. data/lib/space_src/ui/mode.rb +0 -68
  112. data/lib/space_src/ui/plain_reporter.rb +0 -53
  113. data/lib/space_src/ui/reporter.rb +0 -48
  114. data/lib/space_src/version.rb +0 -7
  115. data/lib/space_src.rb +0 -38
@@ -5,7 +5,7 @@ description: >
5
5
  — judgment only: arbitration, judging raw evidence against frozen Acceptance
6
6
  Criteria, splitting iterations into disjoint lanes, kill/continue calls. The
7
7
  BUILDERS are 1-4 parallel cheaper agents run headless (reference harness:
8
- `claude -p`), each in its own git worktree; the architect reviews, merges, and
8
+ `pi -p`), each in its own git worktree; the architect reviews, merges, and
9
9
  integrates their work.
10
10
  The space is the memory: one file per iteration at
11
11
  architecture/I<NN>-<name>.md (Grounds / Specification / Acceptance Criteria /
@@ -19,7 +19,7 @@ description: >
19
19
 
20
20
  You are the ARCHITECT — the judgment role: a strong reasoning model (or a human),
21
21
  run interactively. The BUILDER is a cheaper model run headless (reference
22
- harness: `claude -p`), one or more per iteration — the models filling both roles
22
+ harness: `pi -p`), one or more per iteration — the models filling both roles
23
23
  are an operator choice (see `docs/DESIGN.md` §1–§2), not fixed. The space is the
24
24
  memory — project artifacts live in the space's `architecture/` dir (committed),
25
25
  scratch in `build/` (gitignored); the project spans the repos under `repos/`.
@@ -116,12 +116,13 @@ loop.
116
116
  6. **Audit every status claim** — yours and the builder's — against a tool
117
117
  result from the session before reporting it.
118
118
  7. **Fresh builder context per lane, worktree isolation between lanes.**
119
- `claude -p --continue` (from the lane's worktree) only for follow-ups within
120
- the current lane. Builders never commit — Claude Code has no sandbox to
121
- enforce that, so verify it yourself post-flight (`git -C <worktree> log
122
- <repo-base>..` must be empty). If a run leaves a worktree broken or
123
- committed, discard that lane + re-dispatch over rescue prompting — lanes are
124
- cheap by construction.
119
+ `pi -p --continue --session-dir <space>/build/<id>-<lane>` (from the lane's
120
+ worktree) only for follow-ups within
121
+ the current lane. Builders never commit — the injected builder guard denies
122
+ git writes at dispatch, so verify it yourself post-flight (`git -C
123
+ <worktree> log <repo-base>..` must be empty). If a run leaves a worktree
124
+ broken or committed, discard that lane + re-dispatch over rescue prompting —
125
+ lanes are cheap by construction.
125
126
  8. **Stop conditions:** failing verification you can't root-cause, instructions
126
127
  conflicting with project docs, irreversible/destructive calls, or scope
127
128
  growth beyond the iteration → checkpoint to the handoff and ask the human.
@@ -134,8 +135,8 @@ loop.
134
135
  - Read the project's operating docs in authority order: `CLAUDE.md` /
135
136
  `AGENTS.md` → `README.md` → architecture docs. Learn the exact verification
136
137
  gate (test/lint/typecheck/build commands) from docs or CI config.
137
- - Once per environment: `claude --version` and confirm the builder model
138
- resolves (`echo ok | claude -p --model <builder-model> --max-turns 1`;
138
+ - Once per environment: `pi --version` and confirm the builder model
139
+ resolves (`echo ok | pi -p --model <builder-model>`;
139
140
  details in `dispatch.md`). Past that one-time check, every foreground dispatch
140
141
  self-verifies — it prints a liveness line naming the streamed model and
141
142
  confirming the run log is growing (a WARN line instead if the streamed model
@@ -150,8 +151,9 @@ loop.
150
151
  an iteration index pointing at each iteration file; per-iteration detail lives
151
152
  in the iteration file, never duplicated into the handoff. `architect status`
152
153
  prints project state (iterations, freeze_shas, lanes, verdicts) at any point.
153
- - **Space setup (first time):** `architect space new "Project Name" -r org/repo -r …`
154
- (each repo is a repeatable `-r` flag after the title), then `architect init` inside
154
+ - **Space setup (first time):** `space new "Project Name" -r org/repo -r …`
155
+ (the space-cadet binary; each repo is a repeatable `-r` flag after the title),
156
+ then `architect init` inside
155
157
  the space to scaffold `architecture/ARCHITECT.md`.
156
158
  - Scale to the task: trivial fixes don't need the loop — say so and let the
157
159
  human do it inline or in a normal session. The loop is for iteration-sized
@@ -208,7 +210,7 @@ the builder's work — a capability-gap read whose independence depends on the
208
210
  pairing: a same-lab architect/builder shares the builder's blind spots (so the
209
211
  frozen gates stay the independent check), while a cross-vendor pairing is more
210
212
  independent (see `docs/DESIGN.md` §1/R3). For an extra adversarial pass, pipe the
211
- diff to a fresh read-only `claude -p` reviewer (command in `dispatch.md`) or a
213
+ diff to a fresh read-only `pi -p` reviewer (command in `dispatch.md`) or a
212
214
  fresh-context subagent prompted to break confidence — calibrated to flag only
213
215
  correctness/requirement/invariant gaps with file:line evidence, no style.
214
216
 
@@ -243,8 +245,9 @@ Two scales, two routes:
243
245
  researching well-understood iterations is pure cost.
244
246
 
245
247
  When a trigger fires, read `research.md` next to this file and follow it:
246
- 3–5 narrow non-overlapping questions → parallel read-only `claude -p`
247
- researchers (built-in `WebSearch`/`WebFetch`) in the background → you
248
+ 3–5 narrow non-overlapping questions → parallel detached `pi -p --mode json`
249
+ researchers (read-only by prompt contract; web via pi's loaded web tools) in
250
+ the background → you
248
251
  adversarially verify the load-bearing claims → you write the iteration's
249
252
  **Grounds** section with citations and commit it. Researchers gather; you judge
250
253
  and write Grounds. Findings without a source URL don't enter Grounds.
@@ -277,9 +280,7 @@ contract, self-contained:
277
280
  its **target repo + file-touch set, checked for overlap**: name the repo
278
281
  (`repos/<repo>`) and every file each lane may touch. The machine-readable
279
282
  declaration lives in a fenced ` ```lanes ` block in the Specification — one
280
- entry per lane (`name`, `repo`, `touch` globs; optionally `allowed_tools`/
281
- `append_allowed_tools` to replace/extend the lane's builder tool grant — see
282
- `dispatch.md`) — the single frozen source of
283
+ entry per lane (`name`, `repo`, `touch` globs) — the single frozen source of
283
284
  truth `architect freeze` records into `space.yaml` and `architect provision`
284
285
  materializes. The same boundary is stated twice more — the lane-prompt's
285
286
  may-touch list, and the scope gate that checks the lane's diff at judge
@@ -310,8 +311,11 @@ contract, self-contained:
310
311
  pending status, and a later session in the same worktree audits it once it
311
312
  exits — see `### Long-running sweep` in `dispatch.md`).
312
313
  - **Effort call** — thinking budget set per dispatch with `architect dispatch
313
- --effort <level>`, translated and clamped to the lane's harness (the
314
- escalation keywords `think hard` … `ultrathink` still work in-prompt);
314
+ --effort <level>`, normalized to the canonical
315
+ `off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max` set and passed straight
316
+ through to pi's `--thinking` (each model's `thinkingLevelMap` in
317
+ `~/.pi/agent/models.json` clamps it further per model); the escalation
318
+ keywords `think hard` … `ultrathink` still work in-prompt;
315
319
  default unattended builder work high, downgrade a routine, tightly-specified
316
320
  lane (record which and why). Levels and mechanics: `dispatch.md`.
317
321
 
@@ -404,15 +408,14 @@ dispatch. You needn't sequence Grounds and Specification into separate commits
404
408
  first — the freeze snapshots the whole frozen region and refuses to re-freeze
405
409
  once a frozen section changed afterward.
406
410
 
407
- ### 5. Dispatch (one fresh `claude -p` per lane, worktree-isolated)
411
+ ### 5. Dispatch (one fresh `pi -p` per lane, worktree-isolated)
408
412
 
409
413
  Per the mechanics in `dispatch.md`. The lane lifecycle is **declare → rehearse
410
414
  → freeze → provision → write prompts → dispatch** — every lane gets a worktree;
411
415
  there is no dispatch-in-the-checkout path:
412
416
 
413
417
  - **Declare** — at spec time, each lane is one entry in the Specification's
414
- fenced ` ```lanes ` block (§4): `name`, `repo`, `touch` globs, and optionally
415
- `allowed_tools`/`append_allowed_tools`.
418
+ fenced ` ```lanes ` block (§4): `name`, `repo`, `touch` globs.
416
419
  - **Rehearse** — `architect rehearse <iteration>` dry-runs the drafted gates
417
420
  from the working tree (§4's pre-freeze check), resolving its run dir from the
418
421
  drafted ` ```lanes ` block, and stamps `space.yaml`; `freeze` refuses without
@@ -440,7 +443,9 @@ with `architect section <iteration> prompt --append --lane <lane> --from
440
443
  <that scratch file>`. Then run `architect dispatch <iteration> <lane> --prompt
441
444
  <that scratch file>` — it copies the prompt to the canonical
442
445
  `build/<id>-<lane>/prompt.md` (fed to the builder on stdin), assembles the
443
- canonical `claude -p` argv, pins the model, and streams stream-json to
446
+ canonical `pi -p --mode json` argv, pins the model, copies the vendored
447
+ builder-guard into the lane's build dir and injects it with `-e`, and streams
448
+ pi's JSONL event stream to
444
449
  `build/<id>-<lane>/run.jsonl`. Launch one dispatch per worktree — each as its
445
450
  **own background Bash tool call** (your harness's `run_in_background`), **not**
446
451
  shell `&`. The harness keeps each lane alive for its full run and notifies you
@@ -452,7 +457,7 @@ stay untouchable.
452
457
 
453
458
  Do not block — end the turn or do other judgment work; long runs (30–60 minutes) are
454
459
  normal. Print the lane-prompts too, so the human can run any lane in an
455
- interactive `claude` session instead. Whenever you return to a running lane,
460
+ interactive `pi` session instead. Whenever you return to a running lane,
456
461
  check liveness: the lane's `run.jsonl` must still be growing. If it has been
457
462
  silent 15+ minutes on one in-flight command, follow "Stall detection and
458
463
  rescue" in `dispatch.md` — kill the stuck child process, not the run.
@@ -1,51 +1,58 @@
1
1
  # Builder dispatch reference
2
2
 
3
- Verified against the `claude` CLI (Claude Code) headless mode — the reference
4
- harness — June 2026. The builder is `claude -p` (`--print`, the non-interactive
5
- headless mode) pinned to the configured builder model (`<builder-model>`) — a
6
- cheaper model run headless via the same harness the architect uses. Key facts the
7
- skill encodes: lane-prompts go in on **stdin** (Claude Code has no `@file`, and a
8
- big quoted lane-prompt as a shell argument gets mangled); the model is pinned with
3
+ Verified against `pi` 1.1.0 (badlogic/pi-mono) headless print mode — the reference
4
+ harness. The builder is `pi -p` (`--print`, the non-interactive mode) pinned to
5
+ the configured builder model (`<builder-model>`) — a cheaper model run headless
6
+ via the same harness the architect uses. Key facts the skill encodes: lane-prompts
7
+ go in on **stdin** (piped stdin is prepended to the message, and a big quoted
8
+ lane-prompt as a shell argument gets mangled); the model is pinned with
9
9
  `--model <builder-model>` (a floating alias drifts to whatever ships next — pin
10
- the full id); there is **no `-C`/working-dir flag**, so
11
- per-lane dispatch `cd`s into the worktree; permissions are the **tool
12
- allow/deny lists** (`--allowedTools`/`--disallowedTools`) plus
13
- `--permission-mode`, not a sandbox; web access is the built-in
14
- `WebSearch`/`WebFetch` tools (no extension to install).
10
+ the full id); there is **no working-dir flag**, so per-lane dispatch `cd`s into
11
+ the worktree; there is **no tool allow/deny flag surface and no turn cap** — tool
12
+ control is the injected **builder guard** extension (`-e`), hermeticity comes from
13
+ `--no-approve` (project-trust pinned to untrusted: the lane's `.pi/` project
14
+ resources never load; `AGENTS.md`/`CLAUDE.md` context files still load; explicit
15
+ `-e` paths still load), and the run's bound is the dispatch wall-clock timeout
16
+ (TERM → grace → KILL); web access comes from the web tools loaded by the pi
17
+ extensions configured in `~/.pi/agent` (no per-dispatch setup).
15
18
 
16
19
  **The one load-bearing difference from the Codex design:** Codex's
17
- `--sandbox workspace-write` made `.git` physically read-only. Claude Code has
18
- **no automatic filesystem sandbox** in headless mode (the sandbox is opt-in via
19
- settings, off by default), so `.git` is not hardware-protected. "Builders never
20
- commit" (hard rule 7) is now enforced in three layers, weakest to strongest:
21
- (1) a runtime first line — deny the git-write tools with
22
- `--disallowedTools 'Bash(git commit:*)' …`; (2) worktree isolation between
20
+ `--sandbox workspace-write` made `.git` physically read-only. pi has
21
+ **no filesystem sandbox**, so `.git` is not hardware-protected. "Builders never
22
+ commit" (hard rule 7) is enforced in three layers, weakest to strongest:
23
+ (1) a runtime first line — the **builder guard** extension denies git-write
24
+ commands (and `bash`-unparseable commands) before evaluation (see `### The
25
+ builder guard` below); (2) worktree isolation between
23
26
  lanes; (3) the authoritative check — an architect post-flight
24
- `git -C <worktree> log <repo-base>..` that must be empty. The deny rules are
27
+ `git -C <worktree> log <repo-base>..` that must be empty. The guard's deny is
25
28
  not airtight (a builder can shell out — `sh -c 'git commit …'` — past the
26
- pattern match), so the post-flight `git log` is what the loop actually trusts.
29
+ parse), so the post-flight `git log` is what the loop actually trusts.
27
30
  If a lane committed, treat the worktree as tampered: reset and re-dispatch.
28
31
 
29
- **Preflight (once per environment):** run `claude --version`, and confirm the
30
- builder model resolves with a one-shot
31
- (`echo ok | claude -p --model <builder-model> --max-turns 1`). No API key —
32
- the builder runs on your Claude plan — but note headless `claude -p` draws on
33
- the Agent SDK credit pool (separate from interactive usage since June 15 2026;
34
- see `docs/DESIGN.md` §4). Past that one-time check, no dispatch needs a manual
32
+ **Preflight (once per environment):** run `pi --version`, and confirm the
33
+ builder model resolves with a one-message probe
34
+ (`echo ok | pi -p --model <builder-model>`). No API-key/env ceremony — provider
35
+ config (e.g. the Fireworks key) resolves from `~/.pi/agent`; pi needs no
36
+ `ANTHROPIC_*` env. Past that one-time check, no dispatch needs a manual
35
37
  start-check: every foreground dispatch self-verifies. Shortly after launch it
36
38
  prints a liveness line to stderr naming the streamed model and confirming the
37
39
  run log is growing — or a WARN line instead when the streamed model disagrees
38
- with the pinned `<builder-model>` or the log isn't growing.
40
+ with the pinned `<builder-model>` or the log isn't growing. (Liveness source:
41
+ the run log is pi's JSONL event stream — ~20 event types, `session` →
42
+ `agent_start`/`turn_start` → `message_*`/`tool_execution_*` →
43
+ `turn_end`/`agent_end`/`agent_settled`; the line is parsed from the first
44
+ assistant-role event carrying a `model` field.)
39
45
 
40
46
  ## Canonical dispatch — `architect dispatch <iteration> <lane>`
41
47
 
42
48
  The canonical path is `architect dispatch <iteration> <lane> --prompt <file>`.
43
49
  The tool copies your prompt file to `build/<id>-<lane>/prompt.md` (the CLI owns
44
- that canonical path — you never write it directly), assembles the canonical
45
- `claude -p` argv, pins the builder model (the lane's
50
+ that canonical path — you never write it directly), copies the vendored
51
+ builder-guard to `build/<id>-<lane>/builder-guard.ts`, assembles the canonical
52
+ `pi -p` argv, pins the builder model (the lane's
46
53
  configured model or the CLI's reference default; see `docs/DESIGN.md` §4),
47
54
  feeds the copied lane prompt to the builder on stdin, and streams
48
- `--output-format stream-json --verbose` output to
55
+ pi's `--mode json` event stream to
49
56
  `build/<id>-<lane>/run.jsonl`. Run each lane as its own **background Bash tool
50
57
  call** (`run_in_background`) so your turn doesn't block for the full run (30–60
51
58
  minutes is typical).
@@ -85,7 +92,7 @@ dead-end on a missing worktree.
85
92
  Issue each dispatch as its **own background Bash tool call** — one call per
86
93
  lane. Never use a shell `&` loop. A `for … & done` launcher is a *launcher*
87
94
  process: it returns the instant it has spawned the lane children, the harness
88
- reaps those now-orphaned `claude` processes, and every lane dies at once with no
95
+ reaps those now-orphaned `pi` processes, and every lane dies at once with no
89
96
  `result` — partial diffs, no reports (this exact failure has happened: three
90
97
  lanes killed at the same second, zero output). One blocking dispatch per
91
98
  background Bash tool keeps each lane attached to a harness-tracked task that
@@ -97,26 +104,52 @@ survives the full run and reports completion per lane.
97
104
  and as the manual fallback:
98
105
 
99
106
  ```bash
100
- # dispatch --prompt copies the scratch prompt to build/<id>-<lane>/prompt.md; the
101
- # manual equivalent is that copy followed by:
107
+ # dispatch --prompt copies the scratch prompt to build/<id>-<lane>/prompt.md and
108
+ # the vendored guard to build/<id>-<lane>/builder-guard.ts; the manual equivalent
109
+ # is those copies followed by:
102
110
  ( cd build/<id>-<lane>/wt && \
103
- claude -p --model <builder-model> \
104
- --permission-mode acceptEdits \
105
- --allowedTools 'Read,Edit,Write,Grep,Glob,Bash,WebSearch,WebFetch' \
106
- --disallowedTools 'Bash(git commit:*),Bash(git push:*),Bash(git reset:*),Bash(git merge:*),Bash(git rebase:*),Bash(git checkout:*),Bash(git branch:*)' \
107
- --output-format stream-json --verbose \
108
- --max-turns 200 \
111
+ pi -p --mode json \
112
+ --model <builder-model> \
113
+ --session-dir <space>/build/<id>-<lane> \
114
+ --no-approve \
115
+ -e <space>/build/<id>-<lane>/builder-guard.ts \
116
+ [--thinking <level>] \
109
117
  < <space>/build/<id>-<lane>/prompt.md \
110
118
  > <space>/build/<id>-<lane>/run.jsonl 2>&1 )
111
119
  ```
112
120
 
113
- `acceptEdits` auto-approves file writes; listing `Bash` in `--allowedTools`
114
- auto-approves shell commands so the run never blocks on a prompt; any tool *not*
115
- on the allow list is denied rather than prompted in `-p` mode (so the builder
116
- can't wander outside its toolset), and the `--disallowedTools` deny rules win
117
- over the allow list (deny always takes precedence) as the runtime first line
118
- against commits. Redirect stderr (`2>&1`) into the run-log so a dispatch error
119
- lands somewhere instead of vanishing.
121
+ `--no-approve` pins project-trust to untrusted for the run — a **hermetic
122
+ builder**: the lane's `.pi/` project resources (settings, extensions, skills,
123
+ prompts, themes, SYSTEM.md) never load, while `AGENTS.md`/`CLAUDE.md` context
124
+ files still load root-down and the explicit `-e` guard always loads — the guard
125
+ cannot be shrunk by project-local config. `--session-dir` points pi's session
126
+ storage and lookup at the lane's build dir, which is what makes `--continue`
127
+ follow-ups (below) deterministic per lane. Redirect stderr (`2>&1`) into the
128
+ run-log so a dispatch error lands somewhere instead of vanishing.
129
+
130
+ ### The builder guard — what a builder sees on a deny
131
+
132
+ The guard is a pi extension (`lib/space_architect/pi/builder-guard.ts` in the
133
+ space-architect repo, copied into the lane's build dir and injected via `-e`).
134
+ Its `tool_call` handler intercepts every `bash` call **before evaluation** and
135
+ blocks two classes, returning the deny reason as the bash tool result text —
136
+ that reason is the builder's only feedback, so it says what to do instead:
137
+
138
+ - **Git write operations** — `commit`, `push`, `pull`, `reset`, `merge`,
139
+ `rebase`, `checkout`, `switch`, `branch`, `tag`, and the rest of git's write
140
+ verbs — denied because the architect CLI owns all commits (hard rule 7). The
141
+ deny text surfaces as `architect builder guard: …` in the tool result. The
142
+ parse walks each command segment past git's flags (`git -C <path> commit` is
143
+ still a deny), and reads like `git log --grep=commit` pass — the guard
144
+ classifies the subcommand, it never substring-matches the raw command.
145
+ - **`bash`-unparseable commands** — the guard runs `bash -n` and blocks on a
146
+ parse error (unterminated quote, missing `;` before `then`), with bash's
147
+ stderr in the reason. The parse-check is parse-level only: an unclosed `[`
148
+ (`[ -f /etc/hosts` with no `]`) is a *runtime* `test` failure, syntactically
149
+ valid — not the guard's domain.
150
+
151
+ The guard is a first line, not a sandbox — the post-flight `git log` check
152
+ (Operating guidance below) stays the authoritative no-commits proof.
120
153
 
121
154
  ### Integration (judging session — after per-lane post-flight passes)
122
155
 
@@ -307,13 +340,15 @@ after.
307
340
  echo "The sweep at <run dir> (pid <pid>) has exited. Audit its output
308
341
  against the spec's acceptance criteria, finish the report you
309
342
  pre-structured in phase A, and end at STATUS: COMPLETE." \
310
- | claude -p --continue --model <builder-model> \
311
- --permission-mode acceptEdits \
312
- --allowedTools 'Read,Edit,Write,Grep,Glob,Bash,WebSearch,WebFetch' \
313
- --disallowedTools 'Bash(git commit:*),Bash(git push:*),Bash(git reset:*),Bash(git merge:*),Bash(git rebase:*),Bash(git checkout:*),Bash(git branch:*)' \
314
- --output-format stream-json --verbose \
343
+ | pi -p --continue --session-dir <space>/build/<id>-<lane> \
344
+ --mode json --model <builder-model> \
345
+ --no-approve \
346
+ -e <space>/build/<id>-<lane>/builder-guard.ts \
315
347
  > build/<id>-<lane>/run-b.jsonl 2>&1 )
316
348
  ```
349
+ (`--mode json` because the audit run gets its own run log — `run-b.jsonl`
350
+ stays a pi JSONL event stream like the first. Piped stdin is prepended to
351
+ the resumed message, same as a fresh dispatch.)
317
352
  Phase B runs the post-run audits the spec asked for, then finishes the
318
353
  report at `STATUS: COMPLETE` (or `COMPLETE_WITH_CONCERNS`/`BLOCKED`, per the
319
354
  template) exactly as any other lane would.
@@ -340,43 +375,33 @@ instruction.
340
375
  model bump can't silently change builder behavior mid-project.
341
376
  - Effort = thinking budget. Set it per dispatch: `architect dispatch --effort
342
377
  <level>` (aliases `--thinking`/`--reasoning`) accepts
343
- `off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max` and translates it to the
344
- lane's harness — `claude-code` passes `low`…`max` straight through to its own
345
- `--effort` flag, unclamped (`minimal` clamps to `low`; `off` omits the flag).
346
- The escalation keywords (`think` < `think hard` < `think harder` <
347
- `ultrathink`) and the `MAX_THINKING_TOKENS` env var still raise depth from
348
- inside the block. Default unattended builder work to a high budget; downgrade
378
+ `off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`, normalizes to that
379
+ canonical set, and pi accepts the full set unchanged (`--thinking <level>`) —
380
+ no harness-side clamping; each model's `thinkingLevelMap` in
381
+ `~/.pi/agent/models.json` clamps further per model (e.g. a model whose map
382
+ sends `off`/`minimal` to a higher floor). The escalation keywords (`think` <
383
+ `think hard` < `think harder` < `ultrathink`) still raise depth from inside
384
+ the block. Default unattended builder work to a high budget; downgrade
349
385
  a routine, tightly-specified lane (record which and why in the spec).
350
- - **Tool grant.** The default `--allowedTools` list is overridable per dispatch:
351
- `--allowed-tools <list>` replaces it, `--append-allowed-tools <list>` appends
352
- to it. The same is settable in the frozen lane declaration's ` ```lanes ` block
353
- — `allowed_tools:` (replaces) / `append_allowed_tools:` (appends) — so a lane
354
- that needs an MCP or other non-default tool is granted it reviewably, judged
355
- like every other lane boundary, rather than buried in a shell invocation. The
356
- CLI flag wins over the lane's yaml key when both are given, and dispatch
357
- reports which source resolved. This exists because `claude -p` **denies** a
358
- tool not on the allow list rather than prompting for it — a lane missing a
359
- needed grant doesn't error, it silently can't use the tool and files a
360
- confident, dataless report instead. `--disallowedTools`/`DISALLOWED_TOOLS`
361
- (the builder-never-commits deny rules above) has no override route, by design
362
- — hard rule 7 depends on it staying fixed.
363
- - **Builders never commit, and the architect verifies it.** Claude Code has no
364
- sandbox to make `.git` read-only, so this is enforced by the deny rules at
365
- dispatch *and* checked after the run: before integrating a lane, confirm
386
+ - **Builders never commit, and the architect verifies it.** pi has no sandbox
387
+ to make `.git` read-only, so the injected guard denies git-write commands at
388
+ dispatch (see `### The builder guard` above) *and* it is checked after the
389
+ run: before integrating a lane, confirm
366
390
  `git -C build/<id>-<lane>/wt log <repo-base>..` is empty and
367
391
  `git -C build/<id>-<lane>/wt status` shows only files inside the lane's
368
392
  declared set. A commit or an out-of-bounds write fails the lane — reset and
369
393
  re-dispatch (lanes are cheap, hard rule 7).
370
394
  - Same-iteration follow-up (e.g. answering PHASE 0 disagreements after the
371
- human rules): from the lane's worktree, `claude -p --continue "<rulings +
372
- proceed>"` resumes that worktree's most recent session with full context —
373
- sessions are scoped per directory, so `--continue` (`-c`) is deterministic
374
- even with parallel lanes. (Alternatively pin `--session-id <uuid>` at
375
- dispatch and resume with `--resume <uuid>`.) Resume the **same way you
376
- dispatch** — one background Bash tool call per lane, each a single blocking
377
- `claude -p --continue …`, never a `&` loop (a `&` launcher orphans the
378
- resumed lanes exactly as it does fresh ones). Never resume across iterations —
379
- every iteration gets a fresh context.
395
+ human rules): from the lane's worktree, `pi -p --continue --session-dir
396
+ <space>/build/<id>-<lane> "<rulings + proceed>"` resumes the lane's most
397
+ recent session in that session dir with full context — `-c` continues that
398
+ most recent session, so follow-ups are deterministic even with parallel
399
+ lanes. Resume the **same way you dispatch** — one background Bash tool call
400
+ per lane, each a single blocking `pi -p --continue …`, never a `&` loop (a
401
+ `&` launcher orphans the resumed lanes exactly as it does fresh ones). Add
402
+ `--mode json` and redirect when you want the resumed run logged like a fresh
403
+ dispatch. Never resume across iterations — every iteration gets a fresh
404
+ context.
380
405
  - Capability-gap review gate (high-stakes iterations): the architect outranks the
381
406
  builder, so the architect reading the diff is already a stronger-model,
382
407
  fresh-context pass over it. How independent that read is depends on the pairing
@@ -387,7 +412,7 @@ instruction.
387
412
  ```bash
388
413
  { echo "Review this diff against the spec. Flag ONLY correctness/requirement/invariant gaps with file:line evidence. No style."; \
389
414
  git -C <repo-root> diff <base>...HEAD; } \
390
- | claude -p --model <builder-model> --allowedTools 'Read,Grep,Glob'
415
+ | pi -p --model <builder-model> --tools read,grep,find,ls
391
416
  ```
392
417
  - `build/` is already gitignored by the space, so no extra `.gitignore` entry
393
418
  is needed. Scratch never reaches the space repo; only `architecture/` is
@@ -397,25 +422,26 @@ instruction.
397
422
 
398
423
  A dispatched run is STALLED when its `run.jsonl`
399
424
  (`build/<id>-<lane>/run.jsonl`) has not grown for 15+ minutes AND the last
400
- event is an in-flight `Bash` tool call (a `tool_use` for `Bash` with no
401
- matching `tool_result` yet). Silent gaps between events are normal model
402
- thinking; a shell command that should take seconds sitting in flight for 15+
403
- minutes is not.
425
+ event is an in-flight `bash` tool call (a `tool_execution_start` for `bash`
426
+ with no matching `tool_execution_end` yet). Silent gaps between events are
427
+ normal model thinking; a shell command that should take seconds sitting in
428
+ flight for 15+ minutes is not.
404
429
 
405
- Diagnose before killing: find the command's child under the `claude` PID
406
- (claude → shell → child). Hot-spinning (high CPU) or blocked (zero CPU and none
430
+ Diagnose before killing: find the command's child under the `pi` PID
431
+ (pi → shell → child). Hot-spinning (high CPU) or blocked (zero CPU and none
407
432
  of its expected side effects on disk) — hung either way.
408
433
 
409
- Kill the NARROWEST thing: the stuck child process, not the `claude` run. The
434
+ Kill the NARROWEST thing: the stuck child process, not the `pi` run. The
410
435
  command returns a failure to the builder, which adapts with its full context
411
436
  intact. Kill the whole run only when the builder re-enters the same hang or the
412
437
  worktree is broken; then discard the lane and re-dispatch (hard rule 7).
413
438
 
414
- Claude Code runs the `Bash` tool directly with no sandbox, so the Codex-era
439
+ pi runs the `bash` tool directly with no sandbox, so the Codex-era
415
440
  sandbox-specific hang sources don't apply — but long-running and interactive
416
- commands still hang an unattended run. Spec consequence: give every potentially
417
- long command an explicit timeout in the lane-prompt (the `Bash` tool also takes
418
- a per-call timeout), cap the run with `--max-turns` as a loop backstop, steer
441
+ commands still hang an unattended run, and pi has no turn cap (there is no
442
+ `--max-turns` flag), so the dispatch's wall-clock timeout (TERM → grace →
443
+ KILL) is the run's bound, not a turn budget. Spec consequence: give every
444
+ potentially long command an explicit timeout in the lane-prompt, steer
419
445
  builders toward the repo's existing test fixtures over hand-rolled long-running
420
446
  harnesses, and when a gate needs a runtime that can't run unattended
421
447
  (interactive prompts, servers without a timeout), have the builder record the
@@ -427,10 +453,9 @@ look broken).
427
453
 
428
454
  ## Manual alternative (human-driven)
429
455
 
430
- Paste the lane-prompt into an interactive `claude` session (no `-p`). Claude
431
- Code's agent loop runs plan→act→test against the block's stopping condition
432
- while you watch and steer — approve tools as they come, or set `/permissions`
433
- first. Use when the human wants to babysit a run.
456
+ Paste the lane-prompt into an interactive `pi` session (no `-p`). pi's agent
457
+ loop runs plan→act→test against the block's stopping condition while you watch
458
+ and steer. Use when the human wants to babysit a run.
434
459
 
435
460
  ## Lane-prompt template
436
461
 
@@ -516,22 +541,19 @@ against them, do not edit or work around) ===
516
541
 
517
542
  ## Builder-side standing setup (one time per machine/repo)
518
543
 
519
- - The builder is the same `claude` binary as the architect (reference harness),
544
+ - The builder is the same `pi` binary as the architect (reference harness),
520
545
  running a cheaper model — nothing extra to install. `architect dispatch` pins
521
- the model per dispatch (`--model <builder-model>`); a `~/.claude/settings.json`
522
- `"model"` default is fine interactively, but automations pin it explicitly so a
523
- default can't silently swap the builder.
524
- - Repo `CLAUDE.md` is the builder's standing context — Claude Code loads it
525
- root-down automatically. Put exact build/test commands and repo gotchas there;
526
- the loop's PHASE rules stay in the dispatch block so they version with the
527
- skill. (Claude Code does **not** auto-read `AGENTS.md`; if the repo keeps its
528
- build/test docs there, add `@AGENTS.md` to `CLAUDE.md` to pull it in.)
529
- - The builder is a bare `claude -p` over the block — it is not invoking the
530
- `/architect` skills, the block is its entire instruction set. (`--bare` would
531
- give a leaner builder context but also drops `CLAUDE.md`/skills/hooks — keep
532
- `CLAUDE.md`, so skip `--bare` unless the repo has no standing build/test doc.)
533
- - Billing: headless `claude -p` draws on the Agent SDK credit pool on your
534
- Claude plan (separate from interactive usage limits since June 15 2026).
535
- There's no per-window quota that dies mid-run the way a chat session can, but
536
- a long parallel fan-out does spend that pool. The architect runs as your
537
- interactive Claude Code session.
546
+ the model per dispatch (`--model <builder-model>`); a default model in
547
+ `~/.pi/agent` config is fine interactively, but automations pin it explicitly
548
+ so a default can't silently swap the builder.
549
+ - Repo `AGENTS.md`/`CLAUDE.md` are the builder's standing context — pi loads
550
+ them root-down automatically (`--no-approve` does not gate them). Put exact
551
+ build/test commands and repo gotchas there; the loop's PHASE rules stay in
552
+ the dispatch block so they version with the skill.
553
+ - The builder is a bare `pi -p` over the block — it is not invoking the
554
+ `/architect` skills, the block is its entire instruction set.
555
+ - Billing: the builder draws on the provider account configured in
556
+ `~/.pi/agent` (per-token API usage — e.g. the Fireworks key). There's no
557
+ per-window quota that dies mid-run the way a chat session can, but a long
558
+ parallel fan-out does spend that account. The architect runs as your
559
+ interactive session.
@@ -1,13 +1,15 @@
1
1
  # Research fan-out reference
2
2
 
3
3
  Read this only when a research trigger fires (see SKILL.md step 3). The fan-out
4
- uses `architect research dispatch` to launch parallel read-only `claude -p`
5
- researchers (a cheaper model, read-only — no Edit/Write/Bash) and `architect research wait` to
4
+ uses `architect research dispatch` to launch parallel detached `pi -p --mode
5
+ json` researchers (a cheaper model, read-only by prompt contract — the
6
+ supervisor injects the builder guard, so git writes are denied) and
7
+ `architect research wait` to
6
8
  collect their results. The architect keeps all judgment: it verifies
7
9
  load-bearing claims and writes the iteration's **Grounds** section itself.
8
10
 
9
- **Note:** the `~/.claude/skills/architect-research/` copy of this skill is
10
- synced separately and is NOT updated by this repo.
11
+ **Note:** the installed copy of this skill (`architect install-skills`
12
+ destination) is synced separately and is NOT updated by this repo.
11
13
 
12
14
  ## Fan out
13
15
 
@@ -48,10 +50,14 @@ Verbosity flags for `wait`:
48
50
  - `--thinking`: reveal assistant thinking blocks
49
51
  - `--jsonl`: emit raw lane-tagged JSONL instead of human text
50
52
 
51
- The researchers are READ-ONLY by toolset (`Read,Grep,Glob,WebSearch,WebFetch`
52
- with no Edit/Write/Bash) so they cannot touch the repo. Their final report
53
- is extracted from the terminal `result` event in the stream-json log and
54
- written to `build/research/<id>/report.md` automatically by `wait`.
53
+ The researchers are READ-ONLY by prompt contract (pi has no tool allow-list in
54
+ the loop's machinery — the lane prompt forbids writes, and the supervisor-injected
55
+ builder guard still denies git-write commands), so they cannot touch the repo.
56
+ Their final report
57
+ is extracted from the run's JSONL event stream (the final assistant message)
58
+ and written to `build/research/<id>/report.md` automatically by `wait`.
59
+ Research sessions land under `build/research/<id>/` (`run.jsonl` + pi session
60
+ files), like builder lanes' `build/<id>-<lane>/`.
55
61
 
56
62
  ## Research-prompt template
57
63
 
@@ -44,7 +44,7 @@ use LLM-designed, topic-specific decomposition rather than a fixed lane
44
44
  taxonomy. Lanes are designed per topic, not taken from a template.
45
45
 
46
46
  **Scout (brainstorm scale only):** dispatch ONE cheap researcher (~10
47
- searches, same `claude -p` command as step 3) to map the terrain: canonical
47
+ searches, same `pi -p` command as step 3) to map the terrain: canonical
48
48
  terminology, the 5–10 load-bearing systems/papers/repos, the named people,
49
49
  which source classes look rich vs empty, and the topic's natural fault lines.
50
50
  The scout returns a map, not findings — discovering the topic's actual
@@ -70,26 +70,33 @@ dispatch. State the plan in a few lines; proceed unless the user redirects.
70
70
  One fresh researcher per lane, each launched as its own **background Bash tool
71
71
  call** (`run_in_background`) — one call per lane, not a shell `&` loop (a `&`
72
72
  launcher orphans the lanes and the harness reaps them all at once). Read-only by
73
- toolset (`Read,Grep,Glob`) plus the web tools (`WebSearch,WebFetch`); the
74
- report is the redirected stdout:
73
+ prompt contract (the injected builder guard still denies git writes); the
74
+ report is the extracted final assistant message (see `research.md`):
75
75
 
76
76
  ```bash
77
- claude -p --model <researcher-model> \
78
- --allowedTools 'Read,Grep,Glob,WebSearch,WebFetch' \
79
- --max-turns 40 \
77
+ pi -p --mode json --model <researcher-model> \
78
+ --session-dir <space>/build/research/<NN>-<lane> \
79
+ --no-approve -e <guard> \
80
80
  < build/research/<NN>-<lane>.prompt.md \
81
- > build/research/<NN>-<lane>.md
81
+ > build/research/<NN>-<lane>/run.jsonl 2>&1
82
82
  ```
83
83
 
84
+ (`architect research dispatch` runs this for you — it injects the supervisor-
85
+ copied guard and records the run; the block above is the transparency
86
+ equivalent. There is no `--max-turns`: scope the researcher with the prompt's
87
+ search budget, not a turn cap.)
88
+
84
89
  Write each lane block to a `.prompt.md` file and feed it on stdin — never as a
85
90
  shell argument; a quote-mangling shell will corrupt a big block, the stdin
86
91
  redirect injects it verbatim.
87
92
 
88
- (Web comes from the built-in `WebSearch` and `WebFetch` tools — no extension or
89
- key. With only the read-only + web tools on the allow list, every write/bash
90
- call is denied, so the researcher can't touch the repo. Launch ONE canary lane
93
+ (Web comes from pi's loaded web tools — `web_search`/`fetch_content` from the
94
+ extensions configured in `~/.pi/agent`; no per-run setup. The read-only
95
+ discipline is the prompt contract plus the guard — a researcher that tries to
96
+ write or commit is denied, and a lane that misbehaves is killed and re-dispatched
97
+ with a tighter prompt. Launch ONE canary lane
91
98
  and confirm it actually fetches live URLs before fanning out. These lane blocks
92
- also run verbatim as read-only Claude subagents with web search if you'd rather
99
+ also run verbatim as read-only subagents with web search if you'd rather
93
100
  keep research inside the architect's own session.)
94
101
 
95
102
  Every lane block carries the full contract — objective, output format, source