razzou-harness 0.1.0 → 0.1.2

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 (72) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +351 -33
  3. package/dist/agents/claude-code.js +0 -1
  4. package/dist/checks/runner.js +0 -1
  5. package/dist/ci/harness-ci.js +0 -1
  6. package/dist/cli.js +0 -1
  7. package/dist/daemon/architects.js +0 -1
  8. package/dist/daemon/channel.js +0 -1
  9. package/dist/daemon/design.js +0 -1
  10. package/dist/daemon/items.js +0 -1
  11. package/dist/daemon/main.js +0 -1
  12. package/dist/daemon/manager.js +0 -1
  13. package/dist/daemon/orchestrator.js +0 -1
  14. package/dist/daemon/refs.js +0 -1
  15. package/dist/daemon/requests.js +0 -1
  16. package/dist/daemon/task.js +0 -1
  17. package/dist/daemon/web-read.js +0 -1
  18. package/dist/dashboard/server.js +0 -1
  19. package/dist/dashboard/terminals.js +0 -1
  20. package/dist/design/schemas.js +1 -2
  21. package/dist/git/repo.js +0 -1
  22. package/dist/github/app.js +0 -1
  23. package/dist/github/forge.js +0 -1
  24. package/dist/github/setup.js +0 -1
  25. package/dist/roles.js +0 -1
  26. package/dist/sandbox/allowlist.js +0 -1
  27. package/dist/sandbox/driver.js +0 -1
  28. package/dist/sandbox/openshell.js +0 -1
  29. package/dist/sandbox/policy.js +0 -1
  30. package/dist/setup/doctor.js +2 -3
  31. package/dist/setup/init.js +1 -2
  32. package/dist/setup/probe.js +0 -1
  33. package/dist/setup/recover.js +0 -1
  34. package/dist/state/events.js +0 -1
  35. package/dist/state/state.js +0 -1
  36. package/dist/util.js +0 -1
  37. package/dist/web/THIRD-PARTY-LICENSES.md +372 -0
  38. package/package.json +28 -5
  39. package/dist/agents/claude-code.js.map +0 -1
  40. package/dist/checks/runner.js.map +0 -1
  41. package/dist/ci/harness-ci.js.map +0 -1
  42. package/dist/cli.js.map +0 -1
  43. package/dist/daemon/architects.js.map +0 -1
  44. package/dist/daemon/channel.js.map +0 -1
  45. package/dist/daemon/design.js.map +0 -1
  46. package/dist/daemon/items.js.map +0 -1
  47. package/dist/daemon/main.js.map +0 -1
  48. package/dist/daemon/manager.js.map +0 -1
  49. package/dist/daemon/orchestrator.js.map +0 -1
  50. package/dist/daemon/refs.js.map +0 -1
  51. package/dist/daemon/requests.js.map +0 -1
  52. package/dist/daemon/task.js.map +0 -1
  53. package/dist/daemon/web-read.js.map +0 -1
  54. package/dist/dashboard/server.js.map +0 -1
  55. package/dist/dashboard/terminals.js.map +0 -1
  56. package/dist/design/schemas.js.map +0 -1
  57. package/dist/git/repo.js.map +0 -1
  58. package/dist/github/app.js.map +0 -1
  59. package/dist/github/forge.js.map +0 -1
  60. package/dist/github/setup.js.map +0 -1
  61. package/dist/roles.js.map +0 -1
  62. package/dist/sandbox/allowlist.js.map +0 -1
  63. package/dist/sandbox/driver.js.map +0 -1
  64. package/dist/sandbox/openshell.js.map +0 -1
  65. package/dist/sandbox/policy.js.map +0 -1
  66. package/dist/setup/doctor.js.map +0 -1
  67. package/dist/setup/init.js.map +0 -1
  68. package/dist/setup/probe.js.map +0 -1
  69. package/dist/setup/recover.js.map +0 -1
  70. package/dist/state/events.js.map +0 -1
  71. package/dist/state/state.js.map +0 -1
  72. package/dist/util.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ The notable changes in each release of razzou-harness. Before 1.0, a new minor version
4
+ may change how a project is set up, and its notes say how.
5
+
6
+ ## 0.1.2 (2026-10-11)
7
+
8
+ - The source is public, on GitHub, and the package links to it.
9
+ - Releases are published to npm from GitHub Actions, with provenance: a GitHub release
10
+ tagged `v<version>` publishes that version.
11
+ - A user guide in the README: your first module, the concepts, the commands, the
12
+ configuration, the AWS setup, security, usage, and troubleshooting.
13
+ - The README states what GitHub requires: an organization for the App, and a paid plan
14
+ for rulesets on private repositories.
15
+ - `harness init` and `harness doctor` no longer point to a private setup repository for
16
+ AWS.
17
+ - A security policy, in `SECURITY.md`.
18
+
19
+ ## 0.1.1 (2026-10-11)
20
+
21
+ - The dashboard ships with `THIRD-PARTY-LICENSES.md`, the licenses of the libraries
22
+ bundled into it.
23
+ - The command is also installed as `razzou-harness`, for machines where another
24
+ `harness` comes first on the PATH.
25
+ - The package has no source maps any more: they pointed to sources it doesn't contain.
26
+ - A new description: your AI dev team, with you in charge.
27
+
28
+ ## 0.1.0 (2026-10-11)
29
+
30
+ The first release:
31
+ - an Architect per module that designs it with you, Developers that build it one feature
32
+ at a time, and a Manager that reviews their work, each in its own OpenShell sandbox;
33
+ - a daemon that runs the loop from an event log, and picks up where it stopped after a
34
+ restart;
35
+ - one pull request per plan, opened by a GitHub App with no access to workflows, and CI
36
+ rules that protect the design and its checks;
37
+ - read-only AWS for the agents, Terraform plans against the real state, applies only in
38
+ CI, and post-deploy checks;
39
+ - the dashboard, with the Architect's terminal and live transcripts;
40
+ - `harness init`, `harness doctor` with its probe, and `harness recover`.
package/README.md CHANGED
@@ -1,17 +1,35 @@
1
1
  # razzou-harness
2
2
 
3
- Builds software one module, plan and feature at a time, with Claude Code agents in
4
- [OpenShell](https://github.com/NVIDIA/OpenShell) sandboxes. A small daemon runs the loop;
5
- agents do the work, and fresh sandboxes check it. It follows Anthropic's
3
+ **Your AI dev team, with you in charge.**
4
+
5
+ You describe a module. An Architect designs it with you, Developers build it one feature
6
+ at a time, and a Manager reviews each one. Every agent works in its own
7
+ [OpenShell](https://github.com/NVIDIA/OpenShell) sandbox with no credentials, every
8
+ feature is checked in a fresh one, and nothing reaches `main` until you merge its pull
9
+ request. A small daemon runs the loop, following Anthropic's
6
10
  [Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents).
7
11
 
12
+ - [How it works](#how-it-works)
13
+ - [Requirements](#requirements)
14
+ - [Install and set up a project](#install-and-set-up-a-project)
15
+ - [Your first module](#your-first-module)
16
+ - [Concepts](#concepts)
17
+ - [The dashboard](#the-dashboard)
18
+ - [Commands](#commands)
19
+ - [Configuration](#configuration)
20
+ - [AWS and Terraform](#aws-and-terraform)
21
+ - [Security](#security)
22
+ - [Usage and costs](#usage-and-costs)
23
+ - [Troubleshooting](#troubleshooting)
24
+ - [Development](#development)
25
+
8
26
  ## How it works
9
27
 
10
- The owner starts a module in the dashboard, and talks with its Architect (Claude Code,
11
- interactive) in its terminal. The Architect grills the owner, writes proposals until one
12
- is accepted, then decomposes it into plans, features, checks and owner actions, which
13
- reach `main` as an `Arch:` pull request that the owner merges. Then the daemon builds the
14
- module, feature by feature:
28
+ You start a module in the dashboard, and talk with its Architect (Claude Code,
29
+ interactive) in its terminal. The Architect asks you questions until the need is clear,
30
+ writes proposals until you accept one, then turns it into plans, features, checks and
31
+ owner actions. These reach `main` as an `Arch:` pull request that you merge. Then the
32
+ daemon builds the module, feature by feature:
15
33
  - it starts a Developer (Claude Code) in a fresh sandbox, with a 2-attempt budget and a
16
34
  deadline;
17
35
  - it checks the result in another fresh sandbox, which has no agent;
@@ -23,8 +41,7 @@ module, feature by feature:
23
41
  and the Manager submits it;
24
42
  - the daemon pushes the plan branch and opens one pull request for it, as a GitHub App
25
43
  with no access to workflows. CI checks the PR with the rules of the base branch. If CI
26
- fails, the Manager diagnoses it and a Developer fixes it, twice at most. The owner
27
- merges.
44
+ fails, the Manager diagnoses it and a Developer fixes it, twice at most. You merge.
28
45
 
29
46
  Infrastructure features take the same path. Agents see AWS through a read-only role
30
47
  whose requests the OpenShell proxy signs, so they plan against the real state but never
@@ -40,13 +57,23 @@ without pull requests.
40
57
 
41
58
  ## Requirements
42
59
 
43
- - Linux, Node 24 or later, git, Docker, and `gh` logged in as the owner.
44
- - OpenShell 0.1.2 with a running gateway.
45
- - Claude Code on this machine, with a subscription: its binary goes into the sandbox
46
- image, and `claude setup-token` gives the agents' token.
60
+ - Linux, Node 24 or later, git, Docker, and `gh` logged in as an admin of your
61
+ project's repository.
62
+ - OpenShell 0.1.2 with a running gateway. The harness is tested with this version only,
63
+ and `harness doctor` refuses others.
64
+ - Claude Code on this machine, with a Claude subscription: its binary goes into the
65
+ sandbox image, and `claude setup-token` gives the agents' token. Access with an API
66
+ key is untested.
47
67
  - For terminals in the dashboard: `python3`, `make` and `g++`, to build node-pty at
48
68
  install. Without them, everything else works, and `harness attach` opens the
49
69
  Architect's terminal.
70
+ - On GitHub, for pull requests:
71
+ - **An organization** that owns your project's repository: `harness github-app
72
+ create` creates the harness's App in an organization. A free one works.
73
+ - **Rulesets** protect `main`. GitHub offers them on private repositories only with a
74
+ paid plan, such as Team for an organization; public repositories get them for free.
75
+ Without them, nothing on GitHub makes code wait for your review before it reaches
76
+ `main`, and `harness doctor --probe` reports it.
50
77
 
51
78
  ## Install and set up a project
52
79
 
@@ -58,6 +85,9 @@ harness doctor --probe # everything an agent must not do, tried
58
85
  harness up # the daemon, and the dashboard
59
86
  ```
60
87
 
88
+ The command is also installed as `razzou-harness`, for machines where another `harness`,
89
+ such as the Harness.io CLI, comes first on the PATH.
90
+
61
91
  `harness init` writes the harness's files into the repository, never over existing
62
92
  ones, and prints the setup checklist:
63
93
  1. Docker, and OpenShell with a running gateway.
@@ -66,7 +96,7 @@ ones, and prints the setup checklist:
66
96
  3. The GitHub App, once per machine: `harness github-app create --org <org>`, then install
67
97
  it on the project repositories only.
68
98
  4. The repository rules: `harness github-setup --repo <org>/<repo>`, with your own `gh`.
69
- 5. AWS, for infrastructure: see below.
99
+ 5. AWS, for infrastructure: see [AWS and Terraform](#aws-and-terraform).
70
100
  6. Review the files `init` wrote, commit them, and push them to `main` yourself: the App
71
101
  may not touch workflows.
72
102
  7. `harness doctor --probe`.
@@ -74,7 +104,72 @@ ones, and prints the setup checklist:
74
104
  `harness doctor` checks each step, and says what to do about what fails. `--probe` tries,
75
105
  from a sandbox like an agent's and from outside, everything that must be refused: AWS
76
106
  writes and secret reads, other projects' state, `git push`, unlisted hosts and the
77
- metadata address, a workflow pushed by the App, a merge without the owner's approval.
107
+ metadata address, a workflow pushed by the App, a merge without your approval.
108
+
109
+ `harness init` lets the sandboxes reach only the `core` network bundle. Add your
110
+ stack's bundles, such as `node` for npm, in `harness/config.json` (see
111
+ [Configuration](#configuration)).
112
+
113
+ ## Your first module
114
+
115
+ With `harness up` running and the dashboard open:
116
+
117
+ 1. **Start it.** In **Project**, give the module an id and a line about what it is for,
118
+ then **Start**. An Architect starts in its own sandbox. From a terminal:
119
+ `harness module new greeter "A greeting command-line tool"`.
120
+ 2. **Talk it through.** In **Agents**, open the Architect's terminal and say what you
121
+ need. It reads the repository, asks its questions, then writes a proposal: the
122
+ problem, the design, the plans it expects, and what only you can do. From a terminal:
123
+ `harness attach greeter`, and detach with Ctrl-P, then Ctrl-Q.
124
+ 3. **Accept a proposal.** **Needs you** shows it. Accept it, or ask for changes in the
125
+ terminal, and the Architect writes the next version. Once you accept one, the
126
+ dashboard prompts the Architect, which turns it into plans, features and checks, and
127
+ the daemon opens an `Arch:` pull request with them. Its new checks run once first: they should fail,
128
+ since nothing is built yet, and any that pass are flagged.
129
+ 4. **Merge the design.** Review the Arch PR on GitHub, and merge it with a merge commit
130
+ once CI passes. The build starts.
131
+ 5. **Watch the build.** In **Agents**, each Developer's session and the Manager's are
132
+ live transcripts. **Project** shows each feature's attempts, checks and commits.
133
+ 6. **Answer what comes up.** A question from the Manager, or a feature that ran out of
134
+ attempts, appears in **Needs you** with a form or buttons.
135
+ 7. **Merge the plan.** When all of a plan's features are accepted and its checks pass
136
+ again, the daemon opens one `Plan:` pull request. Merge it with a merge commit once
137
+ CI passes. The next plan starts.
138
+
139
+ You can also write a module's design by hand, on `main`: the daemon builds whatever
140
+ plans it finds there (see [Configuration](#configuration)).
141
+
142
+ ## Concepts
143
+
144
+ - **Module:** a unit of design, with one Architect and one accepted proposal, then its
145
+ plans. Modules are built one after another.
146
+ - **Proposal:** the Architect's design document, in versions v1, v2 and so on. You
147
+ accept one.
148
+ - **Plan:** features that ship together as one pull request. A module's plans are built
149
+ in order.
150
+ - **Feature:** one change that a Developer can make in one attempt, with at least one
151
+ check. Its definition is frozen once its design is merged.
152
+ - **Check:** a shell command, run from the repository root, that exits 0 when the feature
153
+ works. `pre-merge` checks run on every attempt and in CI; `post-deploy` checks run
154
+ after an infrastructure deploy.
155
+ - **Attempt:** one Developer session on one feature, in a fresh sandbox, with a deadline
156
+ (90 minutes) and a budget (2 attempts per feature). A failed attempt's successor
157
+ starts with the Manager's guidance. Once the budget is spent, the feature is escalated
158
+ to you.
159
+ - **Owner actions:** what only you can do, such as creating an account, approving a
160
+ policy or setting a secret. A **gate** holds back the plans or features it blocks
161
+ until you mark it done; a **task** holds nothing back.
162
+ - **Protected files:** the design and its checks, CI, and what agents read as
163
+ instructions: `harness/`, `.github/`, `.claude/`, `CLAUDE.md`, `AGENTS.md`,
164
+ `ARCHITECTURE.md`, `init.sh`, tool configs and the npm scripts that checks run. A
165
+ `Plan:` or `Fix:` pull request may not change them: CI's `verify` job, run with the
166
+ base branch's rules, fails it. They change through an `Arch:` pull request, or your own
167
+ commits. `harness/lock.json` holds their hashes.
168
+ - **`progress.txt`:** one line per accepted feature and satisfied gate, committed with
169
+ the work. It only grows.
170
+ - **Brakes:** counters and deadlines, not judgment. Three failed attempts in a row, a
171
+ Manager that leaves an item waiting for 40 minutes, or CI still failing after two fix
172
+ rounds pause the pipeline until you resume it.
78
173
 
79
174
  ## The dashboard
80
175
 
@@ -88,38 +183,256 @@ behind a token. Its views:
88
183
  transcripts, a message box for the Manager, and a shell in any agent's sandbox.
89
184
  - **Events:** the event log, live, with a filter.
90
185
 
91
- From another terminal, the same project answers to `status`, `down`, `module new`,
92
- `attach`, `accept`, `actions`, `done`, `retry`, `message` and `answer`. If `.harness/` is
93
- lost, `harness recover` rebuilds it from `main` and GitHub.
186
+ The header pauses and resumes the pipeline: a pause lets what runs finish, and starts
187
+ nothing new.
94
188
 
95
- A project describes its work in its own repository, on `main`:
189
+ ## Commands
96
190
 
191
+ Run them in the project's folder, or add `--dir <project>`. `harness --help` lists them
192
+ too.
193
+
194
+ | Command | What it does |
195
+ |---|---|
196
+ | `harness init` | Writes the harness's files into the repository, and prints the setup checklist |
197
+ | `harness doctor [--build-images] [--probe]` | Checks the machine and the project, builds the agents' image, and tries what agents must not do |
198
+ | `harness github-app create --org <org>` | Creates the harness's GitHub App, once per machine |
199
+ | `harness github-app check --repo <org>/<repo>` | Checks that the App can reach the repository |
200
+ | `harness github-setup --repo <org>/<repo>` | Sets merge commits only, and the ruleset on `main`, with your own `gh` |
201
+ | `harness install-ci [--owner <login>]` | Writes the workflow, its CI script and `CODEOWNERS` again, after an upgrade |
202
+ | `harness lock` | Writes `harness/lock.json`, the hashes of the protected files |
203
+ | `harness up`, `harness down` | Starts the daemon and the dashboard, or stops the daemon; sandboxes keep running |
204
+ | `harness status` | Plans, features, modules and open questions, in the terminal |
205
+ | `harness module new <id> <title>` | Starts a module and its Architect |
206
+ | `harness attach <module>` | Opens the Architect's terminal here; detach with Ctrl-P, then Ctrl-Q |
207
+ | `harness accept <module> <vN>` | Accepts the Architect's proposal vN |
208
+ | `harness actions`, `harness done <module>/<id>` | Lists the owner actions, or marks one done |
209
+ | `harness answer <request-id> <text>` | Answers a question from the Manager |
210
+ | `harness message <text>` | Writes to the Manager of the module being built |
211
+ | `harness retry <module>/<plan>/<feature>` | Gives an escalated feature one more attempt; `--note` adds guidance |
212
+ | `harness resume` | Lifts a pause |
213
+ | `harness recover` | Rebuilds a lost `.harness/` from `main` and GitHub |
214
+
215
+ ## Configuration
216
+
217
+ `harness/config.json` holds the project's settings. It is a protected file: it changes
218
+ through an `Arch:` pull request, or a commit of your own on `main`.
219
+
220
+ ```json
221
+ {
222
+ "harnessVersion": "0.1.1",
223
+ "project": { "name": "shop", "repo": "<org>/shop", "bot": "<org>-harness[bot]" },
224
+ "roles": {
225
+ "architect": { "model": "claude-opus-5-5", "effort": "max" },
226
+ "manager": { "model": "claude-opus-5-5", "effort": "high" },
227
+ "developer": { "model": "claude-opus-5-5", "effort": "xhigh", "attemptTimeoutMinutes": 90, "maxAttempts": 2 }
228
+ },
229
+ "network": { "bundles": ["core", "node"], "extraHosts": [] },
230
+ "checks": [{ "id": "unit", "run": "npm test", "phase": "pre-merge" }]
231
+ }
232
+ ```
233
+
234
+ | Field | Default | What it sets |
235
+ |---|---|---|
236
+ | `project.name` | the folder's name, from `init` | Names the sandboxes and the dashboard |
237
+ | `project.repo`, `project.bot` | none | The GitHub repository, and the App's bot login. Without them, the project stays local. |
238
+ | `roles.<role>.model` | `claude-opus-5-5` | The model of the Architect, the Manager and the Developers |
239
+ | `roles.<role>.effort` | `max`, `high`, `xhigh` | Claude Code's `--effort`; `null` for a model without effort levels, such as Haiku |
240
+ | `roles.developer.attemptTimeoutMinutes` | 90 | An attempt's deadline |
241
+ | `roles.developer.maxAttempts` | 2 | Attempts per feature before it is escalated to you |
242
+ | `network.bundles` | `["core"]` | The hosts sandboxes reach, by bundle: `core`, `node`, `python`, `aws`, `terraform`, `go`, `rust`, `jvm`, `opentofu`, `browsers`. The Architect gets them all. |
243
+ | `network.extraHosts` | `[]` | More hosts, read-only |
244
+ | `checks` | `[]` | Checks that every feature must keep passing, such as `npm run lint` |
245
+ | `protect.files`, `protect.jsonFields` | none | More protected files, and protected JSON fields by file, like `{"tsconfig.json": ["/compilerOptions/strict"]}` |
246
+ | `sandbox.image` | `razzou-harness/agent:0.1.0` | The agents' image, which `harness doctor --build-images` builds |
247
+ | `sandbox.claudeProvider` | `claude-sub` | The OpenShell provider that holds your Claude token |
248
+ | `sandbox.env` | `{}` | Environment variables for every sandbox |
249
+ | `github.requiredChecks` | `["verify", "checks"]` | The CI jobs a pull request must pass; `tf-plan` is added with Terraform |
250
+ | `brakes.consecutiveFailures` | 3 | Failed attempts in a row that pause the pipeline |
251
+ | `brakes.managerTurnMinutes` | 20 | How long an item waits for the Manager before a reminder; as long again, and the pipeline pauses |
252
+ | `brakes.ciFixRounds`, `brakes.deployFixRounds` | 2, 2 | Fix rounds after CI fails, and `Fix:` pull requests after a deploy fails |
253
+ | `dashboard.port` | 4777 | The dashboard's port, or the next free one |
254
+ | `aws`, `terraform` | none | See [AWS and Terraform](#aws-and-terraform) |
255
+
256
+ A module's design lives on `main` too, whether the Architect wrote it or you did:
257
+
258
+ ```
259
+ harness/modules/<module>/plans/P1.json a plan and its features
260
+ harness/modules/<module>/checks/... the scripts its checks run, with synthetic data
261
+ harness/modules/<module>/owner-actions.json gates and tasks for you, if any
97
262
  ```
98
- harness/config.json project name, models, budgets, network bundles
99
- harness/modules/<module>/plans/P1.json the plan: features, each with its checks
100
- harness/modules/<module>/checks/... the scripts those checks run
263
+
264
+ A plan, `harness/modules/greeter/plans/P1.json`:
265
+
266
+ ```json
267
+ {
268
+ "id": "P1",
269
+ "module": "greeter",
270
+ "title": "Greeting CLI",
271
+ "goal": "A command that greets a name.",
272
+ "features": [
273
+ {
274
+ "id": "F01",
275
+ "title": "Greet by name",
276
+ "description": "src/greet.js prints \"Hello, <name>!\" for its first argument, and a usage line with exit code 1 without one.",
277
+ "expected": "`node src/greet.js Ada` prints Hello, Ada!",
278
+ "checks": [
279
+ { "id": "F01-greet", "run": "node --test harness/modules/greeter/checks/F01.test.mjs", "phase": "pre-merge" }
280
+ ]
281
+ }
282
+ ]
283
+ }
101
284
  ```
102
285
 
286
+ - **Features** may also have `kind` (`app` or `infra`), `dependsOn` (earlier features of
287
+ the same plan), `gates`, and `resources` (where the Developer should look).
288
+ - **Checks** have `run` (a shell command from the repository root, never a `/sandbox/`
289
+ path), `phase` (`pre-merge` or `post-deploy`), `timeoutSec` (600 by default), and
290
+ `needs`: `tfplan` to get a Terraform plan's JSON in `$HARNESS_PLAN_JSON`, `aws` to use
291
+ the read-only AWS role.
292
+ - **Owner actions** look like this, and a `verify` command, if set, runs in a fresh
293
+ sandbox before the action counts as done:
294
+
295
+ ```json
296
+ [{ "id": "G1", "kind": "gate", "title": "Approve the retention policy", "blocks": ["P2"],
297
+ "instructions": "Markdown for you.", "verify": null }]
298
+ ```
299
+
103
300
  ## AWS and Terraform
104
301
 
105
- A project with infrastructure needs these in AWS (proposal §12):
106
- - a read-only role for its agents, which a gateway user assumes;
107
- - plan and apply roles for CI, trusted through GitHub's OIDC: the apply role only from
108
- the `staging` environment, which only `main` may use;
109
- - a state bucket.
302
+ Infrastructure is optional: skip this section for a project without it. With it, agents
303
+ see your AWS account and plan Terraform against the real state, but never change
304
+ anything. They work through a read-only role whose requests the OpenShell proxy signs,
305
+ and Terraform applies only in CI, after you merge.
306
+
307
+ **What you need in AWS.** The harness doesn't create it:
308
+ 1. **A state bucket** for Terraform, with this project's state under its own key.
309
+ 2. **CI roles, trusted through GitHub's OIDC:** a plan role for pull requests, and an
310
+ apply role trusted only from a GitHub environment named `staging`, which only `main`
311
+ may use.
312
+ 3. **The agents' read-only role**, named `harness-<project>-view`: `harness doctor`
313
+ expects the `harness-` prefix and the `-view` suffix. It has:
314
+ - the AWS managed policies `ViewOnlyAccess` and `SecurityAudit`;
315
+ - list access to the state bucket, read access to this project's state object, and
316
+ `kms:Decrypt` on the state's key through S3;
317
+ - an explicit deny on data and secrets, which no update to the managed policies can
318
+ undo: `s3:GetObject*` on anything but the state object; the DynamoDB item reads
319
+ (`GetItem`, `BatchGetItem`, `Query`, `Scan`, the PartiQL calls, `GetRecords`);
320
+ `secretsmanager:GetSecretValue` and `BatchGetSecretValue`; the SSM parameter reads;
321
+ `kms:Decrypt` on any other key; the CloudWatch Logs content reads (`GetLogEvents`,
322
+ `FilterLogEvents`, `StartQuery`, `GetQueryResults`, `StartLiveTail`);
323
+ `sqs:ReceiveMessage`, `kinesis:GetRecords`, `athena:GetQueryResults` and
324
+ `rds-data:*`; the Cognito user reads; the SNS subscription reads, which hold emails
325
+ and phone numbers; `ec2:GetPasswordData` and `GetConsoleOutput`; and the ECR image
326
+ content (`BatchGetImage`, `GetDownloadUrlForLayer`);
327
+ - a trust policy that lets only the gateway user assume it.
328
+ 4. **A gateway user**, whose only permission is `sts:AssumeRole` on the view roles. Its
329
+ access key goes into OpenShell, never into a repository or a sandbox.
330
+ 5. **A permissions boundary** on the CI apply role that denies changes to the
331
+ `harness-*` roles and to the gateway user, so your project's own Terraform can't widen
332
+ the agents' access.
110
333
 
111
- Then:
112
- 1. In `harness/config.json`, add `aws` (region, provider, services) and `terraform`
113
- (folder, backend config, variables file). `github-setup` then requires `tf-plan`
114
- as well.
334
+ **Then, in the project:**
335
+ 1. In `harness/config.json`, add `aws` and `terraform`:
336
+
337
+ ```json
338
+ "aws": { "region": "us-east-1", "provider": "aws-view-shop", "services": ["sts", "s3", "iam", "sqs"] },
339
+ "terraform": { "dir": "iac", "backendConfig": "backend-configs/staging.hcl", "varFile": "env/staging.tfvars" }
340
+ ```
341
+
342
+ `services` lists the AWS services the project uses: their endpoints become reachable,
343
+ and signed. Keep the `terraform` paths as they are: the CI workflow uses the same
344
+ ones. `harness github-setup` then requires `tf-plan` as well.
115
345
  2. Set the repository variables the workflow reads: `AWS_REGION_STAGING`,
116
346
  `AWS_ROLE_PLAN_ARN_STAGING` and `AWS_ROLE_APPLY_ARN_STAGING`.
117
- 3. `harness doctor` creates the OpenShell AWS provider: it asks for the read-only role,
347
+ 3. `harness doctor` creates the OpenShell AWS provider: it asks for the view role's ARN,
118
348
  and for the gateway user's key at a hidden prompt. Only OpenShell holds the key.
349
+ 4. `harness doctor --probe` checks the result: AWS must answer as the view role, and
350
+ refuse a write, a secret and another project's state.
119
351
 
120
352
  A check with `"needs": ["tfplan"]` finds the plan's JSON in `$HARNESS_PLAN_JSON`. A
121
353
  `post-deploy` check with `"needs": ["aws"]` runs after the apply, as the read-only role.
122
354
 
355
+ Agents can read the Terraform state, so keep secrets out of it: Terraform creates a
356
+ secret, but you or a rotation set its value; a database manages its own password (for
357
+ RDS, `manage_master_user_password`); anything else uses write-only arguments.
358
+
359
+ ## Security
360
+
361
+ What agents can't do, and what stops them:
362
+ - **Hold credentials.** Sandboxes hold placeholders. The OpenShell proxy adds the Claude
363
+ token on `api.anthropic.com` only, and signs AWS requests with the read-only role.
364
+ - **Reach the host.** Each agent runs in its own container, with no host folder mounted;
365
+ code moves in and out as git bundles. The host never runs code or git hooks that an
366
+ agent wrote.
367
+ - **Write to GitHub.** Only the daemon talks to GitHub, as an App with no access to
368
+ workflows or settings. Sandboxes may clone and fetch, never push.
369
+ - **Change the rules.** Checks, CI and the agents' instructions are protected files: a
370
+ Developer's work can't change them, and CI checks that with the base branch's rules.
371
+ They change only through an Arch PR you merge, or your own commits.
372
+ - **Change AWS.** A read-only role with explicit denies, and applies only in CI after
373
+ your merge.
374
+ - **Go anywhere online.** Sandboxes reach listed hosts, read-only. Other pages go
375
+ through `web_read`: GET only, public addresses only, checked again after every
376
+ redirect.
377
+ - **Decide alone.** Code runs the loop: deadlines, attempt budgets, checks in fresh
378
+ sandboxes and your merges are enforced by the daemon, not left to the agents.
379
+
380
+ What remains possible, by design:
381
+ - an agent could send code out in the URL of an allowed GET request;
382
+ - the read-only role shows infrastructure metadata, S3 object names and Lambda
383
+ environment variables: keep user identifiers out of object names, and secrets out of
384
+ Lambda environment variables;
385
+ - the Architect writes the checks: review Arch PRs. Checks that pass before anything is
386
+ built are flagged in them.
387
+
388
+ `harness doctor --probe` tries, from sandboxes set up like the agents', what must be
389
+ refused. Run it after setup, and after upgrading the harness, OpenShell or Claude Code.
390
+ To report a vulnerability, see `SECURITY.md` in the source repository.
391
+
392
+ ## Usage and costs
393
+
394
+ - The agents run on your Claude subscription, so their usage counts against your plan's
395
+ limits. When Claude Code reports that a limit is reached, the attempt under way
396
+ doesn't count, and the pipeline pauses for an hour, then goes on.
397
+ - By default, all three roles run Opus 5.5 at a high effort. To try the harness, or to
398
+ use less of your plan, set each role's `model` to Haiku 4.5
399
+ (`claude-haiku-4-5-20251001`) and its `effort` to `null`.
400
+ - With Haiku 4.5 in our test runs, a Developer attempt took under 90 seconds and 17 to
401
+ 23 turns: 150,000 to 310,000 input tokens, mostly read from the cache, and 2,000 to
402
+ 6,000 output tokens. Claude Code estimated $0.08 to $0.12 per attempt at API prices.
403
+ The Architect and the Manager use more, over their long sessions.
404
+ - The dashboard shows each session's model calls, tokens and Claude Code's cost estimate.
405
+
406
+ ## Troubleshooting
407
+
408
+ `harness doctor` names what fails and what to do. The usual cases:
409
+
410
+ | `harness doctor` says | What to do |
411
+ |---|---|
412
+ | `Docker: … permission denied` | Add yourself to the `docker` group, then log in again |
413
+ | `Tailscale: an exit node is active`, or `container network` fails | An exit node can cut the containers' network: turn it off |
414
+ | `OpenShell: … pinned to 0.1.2` | Install OpenShell 0.1.2 |
415
+ | `OpenShell gateway` fails | `openshell gateway start` |
416
+ | `image: … has Claude Code X, this machine Y` | `harness doctor --build-images`, after each Claude Code update |
417
+ | `terminals: node-pty is missing` | Install `python3`, `make` and `g++`, then reinstall with `--allow-scripts=node-pty`. `harness attach` works meanwhile. |
418
+ | `Claude provider: … is missing` | `claude setup-token`, then `harness doctor` in a terminal: it asks for the token |
419
+ | `GitHub App: … 404` | Install the App on the repository, from the link `harness github-app create` printed, or the organization's settings |
420
+ | `ruleset: …` fails | `harness github-setup --repo <org>/<repo>`. A private repository needs a paid GitHub plan for rulesets. |
421
+ | `.github/… differs from this version of the harness` | `harness install-ci`, then commit the files yourself |
422
+
423
+ While the daemon runs:
424
+ - **The pipeline is paused.** **Needs you** says why: attempts that failed in a row, a
425
+ Manager that didn't act, CI still failing after its fix rounds, a usage limit, or a
426
+ broken environment. Fix the cause, then **Resume**.
427
+ - **A feature is escalated.** It used its attempts. In **Needs you**, give it one more
428
+ attempt with your guidance, send it to the Architect (the design may change), or pause.
429
+ - **The dashboard asks for its token.** Each `harness up` prints a new link: open that
430
+ one.
431
+ - **A merge takes minutes to show up.** The daemon polls GitHub: every minute, then every
432
+ five once nothing has changed for ten minutes.
433
+ - **Closing the terminal stopped the daemon.** Run `harness up` again: it picks up from
434
+ its event log, and the agents kept working meanwhile.
435
+
123
436
  ## Development
124
437
 
125
438
  ```sh
@@ -131,6 +444,11 @@ npm pack # a package of these sources: npm install -g ./razzou-harnes
131
444
  npm run harness -- up --dir path/to/project # from the sources; run `npm run build` once for the dashboard
132
445
  ```
133
446
 
447
+ A GitHub release tagged with the package's version, such as `v0.1.2`, publishes it to
448
+ npm. The release workflow checks the tag against `package.json`, runs the typecheck and
449
+ the tests, then publishes with npm's trusted publishing, so each version shows where it
450
+ was built.
451
+
134
452
  ## License
135
453
 
136
454
  MIT
@@ -183,4 +183,3 @@ function toUsage(usage, cost) {
183
183
  ...(typeof cost === 'number' ? { costUsd: cost } : {}),
184
184
  };
185
185
  }
186
- //# sourceMappingURL=claude-code.js.map
@@ -66,4 +66,3 @@ export async function initEnvironment(driver, sandbox, workDir) {
66
66
  ]);
67
67
  return result;
68
68
  }
69
- //# sourceMappingURL=runner.js.map
@@ -360,4 +360,3 @@ if (process.argv[1] && realpathSync(process.argv[1]) === import.meta.filename) {
360
360
  process.exit(1);
361
361
  });
362
362
  }
363
- //# sourceMappingURL=harness-ci.js.map
package/dist/cli.js CHANGED
@@ -285,4 +285,3 @@ main().catch((error) => {
285
285
  console.error(error.message);
286
286
  process.exitCode = 1;
287
287
  });
288
- //# sourceMappingURL=cli.js.map
@@ -287,4 +287,3 @@ function welcome(module) {
287
287
  'Read them, README.md and ARCHITECTURE.md, then greet the owner in two lines and wait.',
288
288
  ].join('\n'), 2000);
289
289
  }
290
- //# sourceMappingURL=architects.js.map
@@ -81,4 +81,3 @@ export class AgentChannel {
81
81
  this.options.onRequest(request);
82
82
  }
83
83
  }
84
- //# sourceMappingURL=channel.js.map
@@ -122,4 +122,3 @@ async function prBody(o, module, design, summary, suspect, base, lock) {
122
122
  `<!-- harness: design ${module.module} -->`,
123
123
  ].join('\n');
124
124
  }
125
- //# sourceMappingURL=design.js.map
@@ -230,4 +230,3 @@ function checkLines(results, withOutput = false) {
230
230
  });
231
231
  }
232
232
  const short = (sha) => (sha ? sha.slice(0, 8) : '?');
233
- //# sourceMappingURL=items.js.map
@@ -94,4 +94,3 @@ export async function runDaemon(projectDir, options = {}) {
94
94
  function refText(ref) {
95
95
  return [ref.module, ref.plan, ref.feature].filter(Boolean).join('/') + (ref.attempt ? ` #${ref.attempt}` : '');
96
96
  }
97
- //# sourceMappingURL=main.js.map
@@ -262,4 +262,3 @@ export class Managers {
262
262
  };
263
263
  }
264
264
  }
265
- //# sourceMappingURL=manager.js.map
@@ -1128,4 +1128,3 @@ function commitMessage(ref, feature, attempt, results, fix) {
1128
1128
  '',
1129
1129
  ].join('\n');
1130
1130
  }
1131
- //# sourceMappingURL=orchestrator.js.map
@@ -23,4 +23,3 @@ export function probeScript(exitFile, pidFile) {
23
23
  export function waitForFile(file) {
24
24
  return `for i in $(seq 1 100); do test -s ${sh(file)} && exit 0; sleep 0.1; done; echo 'nothing in ${file} after 10 s' >&2; exit 1`;
25
25
  }
26
- //# sourceMappingURL=refs.js.map
@@ -312,4 +312,3 @@ function architectState(o, id) {
312
312
  openEscalations: module.escalations.filter((e) => e.decision === undefined).map((e) => ({ id: e.id, question: clip(e.question, 400), feature: e.feature })),
313
313
  }, null, 2);
314
314
  }
315
- //# sourceMappingURL=requests.js.map
@@ -39,4 +39,3 @@ export function buildTask(input) {
39
39
  lines.push('', '## When you finish', '', 'Commit your work with a meaningful message. Uncommitted changes are discarded.', 'End with your structured report: `done` with a short summary, or `blocked` with a finding when you cannot finish.', 'If a decision is open that the task and the repository do not settle, ask the Manager with `ask_manager` before you guess.');
40
40
  return `${lines.join('\n')}\n`;
41
41
  }
42
- //# sourceMappingURL=task.js.map
@@ -248,4 +248,3 @@ function decodeEntities(text) {
248
248
  return ENTITIES[code.toLowerCase()] ?? entity;
249
249
  });
250
250
  }
251
- //# sourceMappingURL=web-read.js.map
@@ -358,4 +358,3 @@ function sendJson(res, data) {
358
358
  res.writeHead(200, { 'content-type': 'application/json' });
359
359
  res.end(JSON.stringify(data));
360
360
  }
361
- //# sourceMappingURL=server.js.map
@@ -143,4 +143,3 @@ function kill(pty) {
143
143
  // gone already
144
144
  }
145
145
  }
146
- //# sourceMappingURL=terminals.js.map
@@ -112,7 +112,7 @@ export const ConfigSchema = z.object({
112
112
  */
113
113
  aws: z
114
114
  .object({
115
- region: z.string().regex(/^[a-z]{2}(-[a-z]+)+-\d$/, 'an AWS region, like eu-west-1'),
115
+ region: z.string().regex(/^[a-z]{2}(-[a-z]+)+-\d$/, 'an AWS region, like us-east-1'),
116
116
  /** The OpenShell provider holding the view role, like "aws-view-shop". */
117
117
  provider: z.string().min(1),
118
118
  /** The services the project uses: their endpoints are reachable, and signed. */
@@ -271,4 +271,3 @@ function parse(schema, text, path) {
271
271
  throw new Error(`${path}:\n${z.prettifyError(result.error)}`);
272
272
  return result.data;
273
273
  }
274
- //# sourceMappingURL=schemas.js.map
package/dist/git/repo.js CHANGED
@@ -180,4 +180,3 @@ const ISOLATED = { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NO
180
180
  function lines(text) {
181
181
  return text.split('\n').filter((line) => line !== '');
182
182
  }
183
- //# sourceMappingURL=repo.js.map
@@ -121,4 +121,3 @@ export class GitHubError extends Error {
121
121
  this.status = status;
122
122
  }
123
123
  }
124
- //# sourceMappingURL=app.js.map
@@ -88,4 +88,3 @@ export class GitHubForge {
88
88
  }
89
89
  }
90
90
  }
91
- //# sourceMappingURL=forge.js.map