@cardor/agent-harness-kit 1.11.0 → 2.1.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
@@ -88,8 +88,10 @@ ahk init
88
88
  └── creates config, agent definitions, task backlog, health check
89
89
 
90
90
  AI tool opens your project
91
- └── reads .claude/mcp.json, opencode.json, or .codex/config.toml
92
- └── spawns: npx ahk serve (stdio MCP server)
91
+ └── reads .claude/mcp.json, opencode.json, .codex/config.toml, or .grok/config.toml
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
@@ -109,7 +111,9 @@ Everything is stored locally in a SQLite database (`.harness/harness.db`). No cl
109
111
 
110
112
  ## Features
111
113
 
112
- - **Provider-agnostic** — works with Claude Code, OpenCode, Codex CLI, or any MCP-compatible AI tool. Switch providers without losing your task history or reconfiguring your workflow.
114
+ - **Provider-agnostic** — works with Claude Code, OpenCode, Codex CLI, Grok Build, or any MCP-compatible AI tool. Switch providers without losing your task history or reconfiguring your workflow.
115
+
116
+ > **Note:** "Grok Build" here refers to xAI's official Grok Build CLI (`provider: 'grok-cli'`) — it is unrelated to the unofficial, community-maintained `grok-cli`/`grok-dev` npm packages.
113
117
  - **Structured 5-agent workflow** — Lead, Explorer, Consultant, Builder, and Reviewer each have defined responsibilities and can only act within their role.
114
118
  - **Atomic task claiming** — agents use `tasks.claim()` which uses a SQLite transaction to prevent two agents from picking up the same task at the same time.
115
119
  - **Full audit trail** — every action, file touched, tool used, and section written is stored in SQLite and queryable.
@@ -117,7 +121,7 @@ Everything is stored locally in a SQLite database (`.harness/harness.db`). No cl
117
121
  - **Markdown fallback** — `current.md` is always regenerated so agents can understand the session state even without the MCP server.
118
122
  - **Docs search** — agents can call `docs.search(query)` to find relevant content in your project's docs folder before writing code.
119
123
  - **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` does too: it 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).
124
+ - **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
125
  - **Global installation** — `ahk init` can scaffold the harness into your home directory (`~/.claude` or `~/.config/opencode`) to share it across all projects.
122
126
  - **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
127
 
@@ -162,7 +166,7 @@ npx ahk init
162
166
 
163
167
  ## MCP command per package manager
164
168
 
165
- `ahk init` and `ahk build` detect which package manager your project uses and generate the MCP server launch command (`.mcp.json`, `opencode.json`, or `.codex/config.toml`) accordingly, instead of hardcoding `npx`:
169
+ `ahk init` and `ahk build` detect which package manager your project uses and generate the MCP server launch command (`.mcp.json`, `opencode.json`, `.codex/config.toml`, or `.grok/config.toml`) accordingly, instead of hardcoding `npx`:
166
170
 
167
171
  | Package manager | Detected via | Generated command |
168
172
  | -------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------- |
@@ -171,10 +175,15 @@ npx ahk init
171
175
  | yarn classic (v1) | `packageManager` field (major 1) or `yarn.lock` without `.yarnrc.yml` | `yarn run ahk serve --port <port>` |
172
176
  | yarn berry (v2+, PnP or node-modules) | `packageManager` field (major ≥ 2) or `yarn.lock` + `.yarnrc.yml` | `yarn run ahk serve --port <port>` |
173
177
  | bun | `packageManager` field or `bun.lockb`/`bun.lock` | `bunx --no-install ahk serve --port <port>` |
178
+ | **any — no local install** | `@cardor/agent-harness-kit` is not a dependency of your project | `ahk serve --port <port>` |
174
179
 
175
180
  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.
176
181
 
177
- **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.
182
+ **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.
183
+
184
+ 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.
185
+
186
+ **Existing projects:** if you initialized your project before this change, your `.mcp.json`/`opencode.json`/`.codex/config.toml`/`.grok/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.
178
187
 
179
188
  ---
180
189
 
@@ -184,10 +193,9 @@ Detection order: the `packageManager` field in your `package.json` (e.g. `"packa
184
193
 
185
194
  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.
186
195
 
187
- 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):
196
+ 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.
188
197
 
189
- - Claude Code: pick from `inherit` (default), `haiku`, `sonnet`, `opus`, `fable` per agent.
190
- - 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.
198
+ OpenCode, Codex CLI, and Grok Build are unaffected by this prompt — it never appears for those providers. OpenCode and Grok Build have no closed model enum to prompt against, and Codex's model is still set by hand-editing `model = "..."` in its TOML.
191
199
 
192
200
  **Storage scope** — where the harness DB (and its `current.md` fallback) physically lives:
193
201
 
@@ -204,6 +212,7 @@ ahk init
204
212
  # Skip prompts with flags
205
213
  ahk init --name "my-app" --provider claude-code --docs ./docs --tasks local --storage-scope local
206
214
  ahk init --name "my-app" --provider codex-cli --docs ./docs --tasks local --storage-scope global
215
+ ahk init --name "my-app" --provider grok-cli --docs ./docs --tasks local --storage-scope local
207
216
  ```
208
217
 
209
218
  Run this once per project. If the project is already initialized, the command prints an 'already initialized' message with suggested next-step commands (`ahk build`, `ahk build --sync`, `ahk reset`, `ahk serve`) and exits without overwriting anything.
@@ -225,9 +234,25 @@ ahk build --sync # kept for backwards compatibility — now a no-op on every
225
234
 
226
235
  ### Agent files are yours
227
236
 
228
- `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.
237
+ `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`, `.codex/agents/<role>.toml`, or `.grok/agents/<role>.md`) 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.
238
+
239
+ Everything else `build` writes — MCP config and skills — is derived from your config and **is** regenerated on every run.
240
+
241
+ ### `AGENTS.md` and `CLAUDE.md` — derived, but your edits are safe
242
+
243
+ `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.
229
244
 
230
- Everything else `build` writes — `AGENTS.md`, `CLAUDE.md`, MCP config, skills — is derived from your config and **is** regenerated on every run.
245
+ ```
246
+ <!-- ahk:generated 3f7a…c1 -->
247
+ ```
248
+
249
+ On each build the marker lets `build` tell its own untouched output apart from a human edit, byte-for-byte:
250
+
251
+ - **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.
252
+ - **Already up to date** → no-op.
253
+ - **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.
254
+
255
+ 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.
231
256
 
232
257
  > **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).
233
258
 
@@ -240,9 +265,11 @@ ahk build --force
240
265
  ```
241
266
 
242
267
  - **It discards your customizations.** Every agent file is rewritten from the template. Prompt edits, `model:` lines, and restriction tweaks are all lost.
243
- - **It backs up first.** Before overwriting anything, the current content of every affected file is copied to `.harness/backups/agents-<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).
268
+ - **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).
244
269
  - **It names what it touched.** The command prints every file it overwrote and the backup location, so you can diff or restore.
245
270
 
271
+ `--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.
272
+
246
273
  `--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.
247
274
 
248
275
  `--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.
@@ -259,7 +286,11 @@ ahk dashboard --port 8080 # custom port
259
286
  ahk dashboard --no-open # start server without opening browser
260
287
  ```
261
288
 
262
- 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.
289
+ `--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.
290
+
291
+ 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.
292
+
293
+ 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.
263
294
 
264
295
  The dashboard includes:
265
296
 
@@ -338,6 +369,8 @@ ahk serve
338
369
  ahk serve --port 3456 # store a port hint in config (stdio transport only)
339
370
  ```
340
371
 
372
+ `--port` must be an integer between `1` and `65535`; an invalid value is rejected at the CLI with a clear error.
373
+
341
374
  ---
342
375
 
343
376
  ### `ahk task add`
@@ -386,13 +419,14 @@ ahk reset --force # skip all confirmation prompts
386
419
  ahk reset --provider claude-code # also delete agent files for this provider
387
420
  ahk reset --provider opencode
388
421
  ahk reset --provider codex-cli
422
+ ahk reset --provider grok-cli
389
423
  ```
390
424
 
391
425
  What it can reset:
392
426
 
393
427
  - The SQLite `.db` file (plus WAL and SHM files if present)
394
428
  - `.harness/feature_list.json`
395
- - Agent definition files in `.claude/agents/`, `.opencode/agents/`, or `.codex/agents/`
429
+ - Agent definition files in `.claude/agents/`, `.opencode/agents/`, `.codex/agents/`, or `.grok/agents/`
396
430
 
397
431
  After a reset, run `ahk init` to scaffold a fresh harness.
398
432
 
@@ -410,6 +444,7 @@ Migrates provider-specific files from one AI provider to another. Useful when sw
410
444
  ahk migrate provider --to opencode
411
445
  ahk migrate provider --to claude-code
412
446
  ahk migrate provider --to codex-cli
447
+ ahk migrate provider --to grok-cli
413
448
 
414
449
  # Backward-compatible alias (identical behavior):
415
450
  ahk migrate --to opencode
@@ -517,6 +552,23 @@ your-project/
517
552
  └── default.toml ← overrides Codex's built-in default agent → routes to lead
518
553
  ```
519
554
 
555
+ **Grok Build** (`provider: 'grok-cli'`):
556
+
557
+ ```
558
+ your-project/
559
+ ├── agent-harness-kit.config.{json|ts|mjs|cjs}
560
+ ├── AGENTS.md
561
+ ├── health.sh
562
+ ├── .harness/
563
+ └── .grok/
564
+ ├── config.toml ← MCP server registration
565
+ └── agents/
566
+ ├── lead.md
567
+ ├── explorer.md
568
+ ├── builder.md
569
+ └── reviewer.md
570
+ ```
571
+
520
572
  ### What each file does
521
573
 
522
574
  | File | Purpose | Edit it? |
@@ -535,6 +587,8 @@ your-project/
535
587
  | `opencode.json` | MCP server + `default_agent` + compaction config for OpenCode. Merged by `ahk build` | Yes, carefully |
536
588
  | `.codex/agents/*.toml` | Agent role definitions (Codex CLI). Created once, never overwritten (`ahk build --force` regenerates) | **Yes — customize agent behavior** |
537
589
  | `.codex/config.toml` | MCP server registration for Codex CLI. Merged by `ahk build` | Yes, carefully |
590
+ | `.grok/agents/*.md` | Agent role definitions (Grok Build). Created once, never overwritten (`ahk build --force` regenerates) | **Yes — customize agent behavior** |
591
+ | `.grok/config.toml` | MCP server registration for Grok Build. Merged by `ahk build` | Yes, carefully |
538
592
 
539
593
  ---
540
594
 
@@ -560,7 +614,7 @@ const config: HarnessConfig = {
560
614
  docsPath: './docs', // where agents search for documentation
561
615
  },
562
616
 
563
- provider: 'claude-code', // 'claude-code' | 'opencode' | 'codex-cli'
617
+ provider: 'claude-code', // 'claude-code' | 'opencode' | 'codex-cli' | 'grok-cli'
564
618
 
565
619
  // There is no `agents` key. Per-agent settings live in the generated agent
566
620
  // file itself, which is yours to edit — see "Agent files are yours" below.
@@ -758,12 +812,42 @@ You are the builder agent for MyApp. Follow these rules:
758
812
 
759
813
  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.
760
814
 
761
- The equivalent constraint under Claude Code is expressed as `disallowedTools: [Write, Edit]`, and under OpenCode as `permission: { edit: deny }`.
815
+ **Grok Build** (`.grok/agents/*.md`) uses markdown + YAML frontmatter, like Claude Code and OpenCode — but its `tools:` field is an **allowlist**, the inverse shape of Claude's `disallowedTools`. A restricted role must enumerate every tool it IS allowed to use, since there is no way to say "everything except Write/Edit":
816
+
817
+ ```markdown
818
+ ---
819
+ name: explorer
820
+ description: Explorer agent — reads and maps the codebase, never writes
821
+ tools:
822
+ - Bash
823
+ - Read
824
+ - NotebookRead
825
+ - Grep
826
+ - Glob
827
+ - WebFetch
828
+ - WebSearch
829
+ - search_tool
830
+ - use_tool
831
+ ---
832
+
833
+ # Explorer Agent
834
+
835
+ You are the explorer agent for MyApp. Follow these rules:
836
+
837
+ - Map the modules relevant to the task and report where each concern lives
838
+ - Never modify files — record every file you read
839
+ ```
840
+
841
+ For the builder, `tools:` is omitted entirely, same as every other provider — the agent inherits every tool.
842
+
843
+ The equivalent constraint under Claude Code is expressed as `disallowedTools: [Write, Edit]`, under OpenCode as `permission: { edit: deny }`, and under Grok Build as the `tools:` allowlist shown above.
762
844
 
763
845
  ### `.harness/feature_list.json`
764
846
 
765
847
  The human-editable task backlog. Add tasks here, then run `ahk sync` to load them into SQLite.
766
848
 
849
+ `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`.
850
+
767
851
  ```json
768
852
  [
769
853
  {
@@ -799,8 +883,8 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
799
883
  | `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 |
800
884
  | `actions.complete` | `actionId, summary` | Close an action with a one-line summary |
801
885
  | `actions.get` | `taskId` | Full action history for a task (all agents, all sections) |
802
- | `actions.record_file` | `actionId, filePath, operation, notes?` | Register a file touch. The **only** way to populate the Files dashboard. `operation`: `read \| created \| modified \| deleted` |
803
- | `actions.record_tool` | `actionId, toolName, argsJson?, resultSummary?` | Register a tool call. The **only** way to populate the Tools dashboard |
886
+ | `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 |
887
+ | `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 |
804
888
  | `docs.search` | `query` | Search the `docsPath` folder for content matching the query |
805
889
  | `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` |
806
890
  | `deps.snapshot` | _(none)_ | Snapshot current `package.json` dependencies to `.harness/deps-lock.json` |
@@ -819,7 +903,7 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
819
903
  | **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. |
820
904
  | **reviewer** | Verifies all acceptance criteria are met. Approves or blocks. Runs health check before approving. |
821
905
 
822
- > **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.
906
+ > **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, and a `tools:` allowlist in Grok Build. If a config still declares the removed fields they are stripped at load time with a warning.
823
907
  >
824
908
  > **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.
825
909
  >
@@ -871,6 +955,8 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
871
955
  | `opencode.json` | Yes |
872
956
  | `.codex/agents/*.toml` | Yes |
873
957
  | `.codex/config.toml` | Yes |
958
+ | `.grok/agents/*.md` | Yes |
959
+ | `.grok/config.toml` | Yes |
874
960
  | `.harness/harness.db` | **No** (gitignored, local scope only) |
875
961
  | `.harness/current.md` | **No** (gitignored, local scope only) |
876
962
  | `.harness/storage-state.json` | Yes (metadata, not gitignored — always present regardless of scope) |
@@ -978,6 +1064,7 @@ See [SECURITY.md](./SECURITY.md) for the vulnerability reporting process, suppor
978
1064
  - ✅ **Global installation** — `ahk init` can install the harness to your home directory, shared across projects.
979
1065
  - ✅ **Input validation** — all CLI prompts validate and retry on bad values.
980
1066
  - ✅ **Codex CLI provider** — full support for OpenAI Codex CLI. Generates `.codex/agents/*.toml` files with proper `sandbox_mode` per role and merges `.codex/config.toml` for MCP registration. Overrides the built-in `default` agent so the harness lead runs by default.
1067
+ - ✅ **Grok Build provider** — full support for xAI's Grok Build. Generates `.grok/agents/*.md` files with a `tools:` allowlist per role and merges `.grok/config.toml` for MCP registration.
981
1068
  - **Graphify integration** — connect the harness to Graphify to visualize agent workflows, task dependencies, and action timelines as interactive graphs.
982
1069
  - **Open Telemetry integration** — emit OpenTelemetry spans for all agent actions, file operations, and tool calls.
983
1070
  - **Jira task adapter** — pull tasks directly from Jira instead of maintaining `feature_list.json` manually.
@@ -32,36 +32,42 @@ a blocker and stop.
32
32
 
33
33
  ## !! MANDATORY TRACKING — DO THIS FOR EVERY ACTION, NO EXCEPTIONS !!
34
34
 
35
- 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.
36
38
 
37
39
  ### 1. Log every tool call you make
38
40
 
39
- 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:
40
42
 
41
43
  ```
42
- actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
44
+ actions.record_tool(actionId, calls: [
45
+ { toolName: '<ToolName>', argsJson: '<args-summary>', resultSummary: '<why>' },
46
+ ...
47
+ ])
43
48
  ```
44
49
 
45
- Examples:
46
- - `actions.record_tool(actionId, 'Read', 'src/auth/middleware.ts', 'understand existing JWT pattern')`
47
- - `actions.record_tool(actionId, 'Bash', 'npm test --testPathPattern=auth', 'verify auth tests pass')`
48
- - `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' }])`
49
52
 
50
53
  ### 2. Log every file you touch
51
54
 
52
- After **each** file modification (Edit, Write), call:
55
+ Accumulate each file modification (Edit, Write) as you go, then flush with:
53
56
 
54
57
  ```
55
- 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
+ ])
56
62
  ```
57
63
 
58
64
  Operations: `created` | `modified` | `deleted`
59
65
 
60
- 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()' }])`
61
67
 
62
68
  ### 3. Do not complete your action without both logs being up to date
63
69
 
64
- 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.
65
71
 
66
72
  ---
67
73
 
@@ -83,7 +89,7 @@ actions.start(taskId, 'builder') → save the returned actionId
83
89
 
84
90
  ### 3. Implement in small, verifiable steps
85
91
 
86
- 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.
87
93
 
88
94
  ### 4. Follow existing patterns
89
95
 
@@ -167,8 +173,8 @@ Before writing a commit message, detect whether the repo already enforces a comm
167
173
 
168
174
  - **Read the plan and analysis first.** Never implement cold.
169
175
  - **Stay inside the project.** Never write outside the project root.
170
- - **Log every file you touch.** Call `actions.record_file(actionId, path, operation, notes)` after each Edit/Write.
171
- - **Log every tool call.** Call `actions.record_tool(actionId, toolName, args, summary)` after each Read, Edit, Write, Bash invocation.
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.
172
178
  - **Leave tests green.** If tests fail after your changes, fix them before completing.
173
179
  - **Do not refactor beyond the task scope.** Implement what was asked, nothing more.
174
180
  - **If blocked, say so.** Do not invent workarounds for unclear requirements.
@@ -34,18 +34,21 @@ These calls are **not optional**. The dashboard cannot display what you do not r
34
34
 
35
35
  ### Log every tool call you make
36
36
 
37
- After **each** tool invocation (Read, Bash, grep, docs.search), call:
37
+ `actions.record_tool` is **batch-only** — it takes an array of calls, never a single bespoke call. Accumulate the tool invocations you make (Read, Bash, grep, docs.search) as you go, and flush them periodically — every few calls, or at a natural checkpoint like finishing a file or a research thread — via:
38
38
 
39
39
  ```
40
- actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
40
+ actions.record_tool(actionId, calls: [
41
+ { toolName: '<ToolName>', argsJson: '<args-summary>', resultSummary: '<why>' },
42
+ ...
43
+ ])
41
44
  ```
42
45
 
43
- Examples:
44
- - `actions.record_tool(actionId, 'Read', 'src/auth/middleware.ts', 'find existing JWT pattern')`
45
- - `actions.record_tool(actionId, 'Bash', 'grep -r "refreshToken" src/', 'locate all refresh token usages')`
46
- - `actions.record_tool(actionId, 'docs.search', 'authentication middleware', 'check project docs for auth guidance')`
46
+ Even a single tool call must go through this array shape — a one-element array, never a bespoke single-call form.
47
47
 
48
- **Every single tool call must be logged.** No silent reads. The Tools dashboard is built entirely from these `actions.record_tool` calls.
48
+ Example flush after a few calls:
49
+ - `actions.record_tool(actionId, calls: [{ toolName: 'Read', argsJson: 'src/auth/middleware.ts', resultSummary: 'find existing JWT pattern' }, { toolName: 'Bash', argsJson: 'grep -r "refreshToken" src/', resultSummary: 'locate all refresh token usages' }, { toolName: 'docs.search', argsJson: 'authentication middleware', resultSummary: 'check project docs for auth guidance' }])`
50
+
51
+ **Every tool call must be logged, eventually, in a batch.** No silent reads. The Tools dashboard is built entirely from these `actions.record_tool` calls — accumulate as you work and flush before completing, don't let entries pile up unflushed.
49
52
 
50
53
  ---
51
54
 
@@ -81,7 +84,7 @@ Do NOT read the entire codebase. Be targeted.
81
84
 
82
85
  ### 5. Log every tool call as you make it
83
86
 
84
- Log each invocation as described in the **MANDATORY TRACKING** section above — do it immediately after each tool call, not at the end.
87
+ Accumulate each invocation as described in the **MANDATORY TRACKING** section above and flush periodically in batches don't wait until the very end to record everything at once.
85
88
 
86
89
  ### 6. Produce a structured analysis
87
90
 
@@ -95,18 +95,21 @@ These calls are **not optional**. The dashboard cannot display what you do not r
95
95
 
96
96
  ### Log every tool call you make
97
97
 
98
- After **each** tool invocation (Bash, tasks.get, tasks.claim, actions.get), call:
98
+ `actions.record_tool` is **batch-only** — it takes an array of calls, never a single bespoke call. As you work, accumulate the tool invocations you make (Bash, tasks.get, tasks.claim, actions.get) and flush them periodically — every few calls, or at a natural checkpoint — via:
99
99
 
100
100
  ```
101
- actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
101
+ actions.record_tool(actionId, calls: [
102
+ { toolName: '<ToolName>', argsJson: '<args-summary>', resultSummary: '<why/result>' },
103
+ ...
104
+ ])
102
105
  ```
103
106
 
104
- Examples:
105
- - `actions.record_tool(actionId, 'Bash', 'bash health.sh', 'verify codebase health before making changes')`
106
- - `actions.record_tool(actionId, 'tasks.get', 'pending', 'find next task to claim')`
107
- - `actions.record_tool(actionId, 'actions.get', 'taskId=abc123', 'read action history to resume in-progress task')`
107
+ Even a single tool call must go through this array shape — a one-element array, never a bespoke single-call form.
108
108
 
109
- **Log every call.** This applies from the moment you have an `actionId` (after step 3 below).
109
+ Example flush after a few calls:
110
+ - `actions.record_tool(actionId, calls: [{ toolName: 'Bash', argsJson: 'bash health.sh', resultSummary: 'verify codebase health before making changes' }, { toolName: 'tasks.get', argsJson: 'pending', resultSummary: 'find next task to claim' }, { toolName: 'actions.get', argsJson: 'taskId=123', resultSummary: 'read action history to resume in-progress task' }])`
111
+
112
+ **Log every call, batched.** This applies from the moment you have an `actionId` (after step 3 below) — flush at each phase boundary rather than round-tripping once per individual tool use, and never let calls go unrecorded by the time you complete the action.
110
113
 
111
114
  ---
112
115
 
@@ -27,15 +27,19 @@ These calls are **not optional**. The dashboard cannot display what you do not r
27
27
 
28
28
  ### 1. Log every tool call you make
29
29
 
30
- After **each** tool invocation (Read, Bash), call:
30
+ `actions.record_tool` is **batch-only** — it takes an array of calls, never a single bespoke call. Accumulate each tool invocation (Read, Bash) as you go, and flush periodically — every few calls, or at a natural checkpoint — via:
31
31
 
32
32
  ```
33
- actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
33
+ actions.record_tool(actionId, calls: [
34
+ { toolName: '<ToolName>', argsJson: '<args-summary>', resultSummary: '<why>' },
35
+ ...
36
+ ])
34
37
  ```
35
38
 
36
- Examples:
37
- - `actions.record_tool(actionId, 'Read', 'src/auth/middleware.ts', 'verify refresh token logic matches criterion 2')`
38
- - `actions.record_tool(actionId, 'Bash', 'npm test --testPathPattern=auth', 'confirm all auth tests pass')`
39
+ Even a single tool call must go through this array shape — a one-element array, never a bespoke single-call form.
40
+
41
+ Example flush after a few calls:
42
+ - `actions.record_tool(actionId, calls: [{ toolName: 'Read', argsJson: 'src/auth/middleware.ts', resultSummary: 'verify refresh token logic matches criterion 2' }, { toolName: 'Bash', argsJson: 'npm test --testPathPattern=auth', resultSummary: 'confirm all auth tests pass' }])`
39
43
 
40
44
  ### 2. Mark every acceptance criterion as you verify it
41
45
 
@@ -1,5 +1,4 @@
1
1
  // src/core/db.ts
2
- import { randomUUID } from "crypto";
3
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
3
  import { homedir } from "os";
5
4
  import { dirname, join, resolve } from "path";
@@ -10,10 +9,13 @@ var ActionRepository = class {
10
9
  this.driver = driver;
11
10
  }
12
11
  driver;
13
- async create(id, taskId, agent, now) {
14
- await this.driver.exec(
15
- `INSERT INTO actions (id, task_id, agent, status, created_at) VALUES (?, ?, ?, 'in_progress', ?)`,
16
- [id, taskId, agent, now]
12
+ /** Returns the new autoincrement id mirrors TaskRepository.add(). Since
13
+ * task #73, `actions.id` is a driver-generated INTEGER, not an
14
+ * application-generated UUID, so callers no longer pass an id in. */
15
+ async create(taskId, agent, now) {
16
+ return this.driver.insert(
17
+ `INSERT INTO actions (task_id, agent, status, created_at) VALUES (?, ?, 'in_progress', ?)`,
18
+ [taskId, agent, now]
17
19
  );
18
20
  }
19
21
  async complete(actionId, summary, now) {
@@ -359,7 +361,7 @@ var TaskRepository = class {
359
361
  };
360
362
 
361
363
  // src/core/db.ts
362
- var AUTOINCREMENT_TABLES = ["tasks", "task_acceptance", "action_sections", "action_files", "action_tools"];
364
+ var AUTOINCREMENT_TABLES = ["tasks", "task_acceptance", "actions", "action_sections", "action_files", "action_tools"];
363
365
  var TABLE_INSERT_ORDER = ["tasks", "task_acceptance", "actions", "action_sections", "action_files", "action_tools"];
364
366
  var TABLE_DELETE_ORDER = [...TABLE_INSERT_ORDER].reverse();
365
367
  var DEFAULT_SQLITE_PATH = ".harness/harness.db";
@@ -464,9 +466,8 @@ var HarnessDB = class {
464
466
  }
465
467
  // ─── Actions (public facade — delegates to ActionRepository) ──────────────
466
468
  async startAction(taskId, agent) {
467
- const id = randomUUID();
468
469
  const now = (/* @__PURE__ */ new Date()).toISOString();
469
- await this.actions.create(id, taskId, agent, now);
470
+ const id = await this.actions.create(taskId, agent, now);
470
471
  await this.regenerateCurrentMd();
471
472
  return await this.actions.getById(id);
472
473
  }
@@ -494,12 +495,34 @@ var HarnessDB = class {
494
495
  async getActionSections(actionId) {
495
496
  return this.actions.getSections(actionId);
496
497
  }
497
- async recordFile(actionId, filePath, operation, notes) {
498
- return this.actions.addFile(actionId, filePath, operation, notes ?? null);
498
+ /** Batch-only (task #74) records N files in one atomic transaction. There
499
+ * is no single-entry variant; callers pass a one-element array to log a
500
+ * single file. Mirrors the driver.transaction() pattern from claimTask()
501
+ * above: a fresh ActionRepository is bound to the tx driver so every
502
+ * insert in the loop participates in the same transaction and any failure
503
+ * rolls back the whole batch. Returns the number of files recorded. */
504
+ async recordFiles(actionId, files) {
505
+ return this.driver.transaction(async (tx) => {
506
+ const txActions = new ActionRepository(tx);
507
+ for (const f of files) {
508
+ await txActions.addFile(actionId, f.filePath, f.operation, f.notes ?? null);
509
+ }
510
+ return files.length;
511
+ });
499
512
  }
500
- async recordTool(actionId, toolName, argsJson, resultSummary) {
513
+ /** Batch-only (task #74) records N tool calls in one atomic transaction.
514
+ * See recordFiles() above for the pattern; a one-element array is the
515
+ * only way to log a single tool call. Returns the number of calls
516
+ * recorded. */
517
+ async recordTools(actionId, calls) {
501
518
  const now = (/* @__PURE__ */ new Date()).toISOString();
502
- return this.actions.addTool(actionId, toolName, argsJson ?? null, resultSummary ?? null, now);
519
+ return this.driver.transaction(async (tx) => {
520
+ const txActions = new ActionRepository(tx);
521
+ for (const c of calls) {
522
+ await txActions.addTool(actionId, c.toolName, c.argsJson ?? null, c.resultSummary ?? null, now);
523
+ }
524
+ return calls.length;
525
+ });
503
526
  }
504
527
  async getFilesForTask(taskId) {
505
528
  return this.actions.getFilesForTask(taskId);
@@ -696,6 +719,11 @@ async function resetAutoincrementSequences(tx, dbType) {
696
719
  }
697
720
  }
698
721
  async function importFullExport(destDriver, data, destDbType, opts = { truncateFirst: false }) {
722
+ if (data.actions.some((a) => typeof a.id !== "number")) {
723
+ throw new Error(
724
+ "This export was produced by an older version of agent-harness-kit (actions used text/UUID ids, pre-2.0) and cannot be imported into a database using the current integer-id actions schema. Re-exporting from the old build is the only way to fix this \u2014 importing this file as-is is not supported."
725
+ );
726
+ }
699
727
  await destDriver.transaction(async (tx) => {
700
728
  if (opts.truncateFirst) {
701
729
  await truncateAllTables(tx);
@@ -779,13 +807,13 @@ async function openDB(config, cwd, homeDir = homedir()) {
779
807
  const dbConfig = config.database;
780
808
  let driver;
781
809
  if (dbConfig.type === "postgres") {
782
- const { PostgresDriver } = await import("./postgres-IOQE32DM.js");
810
+ const { PostgresDriver } = await import("./postgres-BB4GY4PN.js");
783
811
  driver = new PostgresDriver(dbConfig);
784
812
  } else if (dbConfig.type === "mysql") {
785
- const { MySQLDriver } = await import("./mysql-THKQOXIS.js");
813
+ const { MySQLDriver } = await import("./mysql-AUPKARWA.js");
786
814
  driver = new MySQLDriver(dbConfig);
787
815
  } else {
788
- const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
816
+ const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
789
817
  if (dbConfig.type !== "sqlite") {
790
818
  throw new Error("Invalid database type");
791
819
  }
@@ -823,6 +851,7 @@ export {
823
851
  HarnessDB,
824
852
  getRowCounts,
825
853
  isEmptyDatabase,
854
+ resetAutoincrementSequences,
826
855
  importFullExport,
827
856
  resolveSqlitePathForScope,
828
857
  resolveSqlitePath,
@@ -831,4 +860,4 @@ export {
831
860
  readStorageStateFile,
832
861
  openDB
833
862
  };
834
- //# sourceMappingURL=chunk-6PEIJ2D5.js.map
863
+ //# sourceMappingURL=chunk-JTACLEGM.js.map