@cardor/agent-harness-kit 1.10.5 → 2.0.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/README.md CHANGED
@@ -50,7 +50,7 @@ npx ahk init
50
50
  - [What each file does](#what-each-file-does)
51
51
  - [Tasks schema](#tasks-schema)
52
52
  - [What you can customize](#what-you-can-customize)
53
- - [`agent-harness-kit.config.ts`](#agent-harness-kitconfigts)
53
+ - [`agent-harness-kit.config.{json|ts|mjs|cjs}`](#agent-harness-kitconfigjsontsmjscjs)
54
54
  - [`health.sh`](#healthsh)
55
55
  - [Agent definition files](#agent-definition-files)
56
56
  - [`.harness/feature_list.json`](#harnessfeature_listjson)
@@ -89,7 +89,9 @@ ahk init
89
89
 
90
90
  AI tool opens your project
91
91
  └── reads .claude/mcp.json, opencode.json, or .codex/config.toml
92
- └── spawns: npx ahk serve (stdio MCP server)
92
+ └── spawns: ahk serve (stdio MCP server)
93
+ via your package manager (npx/pnpm exec/yarn run/bunx) when the
94
+ package is a local dependency, or the bare binary when it isn't
93
95
 
94
96
  Agent starts working
95
97
  └── tasks.get() → picks a task from the backlog
@@ -117,7 +119,7 @@ Everything is stored locally in a SQLite database (`.harness/harness.db`). No cl
117
119
  - **Markdown fallback** — `current.md` is always regenerated so agents can understand the session state even without the MCP server.
118
120
  - **Docs search** — agents can call `docs.search(query)` to find relevant content in your project's docs folder before writing code.
119
121
  - **Multi-database support** — SQLite by default (uses `better-sqlite3` on Node ≥ 22 or `bun:sqlite` on Bun). Switch to PostgreSQL or MySQL with a single config line — same schema, same MCP tools, same workflow.
120
- - **Incremental scaffold** — `ahk init` preserves files you've already customized (agent definitions you've edited are kept). `ahk build` always regenerates agent files from the latest templates so they stay up to date.
122
+ - **Incremental scaffold** — `ahk init` preserves files you've already customized (agent definitions you've edited are kept). A hand-written `.harness/feature_list.json` backlog is **merged, never overwritten** — existing tasks survive and any first task you add during init is folded in (deduplicated by slug). `ahk build` also creates missing agent files and never touches existing ones. Use `ahk build --force` to regenerate them from the latest templates, discarding your edits (a backup is written first).
121
123
  - **Global installation** — `ahk init` can scaffold the harness into your home directory (`~/.claude` or `~/.config/opencode`) to share it across all projects.
122
124
  - **Input validation** — CLI prompts validate all inputs (name length, path format, task title, etc.) and retry with the error message instead of silently accepting bad values.
123
125
 
@@ -143,7 +145,18 @@ Then run the interactive setup inside your project:
143
145
  npx ahk init
144
146
  ```
145
147
 
146
- > **Local install recommended.** The generated `agent-harness-kit.config.ts` uses `import type`, which is erased at compile time, so `ahk` no longer needs to resolve the package from your project's `node_modules` at runtime. If it detects a **global-only** install (`npm install -g @cardor/agent-harness-kit`), it prints a non-blocking warning recommending `npm install --save-dev @cardor/agent-harness-kit` (or the `pnpm`/`yarn`/`bun` equivalent) — the command still runs and exits normally either way. This is only about keeping the CLI version pinned and reproducible across your team and CI, not a functional requirement.
148
+ > **The config file format depends on whether the package is installed locally.** `ahk init` checks that first, before anything else:
149
+ >
150
+ > | Local install | Generated config | Why |
151
+ > | --- | --- | --- |
152
+ > | **Not installed** (global-only CLI) | `agent-harness-kit.config.json` | Your project cannot resolve `@cardor/agent-harness-kit`, so a TypeScript config's `import type` would red-underline in your editor and fail `tsc --noEmit` on a package that isn't there. JSON has no imports and no types — nothing to resolve, zero editor errors. |
153
+ > | **Installed** (`npm install --save-dev @cardor/agent-harness-kit`) | `.ts`, `.mjs` or `.cjs` | The package resolves, so you get the full typed config with editor autocompletion. Which of the three is picked is unchanged: `.ts` when a `tsconfig.json` is present, otherwise `.mjs`/`.cjs` based on `package.json` `type`. |
154
+ >
155
+ > The trade-off is autocompletion: a JSON config has no type information behind it, so your editor cannot suggest fields. Installing the package locally and switching to a `.ts` config gets that back. There is no `$schema` key in the generated JSON — no JSON Schema for `HarnessConfig` is published yet, and pointing at a URL that doesn't resolve would only swap a type error for a fetch error.
156
+ >
157
+ > **Existing projects are never converted.** If a config of any extension already exists, it keeps working and keeps its format — installing or removing the package locally will not silently rewrite it. `loadConfig()` reads all five formats, and `ahk init` stops when it finds any of them.
158
+ >
159
+ > **A local install is still recommended** even though it is no longer required: it pins the CLI version so behavior stays reproducible across your team and CI instead of drifting with whatever is installed globally on each machine. On a global-only install `ahk` prints a non-blocking warning suggesting it — the command runs and exits normally either way.
147
160
  >
148
161
  > This check also works with **Yarn Berry (PnP)** projects, which never create a `node_modules` folder — `ahk` detects `.pnp.cjs`/`.pnp.loader.mjs` and falls back to checking that the package is declared in `package.json` instead of requiring a `node_modules` entry.
149
162
 
@@ -160,9 +173,14 @@ npx ahk init
160
173
  | yarn classic (v1) | `packageManager` field (major 1) or `yarn.lock` without `.yarnrc.yml` | `yarn run ahk serve --port <port>` |
161
174
  | yarn berry (v2+, PnP or node-modules) | `packageManager` field (major ≥ 2) or `yarn.lock` + `.yarnrc.yml` | `yarn run ahk serve --port <port>` |
162
175
  | bun | `packageManager` field or `bun.lockb`/`bun.lock` | `bunx --no-install ahk serve --port <port>` |
176
+ | **any — no local install** | `@cardor/agent-harness-kit` is not a dependency of your project | `ahk serve --port <port>` |
163
177
 
164
178
  Detection order: the `packageManager` field in your `package.json` (e.g. `"packageManager": "pnpm@8.15.0"`) takes priority when present; otherwise `ahk` falls back to lockfile heuristics; if nothing is detected, it defaults to npm.
165
179
 
180
+ **Global installs bypass the package manager entirely.** Every command in the table above asks your package manager to resolve a *locally installed* `ahk` binary — `npx --no` deliberately refuses to download one, and `pnpm exec`/`yarn run`/`bunx --no-install` have nothing to point at. If you installed the CLI globally and never added it to the project, all five of those commands fail. So `ahk` checks for the local install first (the same check that decides your config file format, above) and, when there is none, generates the bare `ahk serve --port <port>` — resolved from your `PATH` like any other global binary. The package-manager-specific commands are used only when a local install actually exists. If, on that global-install path, `ahk` is not resolvable on your `PATH` at generation time, `ahk` prints a non-blocking warning (the command still succeeds) pointing you at `npm i -g @cardor/agent-harness-kit` or a local install — moving the "binary not found" failure earlier instead of surfacing it later when the MCP server is spawned.
181
+
182
+ Working inside the `agent-harness-kit` repository itself counts as a local install: the package manager can resolve the workspace binary, so the `pnpm exec` form is generated rather than the bare one.
183
+
166
184
  **Existing projects:** if you initialized your project before this change, your `.mcp.json`/`opencode.json`/`.codex/config.toml` may still have a hardcoded `npx` command. No migration step is needed — `ahk build` always regenerates (merges) these files from scratch on every run, so the command self-corrects the next time you run `ahk build` (or `ahk build --sync`), including if you've since switched package managers.
167
185
 
168
186
  ---
@@ -173,10 +191,9 @@ Detection order: the `packageManager` field in your `package.json` (e.g. `"packa
173
191
 
174
192
  Interactive scaffold. Asks for your project name, description, AI provider, docs path, storage scope, task adapter, and an optional first task. Creates all harness files in the current directory.
175
193
 
176
- For Claude Code and Codex CLI (not OpenCode), you'll also be asked whether to personalize the model per agent (lead/explorer/consultant/builder/reviewer):
194
+ Claude Code only, init asks you to pick a model for each of the 5 core roles (lead, explorer, consultant, builder, reviewer) one at a time: `inherit` (default), `haiku`, `sonnet`, `opus`, or `fable`. Each choice is written straight into that role's generated `.claude/agents/<role>.md` frontmatter as a `model:` line at scaffold time — it is never persisted to the config file. Picking `inherit` (the default) emits no `model:` line at all, leaving Claude Code to apply its own default. This prompt only runs during `ahk init`'s one-time scaffold, not on `ahk build` — agent files are user-owned once generated (see [Agent files are yours](#agent-files-are-yours) below), so after init the model is changed the same way as any other edit: hand-editing the `model:` frontmatter line directly.
177
195
 
178
- - Claude Code: pick from `inherit` (default), `haiku`, `sonnet`, `opus`, `fable` per agent.
179
- - Codex CLI: free-text model name per agent — Codex does not validate this value; leaving it blank or under 3 characters means no override is written to that agent's TOML file.
196
+ OpenCode and Codex CLI are unaffected by this prompt — it never appears for those providers. OpenCode has no closed model enum to prompt against, and Codex's model is still set by hand-editing `model = "..."` in its TOML.
180
197
 
181
198
  **Storage scope** — where the harness DB (and its `current.md` fallback) physically lives:
182
199
 
@@ -208,9 +225,52 @@ Regenerates `AGENTS.md` and provider-specific files from your `agent-harness-kit
208
225
  ```bash
209
226
  ahk build
210
227
  ahk build --watch # watch mode: rebuilds automatically on config changes
211
- ahk build --sync # sync tools: frontmatter in agent files to match current permission constants (claude-code only; no-op for opencode/codex-cli)
228
+ ahk build --force # DESTRUCTIVE: regenerate agent files, discarding your edits
229
+ ahk build --sync # kept for backwards compatibility — now a no-op on every provider
212
230
  ```
213
231
 
232
+ ### Agent files are yours
233
+
234
+ `ahk build` **creates agent files that are missing and never modifies ones that already exist.** Edit `.claude/agents/<role>.md` (or `.opencode/agents/<role>.md`, or `.codex/agents/<role>.toml`) freely — change the role prompt, set a `model:` line, adjust the restriction fields. Rebuilding will not revert your work. `ahk doctor` does not report hand-edited files either; it checks existence only.
235
+
236
+ Everything else `build` writes — MCP config and skills — is derived from your config and **is** regenerated on every run.
237
+
238
+ ### `AGENTS.md` and `CLAUDE.md` — derived, but your edits are safe
239
+
240
+ `AGENTS.md` (all providers) and `CLAUDE.md` (Claude Code only) are generated from your config, so a config change should flow into them — but they are also files people hand-edit. `build` reconciles both concerns with a **provenance marker**: every generated file ends with a comment holding a checksum of the exact bytes we wrote, e.g.
241
+
242
+ ```
243
+ <!-- ahk:generated 3f7a…c1 -->
244
+ ```
245
+
246
+ On each build the marker lets `build` tell its own untouched output apart from a human edit, byte-for-byte:
247
+
248
+ - **Untouched since we wrote it, config changed** → the file is regenerated so your config propagates. No prompt, no backup — it was provably our own output.
249
+ - **Already up to date** → no-op.
250
+ - **You edited the body** (the checksum no longer matches) **or the file has no marker** (written by an older version, e.g. a `CLAUDE.md` you customized before upgrading) → **left untouched**, and `build` prints a loud notice naming the file and telling you to run `--force` if you actually want it regenerated.
251
+
252
+ Because the checksum is over the exact bytes, *any* change — even one space — counts as an edit and is preserved. The behavior is identical with or without a terminal (there is no prompt), so it is safe in scripts and CI. Leave the marker comment in place; deleting it just makes `build` treat the file as hand-edited (preserve it) on the next run.
253
+
254
+ > **If you use OpenCode or Codex CLI, your agent files may be out of date right now.** Those two providers have always preserved existing agent files on build, which means they have never picked up template improvements shipped in newer versions of this package. Claude Code, by contrast, used to overwrite them on every build — that inconsistency was a bug, and it is now fixed in favour of preserving your edits. To pull in the current templates, run `ahk build --force` (read the warning below first).
255
+
256
+ ### `--force`
257
+
258
+ Because `build` no longer overwrites agent files on any provider, `--force` is the **only** way to regenerate them from the packaged templates. It is destructive:
259
+
260
+ ```bash
261
+ ahk build --force
262
+ ```
263
+
264
+ - **It discards your customizations.** Every agent file is rewritten from the template. Prompt edits, `model:` lines, and restriction tweaks are all lost.
265
+ - **It backs up first.** Before overwriting anything, the current content of every affected file is copied under `.harness/backups/` — agent files to `agents-<timestamp>/`, hand-edited `AGENTS.md`/`CLAUDE.md` to `derived-<timestamp>/`. If that backup cannot be written, the command aborts and **no file is modified** — the same fail-safe as [`ahk migrate storage --force`](#storage-migration).
266
+ - **It names what it touched.** The command prints every file it overwrote and the backup location, so you can diff or restore.
267
+
268
+ `--force` also regenerates a hand-edited `AGENTS.md` or `CLAUDE.md` (backing it up first) — the only time you need it for those files, since an *unedited* one already re-generates on its own when config changes.
269
+
270
+ `--watch` never forces, even if you pass both flags: an automatic rebuild triggered by a file change must not destroy your edits in the background.
271
+
272
+ `--sync` used to rewrite the `tools:` frontmatter of agent files so it matched a canonical allowlist. Agent files no longer declare an allowlist at all — they inherit every tool and declare only restrictions — so there is nothing left to synchronise. Use `ahk build --force` to regenerate agent files.
273
+
214
274
  ---
215
275
 
216
276
  ### `ahk dashboard`
@@ -223,7 +283,11 @@ ahk dashboard --port 8080 # custom port
223
283
  ahk dashboard --no-open # start server without opening browser
224
284
  ```
225
285
 
226
- If the requested port (default `4242`) is already in use, `ahk dashboard` automatically tries up to 10 sequential ports (e.g. `4242 4243 4251`). The actual port opened is printed to the console. If all 10 ports are exhausted, the command exits with a clear error message showing which port range was attempted.
286
+ `--port` must be an integer between `1` and `65535`; an invalid value (e.g. `ahk dashboard --port abc` or `--port 99999`) is rejected at the CLI with a clear error naming the flag and the valid range, rather than silently failing.
287
+
288
+ If the requested port (default `4242`) is already in use, `ahk dashboard` automatically tries up to 10 sequential ports (e.g. `4242 → 4243 → … → 4251`), printing `Port 4242 in use, using 4243`. The actual port opened is printed to the console. If all 10 ports are exhausted, the command exits with a clear error message showing which port range was attempted.
289
+
290
+ Port availability is checked against the same network interface the dashboard actually binds to, so an already-running `ahk dashboard` — or any other server holding that port — is reliably detected. The success banner is printed only after the server has genuinely bound; if the bind fails (for example, the port was claimed by another process in the moment between the check and the bind), the command reports an actionable error instead of crashing.
227
291
 
228
292
  The dashboard includes:
229
293
 
@@ -263,7 +327,7 @@ ahk health
263
327
 
264
328
  ### `ahk doctor`
265
329
 
266
- Checks that the installed lib version, agent files, and harness skills are all in sync.
330
+ Checks the installed lib version, that every agent file is present, and that the harness skills are in sync.
267
331
 
268
332
  ```bash
269
333
  ahk doctor
@@ -272,7 +336,7 @@ ahk doctor
272
336
  Reports three categories:
273
337
 
274
338
  - **lib version** — compares installed version against the latest on npm. Shows `[✓]` if up to date, `[!]` if an update is available, or `[~]` if the registry could not be reached.
275
- - **agent files** — reads each agent file on disk and compares against what `ahk build` would generate. Reports `[!]` with the file name if outdated.
339
+ - **agent files** — checks only that a definition file exists for every role. Reports `[!]` with the file name if one is missing. The contents are never read, so **editing an agent file by hand is a fully supported state and is never reported** — customise the body, the description, or the restrictions freely and `ahk doctor` stays green.
276
340
  - **harness skills** — checks that `ahk-ask`, `ahk-consultant`, `ahk-triage`, and `ahk-review` skills exist and match the bundled source. Reports `[!]` if missing or outdated.
277
341
 
278
342
  Run `ahk build` to fix any reported issues.
@@ -302,6 +366,8 @@ ahk serve
302
366
  ahk serve --port 3456 # store a port hint in config (stdio transport only)
303
367
  ```
304
368
 
369
+ `--port` must be an integer between `1` and `65535`; an invalid value is rejected at the CLI with a clear error.
370
+
305
371
  ---
306
372
 
307
373
  ### `ahk task add`
@@ -427,7 +493,7 @@ ahk export --sql --output dump.sql # SQL dump to file
427
493
 
428
494
  ```
429
495
  your-project/
430
- ├── agent-harness-kit.config.{ts|mjs|cjs}
496
+ ├── agent-harness-kit.config.{json|ts|mjs|cjs}
431
497
  ├── AGENTS.md
432
498
  ├── CLAUDE.md
433
499
  ├── health.sh
@@ -450,7 +516,7 @@ your-project/
450
516
 
451
517
  ```
452
518
  your-project/
453
- ├── agent-harness-kit.config.{ts|mjs|cjs}
519
+ ├── agent-harness-kit.config.{json|ts|mjs|cjs}
454
520
  ├── AGENTS.md
455
521
  ├── health.sh
456
522
  ├── opencode.json ← MCP server + default_agent + compaction config
@@ -467,7 +533,7 @@ your-project/
467
533
 
468
534
  ```
469
535
  your-project/
470
- ├── agent-harness-kit.config.{ts|mjs|cjs}
536
+ ├── agent-harness-kit.config.{json|ts|mjs|cjs}
471
537
  ├── AGENTS.md
472
538
  ├── health.sh
473
539
  ├── .harness/
@@ -485,19 +551,19 @@ your-project/
485
551
 
486
552
  | File | Purpose | Edit it? |
487
553
  | ----------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
488
- | `agent-harness-kit.config.ts` | Defines project metadata, provider, storage paths, MCP port | Yes — it's yours |
554
+ | `agent-harness-kit.config.{json\|ts\|mjs\|cjs}` | Defines project metadata, provider, storage paths, MCP port. JSON when the package isn't installed locally, otherwise `.ts`/`.mjs`/`.cjs` | Yes — it's yours |
489
555
  | `AGENTS.md` | Navigation map agents read first. Regenerated by `ahk build` | No — changes will be overwritten |
490
556
  | `health.sh` | Shell script agents run before starting work. Must exit 0 | **Yes — implement your checks here** |
491
557
  | `.harness/feature_list.json` | Task backlog in JSON. Humans edit this, `ahk sync` loads it into SQLite | Yes — add tasks here |
492
558
  | `.harness/harness.db` | SQLite database (local scope only). Source of truth for tasks, actions, sections | No — managed by the harness |
493
559
  | `.harness/current.md` | Auto-generated session snapshot for agents without MCP access (local scope only) | No — regenerated automatically |
494
560
  | `.harness/storage-state.json` | Always project-local. Records the REAL current storage state (`scope`, `projectId`, `dbType`, `migratedAt`) — used by migration tooling | No — managed by the harness |
495
- | `.claude/agents/*.md` | Agent role definitions (Claude Code). Created once, never overwritten | **Yes — customize agent behavior** |
561
+ | `.claude/agents/*.md` | Agent role definitions (Claude Code). Created once, never overwritten (`ahk build --force` regenerates) | **Yes — customize agent behavior** |
496
562
  | `.claude/mcp.json` | MCP server registration for Claude Code. Merged by `ahk build` | Yes, carefully — don't remove the `agent-harness-kit` entry |
497
563
  | `.claude/settings.json` | Sets `agent: "lead"` so lead runs as the default session agent. Merged by `ahk build` | Yes, carefully |
498
- | `.opencode/agents/*.md` | Agent role definitions (OpenCode). Created once, never overwritten | **Yes — customize agent behavior** |
564
+ | `.opencode/agents/*.md` | Agent role definitions (OpenCode). Created once, never overwritten (`ahk build --force` regenerates) | **Yes — customize agent behavior** |
499
565
  | `opencode.json` | MCP server + `default_agent` + compaction config for OpenCode. Merged by `ahk build` | Yes, carefully |
500
- | `.codex/agents/*.toml` | Agent role definitions (Codex CLI). Created once, never overwritten | **Yes — customize agent behavior** |
566
+ | `.codex/agents/*.toml` | Agent role definitions (Codex CLI). Created once, never overwritten (`ahk build --force` regenerates) | **Yes — customize agent behavior** |
501
567
  | `.codex/config.toml` | MCP server registration for Codex CLI. Merged by `ahk build` | Yes, carefully |
502
568
 
503
569
  ---
@@ -510,9 +576,9 @@ The `tasks` table includes an `updated_at` timestamp column, set on creation and
510
576
 
511
577
  ## What you can customize
512
578
 
513
- ### `agent-harness-kit.config.ts`
579
+ ### `agent-harness-kit.config.{json|ts|mjs|cjs}`
514
580
 
515
- Everything in the config file is yours to change:
581
+ Everything in the config file is yours to change. The example below is the TypeScript form, generated when the package is installed locally in your project:
516
582
 
517
583
  ```ts
518
584
  import type { HarnessConfig } from '@cardor/agent-harness-kit'
@@ -526,14 +592,8 @@ const config: HarnessConfig = {
526
592
 
527
593
  provider: 'claude-code', // 'claude-code' | 'opencode' | 'codex-cli'
528
594
 
529
- agents: {
530
- lead: { instructionsPath: null, model: 'sonnet' }, // optional per-agent model override
531
- explorer: { instructionsPath: null, allowedPaths: ['./docs', './src'], model: 'haiku' },
532
- builder: { instructionsPath: null, writablePaths: ['./src', './tests'] },
533
- reviewer: { instructionsPath: null },
534
- consultant: { instructionsPath: null, model: 'haiku' },
535
- custom: [], // define extra agents here
536
- },
595
+ // There is no `agents` key. Per-agent settings live in the generated agent
596
+ // file itself, which is yours to edit see "Agent files are yours" below.
537
597
 
538
598
  // ── Database ──────────────────────────────────────────────────────────────
539
599
  // SQLite (default — zero native deps, Node 22+ or Bun). Note: `database`
@@ -581,6 +641,41 @@ const config: HarnessConfig = {
581
641
  export default config
582
642
  ```
583
643
 
644
+ **The JSON form** (`agent-harness-kit.config.json`, generated when the package is *not* installed locally) holds exactly the same values, minus the comments and the type annotation:
645
+
646
+ ```json
647
+ {
648
+ "project": {
649
+ "name": "My App",
650
+ "description": "What this project does",
651
+ "docsPath": "./docs"
652
+ },
653
+ "provider": "claude-code",
654
+ "database": { "type": "sqlite" },
655
+ "storage": {
656
+ "dir": ".harness",
657
+ "tasks": { "adapter": "local" },
658
+ "sections": {
659
+ "toolsUsed": true,
660
+ "filesModified": true,
661
+ "result": true,
662
+ "blockers": true,
663
+ "nextSteps": false
664
+ },
665
+ "markdownFallback": { "enabled": true, "path": ".harness/current.md" },
666
+ "scope": "local",
667
+ "projectId": "5f2c..."
668
+ },
669
+ "health": { "scriptPath": "./health.sh", "required": true },
670
+ "tools": {
671
+ "mcp": { "enabled": true, "port": 3742 },
672
+ "scripts": { "enabled": true, "outputDir": "./.harness/scripts" }
673
+ }
674
+ }
675
+ ```
676
+
677
+ Every option documented below applies to both forms — the same keys, the same defaults, the same runtime normalization. The only difference is that the JSON form has no type checking or autocompletion behind it, since there is no package to resolve them from. To switch a JSON config to TypeScript, install the package locally (`npm install --save-dev @cardor/agent-harness-kit`) and rename the file to `agent-harness-kit.config.ts`, wrapping the object as shown above. `ahk` will not convert it for you — an existing config always keeps its format.
678
+
584
679
  **`scope: 'global'`** — DB and current.md live under `~/.harness/dbs/<projectId>/`, outside the project tree. Under this scope, `sqlitePath` and `markdownFallback.path` don't exist on the type at all (a type error, not just a no-op) — there's nothing local to declare a path for:
585
680
 
586
681
  ```ts
@@ -620,32 +715,56 @@ echo "All checks passed."
620
715
 
621
716
  ### Agent definition files
622
717
 
623
- Created by `ahk init` (which preserves existing files) and **regenerated by `ahk build`** from the latest templates. If you customise an agent file, re-running `ahk build` will overwrite your editskeep customisations in source control.
718
+ **These files belong to you.** `ahk init` and `ahk build` both create them when missing and **never modify them once they exist**. Customise them freely: rewrite the role prompt, add a `model:` line, adjust the restriction fields. Nothing in the normal workflow will revert your edits, and `ahk doctor` never reports a hand-edited file as drift it checks existence only.
624
719
 
625
- **Claude Code** (`.claude/agents/*.md`) and **OpenCode** (`.opencode/agents/*.md`) use Markdown with YAML frontmatter:
720
+ The trade-off is that you do not automatically receive template improvements from new versions of this package. `ahk build --force` is the only way to pull them in, and it **discards your customisations** (writing a backup to `.harness/backups/agents-<timestamp>/` first). Keep customisations in source control so you can diff against a forced regeneration.
721
+
722
+ There is no `agents` key in `agent-harness-kit.config.ts`. Per-agent settings live here, in the file itself — set the model on the `model:` frontmatter line (`model = "..."` for Codex CLI) and write role instructions in the body. When no model line is present, the provider applies its own default.
723
+
724
+ Agent files do not declare a tool allowlist. Each agent inherits the full tool set of the session — including `Task` and every MCP tool — and the file declares only what the role is *not* allowed to do. Each provider expresses that restriction in its own syntax.
725
+
726
+ **Claude Code** (`.claude/agents/*.md`) uses a `disallowedTools` YAML block sequence:
626
727
 
627
728
  ```markdown
628
729
  ---
629
- name: builder
630
- description: Builder agent — implements the plan produced by explorer and lead
631
- tools:
632
- read: true
633
- write: true
634
- edit: true
635
- bash: true
636
- permissionMode: acceptEdits
730
+ name: explorer
731
+ description: Explorer agent — reads and maps the codebase, never writes
732
+ disallowedTools:
733
+ - Write
734
+ - Edit
637
735
  ---
638
736
 
639
- # Builder Agent
737
+ # Explorer Agent
640
738
 
641
- You are the builder agent for MyApp. Follow these rules:
739
+ You are the explorer agent for MyApp. Follow these rules:
642
740
 
643
- - All API endpoints must be defined in `src/routes/`
644
- - Never modify `src/core/` without lead approval
645
- - Run `npm test` after every change and fix failures before completing
646
- - Use the existing error handling pattern from `src/lib/errors.ts`
741
+ - Map the modules relevant to the task and report where each concern lives
742
+ - Never modify files record every file you read
743
+ - Prefer the existing patterns in `src/lib/` when describing conventions
647
744
  ```
648
745
 
746
+ **OpenCode** (`.opencode/agents/*.md`) uses a `permission` mapping instead. OpenCode has no separate `write` permission — its `edit` key is defined as "file modifications including write/patch", so a single `edit: deny` covers Write, Edit, and patch:
747
+
748
+ ```markdown
749
+ ---
750
+ name: explorer
751
+ description: Explorer agent — reads and maps the codebase, never writes
752
+ permission:
753
+ edit: deny
754
+ ---
755
+
756
+ # Explorer Agent
757
+
758
+ You are the explorer agent for MyApp. Follow these rules:
759
+
760
+ - Map the modules relevant to the task and report where each concern lives
761
+ - Never modify files — record every file you read
762
+ ```
763
+
764
+ > The legacy OpenCode `tools: { write: false }` dict is deprecated upstream in favour of `permission` and is no longer emitted.
765
+
766
+ For the **builder**, which has no restrictions, the key is omitted entirely — no `disallowedTools` under Claude Code, no `permission` under OpenCode.
767
+
649
768
  **Codex CLI** (`.codex/agents/*.toml`) uses TOML format:
650
769
 
651
770
  ```toml
@@ -667,12 +786,16 @@ You are the builder agent for MyApp. Follow these rules:
667
786
  """
668
787
  ```
669
788
 
670
- The `sandbox_mode` field controls Codex's filesystem permissions per agent: `"read-only"` for lead, explorer, and reviewer; `"workspace-write"` for builder. The `permissionMode` field in Claude Code agent files enforces the same constraints at the session level (`plan` for read-only roles, `acceptEdits` for builder).
789
+ Codex CLI has no per-agent tool denylist, so `sandbox_mode` is the only real mechanism: `"read-only"` for lead, explorer, consultant, and reviewer; `"workspace-write"` for builder. Because Codex keeps the write tools *visible* to the model even under a read-only sandbox, the restriction is additionally restated in prose inside `developer_instructions` without it the model burns turns on calls the sandbox will reject.
790
+
791
+ The equivalent constraint under Claude Code is expressed as `disallowedTools: [Write, Edit]`, and under OpenCode as `permission: { edit: deny }`.
671
792
 
672
793
  ### `.harness/feature_list.json`
673
794
 
674
795
  The human-editable task backlog. Add tasks here, then run `ahk sync` to load them into SQLite.
675
796
 
797
+ `ahk init` **never clobbers** this file: an existing backlog is merged into SQLite (deduplicated by slug) alongside any first task you add during init, then re-emitted — so a hand-written backlog is preserved. On a fresh project the file is created (empty `[]` if you skip the first-task prompt). If the file contains invalid JSON, init leaves it untouched and warns you to fix it and run `ahk sync`.
798
+
676
799
  ```json
677
800
  [
678
801
  {
@@ -708,13 +831,13 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
708
831
  | `actions.write` | `actionId, sectionType, content` | Record a text section: `result \| tools_used \| blockers \| next_steps`. Does **not** populate the Files dashboard — use `actions.record_file` for that |
709
832
  | `actions.complete` | `actionId, summary` | Close an action with a one-line summary |
710
833
  | `actions.get` | `taskId` | Full action history for a task (all agents, all sections) |
711
- | `actions.record_file` | `actionId, filePath, operation, notes?` | Register a file touch. The **only** way to populate the Files dashboard. `operation`: `read \| created \| modified \| deleted` |
712
- | `actions.record_tool` | `actionId, toolName, argsJson?, resultSummary?` | Register a tool call. The **only** way to populate the Tools dashboard |
834
+ | `actions.record_file` | `actionId, files: [{ filePath, operation, notes? }, ...]` | Batch-register one or more file touches, atomically. The **only** way to populate the Files dashboard. `operation`: `read \| created \| modified \| deleted`. Batch-only — `files` requires at least one entry; a single touch is still a one-element array |
835
+ | `actions.record_tool` | `actionId, calls: [{ toolName, argsJson?, resultSummary? }, ...]` | Batch-register one or more tool calls, atomically. The **only** way to populate the Tools dashboard. Batch-only — `calls` requires at least one entry; a single call is still a one-element array |
713
836
  | `docs.search` | `query` | Search the `docsPath` folder for content matching the query |
714
837
  | `tasks.acceptance_get` | `taskId` | Returns all acceptance criteria for a task with their `id`, `task_id`, `criterion` text, and `met` status. Use the returned `id` values with `tasks.acceptance.update` |
715
838
  | `deps.snapshot` | _(none)_ | Snapshot current `package.json` dependencies to `.harness/deps-lock.json` |
716
839
  | `deps.check` | _(none)_ | Compare current `package.json` against `.harness/deps-lock.json`. Returns `{ significant, added, removed, majorBumps, advisory }` |
717
- | `ahk.doctor` | _(none)_ | Check lib version, agent files, and harness skills sync status. Returns `{ lib: { current, latest, outdated }, agents: { missing, outdated, ok }, skills: { missing, outdated, ok } }`. The `lib` version lookup (npm registry check) is cached in-memory with a 5-minute TTL — repeated calls within that window do not hit the network again. |
840
+ | `ahk.doctor` | _(none)_ | Check lib version, agent file presence, and harness skills sync status. Returns `{ lib: { current, latest, outdated }, agents: { missing, ok }, skills: { missing, outdated, ok } }`. Agents are existence-checked only, so there is no `outdated` bucket for them; `skills` still has all three. The `lib` version lookup (npm registry check) is cached in-memory with a 5-minute TTL — repeated calls within that window do not hit the network again. |
718
841
 
719
842
  ---
720
843
 
@@ -725,12 +848,18 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
725
848
  | **lead** | Decomposes the task into a plan, assigns sub-agents. Does not write code or read source files. |
726
849
  | **explorer** | Reads and maps the codebase. Never writes files. Records every file read. |
727
850
  | **consultant** | Provides structured technical advisory after explorer. Runs conditionally. Never writes code. Writes advisory to harness via actions.write. |
728
- | **builder** | Implements the plan. Only writes to `writablePaths`. Records every file modified. |
851
+ | **builder** | Implements the plan. The only role that writes its write tools are enabled where every other role's are disabled. Records every file modified. |
729
852
  | **reviewer** | Verifies all acceptance criteria are met. Approves or blocks. Runs health check before approving. |
730
853
 
854
+ > **Scope note.** What a role may not do is enforced **per tool, not per path**. There is no per-agent path scoping and it is not configurable: the `allowedPaths` / `writablePaths` fields were removed because they were only interpolated into prompt text and no provider ever enforced them — they looked like a security control without being one. The real restriction lives in `src/core/materializer/agent-restrictions.ts`, which each provider translates natively: `disallowedTools` in Claude Code, `permission.edit` in OpenCode, `sandbox_mode` in Codex CLI. If a config still declares the removed fields they are stripped at load time with a warning.
855
+ >
856
+ > **The entire `agents` config key has since been removed too**, for the same underlying reason: everything left in it was either dead or better expressed elsewhere. `instructionsPath`, `context` and `custom` were written by the generator and never read by anything; `model` was the only field with an effect, and it now belongs in the agent file's frontmatter alongside the role prompt, since that file is user-owned. A config that still declares `agents` loads normally — the key is ignored, with one aggregated warning pointing at the agent file.
857
+ >
858
+ > **Breaking change for library consumers (compile time).** The `AgentConfig`, `AgentsConfig` and `CustomAgentConfig` types are no longer exported from the package, and `HarnessConfig` no longer has an `agents` property. If you import those types, remove the import; if you construct a `HarnessConfig` in TypeScript, drop the `agents` property. This is separate from the runtime tolerance above: existing config *files* keep loading, but code that references the removed types will not compile. The `AgentName` type is unrelated and unaffected.
859
+
731
860
  ### MCP tool permissions by role
732
861
 
733
- Each agent role has a scoped set of MCP tools enforced through the agent definition files.
862
+ > **Scope note.** This table describes the *intended* division of labour between roles, not a restriction enforced by the agent files. Agent definitions no longer declare a tool allowlist, so every role inherits **all** MCP tools. The per-role `MCP_CLAUDE_PERMISSIONS_*` arrays still exist, but they are only unioned together to populate the allow list in `.claude/settings.local.json` — they are not applied per agent. Treat the table as the convention each role's prompt asks it to follow.
734
863
 
735
864
  | Tool | lead | explorer | consultant | builder | reviewer |
736
865
  | ----------------------------- | :--: | :------: | :--------: | :-----: | :------: |
@@ -754,7 +883,7 @@ Each agent role has a scoped set of MCP tools enforced through the agent definit
754
883
  **lead** and **builder** have identical access, both excluding `tasks.acceptance.update`.
755
884
  **consultant** is advisory-only — reads code, writes to harness, and can call deps tools. Never modifies the codebase.
756
885
 
757
- `permissions.check` compares each `.claude/agents/*.md` tool list against the canonical constants in the package. Returns `{ in_sync: bool, agents: { lead, explorer, consultant, builder, reviewer } }` with per-agent `missing` and `extra` arrays. Run `ahk build --sync` to fix any drift.
886
+ `permissions.check` verifies only that a `.claude/agents/*.md` definition file **exists** for every role. Returns `{ in_sync: bool, agents: { lead, explorer, consultant, builder, reviewer } }` where each agent is `{ ok: true }` or `{ ok: false, reason: 'missing_file' }`. Agent file contents are never inspected — they are meant to be customised freely — so this never reports drift, only absence. Run `ahk build` to restore a missing file.
758
887
 
759
888
  ---
760
889
 
@@ -762,7 +891,7 @@ Each agent role has a scoped set of MCP tools enforced through the agent definit
762
891
 
763
892
  | File | Commit? |
764
893
  | ----------------------------- | ------------------- |
765
- | `agent-harness-kit.config.ts` | Yes |
894
+ | `agent-harness-kit.config.{json\|ts\|mjs\|cjs}` | Yes |
766
895
  | `AGENTS.md` | Yes |
767
896
  | `CLAUDE.md` | Yes |
768
897
  | `health.sh` | Yes |
@@ -5,11 +5,6 @@ description: >
5
5
  and analyzed by explorer. The builder writes, edits, and creates files based on the plan
6
6
  and the explorer's analysis. Invoke only after the explorer has completed its action.
7
7
  Never invoke without a lead plan and explorer analysis available in actions.get(taskId).
8
- tools:
9
- - Read
10
- - Write
11
- - Edit
12
- - Bash
13
8
  ---
14
9
 
15
10
  # Builder Agent — {{projectName}}
@@ -24,46 +19,55 @@ You are the **builder agent** for `{{projectName}}`. Your job is to implement
24
19
  - Run tests after implementing to catch regressions early
25
20
  - Surface blockers clearly rather than guessing through them
26
21
 
27
- ## Writable paths
22
+ ## Scope
28
23
 
29
- You may only write to: `{{writablePaths}}`
24
+ You may write anywhere inside the project.
30
25
 
31
- Do not modify files outside these paths. If the task requires it, record a blocker and stop.
26
+ You are the only role that writes. Stay inside the project root never edit files
27
+ outside it. Breadth of access is not licence to widen scope: implement what the plan
28
+ asks and nothing more. If a change genuinely belongs outside the project root, record
29
+ a blocker and stop.
32
30
 
33
31
  ---
34
32
 
35
33
  ## !! MANDATORY TRACKING — DO THIS FOR EVERY ACTION, NO EXCEPTIONS !!
36
34
 
37
- These three calls are **not optional**. The dashboard cannot display what you do not report. Missing any of them is a failure of your role.
35
+ These calls are **not optional**. The dashboard cannot display what you do not report. Missing them is a failure of your role.
36
+
37
+ Both `actions.record_tool` and `actions.record_file` are **batch-only** — each takes an array of entries, never a single bespoke call. Accumulate as you work and flush periodically (every few tool calls, or at a natural checkpoint/phase boundary) rather than round-tripping once per individual tool use. Even a single entry must still go through the array shape — a one-element array, never a bespoke single-call form, since that form no longer exists.
38
38
 
39
39
  ### 1. Log every tool call you make
40
40
 
41
- After **each** tool invocation (Read, Edit, Write, Bash), call **both**:
41
+ Accumulate each tool invocation (Read, Edit, Write, Bash) as you go, then flush with:
42
42
 
43
43
  ```
44
- actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
44
+ actions.record_tool(actionId, calls: [
45
+ { toolName: '<ToolName>', argsJson: '<args-summary>', resultSummary: '<why>' },
46
+ ...
47
+ ])
45
48
  ```
46
49
 
47
- Examples:
48
- - `actions.record_tool(actionId, 'Read', 'src/auth/middleware.ts', 'understand existing JWT pattern')`
49
- - `actions.record_tool(actionId, 'Bash', 'npm test --testPathPattern=auth', 'verify auth tests pass')`
50
- - `actions.record_tool(actionId, 'Edit', 'src/auth/middleware.ts:45-78', 'add refresh token validation')`
50
+ Example flush after a few calls:
51
+ - `actions.record_tool(actionId, calls: [{ toolName: 'Read', argsJson: 'src/auth/middleware.ts', resultSummary: 'understand existing JWT pattern' }, { toolName: 'Edit', argsJson: 'src/auth/middleware.ts:45-78', resultSummary: 'add refresh token validation' }, { toolName: 'Bash', argsJson: 'npm test --testPathPattern=auth', resultSummary: 'verify auth tests pass' }])`
51
52
 
52
53
  ### 2. Log every file you touch
53
54
 
54
- After **each** file modification (Edit, Write), call:
55
+ Accumulate each file modification (Edit, Write) as you go, then flush with:
55
56
 
56
57
  ```
57
- actions.record_file(actionId, '<file-path>', '<operation>', '<what changed and why>')
58
+ actions.record_file(actionId, files: [
59
+ { filePath: '<file-path>', operation: '<operation>', notes: '<what changed and why>' },
60
+ ...
61
+ ])
58
62
  ```
59
63
 
60
64
  Operations: `created` | `modified` | `deleted`
61
65
 
62
- Example: `actions.record_file(actionId, 'src/auth/middleware.ts', 'modified', 'added refresh token expiry check in validateToken()')`
66
+ Example: `actions.record_file(actionId, files: [{ filePath: 'src/auth/middleware.ts', operation: 'modified', notes: 'added refresh token expiry check in validateToken()' }])`
63
67
 
64
68
  ### 3. Do not complete your action without both logs being up to date
65
69
 
66
- If you touched 5 files and made 12 tool calls, there must be 5 `actions.record_file` calls and 12 `actions.record_tool` calls before you call `actions.complete`.
70
+ If you touched 5 files and made 12 tool calls across the session, every one of those must appear as an entry inside some `actions.record_file`/`actions.record_tool` batch call before you call `actions.complete` — it doesn't need to be 5 and 12 separate MCP round-trips, but the union of all your batched arrays must account for all 5 files and all 12 tool calls.
67
71
 
68
72
  ---
69
73
 
@@ -85,7 +89,7 @@ actions.start(taskId, 'builder') → save the returned actionId
85
89
 
86
90
  ### 3. Implement in small, verifiable steps
87
91
 
88
- Work through the plan item by item. Log each tool call and each file touched as described in the **MANDATORY TRACKING** section above do it as you go, not at the end.
92
+ Work through the plan item by item. Accumulate each tool call and each file touched as described in the **MANDATORY TRACKING** section above, and flush in batches as you go — do not wait until the very end of the session to record everything at once.
89
93
 
90
94
  ### 4. Follow existing patterns
91
95
 
@@ -168,9 +172,9 @@ Before writing a commit message, detect whether the repo already enforces a comm
168
172
  ## Hard rules
169
173
 
170
174
  - **Read the plan and analysis first.** Never implement cold.
171
- - **Only write to `{{writablePaths}}`.** No exceptions.
172
- - **Log every file you touch.** Call `actions.record_file(actionId, path, operation, notes)` after each Edit/Write.
173
- - **Log every tool call.** Call `actions.record_tool(actionId, toolName, args, summary)` after each Read, Edit, Write, Bash invocation.
175
+ - **Stay inside the project.** Never write outside the project root.
176
+ - **Log every file you touch.** Accumulate entries and flush via `actions.record_file(actionId, files: [...])` periodically as you Edit/Write — batch-only, even one file goes through as a one-element array.
177
+ - **Log every tool call.** Accumulate entries and flush via `actions.record_tool(actionId, calls: [...])` periodically as you Read, Edit, Write, Bash — batch-only, even one call goes through as a one-element array.
174
178
  - **Leave tests green.** If tests fail after your changes, fix them before completing.
175
179
  - **Do not refactor beyond the task scope.** Implement what was asked, nothing more.
176
180
  - **If blocked, say so.** Do not invent workarounds for unclear requirements.
@@ -4,9 +4,6 @@ description: >
4
4
  Technical advisor agent for {{projectName}}. Runs after the explorer and before the builder.
5
5
  Provides structured advisory — patterns, best practices, warnings, and risks — written
6
6
  directly to the harness so the builder can read it via actions.get. Never writes code.
7
- tools:
8
- - Read
9
- - Bash
10
7
  ---
11
8
 
12
9
  # Consultant Agent — {{projectName}}