stargate-cli 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,1427 @@
1
+ Metadata-Version: 2.4
2
+ Name: stargate-cli
3
+ Version: 1.0.0
4
+ Summary: Tiny local orchestrator that lets heterogeneous coding-agent CLIs collaborate on one task.
5
+ Author-email: dogo <diautilio@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dogo/stargate
8
+ Project-URL: Source, https://github.com/dogo/stargate
9
+ Project-URL: Issues, https://github.com/dogo/stargate/issues
10
+ Keywords: agents,claude-code,codex,orchestrator,git-worktree
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Build Tools
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: PyYAML<7,>=6
20
+ Provides-Extra: dev
21
+ Requires-Dist: ruff>=0.16; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # Stargate
25
+
26
+ **Your coding agents, working as a team. On one task. Without stepping on each
27
+ other.**
28
+
29
+ Stargate is a small, local orchestrator that gives four roles — architect,
30
+ developer, reviewer, fixer — to whichever agent CLIs you already pay for, and
31
+ runs them against your repository with Git as the isolation boundary. No
32
+ framework, no SDK, no daemon, no cloud: one Python package, one runtime
33
+ dependency (PyYAML), and a `pipx install`.
34
+
35
+ ```bash
36
+ cd ~/dev/my-project
37
+ stargate run "Add pagination to the users endpoint"
38
+ ```
39
+
40
+ That plans in your real checkout (read-only), implements in a throwaway
41
+ worktree, runs your tests, reviews the diff, fixes what the review flags, and
42
+ leaves you a commit on its own branch. Your working tree never moves.
43
+
44
+ ### Why it exists
45
+
46
+ One agent in one terminal is a single point of judgment: the author reviews its
47
+ own work, and every retry costs the same context again. Adding terminals does
48
+ not fix it — N agents on one checkout is a race, and the more of them you run,
49
+ the more of your attention goes to refereeing instead of the work.
50
+
51
+ Stargate splits the roles instead. The planner cannot write. The reviewer has
52
+ never been the author. The developer works in an isolated worktree it cannot
53
+ escape. And an agent is **a command prefix**, not an integration: anything you
54
+ can run non-interactively and read the output of qualifies, configured as an
55
+ argument list. So you can put the sharpest model on review and a cheaper one on
56
+ typing, mix vendors, or run all four roles through one CLI — and adding a new
57
+ one is a few lines of YAML, not a plugin.
58
+
59
+ ### The flow
60
+
61
+ ```text
62
+ you
63
+ │
64
+ ▼
65
+ Claude / architect (read-only planning)
66
+ │
67
+ ▼
68
+ Git worktree + dedicated branch
69
+ │
70
+ ▼
71
+ Codex / developer (implementation)
72
+ │
73
+ ▼
74
+ tests (optional)
75
+ │
76
+ ▼
77
+ Kiro / reviewer
78
+ │
79
+ ├── APPROVED ─────────────► orchestrator commit ──► done
80
+ │
81
+ └── CHANGES_REQUESTED
82
+ │
83
+ ▼
84
+ Codex / fixer
85
+ │
86
+ ▼
87
+ tests
88
+ │
89
+ └──────────────► review again
90
+ ```
91
+
92
+ Got work that splits? [`--fan-out`](#fan-out) turns the plan into a validated
93
+ DAG: ready nodes run concurrently, each on its own branch and worktree, a
94
+ dependent node starts from its dependencies' **commits** so it sees their files,
95
+ and the merged tree goes through review once as a whole.
96
+
97
+ ### What you can count on
98
+
99
+ - **Nothing is ever lost.** Every terminal result with changes is committed on
100
+ the run's own branch — including a reviewer that still requests changes, a
101
+ failing test command, or a token-budget stop. The verdict goes in the commit
102
+ message. Fan-out also commits each completed task and the integration verdict.
103
+ - **A crash is resumable, not wasted.** A crashed run creates no terminal
104
+ commit; `resume` reuses the frozen config, prompts, branches, worktrees and
105
+ completed commits, and commits when the run reaches a verdict. It even
106
+ re-enters a recorded review/fix cycle, so an interrupted fixer does not pay
107
+ for a fresh review. (A fan-out token-budget stop before integration writes its
108
+ RESULT and stays resumable, without an integration commit yet.)
109
+ - **Your branch is untouchable.** The orchestrator never merges into, rebases,
110
+ pushes, or deletes the branch checked out in your repository, and never
111
+ removes a worktree mid-run. Publishing requires `--pr` in that invocation and
112
+ only pushes the run's own branch. The agents are forbidden to commit or push;
113
+ the orchestrator performs those actions.
114
+ - **The budget is yours.** Token budget per task and per run, timeouts, retries
115
+ with backoff, and `--max-review-loops`. Exit codes tell you which wall you hit.
116
+
117
+ ### Agents
118
+
119
+ **Claude Code**, **Codex CLI**, and **Kiro CLI** are validated integrations, not
120
+ a closed list of supported agents. The packaged configuration uses Claude for
121
+ the architect and reviewer and Codex for the developer and fixer; the
122
+ [`examples/`](examples/) directory includes verified single-vendor
123
+ configurations for all three CLIs and documents the differences between them
124
+ (who needs `{output}`, who reports usage, who needs a wrapper). Anything with a
125
+ non-interactive command prefix works — `stargate doctor --probe` tells you
126
+ whether yours does, in one real call per agent.
127
+
128
+ ## Requirements
129
+
130
+ - Python 3.10+
131
+ - Git
132
+ - The agent CLI or CLIs referenced by your effective configuration, installed
133
+ and authenticated
134
+
135
+ The packaged default requires Claude Code and Codex CLI. A single-vendor setup
136
+ can instead start from one of the verified configurations in
137
+ [`examples/`](examples/); the Kiro configuration also uses its included wrapper.
138
+
139
+ ## Install (global)
140
+
141
+ ```bash
142
+ pipx install . # or: uv tool install .
143
+ ```
144
+
145
+ ```bash
146
+ pipx install git+https://github.com/dogo/stargate.git
147
+ ```
148
+
149
+ Not published yet, so install from the repo or a copy of this directory. The
150
+ distribution is named **`stargate-cli`** because `stargate` on PyPI belongs to
151
+ DataStax; the command you run and the package you import are still `stargate`,
152
+ and only `pipx uninstall` / `uv tool uninstall` need the longer name.
153
+ Both prompts and the default
154
+ config ship inside the package, so once installed the source directory can be
155
+ deleted or moved. `make install` / `make install-uv` are the same commands;
156
+ `make uninstall` removes it.
157
+
158
+ Hacking on the orchestrator itself:
159
+
160
+ ```bash
161
+ make dev # editable install into ./.venv
162
+ make test # smoke test with fake agents
163
+ ```
164
+
165
+ ## Configure
166
+
167
+ Seed a user-level config:
168
+
169
+ ```bash
170
+ stargate init-config # writes ~/.config/stargate/agents.yaml
171
+ ```
172
+
173
+ Without `--config`, every existing file in this lookup chain is layered, most
174
+ specific first:
175
+
176
+ 1. `./.stargate.yaml` in the repo you are standing in
177
+ 2. `~/.config/stargate/agents.yaml` (or `$XDG_CONFIG_HOME`)
178
+ 3. the `agents.yaml` packaged with the install
179
+
180
+ `settings` and `workflow` merge by key, and `agents` merges by agent name. An
181
+ agent entry is replaced as a whole, so redefining one must include its complete
182
+ `command`, `probe`, `probe_expect`, `usage_pattern` and `env` as needed; entries
183
+ for all other agents are inherited. Top-level scalar values use the
184
+ most-specific value. A blank mapping section such as `settings:` does not erase
185
+ inherited settings.
186
+
187
+ The top-level `task_sources` list is replaced whole by the most-specific layer.
188
+
189
+ `--config <path>` is the exception: that file is used exactly as given, with no
190
+ project, user or packaged config layered under it. This is also the escape hatch
191
+ for resuming with a repaired agent definition.
192
+
193
+ `stargate doctor` numbers every source and marks the source of each effective
194
+ setting, agent entry and workflow remap. Per-project overrides use
195
+ `.stargate.yaml`, not `agents.yaml`, and lookup never walks to a parent directory
196
+ or follows paths named by a config, so a global install cannot accidentally read
197
+ configuration from an unrelated repository.
198
+
199
+ ## Settings reference
200
+
201
+ Everything under `settings:` in the layered effective config. All are optional.
202
+
203
+ `task_sources` is a separate top-level block, like `agents` and `workflow`;
204
+ see [Read the task from a source](#read-the-task-from-a-source).
205
+
206
+ | key | default | meaning |
207
+ |---|---|---|
208
+ | `test_command` | `""` | Shell command run in the worktree after the developer and after every fixer pass. The same effective command is available to agent commands through `{test_command}`, which the packaged reviewer uses so it can verify the suite. An explicit value always wins over detection. |
209
+ | `test_command_detection` | `report` | What to do when `test_command` is empty: `report` shows likely commands without running one, `auto` runs the highest-priority match, and `off` skips detection. |
210
+ | `commit` | `true` | Commit results on the run's own branch. Linear runs can set this to `false`, or pass `--no-commit`, to leave the worktree dirty. Fan-out requires `true` and rejects `--no-commit`. |
211
+ | `max_review_loops` | `2` | Fixer passes allowed after the first review. `0` reviews once and stops. Overridable per run with `--max-review-loops`. |
212
+ | `blocking_severities` | `[]` | Opt-in severity policy. Empty or absent: the reviewer's explicit verdict decides. A YAML list of `high`, `medium`, `low` derives the verdict from JSON findings. A bare string or an unknown severity is rejected before any agent runs. See [Severity policy](#severity-policy-opt-in). |
213
+ | `max_fanout_tasks` | `8` | Maximum number of DAG nodes the fan-out architect may return. Must be a YAML integer: a quoted `"8"` is rejected, not coerced. |
214
+ | `max_parallel_tasks` | `2` | Maximum ready fan-out tasks run concurrently. Must be a YAML integer, like `max_fanout_tasks`. Overridable for fan-out runs and resumes with `--max-parallel-tasks`; linear invocations reject that flag. |
215
+ | `max_task_tokens` | `0` | Stop between phases once agents have reported this many tokens. `0` means no limit. |
216
+ | `agent_timeout_seconds` | `1800` | Kills a single agent invocation. `0` means no timeout. |
217
+ | `agent_retries` | `0` | Retries after a failed agent invocation. `0` preserves the single-attempt behavior; `2` allows up to three attempts. |
218
+ | `agent_retry_backoff_seconds` | `10` | Initial retry wait. It doubles after each failure: the default waits 10s, then 20s, then 40s. |
219
+ | `test_timeout_seconds` | `900` | Kills the test command; a timeout counts as exit 124. |
220
+ | `probe_timeout_seconds` | `120` | Kills a `doctor --probe` call. Deliberately far shorter than the agent timeout. |
221
+ | `worktree_root` | `""` | Where worktrees are created. Empty means `<repo-parent>/.stargate-worktrees/<repo-name>/`. |
222
+ | `prompts_dir` | `""` | Directory of custom `<role>.md` prompts, checked before the user and packaged ones. Relative paths resolve against the repo you run in. |
223
+
224
+ Per-agent keys live on the agent entry, not here: `command`, `probe`,
225
+ `probe_expect`, `usage_pattern`, `env`.
226
+
227
+ ## Exit codes
228
+
229
+ | code | meaning |
230
+ |---|---|
231
+ | `0` | Approved, and the test command passed or no command ran (`report`, `off`, or no match). |
232
+ | `1` | An agent errored or timed out, configuration was invalid, or another operational error stopped the command. `resume` is offered when a run was already created. |
233
+ | `2` | The CLI arguments were invalid, the reviewer still requested changes after the last allowed fixer pass, or the fixer changed nothing and the loop stopped early. |
234
+ | `3` | Approved, but the explicit test command or an `auto`-detected command failed. |
235
+ | `4` | `max_task_tokens` was reached; the run stopped between phases. A linear stop goes through the normal RESULT/summary/commit path. A fan-out stop before integration also prints a RESULT block and writes `summary.md`, but records resumable task state without an integration terminal commit. |
236
+ | `5` | The run reached a verdict, but Git could not create its commit. The work remains intact and staged where possible; the error prints a manual recovery command. |
237
+ | `6` | The run reached an APPROVED result, but publishing was refused or failed. The verdict, commit and summary are intact; the message prints the manual command. |
238
+ | `129` | The run received SIGHUP. Its state is recorded as failed and `resume` is offered. |
239
+ | `130` | The run was interrupted with Ctrl-C/SIGINT. Its state is recorded as failed and `resume` is offered. |
240
+ | `143` | The run received SIGTERM. Its state is recorded as failed and `resume` is offered. |
241
+
242
+ `doctor` exits `1` when a binary is missing, a prompt cannot be resolved, or a
243
+ `--probe` call fails.
244
+
245
+ `list` exits `0` even when a run has an unreadable or stale `state.json`. It
246
+ exits `1` when the current directory is not inside a Git repository or the runs
247
+ directory itself cannot be read.
248
+
249
+ ## Check setup
250
+
251
+ ```bash
252
+ stargate doctor
253
+ ```
254
+
255
+ It prints the numbered config layers and provenance of the effective settings
256
+ and agents, each role's resolved command, all five resolved prompt files
257
+ (including `fanout.md`), and the configured or detected project test commands
258
+ with their evidence. It makes no external calls, so `FOUND` means only that the
259
+ executable is on `PATH` — see
260
+ [Probing agents](#probing-agents) to actually verify that an agent can run.
261
+ The effective-settings table reports values and provenance. Most settings are only
262
+ validated by the command that uses them, but an invalid `blocking_severities` is reported
263
+ here as an `ERROR` and exits `1`, with the offending value still shown in the table —
264
+ finding out from `doctor` is the point of `doctor`.
265
+
266
+ ## Probing agents
267
+
268
+ `doctor` alone never calls an agent. `doctor --probe` makes one real,
269
+ **billable** call per distinct agent command. It tests the file capability that
270
+ role depends on, so a working credential paired with a broken tool layer fails
271
+ in seconds instead of being discovered after a paid planning stage:
272
+
273
+ ```console
274
+ $ stargate doctor --probe
275
+ Agent probes:
276
+ OK architect, reviewer (read) [4.3s]
277
+ FAIL developer, fixer (write) [6.1s]
278
+ agent exited 0 but did not write probe-1.txt; its file-editing tools are not working
279
+ ```
280
+
281
+ The prompt and required capability live in the config, so the orchestrator
282
+ stays vendor-agnostic. `{probe_file}` becomes an absolute path inside the
283
+ throwaway repository, and `probe_expect` may be `read` or `write`:
284
+
285
+ ```yaml
286
+ architect:
287
+ command: [claude, -p, --output-format, text, --model, opus]
288
+ probe: "Read the file {probe_file} and reply with its contents, nothing else."
289
+ probe_expect: read
290
+ ```
291
+
292
+ For a read probe, stargate seeds the file with a random marker and requires the
293
+ agent to return it. For a write probe, the file starts absent and the agent must
294
+ create it non-empty. The packaged architect and reviewer use `read`: their
295
+ `--disallowedTools "Edit Write NotebookEdit"` setting intentionally prevents
296
+ writes, so demanding one would be a false alarm. The developer and fixer use
297
+ `write` because editing is their job.
298
+
299
+ Because the probe runs the agent's *real* command, it also catches a wrong
300
+ model name or unsupported flag. The existing `{output}` contract remains in
301
+ force: an agent that declares it and exits 0 without writing the final-message
302
+ file fails the probe. Agents with no `probe` key report `SKIP` and do not fail
303
+ the exit code. Probes remain opt-in, run in a throwaway Git repository rather
304
+ than yours, and use `probe_timeout_seconds` (120) rather than the much longer
305
+ agent timeout. Agent-command placeholders are expanded for probes too, so
306
+ `doctor --probe` verifies the same effective command that a real role uses.
307
+
308
+ ## Use it against a repository
309
+
310
+ Stand in the repository you want the agents to modify:
311
+
312
+ ```bash
313
+ cd ~/dev/my-project
314
+ stargate run "Add pagination to the users endpoint"
315
+ ```
316
+
317
+ Before creating run artifacts or calling an agent, Stargate resolves the base
318
+ ref to a commit and checks a local branch against its configured upstream. It
319
+ stops if the branch is behind or diverged, then uses the frozen commit for the
320
+ whole run so a concurrently moving branch cannot change the developer's base.
321
+ For a remote upstream it queries the remote directly without fetching; if the
322
+ remote cannot be checked, the run does not start.
323
+
324
+ Per-project settings (usually just the test command) go in a `.stargate.yaml`
325
+ at the repo root:
326
+
327
+ ```yaml
328
+ settings:
329
+ test_command: "npm test"
330
+ ```
331
+
332
+ Choose another starting ref:
333
+
334
+ ```bash
335
+ stargate run \
336
+ --base-ref main \
337
+ "Add passkey authentication"
338
+ ```
339
+
340
+ Limit review/fix loops:
341
+
342
+ ```bash
343
+ stargate run \
344
+ --max-review-loops 1 \
345
+ "Refactor the image cache"
346
+ ```
347
+
348
+ Give the run and branch a short name before anything is created:
349
+
350
+ ```bash
351
+ stargate run --name "passkey auth" "Add passkey authentication to account settings"
352
+ ```
353
+
354
+ `--name` takes precedence over the architect's suggestion and uses at most five
355
+ whole words (32 characters), never a word truncated in the middle.
356
+
357
+ Use `--from URL` instead of the positional task to read it from a configured source.
358
+
359
+ ## Read the task from a source
360
+
361
+ ```bash
362
+ stargate run --from https://tracker.example/team/project/items/42
363
+ ```
364
+
365
+ Configure the commands that print the task as plain text. `task_sources` goes at
366
+ the top level of your config:
367
+
368
+ ```yaml
369
+ task_sources:
370
+ - hosts: [tracker.example, tracker.internal.example]
371
+ commands:
372
+ - [tracker-cli, show, "{url}", --text]
373
+ - [fetch-task, "{url}"] # your fallback wrapper script
374
+ env:
375
+ SOURCE_PROFILE: work
376
+ SOURCE_TOKEN: null # remove an inherited variable
377
+ ```
378
+
379
+ These executable names are examples; supply your installed CLI or wrapper.
380
+ Each entry contains a **list of commands**, each an argument list. Stargate
381
+ matches the URL's hostname case-insensitively, ignoring the port, and uses the
382
+ first matching entry. An unconfigured host is refused by name: Stargate never
383
+ fetches a URL itself.
384
+
385
+ Commands run in order from the invocation directory. The first command that
386
+ exits 0 with non-empty output wins. Missing executables, nonzero exits, and a
387
+ 120-second timeout advance to the next command. **Empty or whitespace-only
388
+ output is a failed fetch**, even with exit 0. If all commands fail, the error
389
+ names every attempt with its own stderr; no run artifacts or agents are started.
390
+ `stargate doctor` lists all hosts and commands and checks every command's first
391
+ element against PATH. A missing fallback executable makes doctor exit 1 too.
392
+
393
+ The only placeholders are `{url}` (the supplied URL), `{host}` (its hostname),
394
+ and `{path}` (its path, excluding query and fragment). Substitution is literal
395
+ within each argument; there is no shell or environment-variable expansion.
396
+ Stargate never derives an API URL from a web URL. Put URL conversion, JSON
397
+ extraction, or authentication logic in a wrapper and list its executable as a
398
+ command so doctor can check it. `env:` works as on agent entries: values override
399
+ the inherited environment, and `null` removes a variable. Existing CLI login or
400
+ inherited tokens work too; doctor prints variable names, never their values.
401
+
402
+ The fetched text is **untrusted input that the operator owns**. There is no
403
+ confirmation gate: it enters the architect's prompt verbatim, like a typed task.
404
+ The operator is responsible for what the command prints, including changes made
405
+ at the source since they last read it. A source can describe an issue or a pull
406
+ request; its command decides whether to include a description, a diff, or both.
407
+
408
+ `--from` and a positional task are mutually exclusive; their conflict is rejected
409
+ before any run is reserved. The initial branch uses the URL's last non-empty path
410
+ segment, for example `stargate/42-<task-slug>-<timestamp>`. If the segment has no
411
+ usable characters, ordinary task naming applies silently. `--name` still wins,
412
+ and the architect can still suggest a branch name as usual. `--from` also works
413
+ with `--fan-out`.
414
+
415
+ The exact fetched task and its separate `task_source` URL are frozen in
416
+ `state.json` for audit; `summary.md` records `Task source:` too. `resume` reuses
417
+ the recorded text and never re-fetches, even if the source is now unreachable.
418
+
419
+ ## Open a pull request
420
+
421
+ ```bash
422
+ stargate run --pr "Add the requested feature"
423
+ stargate run --pr --from https://tracker.example/team/project/items/42
424
+ stargate resume <run-id> --pr
425
+ ```
426
+
427
+ The top-level `pull_request:` block says **how** to publish. Only `--pr` says
428
+ **whether** to publish, for that invocation:
429
+
430
+ ```yaml
431
+ pull_request:
432
+ command: [review-cli, create, --head, "{branch}", --title, "{title}", --body-file, "-"]
433
+ env:
434
+ REVIEW_PROFILE: work
435
+ REVIEW_TOKEN: null # remove an inherited variable
436
+ ```
437
+
438
+ `review-cli` is an example: supply your installed CLI or wrapper. The command
439
+ must be a non-empty argument list containing `{branch}`. `{title}` is the task's
440
+ first non-empty line with whitespace collapsed, whether typed or fetched, falling
441
+ back to `stargate run` if none exists. Stargate never parses source text for title
442
+ fields and never uses the architect's branch name as the title.
443
+ Placeholders are substituted once within each argument, without shell expansion.
444
+ Embedding `{title}` in a `/bin/sh -c` script turns task text into shell code,
445
+ including text supplied by whoever filed a `--from` tracker item. Pass placeholders
446
+ as positional arguments and quote their expansions inside the script:
447
+
448
+ ```yaml
449
+ pull_request:
450
+ command:
451
+ - /bin/sh
452
+ - -c
453
+ - 'exec review-cli create --head "$1" --title "$2" --body-file -'
454
+ - _
455
+ - "{branch}"
456
+ - "{title}"
457
+ ```
458
+
459
+ The command runs in the original repository with the PR body on **stdin**.
460
+ `env:` overrides or removes inherited variables just as it does for agents.
461
+ `doctor` validates the block and checks the command's executable without running it.
462
+
463
+ **Stargate pushes; the configured command only opens the PR.** Stargate chooses
464
+ `origin`, otherwise the sole remote; multiple remotes without `origin` are
465
+ refused. It requires one push destination and checks that destination (including
466
+ `pushurl`) before pushing. No remote, an unqueryable remote, or a branch already
467
+ there causes a refusal **before attempting a push**. The push creates only the
468
+ run's `stargate/*` branch, with an empty-ref lease that also prevents overwriting
469
+ one created after the check. It never force-updates an existing branch, pushes
470
+ other branches or tags, or changes remote configuration. Git credential prompts
471
+ are disabled and publication commands have a 300-second timeout.
472
+
473
+ The echoed command shows the remote's name rather than its URL, so a credential
474
+ embedded in that URL does not appear in the terminal. Git's own output is written
475
+ to `pull-request-remote.log` and `pull-request-push.log` verbatim, though, so a
476
+ URL that carries a token can still reach those files if Git repeats it in an
477
+ error. Use a credential helper rather than a token in the remote URL — a URL with
478
+ a credential is also visible to anything that can read the process list, which no
479
+ amount of redaction here changes.
480
+
481
+ **Only `APPROVED` with no failing test command and a successful commit can
482
+ publish.** `CHANGES_REQUESTED`, `BUDGET_EXCEEDED`, failed tests, or a failed commit
483
+ publish nothing. Stargate prints the branch and worktree location plus the exact
484
+ manual push and PR commands, leaving that decision to the operator. Their normal
485
+ exit codes are unchanged. An approved run whose publication fails exits `6`; its
486
+ verdict, commit and summary remain intact. If the push succeeded but the PR command
487
+ failed, the branch stays on the remote and the message gives the command to retry.
488
+
489
+ With `--pr`, `.stargate/runs/<run-id>/pull-request.md` holds the body: verdict,
490
+ findings table, test command and exit code (or why tests did not run), task text,
491
+ and the source URL when present. It is also the input file for the printed manual
492
+ command. A run without `--pr` creates no publication artifacts and publishes
493
+ nothing, even when the block is configured. This works with `--fan-out` too;
494
+ only the reviewed integration branch is published.
495
+
496
+ **Resume requires `--pr` again.** Publish intent is never written to `state.json`.
497
+ Resuming a finished run with `stargate resume <run-id> --pr` re-runs the review at
498
+ token cost and may replace the recorded verdict in `state.json` and `summary.md`.
499
+ If the fresh result is unapproved, nothing is published.
500
+
501
+ A resumed run uses its frozen config unless `--config` overrides it, and validates
502
+ that actual config before any agent runs. `--pr` without `pull_request.command`,
503
+ with `settings.commit: false`, or with `--no-commit` is rejected before new run
504
+ artifacts or agent calls. Malformed publication blocks are also rejected early.
505
+
506
+ There is no setting that publishes automatically, by design: **standing config
507
+ must not become standing authorization.** Stargate never publishes on its own —
508
+ it publishes when a person says so, in that invocation.
509
+
510
+ ## Fan-out
511
+
512
+ Use `--fan-out` when one request contains work that can be split across
513
+ independent branches:
514
+
515
+ ```bash
516
+ stargate run --fan-out --max-parallel-tasks 3 \
517
+ "Add passkey authentication across the backend, frontend, and docs"
518
+ ```
519
+
520
+ The architect returns a validated `tasks.json` instead of a prose plan. Each
521
+ node has an ID and a self-contained task; `acceptance` and `depends_on` are
522
+ optional lists, and an explicit empty list is equivalent to omitting either
523
+ field. Stargate rejects duplicate or malformed IDs, unknown, duplicate or
524
+ self-dependencies, cycles, malformed JSON, and graphs larger than
525
+ `max_fanout_tasks` before a developer starts.
526
+
527
+ Ready nodes run concurrently, each on its own branch and worktree. A dependent
528
+ node starts from the commits produced by its dependencies, so it sees their
529
+ files rather than merely waiting for them. Completed nodes are committed by the
530
+ orchestrator, merged in topological order into the run's integration branch,
531
+ tested together, and passed through the normal reviewer/fixer loop as one
532
+ combined tree.
533
+
534
+ Fan-out requires `commit: true`: commits are the protocol that moves work
535
+ between isolated worktrees, even though nothing is merged into the user's
536
+ original branch. Publishing the integration branch requires `--pr` in that
537
+ invocation. `--no-commit` is rejected before a new run is
538
+ created. `resume` restores fan-out mode from `state.json`; it reuses the frozen
539
+ config and prompts, `tasks.json`, task branches/worktrees and every completed
540
+ task commit, plus the integration branch/worktree if they were already created.
541
+ Only unfinished or failed nodes are retried. The effective parallelism can be
542
+ changed when resuming with `--max-parallel-tasks`; `--redo` is not supported for
543
+ fan-out resumes.
544
+
545
+ The contract produced by the architect is:
546
+
547
+ ```json
548
+ {
549
+ "name": "passkey authentication",
550
+ "tasks": [
551
+ {
552
+ "id": "api-contract",
553
+ "task": "Define the passkey data model and API contract.",
554
+ "depends_on": [],
555
+ "acceptance": ["Request and response shapes are documented"]
556
+ },
557
+ {
558
+ "id": "backend",
559
+ "task": "Implement the passkey endpoints and focused tests.",
560
+ "depends_on": ["api-contract"],
561
+ "acceptance": ["Registration and authentication tests pass"]
562
+ }
563
+ ]
564
+ }
565
+ ```
566
+
567
+ Tasks that modify the same files should be joined by a dependency or kept as
568
+ one node. Git conflicts during dependency preparation or final integration stop
569
+ the run without merging into the original checkout; fix the task split or the
570
+ preserved branch, then resume.
571
+
572
+ ## What gets created
573
+
574
+ Suppose the architect names the work `passkey auth`. The implementation gets a
575
+ short branch similar to:
576
+
577
+ ```text
578
+ stargate/passkey-auth-20260830-181500
579
+ ```
580
+
581
+ When the run reaches a terminal result, this branch also receives a local
582
+ commit. The user's `main` (or other checked-out branch), index and worktree do
583
+ not move. A follow-up can branch directly from it:
584
+
585
+ ```bash
586
+ stargate run --base-ref stargate/passkey-auth-20260830-181500 "Add recovery codes"
587
+ ```
588
+
589
+ For a linear run, the packaged architect prompt asks for a `NAME:` first line.
590
+ Stargate strips a valid line before forwarding the plan to the developer,
591
+ reviewer, and fixer. A custom/older prompt that omits it, or a malformed name,
592
+ safely falls back to the task slug and leaves the plan untouched. The fan-out
593
+ prompt carries the corresponding name in the top-level JSON `name` field.
594
+
595
+ The run ID and artifacts directory must exist before the architect runs, so an
596
+ architect suggestion renames the branch only. Use `--name "passkey auth"` when
597
+ the run ID should also be short; that produces
598
+ `20260830-181500-passkey-auth`. Existing run IDs are not parsed or migrated, so
599
+ they continue to work with `list` and `resume`. If a name is reserved twice in
600
+ the same second, both the run ID and branch receive the same `-2`, `-3`, …
601
+ discriminator.
602
+
603
+ And its own worktree outside the target repository:
604
+
605
+ ```text
606
+ <repo-parent>/
607
+ ├── my-project/
608
+ └── .stargate-worktrees/
609
+ └── my-project/
610
+ └── 20260830-181500-add-passkey-authentication/
611
+ ```
612
+
613
+ Run artifacts are stored under the target repo:
614
+
615
+ ```text
616
+ my-project/.stargate/runs/<run-id>/
617
+ ├── state.json # stage, status, commit, review cycle -- what `resume` reads
618
+ ├── config.yaml # the fully merged effective config, frozen at run start
619
+ ├── prompts/ # role and fan-out prompts, frozen at run start
620
+ ├── plan.md
621
+ ├── plan.md.log # the agent's full trace, written live
622
+ ├── plan.md.attempt-2.log # later attempts keep separate traces
623
+ ├── developer.txt
624
+ ├── developer.txt.log
625
+ ├── review-1.md
626
+ ├── fix-1.txt # only when needed
627
+ ├── tests-*.txt # if an explicit or auto-detected command runs
628
+ ├── commit-message.txt # exact traceable message passed to Git, if attempted
629
+ └── summary.md
630
+ ```
631
+
632
+ A fan-out run additionally stores `tasks.json` and one `tasks/<id>/` artifact
633
+ directory per node. Each task branch is normally `<run-branch>-<task-id>` (with
634
+ a numeric suffix if that branch already exists).
635
+ The integration worktree uses the normal run path. Each task worktree is its
636
+ sibling at `<run-worktree-parent>/<run-id>-<task-id>`.
637
+
638
+ The tree above shows the linear stage filenames. Fan-out uses
639
+ `architect-tasks.json` and `tasks.json` in place of `plan.md`, then records each
640
+ task separately:
641
+
642
+ ```text
643
+ my-project/.stargate/runs/<run-id>/
644
+ ├── architect-tasks.json # architect's raw final response
645
+ ├── architect-tasks.json.log # architect's full trace
646
+ ├── tasks.json # validated, normalized DAG reused by resume
647
+ └── tasks/
648
+ └── <id>/
649
+ ├── developer.txt
650
+ ├── developer.txt.log
651
+ ├── state.json
652
+ ├── tests-task.txt # when a test command runs
653
+ ├── commit-message.txt # when the task commit is attempted
654
+ └── result.json # after successful task completion
655
+ ```
656
+
657
+ Integration tests, reviews, fixes, the terminal commit message and `summary.md`
658
+ remain at the run-artifact root. A fan-out budget stop before integration writes
659
+ `summary.md`; resume continues from the saved task state.
660
+
661
+ Each role prints its `.log` path before starting, so it can be followed with
662
+ `tail -f`, and its exit code and duration when it ends. While an agent runs it
663
+ prints a heartbeat every 30 seconds — elapsed time and bytes written — so a
664
+ long silent stage is visibly moving rather than possibly hung:
665
+
666
+ ```console
667
+ === DEVELOPER ===
668
+ trace: tail -f .../developer.txt.log
669
+ ... 30s elapsed, 41,238 bytes written
670
+ ... 60s elapsed, 96,004 bytes written
671
+ [developer] exit 0 in 512s
672
+ ```
673
+
674
+ The trace itself is never echoed to the terminal.
675
+
676
+ `.stargate/` ignores itself (it writes its own `.gitignore`), so run
677
+ artifacts never show up in the target repo's `git status`.
678
+
679
+ ## Configure tests
680
+
681
+ When `test_command` is empty, stargate inspects the repository and prints every
682
+ likely test command and the file that supplied the evidence. `stargate doctor`
683
+ shows the same information before a run. Detection has a fixed priority rather
684
+ than accidental first-match-wins:
685
+
686
+ 1. `make test` for a `test` target in `Makefile`, `makefile`, or `GNUmakefile`
687
+ 2. `npm test`, `pnpm test`, or `yarn test` for a non-placeholder
688
+ `package.json` `scripts.test` (lock files choose pnpm/yarn)
689
+ 3. `cargo test` for `Cargo.toml`
690
+ 4. `go test ./...` for `go.mod`
691
+ 5. `swift test` for `Package.swift`
692
+ 6. `pytest -q` only with explicit pytest configuration/dependencies or
693
+ `tests/test_*.py`
694
+
695
+ A root-level `test_*.py` alone is deliberately not a pytest signal: projects
696
+ often have executable smoke tests with custom arguments that pytest cannot
697
+ collect. Detection reads the original repository once, before agent edits, so
698
+ a developer cannot cause a newly written command to execute later in the same
699
+ run.
700
+
701
+ The default mode is `report`. A detected command is a command the user did not
702
+ write into stargate configuration, and the wrong guess could invoke a watcher,
703
+ an unexpectedly broad suite, or a misleading reviewer verdict several times.
704
+ Report-only mode fixes the previous silent failure without taking that extra
705
+ authority: it prints the top choice and alternatives, and tells the reviewer
706
+ what was found but not run.
707
+
708
+ Confirm the project command explicitly in `.stargate.yaml`:
709
+
710
+ ```yaml
711
+ settings:
712
+ test_command: "pytest -q"
713
+ ```
714
+
715
+ Or opt into automatically running the highest-priority match:
716
+
717
+ ```yaml
718
+ settings:
719
+ test_command_detection: auto
720
+ ```
721
+
722
+ Set the mode to `off` when the repository intentionally has no orchestrator
723
+ test command. An explicit `test_command` always wins and suppresses detection.
724
+
725
+ Examples:
726
+
727
+ ```yaml
728
+ test_command: "swift test"
729
+ ```
730
+
731
+ ```yaml
732
+ test_command: "npm test"
733
+ ```
734
+
735
+ ```yaml
736
+ test_command: "xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17 Pro'"
737
+ ```
738
+
739
+ The command is intentionally project-specific. It runs through `/bin/sh -lc`
740
+ inside the agent worktree, after the developer and after every fixer pass. The
741
+ tail of its output is fed into the reviewer and fixer prompts, so a red suite
742
+ is a blocking review finding rather than a number nobody reads. The packaged
743
+ reviewer may also run that exact command in the worktree, letting it reproduce
744
+ the result itself instead of trusting the pasted report alone.
745
+
746
+ The reviewer grant follows only a command Stargate itself selected. An explicit
747
+ `test_command` is granted, as is the top detection candidate in `auto` mode.
748
+ `report`, `off`, an empty setting, or no detected command grants nothing. A
749
+ report-only candidate remains unapproved even though `doctor` and the reviewer
750
+ prompt show it.
751
+
752
+ Timeouts (seconds) cap a stuck agent or suite:
753
+
754
+ ```yaml
755
+ settings:
756
+ agent_timeout_seconds: 1800
757
+ test_timeout_seconds: 900
758
+ ```
759
+
760
+ ## Customize prompts
761
+
762
+ The version 5 packaged configuration includes five prompts to tune: four role
763
+ prompts plus the fan-out architect contract. Copy them somewhere writable:
764
+
765
+ ```bash
766
+ stargate init-prompts # writes ~/.config/stargate/prompts/*.md
767
+ ```
768
+
769
+ Then delete the ones you don't want to change. Lookup is per-file, first hit
770
+ wins:
771
+
772
+ 1. `settings.prompts_dir` from the active config, if set
773
+ 2. `~/.config/stargate/prompts/`
774
+ 3. the prompts packaged with the install
775
+
776
+ So keeping only a custom `reviewer.md` leaves the other four on the defaults.
777
+ `stargate doctor` prints the file each prompt resolved to.
778
+
779
+ The packaged `architect.md` also asks the architect to begin its response with
780
+ `NAME: <two to four words>`. Stargate treats that line as optional for custom
781
+ and frozen prompts: only a valid first non-empty `NAME:` line is removed, and
782
+ the rest of the plan is forwarded unchanged.
783
+
784
+ `fanout.md` is separate because its output is machine-readable. It requires a
785
+ bare JSON object and receives `{max_tasks}` in addition to the task and base
786
+ ref; malformed output stops before any implementation work starts.
787
+
788
+ Prompts committed with a project:
789
+
790
+ ```yaml
791
+ # .stargate.yaml
792
+ settings:
793
+ prompts_dir: .stargate-prompts # relative to the repo you run in
794
+ ```
795
+
796
+ Two things the prompt templates have to respect:
797
+
798
+ - Only known placeholders are substituted, by literal replacement: `{task}` and
799
+ `{base_ref}` everywhere, plus `{plan}` (developer, reviewer, fixer),
800
+ `{tests}` (reviewer, fixer), `{review}` (fixer) and `{known_findings}` (architect only).
801
+ An architect template that omits `{known_findings}` simply receives no inherited section,
802
+ including custom templates and older runs' frozen prompts. Every other brace is left
803
+ alone, so a prompt may contain JSON, CSS or an f-string example verbatim. The
804
+ fan-out prompt additionally receives `{max_tasks}`.
805
+ These are separate from the agent-command placeholders described below.
806
+ - `reviewer.md` is a contract with the orchestrator, and either form satisfies
807
+ it: one JSON object as described in
808
+ [Structured review findings](#structured-review-findings), or a response whose
809
+ last line is exactly `VERDICT: APPROVED` or `VERDICT: CHANGES_REQUESTED`.
810
+ Anything else aborts the run.
811
+
812
+ ## Structured review findings
813
+
814
+ The packaged `reviewer.md` asks for one JSON object and nothing else:
815
+
816
+ ```json
817
+ {
818
+ "verdict": "CHANGES_REQUESTED",
819
+ "findings": [
820
+ {
821
+ "severity": "high",
822
+ "file": "app/storage.py",
823
+ "line": 42,
824
+ "finding": "Saving truncates the existing file before validating the replacement.",
825
+ "why": "Invalid input permanently destroys previously saved user data."
826
+ }
827
+ ]
828
+ }
829
+ ```
830
+
831
+ `verdict` is required and must be exactly `APPROVED` or `CHANGES_REQUESTED`. **By default,
832
+ the orchestrator acts on the reviewer's decision as given.** Only an explicit
833
+ `settings.blocking_severities` policy can override it. `findings` may be empty. Each entry needs a
834
+ `severity` of `high`, `medium` or `low` and a non-empty `finding`; `file`, `line` and `why`
835
+ are optional, and unknown keys are ignored. A `CHANGES_REQUESTED` with no findings, or an
836
+ `APPROVED` carrying a `high` one, is accepted rather than treated as an error.
837
+
838
+ Without a policy, severity is reported but does not decide approval or select what the
839
+ fixer works on. It is classified by demonstrated impact — the packaged prompt carries the
840
+ full rubric. It orders the summary table, and a value outside the three labels aborts the
841
+ run as a malformed review.
842
+
843
+ The findings reach three places:
844
+
845
+ - `{review}` in the fixer prompt receives the reviewer's response with no findings
846
+ renderer applied, JSON or prose. Its content is never rewritten — a finding containing
847
+ the literal text `{tests}` reaches the fixer as the reviewer wrote it — though the outer
848
+ whitespace is trimmed.
849
+ - `summary.md` gets a `## findings` table when the findings are non-empty, highest
850
+ severity first. The stored order remains the reviewer's.
851
+ - `state.json` records them under `findings`, separate from the `review` checkpoint that
852
+ `resume` uses. An empty list is stored as `null`, like the neighbouring `fanout` and
853
+ `review` keys, so a consumer must not assume the value is iterable. The checkpoint ends
854
+ with the run — except when the commit failed, which keeps it so `resume` can retry only
855
+ the commit — while the findings stay. That is what lets the summary of an approved resume
856
+ that skips the review still show its table.
857
+
858
+ Both hold the **most recent completed review** only, which is not always the tree the run
859
+ ends on: a fixer edit followed by a token-budget stop finishes with the previous review's
860
+ findings, and the summary does not label which pass they came from. A later review replaces
861
+ them. The per-pass `review-N.md` artifacts hold the
862
+ passes still available: resuming a finished run starts the numbering over and can overwrite
863
+ `review-1.md`, so they are not an immutable history.
864
+
865
+ Who decides approval did not change with the V1 contract, though what the parser accepts
866
+ did: bare JSON now succeeds where it used to abort, and a malformed payload is a new way to
867
+ stop. A reviewer that answers in prose, ending
868
+ in `VERDICT: APPROVED` or `VERDICT: CHANGES_REQUESTED`, is still read exactly as before —
869
+ which is what keeps a custom `prompts_dir` reviewer working, and what lets a run created
870
+ before this existed still `resume` against its frozen prompt. What a new prompt can change is
871
+ the model's own judgment, and malformed output is a new way for a run to stop: neither is a
872
+ change to the orchestrator's rules.
873
+
874
+ ### Severity policy (opt-in)
875
+
876
+ The default is unchanged: absent `settings.blocking_severities`, `[]`, or YAML `null`
877
+ keeps the reviewer's explicit verdict. To opt in, configure a list:
878
+
879
+ ```yaml
880
+ settings:
881
+ blocking_severities: [high, medium]
882
+ ```
883
+
884
+ For a JSON review with findings, the orchestrator derives `CHANGES_REQUESTED` when **any
885
+ finding has a severity in that list**, and `APPROVED` otherwise. The derived verdict
886
+ controls the review/fix loop, summary, exit code and terminal commit message. Findings
887
+ remain in the report even when they do not block. This applies to both linear runs and
888
+ the combined integration review in fan-out; task scheduling is unchanged.
889
+
890
+ - A review with **no findings** always keeps its explicit verdict, including
891
+ `CHANGES_REQUESTED`: there is no evidence to justify deriving an approval.
892
+ - **Prose** always keeps its literal final `VERDICT:` line, regardless of policy.
893
+ It carries no structured findings to derive from.
894
+ - The fixer still receives the reviewer's response unchanged, with no findings renderer.
895
+ When the policy is active, a separate orchestrator note accompanies the review in the
896
+ fixer prompt, naming the blocking severities and explaining these exceptions.
897
+
898
+ The setting must be a YAML list: a bare `high`, an empty string, or an unknown name such
899
+ as `critical` fails before any agent is called. Names are case-insensitive, surrounding
900
+ whitespace is ignored, and duplicates are collapsed. `stargate doctor` shows the value
901
+ and which config layer supplied it.
902
+
903
+ A resume normally uses the run's frozen config. An explicit `--config` can tighten,
904
+ loosen, or disable the policy. While the worktree fingerprint still matches, Stargate
905
+ re-evaluates the saved `review-<n>.md` under that policy: saved findings can go straight
906
+ to the fixer or become approved without paying for another review of the same tree.
907
+ Re-evaluating an active or newly disabled policy needs a usable artifact; a missing or
908
+ malformed one requires a fresh review. Be aware of what that re-reading implies: while a
909
+ policy is active the artifact **outranks** the recorded verdict in both directions, so a
910
+ hand-edited `review-<n>.md` claiming `APPROVED` with no findings can approve a run that
911
+ recorded `CHANGES_REQUESTED`, with only the worktree fingerprint as a guard. Without a
912
+ policy, V1's rule still holds — an artifact can confirm recorded changes but never create
913
+ an approval. An active policy is recorded as
914
+ `review.blocking_severities` in the checkpoint so later resumes can recognize its removal,
915
+ including when it was enabled by an earlier resume. Runs that never used a policy keep
916
+ V1's replay rules: an approval retries the commit without reading the artifact, and
917
+ recorded changes reuse only an artifact that still requests changes.
918
+ The existing review-loop budget still applies: if a saved pass falls outside a
919
+ reduced budget, it is not replayed.
920
+
921
+ **There is no behavior-neutral derived default.** A lenient list such as `[high, medium]`
922
+ can approve a low-only `CHANGES_REQUESTED`; a strict list such as `[high, medium, low]`
923
+ can block an `APPROVED` carrying findings. Activating either direction changes approval
924
+ behavior. That is why the policy is off by default. The model still judges severity:
925
+ **a misclassified severity can end a run before the fix.**
926
+
927
+ ### Inheriting findings across runs
928
+
929
+ For linear runs, `stargate run --base-ref stargate/<previous-branch> "next task"`
930
+ automatically includes the previous run's findings in the architect prompt. This is the
931
+ continuation command printed at the end of a committed run; no opt-in flag is needed.
932
+ The resolved base ref must match a recorded `state.json` **branch** field exactly: Stargate
933
+ scans `.stargate/runs/*/state.json`, without trying to reconstruct a run id from the branch
934
+ name. The current branch also qualifies when `--base-ref` is omitted. A SHA, tag or
935
+ `refs/heads/stargate/...` spelling does not qualify.
936
+
937
+ Only the immediately previous run's **last completed review** travels, including findings
938
+ from an `APPROVED` review. All findings pass through, unfiltered and without a size cap,
939
+ so a large review can lengthen the prompt; earlier runs are not accumulated. The
940
+ `## Known unresolved findings` section names the source run and warns that the findings
941
+ describe the tree that review saw: an edit after that review may already have resolved one.
942
+ The architect checks the current code and plans a fix or explains why a finding stays.
943
+ Inheritance injects text only into the architect prompt and does not change verdict rules.
944
+
945
+ A non-Stargate base ref, no matching run, unreadable state, or absent, `null` or empty
946
+ findings silently adds nothing: no heading, empty section, warning or error. Fan-out runs
947
+ do not inherit findings.
948
+
949
+ **Inheritance therefore requires `commit: true`,** the same way fan-out does. A run whose
950
+ commit was disabled or failed leaves a branch that still points at the base, so it does not
951
+ carry the work its findings describe; inheriting from it would hand the architect a review
952
+ of a tree the new run cannot see. Such a run is skipped like any other case inheritance
953
+ cannot answer. With `commit: false`, `--base-ref stargate/<branch>` gives you neither the
954
+ previous run's code nor its findings.
955
+
956
+ ## Final message vs. stdout
957
+
958
+ What an agent *prints* is not what it *answered*. `codex exec` streams the whole
959
+ session to stdout — reasoning, every command it ran, a token footer — while
960
+ `claude -p --output-format text` prints only the final message.
961
+
962
+ That matters because the architect's answer becomes `{plan}` in three later
963
+ prompts and the reviewer's becomes `{review}` in the fixer prompt. Forward a
964
+ trace and every hop pays for the previous hop's trace.
965
+
966
+ So an agent can be handed a file to write its last message to. Put `{output}`
967
+ anywhere in its command and the orchestrator substitutes a path, forwards
968
+ whatever lands in that file, and keeps stdout beside it as a `.log`:
969
+
970
+ ```yaml
971
+ developer:
972
+ command: [codex, exec, --sandbox, workspace-write, --output-last-message, "{output}"]
973
+ ```
974
+
975
+ Agents without `{output}` keep using stdout, which is correct for the Claude
976
+ roles as configured. If a command declares `{output}` and writes nothing there,
977
+ the run stops rather than silently forwarding an empty plan.
978
+
979
+ This also protects the verdict: a reviewer's trailing session footer would
980
+ otherwise sit after the `VERDICT:` line the orchestrator parses.
981
+
982
+ Agent commands have two placeholders:
983
+
984
+ - `{output}` becomes the final-message path described above.
985
+ - `{test_command}` becomes the exact explicit or `auto`-detected command that
986
+ Stargate will run. The packaged reviewer places it in Claude Code's
987
+ `Bash(...)` allow rule.
988
+
989
+ When no test command will run, Stargate removes the argument containing
990
+ `{test_command}` and, for a separate option/value pair, its introducing option.
991
+ This avoids `Bash()`, an empty argv item, or a dangling option consuming the
992
+ prompt. Put the placeholder in an option value, as the packaged config does;
993
+ it cannot be the executable. Commands without the placeholder are unchanged,
994
+ including custom configs and configs frozen by older runs.
995
+
996
+ The test command is arbitrary shell text, but an allow rule is a pattern. To
997
+ keep interpolation from silently widening the reviewer's authority, Stargate
998
+ does not interpolate commands containing `(`, `)`, `,`, `*`, or control
999
+ characters. Stargate can still run such a configured command itself; the
1000
+ reviewer receives only the test report. `stargate doctor` prints every expanded
1001
+ agent command and says whether the grant is effective, absent, or refused.
1002
+
1003
+ ## Stages that produce nothing
1004
+
1005
+ The architect already has to return a non-empty plan. The developer has a
1006
+ different output contract: it must change the worktree. Tracked edits,
1007
+ deletions, commits and untracked new files all count. If it exits 0 without any
1008
+ change, the run fails before tests or review and the developer is not recorded
1009
+ as complete, so a normal `resume` reruns it.
1010
+
1011
+ A fixer can legitimately conclude that a review finding needs no code change,
1012
+ so the same situation is not treated as an agent failure. Re-reviewing an
1013
+ identical worktree would only buy the same verdict again; stargate stops the
1014
+ loop immediately with `CHANGES_REQUESTED` instead.
1015
+
1016
+ ## Committing the result
1017
+
1018
+ Committing is on by default. This changes older behavior deliberately: a
1019
+ default-off feature would leave every user who did not discover the setting
1020
+ with the same manual branching and committing step, while completed work would
1021
+ still exist only in a removable worktree. The scope of the mutation stays
1022
+ narrow: Git advances only Stargate-created branches inside Stargate worktrees.
1023
+ For a linear run, use `settings.commit: false` or `--no-commit` when that
1024
+ tradeoff is not wanted. Fan-out requires commits and rejects both configurations.
1025
+
1026
+ The linear workflow commits only after a terminal verdict, never after
1027
+ individual fixer passes, and therefore normally produces one commit. A resume
1028
+ of already committed work finds an empty index and produces no second commit; a
1029
+ later `--redo` that genuinely adds work can produce a new terminal commit.
1030
+ Empty commits are not created in the linear workflow. Fan-out commits completed
1031
+ tasks, merges those commits into the integration branch, then records the
1032
+ integration verdict in a terminal commit; that final commit can be empty when
1033
+ the merges already contain every file change.
1034
+
1035
+ Red outcomes that reach the finish path are durable too. `CHANGES_REQUESTED`, a
1036
+ failed test command and a linear `BUDGET_EXCEEDED` are recorded in the subject
1037
+ and `Stargate-Verdict` trailer rather than being presented as success. A fan-out
1038
+ budget stop before integration writes a RESULT and summary but deliberately
1039
+ does not put a terminal commit at the base of the integration branch. Crashes,
1040
+ agent errors, invalid reviewer output and catchable signals also have no
1041
+ terminal commit; a successful `resume` records the eventual integration verdict.
1042
+
1043
+ The commit stages tracked edits and deletions plus new untracked implementation
1044
+ files. Gitignored files remain ignored. Stargate also snapshots untracked
1045
+ entries around every orchestrator test command and excludes entries that first
1046
+ appeared during testing, so an incomplete `.gitignore` does not silently add
1047
+ `build/`, `.venv/` or similar output. Tracked files rewritten by a test,
1048
+ such as an intentional snapshot or lockfile, remain part of the reviewed diff
1049
+ and are committed. The untracked snapshot uses Git's directory grouping: if a
1050
+ suite creates an artifact inside an untracked directory that already existed
1051
+ before the suite, Git cannot distinguish the nested artifact from the
1052
+ implementation directory; the durable fix for that mixed case is a project
1053
+ `.gitignore`.
1054
+
1055
+ The subject begins with `stargate:`, includes the terminal verdict, and stays
1056
+ within 72 characters. The body names the task, test result and base ref, while
1057
+ Git trailers carry the run ID, verdict, base ref and test exit when available.
1058
+ That run ID maps the commit back to `.stargate/runs/<run-id>/`. Git still uses
1059
+ the repository's configured user, signing policy and hooks: Stargate supplies
1060
+ neither `--author`, `--no-gpg-sign` nor `--no-verify`.
1061
+
1062
+ A failed hook, signing problem or missing Git identity returns exit code 5.
1063
+ The result remains staged where possible, `state.json` keeps the terminal
1064
+ verdict plus `commit_error`, and the terminal names both recoveries: `stargate
1065
+ resume <run-id>`, which reuses the recorded verdict and retries only the
1066
+ commit, and a manual `git commit` in the intact worktree. A hook that succeeds but rewrites files
1067
+ after staging leaves those rewrites uncommitted with a warning; Stargate never
1068
+ makes an automatic second commit to conceal that state.
1069
+
1070
+ ## Retrying a transient failure
1071
+
1072
+ Retries are off by default. Enable them per project with a count of retries
1073
+ *after* the first attempt and a base wait:
1074
+
1075
+ ```yaml
1076
+ settings:
1077
+ agent_retries: 2
1078
+ agent_retry_backoff_seconds: 10
1079
+ ```
1080
+
1081
+ This allows at most three attempts of the agent that failed, waiting 10 seconds
1082
+ and then 20 seconds. A successful attempt is never repeated, nor are agents
1083
+ from an already completed stage. Each attempt announces its number and wait in
1084
+ the terminal and writes a separate trace such as `plan.md.log`,
1085
+ `plan.md.attempt-2.log`, and `plan.md.attempt-3.log`.
1086
+
1087
+ Non-zero agent exits and timeouts are retried because rate limits, server
1088
+ errors and dropped connections all appear that way at this boundary. A binary
1089
+ that cannot start is not retried, nor is an agent that exits successfully but
1090
+ violates its `{output}` contract. Stargate does not maintain a brittle list of
1091
+ vendor error messages. Instead, if two consecutive failures have the same
1092
+ normalized trace tail, it treats the failure as deterministic and stops early;
1093
+ this avoids repeatedly paying for a wrong model name or unsupported flag. The
1094
+ tradeoff is that an identical transient response twice also stops the retries.
1095
+
1096
+ Every retry is a fresh, potentially billable call. Reported usage is counted
1097
+ once for every attempt actually made, including failed attempts, so retries can
1098
+ reach `max_task_tokens` sooner. The agent timeout applies independently to each
1099
+ attempt; configure the retry count and backoff with the resulting worst-case
1100
+ runtime in mind. After the last attempt, the normal error and `Resume with:`
1101
+ hint are preserved.
1102
+
1103
+ ## Listing runs
1104
+
1105
+ `stargate list` shows the runs recorded in the current repository, newest
1106
+ first, without requiring an active config:
1107
+
1108
+ ```console
1109
+ $ stargate list
1110
+ Runs in /home/me/project (newest first):
1111
+
1112
+ * 20260831-101304-add-passkey-authentication [failed]
1113
+ updated 2026-08-31 10:19:42 | stage developer
1114
+ task Add passkey authentication
1115
+ branch stargate/add-passkey-authentication-20260831-101304
1116
+ worktree /home/me/.stargate-worktrees/project/20260831-101304-add-passkey-authentication (MISSING)
1117
+
1118
+ 20260830-181500-update-docs [approved]
1119
+ updated 2026-08-30 18:22:11 | stage review
1120
+ task Update docs
1121
+ branch stargate/update-docs-20260830-181500
1122
+ worktree /home/me/.stargate-worktrees/project/20260830-181500-update-docs
1123
+
1124
+ * resumable. Resume the newest with: stargate resume 20260831-101304-add-passkey-authentication
1125
+ ```
1126
+
1127
+ The former `stargate runs` spelling remains an alias.
1128
+ Each run appears in a separate block. Task summaries wrap to the terminal width
1129
+ (80 columns when unavailable), with `...` after three lines if truncated.
1130
+ Branch names and worktree paths remain complete for copying.
1131
+
1132
+ Failed runs and rows still reading `running` are marked with `*`. A `running`
1133
+ row normally means the run is still in flight, but it can also mean the process
1134
+ was stopped by an uncatchable hard kill such as SIGKILL or power loss. Missing
1135
+ or corrupt `state.json` files are shown as unknown rather than crashing or
1136
+ hiding the run. If `.stargate/runs/` does not exist or is empty, the command
1137
+ reports that there are no runs. Listing only reads recorded state: it never
1138
+ creates, repairs or deletes run artifacts.
1139
+
1140
+ ## Cleaning runs
1141
+
1142
+ `stargate clean <run-id>` removes one run. `stargate clean --all` preflights
1143
+ every recorded run and removes each run that passes; runs that fail a safety
1144
+ check are skipped, each reported with its reason, and the command exits `1`.
1145
+ Atomicity is per run: a single run is never partially removed, so a fan-out
1146
+ run keeps all of its worktrees, branches and artifacts if any one of its
1147
+ targets fails. A run is removed only when its branch
1148
+ is merged into the current `HEAD` and Git accepts the worktree as clean. There
1149
+ is no force option. After those checks, Stargate removes the linked worktree,
1150
+ its `stargate/*` branch and the run's `.stargate/runs/<run-id>/` artifacts. If a
1151
+ worktree is missing but still registered, Stargate removes only that stale Git
1152
+ registration before removing the remaining branch and artifacts.
1153
+
1154
+ ## Resuming a failed run
1155
+
1156
+ Every stage is recorded in the run's `state.json` before and after it runs. If
1157
+ a stage fails — the CLI is out of credits, the machine sleeps, a package
1158
+ upgrade removes the install mid-run — the run stops and prints:
1159
+
1160
+ ```text
1161
+ Resume with: stargate resume 20260831-101304-add-passkey-authentication
1162
+ ```
1163
+
1164
+ For a linear run, `resume` reuses the plan, branch, worktree, frozen config and
1165
+ frozen prompts, and skips the stages already marked complete. It does not
1166
+ produce a second plan, branch or worktree. A developer that exited successfully
1167
+ but changed nothing is deliberately not marked complete, so plain `resume`
1168
+ reruns it. Fan-out reuse is described in [Fan-out](#fan-out).
1169
+ Untracked test-artifact exclusions are stored in `state.json`, so output
1170
+ created before a crash remains excluded when a later process resumes. If a
1171
+ previous invocation already committed and no new work was added, the resumed
1172
+ finish detects the empty index and does not make a duplicate commit.
1173
+
1174
+ By default it runs under the config frozen into the run, so resuming cannot
1175
+ silently change the agents the earlier stages ran under. Pass `--config` to
1176
+ override that, which is how you resume past a broken agent definition:
1177
+
1178
+ ```bash
1179
+ stargate --config ./fixed.yaml resume <run-id>
1180
+ ```
1181
+
1182
+ To rerun a linear stage that was already recorded as complete, use the supported
1183
+ `--redo` option instead of editing `state.json`:
1184
+
1185
+ ```bash
1186
+ stargate resume <run-id> --redo developer
1187
+ ```
1188
+
1189
+ For linear runs, `--redo` accepts `architect` or `developer` and is repeatable.
1190
+ Fan-out resumes reject it. Redoing only the
1191
+ architect leaves the developer marked complete, which means the new plan would
1192
+ not be implemented; stargate warns in that case, and you can pass both
1193
+ `--redo architect --redo developer`.
1194
+
1195
+ ### Resuming into the review/fix loop
1196
+
1197
+ The review/fix cycle is recorded in `state.json` alongside a fingerprint of the
1198
+ worktree the reviewer judged, so a resume re-enters the loop where it stopped
1199
+ instead of buying a second opinion on the same tree:
1200
+
1201
+ - A **fixer that died** — a vendor usage limit, a crash — resumes straight into
1202
+ that fixer, reusing the `review-<n>.md` already on disk. The pass is
1203
+ announced as `REVIEW <n> (skipped, reusing …)`.
1204
+ - A **run that reached its verdict but could not commit** — a signing prompt
1205
+ that timed out, a hook that rejected the tree — resumes straight into the
1206
+ commit, announced as `REVIEW <n> (skipped, already APPROVED)`. Such a run is
1207
+ marked resumable by `stargate list` even though it recorded a terminal
1208
+ verdict, and the commit error names `stargate resume <run-id>` as the cheap
1209
+ recovery.
1210
+ - A **policy changed by `--config`**, including disabling it, re-evaluates the saved
1211
+ review against the effective policy. With an unchanged fingerprint, a recorded approval
1212
+ can now go to the fixer and recorded changes can now approve, without another review.
1213
+ - **Anything that changed the worktree** since the verdict — a hand edit, a
1214
+ fixer that got partway through — invalidates the record, and the loop starts
1215
+ over with a real review. A verdict never covers a tree its reviewer did not
1216
+ see.
1217
+
1218
+ `max_review_loops` is a budget for the run, not a fresh allowance per resume:
1219
+ a resumed loop continues the count rather than restarting it. Resuming a run
1220
+ that actually finished discards the record and reviews from scratch, which is
1221
+ what makes `--config` with a corrected reviewer take effect.
1222
+
1223
+ ## Terminating a run
1224
+
1225
+ Ctrl-C already follows the normal failure path. SIGTERM and SIGHUP do too: the
1226
+ direct agent process stargate started is killed, `state.json` is changed from
1227
+ `running` to `failed` with the signal recorded, and the same `Resume with:` hint
1228
+ is printed. The orchestrator exits with the conventional `128 + signal` code:
1229
+ 130 for SIGINT, 143 for SIGTERM and 129 for SIGHUP. Cleanup is bounded, and a
1230
+ second termination signal uses the operating system's default action so it
1231
+ cannot leave the orchestrator stuck.
1232
+
1233
+ SIGKILL and sudden power loss cannot be handled by any process. A run stopped
1234
+ that way can still show as `running`; inspect it before resuming. Only the direct
1235
+ agent process is killed during catchable-signal cleanup. Subprocesses it spawned,
1236
+ such as test runs or tool calls, may be orphaned and continue running, so check
1237
+ the worktree before resuming. Signal handling also starts only after the CLI has
1238
+ enough information to enter the run path, so a termination during very early
1239
+ setup may leave no state file rather than a false `running` state.
1240
+
1241
+ ## Token budget
1242
+
1243
+ ```yaml
1244
+ settings:
1245
+ max_task_tokens: 120000 # 0 or unset = no limit
1246
+ ```
1247
+
1248
+ Be clear about what this can and cannot do. Most of a run's tokens are the
1249
+ model reading the repository — that never crosses this process, so the
1250
+ orchestrator cannot meter it and cannot interrupt an agent mid-flight. The
1251
+ number it tracks is whatever the agent's CLI *prints*, extracted by a regex the
1252
+ config supplies:
1253
+
1254
+ ```yaml
1255
+ developer:
1256
+ command: [codex, exec, --sandbox, workspace-write, --output-last-message, "{output}"]
1257
+ usage_pattern: 'tokens used\s+([\d,]+)'
1258
+ ```
1259
+
1260
+ The totals are summed across phases and checked at each phase boundary. So a
1261
+ budget stops the *next* agent from starting, never one already running. Parallel
1262
+ fan-out work can overshoot by the agents that were already scheduled; the RESULT
1263
+ and `summary.md` report the run's true totals as `Tokens: X of Y`, so the
1264
+ overshoot is visible there as a total above the cap, and the scheduler explains
1265
+ it on stderr when it stops. Hitting the cap ends the
1266
+ run with verdict `BUDGET_EXCEEDED` and exit code 4, leaving its worktree state
1267
+ intact. A linear stop commits changes through its normal finish path. Completed
1268
+ fan-out tasks already have task commits, but a stop before integration does not
1269
+ create a terminal integration commit; `resume` continues from the saved task
1270
+ state.
1271
+
1272
+ For a genuine in-flight cap, use a vendor flag in the command itself. Claude
1273
+ has one; Codex has no equivalent today:
1274
+
1275
+ ```yaml
1276
+ architect:
1277
+ command: [claude, -p, --output-format, text, --max-budget-usd, "0.50"]
1278
+ ```
1279
+
1280
+ An agent with no `usage_pattern` contributes zero to the total, which makes the
1281
+ budget blind to it. `stargate doctor` flags that per role whenever a cap is set.
1282
+
1283
+ ## Per-agent environment
1284
+
1285
+ An agent inherits the orchestrator's environment. `env` overrides it for that
1286
+ agent only, and a `null` value **removes** a variable:
1287
+
1288
+ ```yaml
1289
+ architect:
1290
+ command: [claude, -p, --output-format, text]
1291
+ env:
1292
+ ANTHROPIC_API_KEY: null # use the CLI's own login instead
1293
+ ANTHROPIC_BASE_URL: "https://internal.example/v1"
1294
+ ```
1295
+
1296
+ The null case is the one that earns the feature. A globally exported
1297
+ `ANTHROPIC_API_KEY` takes precedence over the Claude CLI's claude.ai login, so
1298
+ every Claude role fails with `Credit balance is too low` while the Codex roles
1299
+ are fine — and the only other fix is unsetting it for the whole orchestrator.
1300
+
1301
+ Two consequences worth knowing:
1302
+
1303
+ - Probe deduplication keys on command **and** environment. Two roles running
1304
+ the same command under different credentials are two separate probes, not
1305
+ one; deduping on the command alone would report an agent that was never
1306
+ called.
1307
+ - `doctor` prints the variable **names** an agent overrides, never the values.
1308
+
1309
+ ## Change who does what
1310
+
1311
+ The roles are aliases:
1312
+
1313
+ ```yaml
1314
+ workflow:
1315
+ architect: architect
1316
+ developer: developer
1317
+ reviewer: reviewer
1318
+ fixer: fixer
1319
+ ```
1320
+
1321
+ Every agent is just a command prefix. For example, assigning the reviewer role
1322
+ to a Kiro agent only requires its agent definition plus:
1323
+
1324
+ ```yaml
1325
+ workflow:
1326
+ reviewer: kiro_reviewer
1327
+ ```
1328
+
1329
+ The mapping is vendor-agnostic: Claude, Codex, Kiro, or another compatible CLI
1330
+ can fill any role whose permission and output requirements it supports.
1331
+
1332
+ ## Adding a different agent CLI
1333
+
1334
+ Nothing in the package names a vendor: the only mentions of Claude or Codex in
1335
+ `stargate/*.py` are comments and the `--help` line. An agent is six YAML keys —
1336
+ `command`, `env`, `probe`, `probe_expect`, `usage_pattern`, and the `{output}` /
1337
+ `{test_command}` placeholders — so adding a CLI is a config change.
1338
+
1339
+ Finding out *what to put* in that config is the actual work, and `doctor` only
1340
+ catches the first of the four things that go wrong:
1341
+
1342
+ 1. **It is not on PATH, or the prompt is not the last argument.** `doctor`
1343
+ reports this immediately; `doctor --probe` also proves the CLI can read and
1344
+ write when its role needs to.
1345
+ 2. **Its stdout is a session trace, not the answer.** Then it needs `{output}`
1346
+ (see [Final message vs. stdout](#final-message-vs-stdout)). A CLI with no
1347
+ flag for that needs a small wrapper.
1348
+ 3. **`usage_pattern` matches nothing, or matches the wrong number.** This fails
1349
+ silently: the run finishes and reports `Tokens reported: 0`, so a
1350
+ `max_task_tokens` budget quietly never applies.
1351
+ 4. **Its permission flags are coarser than the packaged ones.** `doctor`
1352
+ validates that the `{test_command}` placeholder expanded, not what the
1353
+ vendor's flag actually grants.
1354
+
1355
+ Points 2 through 4 only surface in a real run, so finish with one against a
1356
+ throwaway repository before trusting a new agent with your own.
1357
+
1358
+ [`examples/`](examples/) has a folder per CLI — [claude](examples/claude/),
1359
+ [codex](examples/codex/), [kiro](examples/kiro/) — each running every role on
1360
+ that one vendor, with a table comparing how they map onto the knobs above and
1361
+ notes on which of the four problems each one actually hit.
1362
+
1363
+ ## Why command prefixes instead of SDKs?
1364
+
1365
+ The CLI boundary is what keeps this small:
1366
+
1367
+ - it uses the authentication you already have in each CLI;
1368
+ - upgrading an agent CLI does not couple the orchestrator to an SDK;
1369
+ - each role can have independent permissions/model flags;
1370
+ - replacing one agent is just YAML;
1371
+ - the Git worktree remains the shared protocol between agents.
1372
+
1373
+ The cost is that agent output is parsed as text rather than consumed as a
1374
+ typed event stream. A typed SDK is the natural next step if that parsing ever
1375
+ becomes the thing that breaks.
1376
+
1377
+ ## Safety model
1378
+
1379
+ The important separation is:
1380
+
1381
+ - Text fetched through `--from` is untrusted input that the operator owns. It
1382
+ enters the architect's prompt without confirmation; `state.json` retains the
1383
+ task and its separate source URL for audit.
1384
+ - Claude architect/reviewer runs with `--disallowedTools "Edit Write
1385
+ NotebookEdit"` in the default config. The packaged reviewer additionally may
1386
+ execute only the effective test command, and it runs in the isolated
1387
+ worktree.
1388
+ - The architect runs in your real repository, not in the worktree, so the
1389
+ packaged architect deliberately has no `{test_command}` grant. `doctor`
1390
+ warns if a custom config adds one because that grants project-command
1391
+ execution in the real checkout.
1392
+ - Codex developer/fixer gets workspace write access in the isolated worktree.
1393
+ - Git commit, destructive and publishing actions remain forbidden to agents by
1394
+ prompt.
1395
+ - At a terminal result, the orchestrator creates a local commit on the run's
1396
+ own branch using the repository's identity, signing configuration and hooks.
1397
+ - A fan-out run merges task branches only into its own integration branch. The
1398
+ orchestrator never pushes anything unless `--pr` is given in that invocation,
1399
+ and then only the run's own `stargate/*` branch, never force-updating or
1400
+ overwriting an existing remote branch. It never merges into the user's original
1401
+ branch, rebases, deletes worktrees during a run, modifies remotes, or touches the
1402
+ branch/index/worktree in the user's original checkout.
1403
+ - The final branch/worktree and its traceable commit are left for human
1404
+ inspection.
1405
+
1406
+ The agents' own CLI sandbox and permission settings remain the real enforcement
1407
+ boundary; prompts are guidance, not a security boundary.
1408
+
1409
+ ## Useful next additions
1410
+
1411
+ Shipped since this list was written: fan-out DAG execution, token accounting,
1412
+ timeouts, retries,
1413
+ persistent run state, `list`, `resume`, catchable-signal handling, capability
1414
+ probes, empty-stage detection, and terminal commits on run branches.
1415
+
1416
+ Task input from configured sources shipped: see
1417
+ [Read the task from a source](#read-the-task-from-a-source). Opt-in publication also
1418
+ shipped: see [Open a pull request](#open-a-pull-request).
1419
+
1420
+ Structured review output shipped: see
1421
+ [Structured review findings](#structured-review-findings). Severity policy also shipped
1422
+ as an [opt-in setting](#severity-policy-opt-in), off by default: no derived default
1423
+ preserves current approval behavior in both directions.
1424
+
1425
+ ## License
1426
+
1427
+ MIT. See [LICENSE](LICENSE).