razzou-harness 0.2.0 → 0.4.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 (51) hide show
  1. package/CHANGELOG.md +184 -4
  2. package/README.md +90 -41
  3. package/dist/agents/claude-code.js +9 -2
  4. package/dist/anthropic/usage.js +2 -1
  5. package/dist/checks/runner.js +2 -1
  6. package/dist/ci/harness-ci.js +115 -32
  7. package/dist/cli.js +60 -30
  8. package/dist/daemon/architects.js +6 -4
  9. package/dist/daemon/channel.js +7 -1
  10. package/dist/daemon/design.js +16 -2
  11. package/dist/daemon/items.js +28 -3
  12. package/dist/daemon/main.js +84 -12
  13. package/dist/daemon/manager.js +28 -21
  14. package/dist/daemon/orchestrator.js +523 -184
  15. package/dist/daemon/requests.js +21 -19
  16. package/dist/daemon/review.js +15 -9
  17. package/dist/daemon/sandboxes.js +115 -0
  18. package/dist/daemon/web-read.js +143 -33
  19. package/dist/dashboard/server.js +50 -10
  20. package/dist/dashboard/terminals.js +22 -5
  21. package/dist/design/owner-time.js +8 -1
  22. package/dist/design/schemas.js +14 -4
  23. package/dist/git/repo.js +52 -19
  24. package/dist/github/app.js +7 -5
  25. package/dist/github/forge.js +17 -6
  26. package/dist/github/setup.js +10 -3
  27. package/dist/review/paid-calls.js +39 -14
  28. package/dist/roles.js +1 -1
  29. package/dist/sandbox/docker.js +66 -0
  30. package/dist/sandbox/driver.js +2 -1
  31. package/dist/sandbox/openshell.js +118 -13
  32. package/dist/sandbox/policy.js +2 -1
  33. package/dist/sandbox/refusals.js +92 -0
  34. package/dist/setup/doctor.js +86 -12
  35. package/dist/setup/images.js +41 -0
  36. package/dist/setup/init.js +5 -4
  37. package/dist/setup/recover.js +11 -6
  38. package/dist/setup/setup.js +163 -70
  39. package/dist/state/state.js +42 -11
  40. package/dist/util.js +7 -2
  41. package/dist/web/assets/{index-CMViLVQH.css → index-CRYKs8kt.css} +1 -1
  42. package/dist/web/assets/index-hRWASvyl.js +96 -0
  43. package/dist/web/index.html +2 -2
  44. package/images/agent/Dockerfile +11 -2
  45. package/package.json +1 -1
  46. package/roles/architect.md +5 -2
  47. package/roles/manager.md +10 -1
  48. package/shim/harness-mcp.mjs +11 -2
  49. package/src/ci/harness-ci.ts +105 -32
  50. package/templates/harness.yml +5 -2
  51. package/dist/web/assets/index-DLttwGM6.js +0 -96
package/CHANGELOG.md CHANGED
@@ -3,6 +3,186 @@
3
3
  The notable changes in each release of razzou-harness. Before 1.0, a new minor version
4
4
  may change how a project is set up, and its notes say how.
5
5
 
6
+ ## 0.4.0 (2026-11-08)
7
+
8
+ **Upgrading a project from 0.3.x:** install the new version, then rebuild the agents'
9
+ image with `harness doctor --build-images`, or run `harness setup`, which does it: the
10
+ image's tag follows the harness's version. The doctor now removes the 0.3.0 image once
11
+ no container uses it. Nothing else changes in a project.
12
+
13
+ - A **Sandboxes** view in the dashboard lists the sandboxes on the OpenShell gateway:
14
+ this project's, with what each is for and whether anything still uses it, then the
15
+ others'. A left-over sandbox of this project, which a crash or a failed delete left,
16
+ can be deleted there; one in use, or another project's, can't. Selecting a sandbox
17
+ shows the network requests its policy refused, from its log: the program, the host,
18
+ why, and how often. The daemon labels the sandboxes it makes with the project's name
19
+ (`harness.project`) to tell them apart; a left-over sandbox from 0.3.0 or earlier has
20
+ no label, so it shows among the others.
21
+ - The view shows what each sandbox uses of the machine now, its CPUs and memory against
22
+ its caps, and the total for every OpenShell sandbox, read from `docker stats` every
23
+ five seconds while the view is open.
24
+ - **Caps on each sandbox**, set in the Sandboxes view: how many CPUs and how much memory
25
+ each new sandbox may use, so that Developers, check runs and reviewers in parallel
26
+ leave the machine usable. The daemon records them, and creates every sandbox with
27
+ them from then on; those already there keep theirs. There is no cap until you set one.
28
+ - An attempt keeps the network requests its sandbox's policy refused. OpenShell keeps
29
+ only a sandbox's last 2,000 log lines, so the daemon reads the log every minute while
30
+ the Developer works, and once more before the sandbox goes. The attempt's card shows
31
+ them, and the Manager gets them with a failed attempt. A refused host is the
32
+ Manager's to resolve: it guides the next Developer around the host, or asks the owner
33
+ to allow it, since only the owner widens the network policy.
34
+
35
+ Disk space:
36
+ - The git bundles the daemon hands to sandboxes, each a copy of the plan branch's whole
37
+ history, are deleted once uploaded, and the ones it takes back once fetched. One was
38
+ kept for every attempt and every review, and never deleted, so `.harness/` grew with
39
+ each. The daemon deletes the ones earlier versions left in `.harness/modules/` when it
40
+ starts.
41
+ - The daemon deletes this project's left-over sandboxes when it starts and every hour:
42
+ those the Sandboxes view offers to delete, so a delete that failed is tried again. It
43
+ spares an Architect's, which holds the owner's conversation with it.
44
+ - `harness doctor --build-images` removes the agents' images the new one replaces, about
45
+ 2 GB each, once no container uses them: the other versions' tags, and the untagged
46
+ images that building a tag again leaves. It also shows the size of Docker's build
47
+ cache, with `docker builder prune` to free it; that cache is shared with every other
48
+ build on the machine, so the doctor leaves it alone.
49
+
50
+ ## 0.3.0 (2026-11-07)
51
+
52
+ **Upgrading a project from 0.2.x:** install the new version, then run `harness setup`
53
+ in the project, which does both steps, or do them by hand. Run `harness install-ci` and
54
+ commit the CI files it writes before the next `Arch:` pull request: the protected paths
55
+ changed, and the lock the daemon writes lists more, which the old CI script on `main`
56
+ would refuse. Rebuild the agents' image with `harness doctor --build-images`: its tag
57
+ now follows the harness's version. Then open the dashboard from the link the daemon
58
+ prints, once.
59
+
60
+ Fixes from a review of 0.2.0.
61
+
62
+ Protected files:
63
+ - Paths with a non-ASCII character, a quote or a backslash passed every protection rule,
64
+ in CI's `verify` and in the daemon's own checks, because git quoted them: a Developer
65
+ could add `.claude/skills/é/SKILL.md`, and a `CODE` plan could add Terraform in
66
+ `iac/ñ.tf`. Paths are now read NUL-separated.
67
+ - `CLAUDE.md`, `CLAUDE.local.md`, `AGENTS.md` and `.claude/` are protected in every
68
+ folder, as Claude Code reads them there; so are the configs that decide what a check
69
+ runs (`vitest.config.mts` overrode a protected `vitest.config.js`; Vite, Babel, Mocha,
70
+ pytest and tox configs, and package.json's `jest`, `babel` and `mocha` fields, weren't
71
+ protected), and the npm script of `npm run -s lint` or `npm --prefix . test`.
72
+ - A Plan or Fix PR may not add a local link to `package-lock.json`, which could swap a
73
+ check's tool for a stub. An Arch PR is judged by the base branch's rules, so it can't
74
+ protect `src/**` to change `src/` in the same PR.
75
+ - `protect.files` takes globs, `./x` and `dir/`, which matched nothing before, and
76
+ `terraform.dir` may be written `./iac` or `iac/`. `harness lock` leaves out what git
77
+ ignores.
78
+
79
+ Swarm mode:
80
+ - Every Developer under way reaches the Manager: `ask_manager` from a second or third
81
+ attempt at once was refused, and the Manager could answer or stop only the first. A
82
+ plan's Manager acts on its own plan only.
83
+ - Plans in parallel no longer stop on `progress.txt`: both add lines to it, and the
84
+ daemon keeps both sides' lines when it merges `main` into a plan branch. Before it
85
+ merges a pull request, it merges `main` into it when `main` moved since CI ran, so CI
86
+ and the reviewers judge what `main` gets.
87
+ - Modules are built one after another in Swarm too, as the design says: the first plan
88
+ of a second module started alongside the first module's plans, with its own
89
+ Developers, Manager and reviewers. Only the module being built has those now. Any
90
+ module's Architect still works meanwhile, so the next module can be designed while one
91
+ is built; its plans wait their turn. Two modules already under way at the upgrade are
92
+ finished one after the other, the older first: the other's attempts are interrupted,
93
+ and start again in its turn.
94
+ - A plan that a design change drops while it is built no longer holds a place under
95
+ `swarm.maxPlans`, and its Manager is no longer started over and over, each time in a
96
+ new sandbox and session.
97
+ - An `INFRA` or `MIXED` plan's estimate counts only for the head it was made on, and is
98
+ read only once the `cost` job itself succeeded: after a fix round, a head without its
99
+ own estimate merged on the earlier head's.
100
+ - `Arch:` and `Fix:` pull requests get the reviewers' statuses as "not reviewed", so the
101
+ owner merges them without bypassing the rules, which also skipped CI.
102
+ - A blocking finding blocks, whatever the reviewer's verdict says. A usage limit is not
103
+ one of a reviewer's two failures. Two reviews at once each keep their own transcript.
104
+ - Granting the App "Commit statuses: write" takes effect at its next token, within the
105
+ hour, without restarting the daemon; the doctor reads what the installation accepted.
106
+ Swarm's readiness note reads every ruleset on `main`, on every page.
107
+ - The pay-per-use calls the cost reviewer is told about are found in every SDK's
108
+ spelling, without false hits on words in comments.
109
+
110
+ Both modes:
111
+ - A usage limit never shortens a pause already there, such as the owner's or a brake's:
112
+ it replaced it with an hour's pause that lifted itself. An attempt that reports a usage
113
+ limit counts from the third time on the same feature, as other interruptions do.
114
+ - A plan whose step keeps failing, such as a push GitHub refuses, no longer stops every
115
+ other plan: it is tried again for a while, then the pipeline pauses with its error.
116
+ - In Manual, a module whose design lands while another is built waits for it, and the
117
+ other's pull request is still followed: a module whose id sorted first took over.
118
+ - A design change that redefines a feature under way interrupts its attempt in Manual
119
+ too. An accepted feature it redefines is built again while its plan is. A plan past its
120
+ build is frozen: `submit_design` refuses a change to its features. A feature a design
121
+ change drops no longer breaks the Manager's items.
122
+ - Dead ends after a failure: the owner's push to a plan's pull request branch is built
123
+ on, instead of pausing; a CI run the owner starts again on GitHub counts once it passes;
124
+ on resume, a failed deploy is looked at again, and a later successful apply on `main`
125
+ covers an earlier merge, as when GitHub cancels a pending run.
126
+ - An invalid design on `main` pauses the pipeline with the reason, instead of a log line.
127
+ - `harness budget --owner-hours` with the daemon down replaces hours already set, and
128
+ `harness mode` with the daemon down records the switch for it.
129
+
130
+ Setup and the CLI:
131
+ - `harness setup` on a machine without the App creates it and waits for its
132
+ installation before it writes `harness/config.json`: the config had no `project.repo`
133
+ or `project.bot`, and the project stayed local. A later run fills them in.
134
+ - The setup's commit holds the files it wrote, and nothing else you had staged, and it
135
+ pushes only from `main`: run on another branch, it pushed that branch's commits to
136
+ `main`. A setup commit that wasn't pushed is offered again. A ruleset GitHub refuses,
137
+ as on a free plan's private repository, no longer stops the setup.
138
+ - `harness recover` reads GitHub's `main`, not your checkout's, and says what it can't
139
+ rebuild.
140
+ - A daemon is running only if its process started when `daemon.json` says: after a crash
141
+ or a reboot, `harness down` could stop another process that reused the pid. A lock
142
+ keeps two `harness up` apart, and closing the terminal stops the daemon cleanly.
143
+ - The browser opens the dashboard with a code that works once, not the token, which
144
+ showed on xdg-open's command line.
145
+ - Refused owner actions and unknown commands exit with an error.
146
+ - The doctor: the probe's `web_read` check passes only on the harness's own refusal; the
147
+ workflow probe fails when the App's installation grants workflows; the ruleset must
148
+ apply to `main`; the statuses warning reads what the installation accepted; a Claude
149
+ token the usage endpoint refuses is reported; a half-created AWS provider is deleted
150
+ so the next run asks for the key again, and setup asks for the view role's ARN once.
151
+ - AWS providers are named after the repository: two projects in folders of the same name
152
+ shared one.
153
+ - The owner's time: a design without owner actions always fits, and actions done before
154
+ a redesign dropped them still count.
155
+ - Hashes no longer depend on the machine's locale, a failed `terraform show` fails the
156
+ check that needs the plan, and the usage endpoint has a timeout.
157
+
158
+ The sandboxes and the dashboard:
159
+ - `web_read` refuses this machine's own addresses and the networks it is on: over IPv6,
160
+ or with a public IPv4 on an interface, an agent could read the host's services or the
161
+ router. A compressed page past the size limit stops decompressing, instead of growing
162
+ the daemon's memory, and a page full of unclosed comments or tags no longer freezes
163
+ the daemon for minutes. One deadline of 75 seconds covers a read and its redirects,
164
+ within the 90 seconds an agent waits. claude.ai and chatgpt.com are never read. An
165
+ HTML comment ends at `--!>` too, as browsers read it: the text after one was lost.
166
+ - Usage limits are recognised in Claude Code 2.1's words ("You've hit your 5-hour
167
+ limit"): an attempt that hit one counted as failed.
168
+ - An agent can no longer crash the daemon with a line too long for its follower, and a
169
+ terminal whose sandbox ends at once no longer crashes it either. Transcripts and the
170
+ agents' channels pick up again after the OpenShell gateway restarts, a hung `openshell`
171
+ call is stopped, and a tool call too large gets an error, not a wait.
172
+ - A torn line in the Architect's inbox no longer breaks its prompt hook, and an
173
+ `extraHosts` entry for a bundle's host, such as github.com, no longer takes away its
174
+ git access.
175
+ - The dashboard's cookie is named after its port, so two projects' dashboards no longer
176
+ log each other out: after upgrading, open the printed link once. Agent-written
177
+ Markdown loses its styles, which could hide parts of Needs you. The reviewers' "Shell
178
+ in" button works while a review runs.
179
+ - The agents' image pins the AWS CLI, 2.39.6, by checksum.
180
+
181
+ Releases:
182
+ - The release workflow runs the install and the tests in a job without the npm OIDC
183
+ token, and a second job publishes the tarball the first one packed: no dependency's
184
+ code runs where the token can publish.
185
+
6
186
  ## 0.2.0 (2026-11-06)
7
187
 
8
188
  **Upgrading a project from 0.1.x:** install the new version, then run `harness setup`
@@ -14,10 +194,10 @@ new attempt clock of 120 minutes and Manager turn of 90.
14
194
  - **Swarm mode**, designed in `docs/architecture/proposal-v3.md`: a second mode, switched
15
195
  in the dashboard or with `harness mode`, in which the daemon merges a plan's pull
16
196
  request once CI and the reviewers' statuses pass. `CODE` plans merge when
17
- `swarm.merge.code` is `daemon`; `INFRA` and `MIXED` plans need the cost estimate
18
- under `swarm.merge.infraMonthlyUsdCeiling`, which no project produces yet, so they
19
- wait for the owner. A pull request the policy leaves to the owner shows the reason
20
- under Needs you. `Arch:` and `Fix:` pull requests stay the owner's.
197
+ `swarm.merge.code` is `daemon`; `INFRA` and `MIXED` plans need the cost job's
198
+ estimate under `swarm.merge.infraMonthlyUsdCeiling`, or the owner's button (both
199
+ below). A pull request the policy leaves to the owner shows the reason under Needs
200
+ you. `Arch:` and `Fix:` pull requests stay the owner's.
21
201
  - A plan is `CODE`, `INFRA` or `MIXED`, from its features' kinds. Plan pull requests
22
202
  carry the tag in their title, and CI's `verify` job keeps a `CODE` plan's pull
23
203
  request off the Terraform folder.
package/README.md CHANGED
@@ -137,14 +137,17 @@ the token from `claude setup-token` at a hidden prompt, two clicks on GitHub for
137
137
  App, since GitHub creates and installs Apps only in the browser, three questions (your
138
138
  stack's network bundles, AWS or not, the hours of your time), and, with AWS, the role
139
139
  ARNs from your bootstrap and the gateway key. In order, it logs you in to `gh`; creates
140
- the repository on GitHub if the project has no remote, and writes the harness's files;
141
- asks the questions and writes `harness/config.json`, with the `staging` environment
142
- and the variables on GitHub for AWS; commits and pushes the files to `main` with your
143
- git, once you agree; runs `harness doctor` and fixes what it can (starts the OpenShell
144
- gateway, builds the agents' image, imports the Claude profile and creates the provider
145
- with your token, creates the App from its manifest and waits for you to install it,
140
+ the repository on GitHub if the project has no remote; creates the App from its
141
+ manifest when this machine has none, and waits for you to install it; writes the
142
+ harness's files; asks the questions and writes `harness/config.json`, with the
143
+ `staging` environment and the variables on GitHub for AWS; commits the files it wrote,
144
+ and only those, with your git once you agree, and pushes them when you are on `main`;
145
+ runs `harness doctor` and fixes what it can (starts the OpenShell gateway, builds the
146
+ agents' image, imports the Claude profile and creates the provider with your token,
146
147
  applies the ruleset on `main`, creates the AWS provider with your key); runs the probe;
147
- and offers to start the daemon. Run it again at any time: what is done is skipped.
148
+ and offers to start the daemon. Run it again at any time: what is done is skipped, and a
149
+ setup commit that wasn't pushed is offered again. On a private repository of a free
150
+ plan, where GitHub has no rulesets, it says so and goes on.
148
151
 
149
152
  The same by hand: `harness init` writes the harness's files into the repository, never
150
153
  over existing ones, and prints the setup checklist:
@@ -202,7 +205,10 @@ plans it finds there (see [Configuration](#configuration)).
202
205
  ## Concepts
203
206
 
204
207
  - **Module:** a part of the system you want to build, with one Architect and one
205
- accepted proposal, then its plans. Modules are built one after another. Size a module
208
+ accepted proposal, then its plans. Modules are built one after another, in both modes:
209
+ the one under way is finished before the next starts, and only it has Developers,
210
+ Managers and reviewers. Every module's Architect stays available, so you can design the
211
+ next module while one is built; its plans wait their turn. Size a module
206
212
  so that its design fits in one conversation. With the default model, Opus 5.5, the
207
213
  Architect's context holds up to a million tokens, and a well-sized module never fills
208
214
  it. If a module's design keeps growing, split it into smaller modules: each gets its
@@ -214,7 +220,10 @@ plans it finds there (see [Configuration](#configuration)).
214
220
  tagged in the pull request's title, and a `CODE` plan's pull request may not touch
215
221
  the Terraform folder. Give infrastructure plans of its own.
216
222
  - **Feature:** one change that a Developer can make in one attempt, with at least one
217
- check. Its definition is frozen once its design is merged.
223
+ check. A design change can redefine it while its plan is built: an attempt under way
224
+ stops, and the feature is built again with a fresh budget, even if it was accepted.
225
+ Once its plan is past its build (every feature of it accepted, then its pull request
226
+ and merge), it is frozen: a change goes in a new plan.
218
227
  - **Check:** a shell command, run from the repository root, that exits 0 when the feature
219
228
  works. `pre-merge` checks run on every attempt and in CI; `post-deploy` checks run
220
229
  after an infrastructure deploy.
@@ -226,16 +235,22 @@ plans it finds there (see [Configuration](#configuration)).
226
235
  policy or setting a secret. A **gate** holds back the plans or features it blocks
227
236
  until you mark it done; a **task** holds nothing back.
228
237
  - **Protected files:** the design and its checks, CI, and what agents read as
229
- instructions: `harness/`, `.github/`, `.claude/`, `CLAUDE.md`, `AGENTS.md`,
230
- `ARCHITECTURE.md`, `init.sh`, tool configs and the npm scripts that checks run. A
231
- `Plan:` or `Fix:` pull request may not change them: CI's `verify` job, run with the
232
- base branch's rules, fails it. They change through an `Arch:` pull request, or your own
233
- commits. `harness/lock.json` holds their hashes.
238
+ instructions: `harness/`, `.github/`, `ARCHITECTURE.md`, `init.sh`, and in any folder
239
+ `.claude/`, `CLAUDE.md`, `CLAUDE.local.md` and `AGENTS.md`, which Claude Code reads
240
+ wherever they are; the configs that decide what a check runs (ESLint, Prettier, Biome,
241
+ Vitest, Vite, Jest, Babel, Playwright, Mocha, pytest, tox, and package.json's `jest`,
242
+ `babel` and `mocha` fields); and the npm scripts that checks run. A `Plan:` or `Fix:`
243
+ pull request may not change them, nor add a local link to `package-lock.json`: CI's
244
+ `verify` job, run with the base branch's rules, fails it, and the daemon refuses it
245
+ first. They change through an `Arch:` pull request, also judged by the base branch's
246
+ rules, so what it newly protects counts from the next one, or your own commits.
247
+ `harness/lock.json` holds their hashes.
234
248
  - **`progress.txt`:** one line per accepted feature and satisfied gate, committed with
235
- the work. It only grows.
249
+ the work. It only grows. When plans in parallel both add lines, the daemon keeps both.
236
250
  - **Brakes:** counters and deadlines, not judgment. Three failed attempts in a row, a
237
251
  Manager that leaves an item waiting for 180 minutes, or CI still failing after two fix
238
- rounds pause the pipeline until you resume it.
252
+ rounds pause the pipeline until you resume it. A usage limit pauses it for an hour,
253
+ and never shortens a pause already there.
239
254
 
240
255
  ## The dashboard
241
256
 
@@ -246,7 +261,18 @@ behind a token. Its views:
246
261
  - **Project:** modules, proposals, plans and features, with attempts, findings, CI,
247
262
  deploys and `progress.txt`.
248
263
  - **Agents:** the Architect's terminal, the Manager's and the Developers' live
249
- transcripts, a message box for the Manager, and a shell in any agent's sandbox.
264
+ transcripts, a message box for the Manager, and a shell in any agent's sandbox. An
265
+ attempt's card keeps the network requests its sandbox's policy refused.
266
+ - **Sandboxes:** what runs on the OpenShell gateway. This project's sandboxes, with what
267
+ each is for, whether anything still uses it, and the CPUs and memory it uses now,
268
+ read from `docker stats` every five seconds. A left-over one, which a crash or a
269
+ failed delete left, has a button to delete it; the daemon deletes them itself when it
270
+ starts and every hour, except an Architect's. Select one to see the network requests
271
+ its policy refused, from its log: the program, the host, why, and how often. Then the
272
+ other projects' sandboxes, which the dashboard only lists, and the gateway's status.
273
+ Above them, the caps on each new sandbox: how many CPUs and how much memory it may
274
+ use, so that agents and checks in parallel leave the machine usable. There is no cap
275
+ until you set one.
250
276
  - **Events:** the event log, live, with a filter.
251
277
 
252
278
  The header pauses and resumes the pipeline: a pause lets what runs finish, and starts
@@ -280,10 +306,17 @@ once CI passed and every status in `swarm.requiredStatuses` is green on its head
280
306
  estimate over the ceiling still waiting for you. The button is off by default, and
281
307
  the choice is recorded in the event log.
282
308
 
309
+ Only an estimate of the pull request's current head counts: after a fix round, the new
310
+ head needs its own. Before it merges, the daemon merges `main` into the pull request's
311
+ branch if `main` moved since CI ran, so CI and the reviewers judge what `main` gets; a
312
+ conflict leaves the pull request to you, to resolve on GitHub.
313
+
283
314
  Anything else waits for you, under **Needs you** with the reason. `Arch:` and `Fix:`
284
315
  pull requests are yours in both modes. In Swarm, GitHub's rules need no approval but
285
316
  require the reviewers' statuses, pinned to the App, so even the daemon can't merge
286
- before they pass.
317
+ before they pass. The reviewers read plan pull requests only: the daemon gives an
318
+ `Arch:` or `Fix:` pull request the two statuses as "not reviewed", so you merge it
319
+ without bypassing the rules, and CI still gates it.
287
320
 
288
321
  **Before a launch.** In Swarm the daemon reads your subscription's usage before it
289
322
  starts an attempt, and at or above `swarm.usageCeilingPercent` the pipeline waits for
@@ -302,9 +335,10 @@ with a verdict and findings. The daemon posts the findings on the pull request a
302
335
  review, with comments on the lines of the diff, and sets the reviewer's commit status,
303
336
  `harness/security` or `harness/cost`. A blocking verdict opens a fix round on the
304
337
  plan branch, like a CI failure: a Developer fixes with the findings as guidance, the
305
- Manager reviews, CI and the reviewers run again on the new head. After
306
- `swarm.securityFixRounds` or `swarm.costFixRounds`, the pull request is yours. In
307
- Manual, `swarm.reviewersInManual` runs them as comments only.
338
+ Manager reviews, CI and the reviewers run again on the new head. A blocking finding
339
+ blocks, whatever the verdict says. After `swarm.securityFixRounds` or
340
+ `swarm.costFixRounds`, the pull request is yours. In Manual,
341
+ `swarm.reviewersInManual` runs them as comments and statuses that gate nothing.
308
342
 
309
343
  The App needs **Commit statuses: write** for the verdicts. An App created before this
310
344
  version has read only: give it the permission on GitHub, accept it on the installation,
@@ -318,16 +352,19 @@ Architect is told to declare every dependency. Each attempt starts from the plan
318
352
  branch as it is. When another feature lands first, the daemon merges the new head
319
353
  into the attempt before its checks, so they see the accepted work, and the feature
320
354
  lands as one commit on the head. A conflict interrupts the attempt, without counting
321
- it, and the next one starts from the new head. A design change merged while a feature
322
- is built interrupts its attempt the same way, and the next one builds the new
323
- definition. Manual mode builds one feature at a time, as before. In both modes, the
355
+ it, and the next one starts from the new head. In both modes, a design change merged
356
+ while a feature is built interrupts its attempt the same way, and the next one builds
357
+ the new definition. Manual mode builds one feature at a time, as before. In both modes, the
324
358
  Architect hears in one line when a plan's pull request opens, waits for you, is
325
359
  blocked by a reviewer, or merges.
326
360
 
327
361
  **Parallel plans.** A plan may declare `dependsOn`, the plans of its module that must
328
362
  be merged before it starts; without it, it waits for the previous plan, as always. In
329
- Swarm, up to `swarm.maxPlans` plans whose dependencies are merged build at once, each
330
- on its own branch with its own Manager, keyed `module/plan` in the Agents view; a
363
+ Swarm, up to `swarm.maxPlans` plans of the module under way whose dependencies are
364
+ merged build at once, each on its own branch with its own Manager, keyed `module/plan`
365
+ in the Agents view. When one merges, `main` is merged into the others, `progress.txt`
366
+ keeping both sides' lines; a conflict in other files is resolved in that plan's pull
367
+ request. A
331
368
  feature may also depend on a feature of an earlier plan, as `P1/F03`, and then waits
332
369
  for that plan's merge. The Project view draws each module's features as a graph, plan
333
370
  by plan, with an edge per dependency: green accepted, blue under way, red escalated,
@@ -337,7 +374,7 @@ as before.
337
374
  **The cost job.** `harness install-ci` adds a `cost` job to the workflow: on a pull
338
375
  request that has `iac/`, it runs [Infracost](https://www.infracost.io) on the base and
339
376
  the head and uploads the estimate as the `cost` artifact, which the daemon reads once
340
- CI passed. It needs the repository secret `INFRACOST_API_KEY`, a free key from
377
+ CI passed and the `cost` job itself succeeded. It needs the repository secret `INFRACOST_API_KEY`, a free key from
341
378
  `infracost auth login`; without it the job does nothing, no estimate exists, and
342
379
  `INFRA` and `MIXED` plans wait for you with that reason. Infracost sees your Terraform's
343
380
  resource types and sizes, not your code or state. The cost reviewer gets the estimate in
@@ -371,7 +408,7 @@ too.
371
408
  | `harness github-app create --org <org>` | Creates the harness's GitHub App, once per machine |
372
409
  | `harness github-app check --repo <org>/<repo>` | Checks that the App can reach the repository |
373
410
  | `harness github-setup --repo <org>/<repo> [--mode manual\|swarm]` | Sets merge commits only, and the ruleset on `main` for the daemon's mode, with your own `gh` |
374
- | `harness budget --owner-hours <n>` | Sets the hours of your time for the whole project; with the daemon down, it records them when it starts (`--agent-hours` sets the threshold) |
411
+ | `harness budget --owner-hours <n>` | Sets the hours of your time for the whole project; with the daemon down, it records them when it starts, over hours set before (`--agent-hours` sets the threshold) |
375
412
  | `harness setup [--repo <owner/name>] [--owner <login>]` | Sets up this machine and the project, asking only for what is yours; run it again any time |
376
413
  | `harness install-ci [--owner <login>]` | Writes the workflow, its CI script and `CODEOWNERS` again, after an upgrade |
377
414
  | `harness lock` | Writes `harness/lock.json`, the hashes of the protected files |
@@ -385,8 +422,8 @@ too.
385
422
  | `harness message <text>` | Writes to the Manager of the module being built |
386
423
  | `harness retry <module>/<plan>/<feature>` | Gives an escalated feature one more attempt; `--note` adds guidance |
387
424
  | `harness resume` | Lifts a pause |
388
- | `harness mode [manual\|swarm]` | Shows the mode, or switches it |
389
- | `harness recover` | Rebuilds a lost `.harness/` from `main` and GitHub |
425
+ | `harness mode [manual\|swarm]` | Shows the mode, or switches it; with the daemon down, it records the switch for when it starts |
426
+ | `harness recover` | Rebuilds a lost `.harness/` from GitHub's `main` and branches, and lists what it can't: the mode, the budgets, the owner tasks done |
390
427
 
391
428
  ## Configuration
392
429
 
@@ -395,7 +432,7 @@ through an `Arch:` pull request, or a commit of your own on `main`.
395
432
 
396
433
  ```json
397
434
  {
398
- "harnessVersion": "0.1.1",
435
+ "harnessVersion": "0.4.0",
399
436
  "project": { "name": "shop", "repo": "<org>/shop", "bot": "<org>-harness[bot]" },
400
437
  "roles": {
401
438
  "architect": { "model": "claude-opus-5-5", "effort": "max" },
@@ -418,8 +455,8 @@ through an `Arch:` pull request, or a commit of your own on `main`.
418
455
  | `network.bundles` | `["core"]` | The hosts sandboxes reach, by bundle: `core`, `node`, `python`, `aws`, `terraform`, `go`, `rust`, `jvm`, `opentofu`, `browsers`. The Architect gets them all. |
419
456
  | `network.extraHosts` | `[]` | More hosts, read-only |
420
457
  | `checks` | `[]` | Checks that every feature must keep passing, such as `npm run lint` |
421
- | `protect.files`, `protect.jsonFields` | none | More protected files, and protected JSON fields by file, like `{"tsconfig.json": ["/compilerOptions/strict"]}` |
422
- | `sandbox.image` | `razzou-harness/agent:0.1.0` | The agents' image, which `harness doctor --build-images` builds |
458
+ | `protect.files`, `protect.jsonFields` | none | More protected files, as paths or globs (`scripts/*.sh`, `docs/`, `**/Makefile`), and protected JSON fields by file, like `{"tsconfig.json": ["/compilerOptions/strict"]}` |
459
+ | `sandbox.image` | `razzou-harness/agent:<version>` | The agents' image, tagged with the harness's version, which `harness doctor --build-images` builds |
423
460
  | `sandbox.claudeProvider` | `claude-sub` | The OpenShell provider that holds your Claude token |
424
461
  | `sandbox.env` | `{}` | Environment variables for every sandbox |
425
462
  | `github.requiredChecks` | `["verify", "checks"]` | The CI jobs a pull request must pass; `tf-plan` is added with Terraform |
@@ -519,7 +556,7 @@ and Terraform applies only in CI, after you merge.
519
556
  1. In `harness/config.json`, add `aws` and `terraform`:
520
557
 
521
558
  ```json
522
- "aws": { "region": "us-east-1", "provider": "aws-view-shop", "services": ["sts", "s3", "iam", "sqs"] },
559
+ "aws": { "region": "us-east-1", "provider": "aws-view-<org>-shop", "services": ["sts", "s3", "iam", "sqs"] },
523
560
  "terraform": { "dir": "iac", "backendConfig": "backend-configs/staging.hcl", "varFile": "env/staging.tfvars" }
524
561
  ```
525
562
 
@@ -558,8 +595,9 @@ What agents can't do, and what stops them:
558
595
  - **Change AWS.** A read-only role with explicit denies, and applies only in CI after
559
596
  your merge.
560
597
  - **Go anywhere online.** Sandboxes reach listed hosts, read-only. Other pages go
561
- through `web_read`: GET only, public addresses only, checked again after every
562
- redirect.
598
+ through `web_read`: GET only, public addresses only, never this machine's own
599
+ addresses or the networks it is on (IPv6 included), checked again after every
600
+ redirect, within 75 seconds and 2 MB, decompressed.
563
601
  - **Decide alone.** Code runs the loop: deadlines, attempt budgets, checks in fresh
564
602
  sandboxes and the merges, yours or the Swarm policy's, are enforced by the daemon,
565
603
  not left to the agents.
@@ -608,20 +646,31 @@ To report a vulnerability, see `SECURITY.md` in the source repository.
608
646
  | `Claude provider: … is missing` | `claude setup-token`, then `harness doctor` in a terminal: it asks for the token |
609
647
  | `GitHub App: … 404` | Install the App on the repository, from the link `harness github-app create` printed, or the organization's settings |
610
648
  | `ruleset: …` fails | `harness github-setup --repo <org>/<repo>`. A private repository needs a paid GitHub plan for rulesets. |
649
+ | `ruleset: applies to the default branch, master, not to main` | Make `main` the repository's default branch on GitHub, then `harness github-setup --repo <org>/<repo>` |
611
650
  | `.github/… differs from this version of the harness` | `harness install-ci`, then commit the files yourself |
612
651
 
613
652
  While the daemon runs:
614
653
  - **The pipeline is paused.** **Needs you** says why: attempts that failed in a row, a
615
- Manager that didn't act, CI still failing after its fix rounds, a usage limit, or a
616
- broken environment. Fix the cause, then **Resume**.
654
+ Manager that didn't act, CI still failing after its fix rounds, a deploy still failing
655
+ after its Fix PRs, a usage limit, an invalid design on `main`, or a broken environment.
656
+ Fix the cause, then **Resume**. On resume, a failed full check run runs again and a
657
+ failed deploy is looked at again: a later successful apply on `main` counts.
658
+ - **CI fails for a reason outside the code.** Fix it (a secret, the runner), then re-run
659
+ the jobs on GitHub: a passing re-run counts. To fix the code yourself, push to the pull
660
+ request's branch, or resolve a conflict in GitHub's editor: the daemon builds on your
661
+ push, and CI runs again.
617
662
  - **A feature is escalated.** It used its attempts. In **Needs you**, give it one more
618
663
  attempt with your guidance, send it to the Architect (the design may change), or pause.
619
664
  - **The dashboard asks for its token.** Each `harness up` prints a new link: open that
620
- one.
665
+ one. Each project's dashboard keeps its own cookie, named after its port, so several
666
+ can be open at once. The browser it opens by itself logs in with a code that works once, for two
667
+ minutes, so the token never shows on a command line.
621
668
  - **A merge takes minutes to show up.** The daemon polls GitHub: every minute, then every
622
669
  five once nothing has changed for ten minutes.
623
- - **Closing the terminal stopped the daemon.** Run `harness up` again: it picks up from
624
- its event log, and the agents kept working meanwhile.
670
+ - **Closing the terminal stopped the daemon.** It stops cleanly, as with `harness down`.
671
+ Run `harness up` again: it picks up from its event log, and the agents kept working
672
+ meanwhile. A daemon that crashed leaves `.harness/daemon.json` and
673
+ `.harness/daemon.lock` behind: the next `harness up` sees that their process is gone.
625
674
 
626
675
  ## Development
627
676
 
@@ -170,6 +170,13 @@ export function reviewJsonSchema() {
170
170
  const { $schema: _, ...schema } = z.toJSONSchema(ReviewReportSchema, { target: 'draft-7' });
171
171
  return JSON.stringify(schema);
172
172
  }
173
+ /**
174
+ * How a session's error result says the subscription ran out. Claude Code 2.1.x writes
175
+ * "You've hit your 5-hour limit · resets 3pm", "You've reached your …", "You're out of
176
+ * usage credits", "You're out of extra usage" or "Your org is out of usage …"; older
177
+ * versions wrote "Claude AI usage limit reached|<epoch>".
178
+ */
179
+ const USAGE_LIMIT = /usage limit|limit reached|rate.?limit|you['’]ve (?:hit|reached) your|you['’]re out of (?:usage credits|extra usage)|your org is out of usage/i;
173
180
  /** A reviewer's session: its report, or why there is none, read like a Developer's. */
174
181
  export function readReview(stream, exitCode, stderr = '') {
175
182
  const result = lastResult(stream);
@@ -179,7 +186,7 @@ export function readReview(stream, exitCode, stderr = '') {
179
186
  }
180
187
  const usage = toUsage(result.usage, result.total_cost_usd);
181
188
  const text = typeof result.result === 'string' ? result.result : '';
182
- if (result.is_error === true && /usage limit|limit reached|rate.?limit/i.test(text))
189
+ if (result.is_error === true && USAGE_LIMIT.test(text))
183
190
  return { outcome: 'usage_limit', detail: text.slice(0, 500), usage };
184
191
  const report = ReviewReportSchema.safeParse(result.structured_output);
185
192
  if (!report.success)
@@ -198,7 +205,7 @@ export function readResult(stream, exitCode, stderr = '') {
198
205
  }
199
206
  const usage = toUsage(result.usage, result.total_cost_usd);
200
207
  const text = typeof result.result === 'string' ? result.result : '';
201
- if (result.is_error === true && /usage limit|limit reached|rate.?limit/i.test(text)) {
208
+ if (result.is_error === true && USAGE_LIMIT.test(text)) {
202
209
  return { outcome: 'usage_limit', detail: text.slice(0, 500), usage };
203
210
  }
204
211
  const report = ReportSchema.safeParse(result.structured_output);
@@ -2,10 +2,11 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'n
2
2
  import { join } from 'node:path';
3
3
  import { configDir } from '../github/app.js';
4
4
  export const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
5
- /** Reads the usage with the token the provider was created with. Throws when the endpoint refuses or answers something else. */
5
+ /** Reads the usage with the token the provider was created with. Throws when the endpoint refuses, answers something else, or not within 15 seconds. */
6
6
  export async function readUsage(token, fetchImpl = fetch) {
7
7
  const response = await fetchImpl(USAGE_URL, {
8
8
  headers: { authorization: `Bearer ${token}`, accept: 'application/json', 'anthropic-beta': 'oauth-2025-04-20', 'user-agent': 'razzou-harness' },
9
+ signal: AbortSignal.timeout(15_000),
9
10
  });
10
11
  if (!response.ok)
11
12
  throw new Error(`the usage endpoint answered ${response.status}`);
@@ -50,7 +50,8 @@ async function makePlan(driver, sandbox, workDir, options) {
50
50
  `cd ${sh(`${workDir}/${tf.dir}`)} || exit 1`,
51
51
  `terraform init -input=false -no-color -backend-config=${sh(tf.backendConfig)} > ${sh(`${out}/terraform-init.log`)} 2>&1 || { tail -c 3000 ${sh(`${out}/terraform-init.log`)}; exit 1; }`,
52
52
  `terraform plan -input=false -no-color -lock=false -var-file=${sh(tf.varFile)} -out=${sh(`${out}/plan.bin`)} > ${sh(`${out}/terraform-plan.log`)} 2>&1 || { tail -c 3000 ${sh(`${out}/terraform-plan.log`)}; exit 1; }`,
53
- `terraform show -json ${sh(`${out}/plan.bin`)} > ${sh(`${out}/plan.json`)}`,
53
+ // Without the JSON, the checks would read an empty $HARNESS_PLAN_JSON: the plan fails instead, with terraform's error.
54
+ `terraform show -json ${sh(`${out}/plan.bin`)} > ${sh(`${out}/plan.json`)} || exit 1`,
54
55
  `grep -E '^(Plan:|No changes)' ${sh(`${out}/terraform-plan.log`)} || true`,
55
56
  ].join('\n');
56
57
  const result = await driver.exec(sandbox, script, { timeoutSec: 900 });