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.
- package/CHANGELOG.md +184 -4
- package/README.md +90 -41
- 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 +6 -4
- package/dist/daemon/channel.js +7 -1
- package/dist/daemon/design.js +16 -2
- package/dist/daemon/items.js +28 -3
- package/dist/daemon/main.js +84 -12
- package/dist/daemon/manager.js +28 -21
- package/dist/daemon/orchestrator.js +523 -184
- package/dist/daemon/requests.js +21 -19
- package/dist/daemon/review.js +15 -9
- package/dist/daemon/sandboxes.js +115 -0
- package/dist/daemon/web-read.js +143 -33
- package/dist/dashboard/server.js +50 -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/docker.js +66 -0
- package/dist/sandbox/driver.js +2 -1
- package/dist/sandbox/openshell.js +118 -13
- package/dist/sandbox/policy.js +2 -1
- package/dist/sandbox/refusals.js +92 -0
- package/dist/setup/doctor.js +86 -12
- package/dist/setup/images.js +41 -0
- 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 +42 -11
- package/dist/util.js +7 -2
- package/dist/web/assets/{index-CMViLVQH.css → index-CRYKs8kt.css} +1 -1
- package/dist/web/assets/index-hRWASvyl.js +96 -0
- package/dist/web/index.html +2 -2
- package/images/agent/Dockerfile +11 -2
- package/package.json +1 -1
- package/roles/architect.md +5 -2
- package/roles/manager.md +10 -1
- package/shim/harness-mcp.mjs +11 -2
- package/src/ci/harness-ci.ts +105 -32
- package/templates/harness.yml +5 -2
- 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
|
|
18
|
-
under `swarm.merge.infraMonthlyUsdCeiling`,
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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
|
|
|
@@ -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.
|
|
306
|
-
|
|
307
|
-
|
|
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.
|
|
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
|
|
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
|
|
330
|
-
on its own branch with its own Manager, keyed `module/plan`
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
562
|
-
|
|
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
|
|
616
|
-
|
|
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.**
|
|
624
|
-
its event log, and the agents kept working
|
|
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 &&
|
|
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 });
|