razzou-harness 0.2.0 → 0.3.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.
- package/CHANGELOG.md +140 -4
- package/README.md +78 -40
- package/dist/agents/claude-code.js +9 -2
- package/dist/anthropic/usage.js +2 -1
- package/dist/checks/runner.js +2 -1
- package/dist/ci/harness-ci.js +115 -32
- package/dist/cli.js +60 -30
- package/dist/daemon/architects.js +2 -0
- package/dist/daemon/channel.js +7 -1
- package/dist/daemon/design.js +16 -2
- package/dist/daemon/items.js +6 -3
- package/dist/daemon/main.js +84 -12
- package/dist/daemon/manager.js +25 -19
- package/dist/daemon/orchestrator.js +366 -157
- package/dist/daemon/requests.js +21 -19
- package/dist/daemon/review.js +14 -8
- package/dist/daemon/web-read.js +143 -33
- package/dist/dashboard/server.js +27 -10
- package/dist/dashboard/terminals.js +22 -5
- package/dist/design/owner-time.js +8 -1
- package/dist/design/schemas.js +14 -4
- package/dist/git/repo.js +52 -19
- package/dist/github/app.js +7 -5
- package/dist/github/forge.js +17 -6
- package/dist/github/setup.js +10 -3
- package/dist/review/paid-calls.js +39 -14
- package/dist/roles.js +1 -1
- package/dist/sandbox/openshell.js +74 -13
- package/dist/sandbox/policy.js +2 -1
- package/dist/setup/doctor.js +74 -12
- package/dist/setup/init.js +5 -4
- package/dist/setup/recover.js +11 -6
- package/dist/setup/setup.js +163 -70
- package/dist/state/state.js +31 -11
- package/dist/util.js +2 -2
- package/dist/web/assets/{index-DLttwGM6.js → index-CPJJsPL6.js} +1 -1
- package/dist/web/index.html +1 -1
- package/images/agent/Dockerfile +11 -2
- package/package.json +1 -1
- package/roles/architect.md +5 -2
- package/roles/manager.md +2 -1
- package/shim/harness-mcp.mjs +11 -2
- package/src/ci/harness-ci.ts +105 -32
- package/templates/harness.yml +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,142 @@
|
|
|
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.3.0 (2026-11-07)
|
|
7
|
+
|
|
8
|
+
**Upgrading a project from 0.2.x:** install the new version, then run `harness setup`
|
|
9
|
+
in the project, which does both steps, or do them by hand. Run `harness install-ci` and
|
|
10
|
+
commit the CI files it writes before the next `Arch:` pull request: the protected paths
|
|
11
|
+
changed, and the lock the daemon writes lists more, which the old CI script on `main`
|
|
12
|
+
would refuse. Rebuild the agents' image with `harness doctor --build-images`: its tag
|
|
13
|
+
now follows the harness's version. Then open the dashboard from the link the daemon
|
|
14
|
+
prints, once.
|
|
15
|
+
|
|
16
|
+
Fixes from a review of 0.2.0.
|
|
17
|
+
|
|
18
|
+
Protected files:
|
|
19
|
+
- Paths with a non-ASCII character, a quote or a backslash passed every protection rule,
|
|
20
|
+
in CI's `verify` and in the daemon's own checks, because git quoted them: a Developer
|
|
21
|
+
could add `.claude/skills/é/SKILL.md`, and a `CODE` plan could add Terraform in
|
|
22
|
+
`iac/ñ.tf`. Paths are now read NUL-separated.
|
|
23
|
+
- `CLAUDE.md`, `CLAUDE.local.md`, `AGENTS.md` and `.claude/` are protected in every
|
|
24
|
+
folder, as Claude Code reads them there; so are the configs that decide what a check
|
|
25
|
+
runs (`vitest.config.mts` overrode a protected `vitest.config.js`; Vite, Babel, Mocha,
|
|
26
|
+
pytest and tox configs, and package.json's `jest`, `babel` and `mocha` fields, weren't
|
|
27
|
+
protected), and the npm script of `npm run -s lint` or `npm --prefix . test`.
|
|
28
|
+
- A Plan or Fix PR may not add a local link to `package-lock.json`, which could swap a
|
|
29
|
+
check's tool for a stub. An Arch PR is judged by the base branch's rules, so it can't
|
|
30
|
+
protect `src/**` to change `src/` in the same PR.
|
|
31
|
+
- `protect.files` takes globs, `./x` and `dir/`, which matched nothing before, and
|
|
32
|
+
`terraform.dir` may be written `./iac` or `iac/`. `harness lock` leaves out what git
|
|
33
|
+
ignores.
|
|
34
|
+
|
|
35
|
+
Swarm mode:
|
|
36
|
+
- Every Developer under way reaches the Manager: `ask_manager` from a second or third
|
|
37
|
+
attempt at once was refused, and the Manager could answer or stop only the first. A
|
|
38
|
+
plan's Manager acts on its own plan only.
|
|
39
|
+
- Plans in parallel no longer stop on `progress.txt`: both add lines to it, and the
|
|
40
|
+
daemon keeps both sides' lines when it merges `main` into a plan branch. Before it
|
|
41
|
+
merges a pull request, it merges `main` into it when `main` moved since CI ran, so CI
|
|
42
|
+
and the reviewers judge what `main` gets.
|
|
43
|
+
- Modules are built one after another in Swarm too, as the design says: the first plan
|
|
44
|
+
of a second module started alongside the first module's plans, with its own
|
|
45
|
+
Developers, Manager and reviewers. Only the module being built has those now. Any
|
|
46
|
+
module's Architect still works meanwhile, so the next module can be designed while one
|
|
47
|
+
is built; its plans wait their turn. Two modules already under way at the upgrade are
|
|
48
|
+
finished one after the other, the older first: the other's attempts are interrupted,
|
|
49
|
+
and start again in its turn.
|
|
50
|
+
- A plan that a design change drops while it is built no longer holds a place under
|
|
51
|
+
`swarm.maxPlans`, and its Manager is no longer started over and over, each time in a
|
|
52
|
+
new sandbox and session.
|
|
53
|
+
- An `INFRA` or `MIXED` plan's estimate counts only for the head it was made on, and is
|
|
54
|
+
read only once the `cost` job itself succeeded: after a fix round, a head without its
|
|
55
|
+
own estimate merged on the earlier head's.
|
|
56
|
+
- `Arch:` and `Fix:` pull requests get the reviewers' statuses as "not reviewed", so the
|
|
57
|
+
owner merges them without bypassing the rules, which also skipped CI.
|
|
58
|
+
- A blocking finding blocks, whatever the reviewer's verdict says. A usage limit is not
|
|
59
|
+
one of a reviewer's two failures. Two reviews at once each keep their own transcript.
|
|
60
|
+
- Granting the App "Commit statuses: write" takes effect at its next token, within the
|
|
61
|
+
hour, without restarting the daemon; the doctor reads what the installation accepted.
|
|
62
|
+
Swarm's readiness note reads every ruleset on `main`, on every page.
|
|
63
|
+
- The pay-per-use calls the cost reviewer is told about are found in every SDK's
|
|
64
|
+
spelling, without false hits on words in comments.
|
|
65
|
+
|
|
66
|
+
Both modes:
|
|
67
|
+
- A usage limit never shortens a pause already there, such as the owner's or a brake's:
|
|
68
|
+
it replaced it with an hour's pause that lifted itself. An attempt that reports a usage
|
|
69
|
+
limit counts from the third time on the same feature, as other interruptions do.
|
|
70
|
+
- A plan whose step keeps failing, such as a push GitHub refuses, no longer stops every
|
|
71
|
+
other plan: it is tried again for a while, then the pipeline pauses with its error.
|
|
72
|
+
- In Manual, a module whose design lands while another is built waits for it, and the
|
|
73
|
+
other's pull request is still followed: a module whose id sorted first took over.
|
|
74
|
+
- A design change that redefines a feature under way interrupts its attempt in Manual
|
|
75
|
+
too. An accepted feature it redefines is built again while its plan is. A plan past its
|
|
76
|
+
build is frozen: `submit_design` refuses a change to its features. A feature a design
|
|
77
|
+
change drops no longer breaks the Manager's items.
|
|
78
|
+
- Dead ends after a failure: the owner's push to a plan's pull request branch is built
|
|
79
|
+
on, instead of pausing; a CI run the owner starts again on GitHub counts once it passes;
|
|
80
|
+
on resume, a failed deploy is looked at again, and a later successful apply on `main`
|
|
81
|
+
covers an earlier merge, as when GitHub cancels a pending run.
|
|
82
|
+
- An invalid design on `main` pauses the pipeline with the reason, instead of a log line.
|
|
83
|
+
- `harness budget --owner-hours` with the daemon down replaces hours already set, and
|
|
84
|
+
`harness mode` with the daemon down records the switch for it.
|
|
85
|
+
|
|
86
|
+
Setup and the CLI:
|
|
87
|
+
- `harness setup` on a machine without the App creates it and waits for its
|
|
88
|
+
installation before it writes `harness/config.json`: the config had no `project.repo`
|
|
89
|
+
or `project.bot`, and the project stayed local. A later run fills them in.
|
|
90
|
+
- The setup's commit holds the files it wrote, and nothing else you had staged, and it
|
|
91
|
+
pushes only from `main`: run on another branch, it pushed that branch's commits to
|
|
92
|
+
`main`. A setup commit that wasn't pushed is offered again. A ruleset GitHub refuses,
|
|
93
|
+
as on a free plan's private repository, no longer stops the setup.
|
|
94
|
+
- `harness recover` reads GitHub's `main`, not your checkout's, and says what it can't
|
|
95
|
+
rebuild.
|
|
96
|
+
- A daemon is running only if its process started when `daemon.json` says: after a crash
|
|
97
|
+
or a reboot, `harness down` could stop another process that reused the pid. A lock
|
|
98
|
+
keeps two `harness up` apart, and closing the terminal stops the daemon cleanly.
|
|
99
|
+
- The browser opens the dashboard with a code that works once, not the token, which
|
|
100
|
+
showed on xdg-open's command line.
|
|
101
|
+
- Refused owner actions and unknown commands exit with an error.
|
|
102
|
+
- The doctor: the probe's `web_read` check passes only on the harness's own refusal; the
|
|
103
|
+
workflow probe fails when the App's installation grants workflows; the ruleset must
|
|
104
|
+
apply to `main`; the statuses warning reads what the installation accepted; a Claude
|
|
105
|
+
token the usage endpoint refuses is reported; a half-created AWS provider is deleted
|
|
106
|
+
so the next run asks for the key again, and setup asks for the view role's ARN once.
|
|
107
|
+
- AWS providers are named after the repository: two projects in folders of the same name
|
|
108
|
+
shared one.
|
|
109
|
+
- The owner's time: a design without owner actions always fits, and actions done before
|
|
110
|
+
a redesign dropped them still count.
|
|
111
|
+
- Hashes no longer depend on the machine's locale, a failed `terraform show` fails the
|
|
112
|
+
check that needs the plan, and the usage endpoint has a timeout.
|
|
113
|
+
|
|
114
|
+
The sandboxes and the dashboard:
|
|
115
|
+
- `web_read` refuses this machine's own addresses and the networks it is on: over IPv6,
|
|
116
|
+
or with a public IPv4 on an interface, an agent could read the host's services or the
|
|
117
|
+
router. A compressed page past the size limit stops decompressing, instead of growing
|
|
118
|
+
the daemon's memory, and a page full of unclosed comments or tags no longer freezes
|
|
119
|
+
the daemon for minutes. One deadline of 75 seconds covers a read and its redirects,
|
|
120
|
+
within the 90 seconds an agent waits. claude.ai and chatgpt.com are never read. An
|
|
121
|
+
HTML comment ends at `--!>` too, as browsers read it: the text after one was lost.
|
|
122
|
+
- Usage limits are recognised in Claude Code 2.1's words ("You've hit your 5-hour
|
|
123
|
+
limit"): an attempt that hit one counted as failed.
|
|
124
|
+
- An agent can no longer crash the daemon with a line too long for its follower, and a
|
|
125
|
+
terminal whose sandbox ends at once no longer crashes it either. Transcripts and the
|
|
126
|
+
agents' channels pick up again after the OpenShell gateway restarts, a hung `openshell`
|
|
127
|
+
call is stopped, and a tool call too large gets an error, not a wait.
|
|
128
|
+
- A torn line in the Architect's inbox no longer breaks its prompt hook, and an
|
|
129
|
+
`extraHosts` entry for a bundle's host, such as github.com, no longer takes away its
|
|
130
|
+
git access.
|
|
131
|
+
- The dashboard's cookie is named after its port, so two projects' dashboards no longer
|
|
132
|
+
log each other out: after upgrading, open the printed link once. Agent-written
|
|
133
|
+
Markdown loses its styles, which could hide parts of Needs you. The reviewers' "Shell
|
|
134
|
+
in" button works while a review runs.
|
|
135
|
+
- The agents' image pins the AWS CLI, 2.39.6, by checksum.
|
|
136
|
+
|
|
137
|
+
Releases:
|
|
138
|
+
- The release workflow runs the install and the tests in a job without the npm OIDC
|
|
139
|
+
token, and a second job publishes the tarball the first one packed: no dependency's
|
|
140
|
+
code runs where the token can publish.
|
|
141
|
+
|
|
6
142
|
## 0.2.0 (2026-11-06)
|
|
7
143
|
|
|
8
144
|
**Upgrading a project from 0.1.x:** install the new version, then run `harness setup`
|
|
@@ -14,10 +150,10 @@ new attempt clock of 120 minutes and Manager turn of 90.
|
|
|
14
150
|
- **Swarm mode**, designed in `docs/architecture/proposal-v3.md`: a second mode, switched
|
|
15
151
|
in the dashboard or with `harness mode`, in which the daemon merges a plan's pull
|
|
16
152
|
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
|
|
18
|
-
under `swarm.merge.infraMonthlyUsdCeiling`,
|
|
19
|
-
|
|
20
|
-
|
|
153
|
+
`swarm.merge.code` is `daemon`; `INFRA` and `MIXED` plans need the cost job's
|
|
154
|
+
estimate under `swarm.merge.infraMonthlyUsdCeiling`, or the owner's button (both
|
|
155
|
+
below). A pull request the policy leaves to the owner shows the reason under Needs
|
|
156
|
+
you. `Arch:` and `Fix:` pull requests stay the owner's.
|
|
21
157
|
- A plan is `CODE`, `INFRA` or `MIXED`, from its features' kinds. Plan pull requests
|
|
22
158
|
carry the tag in their title, and CI's `verify` job keeps a `CODE` plan's pull
|
|
23
159
|
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
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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.
|
|
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/`,
|
|
230
|
-
`
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
|
@@ -280,10 +295,17 @@ once CI passed and every status in `swarm.requiredStatuses` is green on its head
|
|
|
280
295
|
estimate over the ceiling still waiting for you. The button is off by default, and
|
|
281
296
|
the choice is recorded in the event log.
|
|
282
297
|
|
|
298
|
+
Only an estimate of the pull request's current head counts: after a fix round, the new
|
|
299
|
+
head needs its own. Before it merges, the daemon merges `main` into the pull request's
|
|
300
|
+
branch if `main` moved since CI ran, so CI and the reviewers judge what `main` gets; a
|
|
301
|
+
conflict leaves the pull request to you, to resolve on GitHub.
|
|
302
|
+
|
|
283
303
|
Anything else waits for you, under **Needs you** with the reason. `Arch:` and `Fix:`
|
|
284
304
|
pull requests are yours in both modes. In Swarm, GitHub's rules need no approval but
|
|
285
305
|
require the reviewers' statuses, pinned to the App, so even the daemon can't merge
|
|
286
|
-
before they pass.
|
|
306
|
+
before they pass. The reviewers read plan pull requests only: the daemon gives an
|
|
307
|
+
`Arch:` or `Fix:` pull request the two statuses as "not reviewed", so you merge it
|
|
308
|
+
without bypassing the rules, and CI still gates it.
|
|
287
309
|
|
|
288
310
|
**Before a launch.** In Swarm the daemon reads your subscription's usage before it
|
|
289
311
|
starts an attempt, and at or above `swarm.usageCeilingPercent` the pipeline waits for
|
|
@@ -302,9 +324,10 @@ with a verdict and findings. The daemon posts the findings on the pull request a
|
|
|
302
324
|
review, with comments on the lines of the diff, and sets the reviewer's commit status,
|
|
303
325
|
`harness/security` or `harness/cost`. A blocking verdict opens a fix round on the
|
|
304
326
|
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.
|
|
306
|
-
|
|
307
|
-
|
|
327
|
+
Manager reviews, CI and the reviewers run again on the new head. A blocking finding
|
|
328
|
+
blocks, whatever the verdict says. After `swarm.securityFixRounds` or
|
|
329
|
+
`swarm.costFixRounds`, the pull request is yours. In Manual,
|
|
330
|
+
`swarm.reviewersInManual` runs them as comments and statuses that gate nothing.
|
|
308
331
|
|
|
309
332
|
The App needs **Commit statuses: write** for the verdicts. An App created before this
|
|
310
333
|
version has read only: give it the permission on GitHub, accept it on the installation,
|
|
@@ -318,16 +341,19 @@ Architect is told to declare every dependency. Each attempt starts from the plan
|
|
|
318
341
|
branch as it is. When another feature lands first, the daemon merges the new head
|
|
319
342
|
into the attempt before its checks, so they see the accepted work, and the feature
|
|
320
343
|
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.
|
|
322
|
-
is built interrupts its attempt the same way, and the next one builds
|
|
323
|
-
definition. Manual mode builds one feature at a time, as before. In both modes, the
|
|
344
|
+
it, and the next one starts from the new head. In both modes, a design change merged
|
|
345
|
+
while a feature is built interrupts its attempt the same way, and the next one builds
|
|
346
|
+
the new definition. Manual mode builds one feature at a time, as before. In both modes, the
|
|
324
347
|
Architect hears in one line when a plan's pull request opens, waits for you, is
|
|
325
348
|
blocked by a reviewer, or merges.
|
|
326
349
|
|
|
327
350
|
**Parallel plans.** A plan may declare `dependsOn`, the plans of its module that must
|
|
328
351
|
be merged before it starts; without it, it waits for the previous plan, as always. In
|
|
329
|
-
Swarm, up to `swarm.maxPlans` plans
|
|
330
|
-
on its own branch with its own Manager, keyed `module/plan`
|
|
352
|
+
Swarm, up to `swarm.maxPlans` plans of the module under way whose dependencies are
|
|
353
|
+
merged build at once, each on its own branch with its own Manager, keyed `module/plan`
|
|
354
|
+
in the Agents view. When one merges, `main` is merged into the others, `progress.txt`
|
|
355
|
+
keeping both sides' lines; a conflict in other files is resolved in that plan's pull
|
|
356
|
+
request. A
|
|
331
357
|
feature may also depend on a feature of an earlier plan, as `P1/F03`, and then waits
|
|
332
358
|
for that plan's merge. The Project view draws each module's features as a graph, plan
|
|
333
359
|
by plan, with an edge per dependency: green accepted, blue under way, red escalated,
|
|
@@ -337,7 +363,7 @@ as before.
|
|
|
337
363
|
**The cost job.** `harness install-ci` adds a `cost` job to the workflow: on a pull
|
|
338
364
|
request that has `iac/`, it runs [Infracost](https://www.infracost.io) on the base and
|
|
339
365
|
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
|
|
366
|
+
CI passed and the `cost` job itself succeeded. It needs the repository secret `INFRACOST_API_KEY`, a free key from
|
|
341
367
|
`infracost auth login`; without it the job does nothing, no estimate exists, and
|
|
342
368
|
`INFRA` and `MIXED` plans wait for you with that reason. Infracost sees your Terraform's
|
|
343
369
|
resource types and sizes, not your code or state. The cost reviewer gets the estimate in
|
|
@@ -371,7 +397,7 @@ too.
|
|
|
371
397
|
| `harness github-app create --org <org>` | Creates the harness's GitHub App, once per machine |
|
|
372
398
|
| `harness github-app check --repo <org>/<repo>` | Checks that the App can reach the repository |
|
|
373
399
|
| `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) |
|
|
400
|
+
| `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
401
|
| `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
402
|
| `harness install-ci [--owner <login>]` | Writes the workflow, its CI script and `CODEOWNERS` again, after an upgrade |
|
|
377
403
|
| `harness lock` | Writes `harness/lock.json`, the hashes of the protected files |
|
|
@@ -385,8 +411,8 @@ too.
|
|
|
385
411
|
| `harness message <text>` | Writes to the Manager of the module being built |
|
|
386
412
|
| `harness retry <module>/<plan>/<feature>` | Gives an escalated feature one more attempt; `--note` adds guidance |
|
|
387
413
|
| `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
|
|
414
|
+
| `harness mode [manual\|swarm]` | Shows the mode, or switches it; with the daemon down, it records the switch for when it starts |
|
|
415
|
+
| `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
416
|
|
|
391
417
|
## Configuration
|
|
392
418
|
|
|
@@ -395,7 +421,7 @@ through an `Arch:` pull request, or a commit of your own on `main`.
|
|
|
395
421
|
|
|
396
422
|
```json
|
|
397
423
|
{
|
|
398
|
-
"harnessVersion": "0.
|
|
424
|
+
"harnessVersion": "0.3.0",
|
|
399
425
|
"project": { "name": "shop", "repo": "<org>/shop", "bot": "<org>-harness[bot]" },
|
|
400
426
|
"roles": {
|
|
401
427
|
"architect": { "model": "claude-opus-5-5", "effort": "max" },
|
|
@@ -418,8 +444,8 @@ through an `Arch:` pull request, or a commit of your own on `main`.
|
|
|
418
444
|
| `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
445
|
| `network.extraHosts` | `[]` | More hosts, read-only |
|
|
420
446
|
| `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
|
|
447
|
+
| `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"]}` |
|
|
448
|
+
| `sandbox.image` | `razzou-harness/agent:<version>` | The agents' image, tagged with the harness's version, which `harness doctor --build-images` builds |
|
|
423
449
|
| `sandbox.claudeProvider` | `claude-sub` | The OpenShell provider that holds your Claude token |
|
|
424
450
|
| `sandbox.env` | `{}` | Environment variables for every sandbox |
|
|
425
451
|
| `github.requiredChecks` | `["verify", "checks"]` | The CI jobs a pull request must pass; `tf-plan` is added with Terraform |
|
|
@@ -519,7 +545,7 @@ and Terraform applies only in CI, after you merge.
|
|
|
519
545
|
1. In `harness/config.json`, add `aws` and `terraform`:
|
|
520
546
|
|
|
521
547
|
```json
|
|
522
|
-
"aws": { "region": "us-east-1", "provider": "aws-view
|
|
548
|
+
"aws": { "region": "us-east-1", "provider": "aws-view-<org>-shop", "services": ["sts", "s3", "iam", "sqs"] },
|
|
523
549
|
"terraform": { "dir": "iac", "backendConfig": "backend-configs/staging.hcl", "varFile": "env/staging.tfvars" }
|
|
524
550
|
```
|
|
525
551
|
|
|
@@ -558,8 +584,9 @@ What agents can't do, and what stops them:
|
|
|
558
584
|
- **Change AWS.** A read-only role with explicit denies, and applies only in CI after
|
|
559
585
|
your merge.
|
|
560
586
|
- **Go anywhere online.** Sandboxes reach listed hosts, read-only. Other pages go
|
|
561
|
-
through `web_read`: GET only, public addresses only,
|
|
562
|
-
|
|
587
|
+
through `web_read`: GET only, public addresses only, never this machine's own
|
|
588
|
+
addresses or the networks it is on (IPv6 included), checked again after every
|
|
589
|
+
redirect, within 75 seconds and 2 MB, decompressed.
|
|
563
590
|
- **Decide alone.** Code runs the loop: deadlines, attempt budgets, checks in fresh
|
|
564
591
|
sandboxes and the merges, yours or the Swarm policy's, are enforced by the daemon,
|
|
565
592
|
not left to the agents.
|
|
@@ -608,20 +635,31 @@ To report a vulnerability, see `SECURITY.md` in the source repository.
|
|
|
608
635
|
| `Claude provider: … is missing` | `claude setup-token`, then `harness doctor` in a terminal: it asks for the token |
|
|
609
636
|
| `GitHub App: … 404` | Install the App on the repository, from the link `harness github-app create` printed, or the organization's settings |
|
|
610
637
|
| `ruleset: …` fails | `harness github-setup --repo <org>/<repo>`. A private repository needs a paid GitHub plan for rulesets. |
|
|
638
|
+
| `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
639
|
| `.github/… differs from this version of the harness` | `harness install-ci`, then commit the files yourself |
|
|
612
640
|
|
|
613
641
|
While the daemon runs:
|
|
614
642
|
- **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
|
|
616
|
-
|
|
643
|
+
Manager that didn't act, CI still failing after its fix rounds, a deploy still failing
|
|
644
|
+
after its Fix PRs, a usage limit, an invalid design on `main`, or a broken environment.
|
|
645
|
+
Fix the cause, then **Resume**. On resume, a failed full check run runs again and a
|
|
646
|
+
failed deploy is looked at again: a later successful apply on `main` counts.
|
|
647
|
+
- **CI fails for a reason outside the code.** Fix it (a secret, the runner), then re-run
|
|
648
|
+
the jobs on GitHub: a passing re-run counts. To fix the code yourself, push to the pull
|
|
649
|
+
request's branch, or resolve a conflict in GitHub's editor: the daemon builds on your
|
|
650
|
+
push, and CI runs again.
|
|
617
651
|
- **A feature is escalated.** It used its attempts. In **Needs you**, give it one more
|
|
618
652
|
attempt with your guidance, send it to the Architect (the design may change), or pause.
|
|
619
653
|
- **The dashboard asks for its token.** Each `harness up` prints a new link: open that
|
|
620
|
-
one.
|
|
654
|
+
one. Each project's dashboard keeps its own cookie, named after its port, so several
|
|
655
|
+
can be open at once. The browser it opens by itself logs in with a code that works once, for two
|
|
656
|
+
minutes, so the token never shows on a command line.
|
|
621
657
|
- **A merge takes minutes to show up.** The daemon polls GitHub: every minute, then every
|
|
622
658
|
five once nothing has changed for ten minutes.
|
|
623
|
-
- **Closing the terminal stopped the daemon.**
|
|
624
|
-
its event log, and the agents kept working
|
|
659
|
+
- **Closing the terminal stopped the daemon.** It stops cleanly, as with `harness down`.
|
|
660
|
+
Run `harness up` again: it picks up from its event log, and the agents kept working
|
|
661
|
+
meanwhile. A daemon that crashed leaves `.harness/daemon.json` and
|
|
662
|
+
`.harness/daemon.lock` behind: the next `harness up` sees that their process is gone.
|
|
625
663
|
|
|
626
664
|
## Development
|
|
627
665
|
|
|
@@ -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 &&
|
|
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 &&
|
|
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);
|
package/dist/anthropic/usage.js
CHANGED
|
@@ -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
|
|
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}`);
|
package/dist/checks/runner.js
CHANGED
|
@@ -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
|
-
|
|
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 });
|