cowork-harness 1.8.0 → 1.10.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.
Files changed (45) hide show
  1. package/.claude/skills/cowork-harness/SKILL.md +11 -86
  2. package/.claude/skills/cowork-harness/references/ci-recipe.md +5 -5
  3. package/.claude/skills/cowork-harness/references/fidelity-and-answers.md +1 -1
  4. package/.claude/skills/cowork-harness/references/scenario-schema.md +4 -2
  5. package/.claude/skills/cowork-harness/references/task-recipes.md +1 -1
  6. package/AGENTS.md +2 -1
  7. package/CHANGELOG.md +123 -0
  8. package/CONTRIBUTING.md +4 -2
  9. package/README.md +16 -9
  10. package/RELEASING.md +8 -8
  11. package/SPEC.md +7 -0
  12. package/dist/agent/session.js +46 -4
  13. package/dist/assert.js +12 -4
  14. package/dist/critique/command.js +39 -15
  15. package/dist/critique/package-evidence.js +10 -0
  16. package/dist/hostloop/plugins-handler.js +208 -0
  17. package/dist/hostloop/skills-handler.js +197 -0
  18. package/dist/loop-decision.js +55 -0
  19. package/dist/run/chat.js +35 -9
  20. package/dist/run/diff.js +12 -2
  21. package/dist/run/execute.js +17 -6
  22. package/dist/run/skill-flag-surface.js +1 -1
  23. package/dist/run/skill-metadata.js +72 -15
  24. package/dist/run/verdict.js +5 -3
  25. package/dist/runtime/container.js +29 -3
  26. package/dist/runtime/hostloop.js +33 -5
  27. package/dist/runtime/lima.js +13 -1
  28. package/dist/session.js +94 -3
  29. package/dist/sync/cowork-sync.js +5 -4
  30. package/docs/README.md +13 -0
  31. package/docs/boundary.md +15 -2
  32. package/docs/cassette.md +1 -1
  33. package/docs/critique.md +32 -4
  34. package/docs/debugging.md +3 -1
  35. package/docs/fidelity-gaps.md +48 -26
  36. package/docs/scenario.md +10 -7
  37. package/docs/session.md +8 -0
  38. package/examples/README.md +2 -1
  39. package/examples/replays/README.md +1 -1
  40. package/llms.txt +1 -0
  41. package/package.json +1 -1
  42. package/python/README.md +4 -2
  43. package/schema/critique-report.json +221 -0
  44. package/schema/session.schema.json +8 -0
  45. package/scripts/check-versions.ts +33 -0
@@ -3,8 +3,8 @@ name: cowork-harness
3
3
  description: Test or debug a Claude Code skill/plugin under Claude Cowork's runtime — sandboxed agent, default-deny egress, the can_use_tool permission/question protocol — using the cowork-harness CLI. Use when validating or regression-testing a skill, authoring or debugging a scenario YAML (prompt + scripted answers + assert:), choosing a fidelity tier, scripting AskUserQuestion / tool-permission answers, or asserting artifacts, egress, or sub-agent dispatch. Especially when a harness run no-ops an assertion, fails on an unanswered gate, false-greens, a steered answer never reaches the model, or a web_fetch is unexpectedly denied or gated. Also when iterating or hardening a skill across fixes, or grounding a skill's self-critique against its own run evidence — including a document-analysis skill (cap table, deck, financial model, transcript) that needs an uploaded file attached to be critiqued at all. NOT for generic unit testing (pytest/vitest of your own scripts) or non-Cowork CI. Covers the skill / run / chat / record / replay / trace / decide / assertions / scaffold commands and the session-vs-scenario split.
4
4
  metadata:
5
5
  author: cowork-harness
6
- version: 1.8.0
7
- tracks-harness: cowork-harness 1.8.0 (baseline desktop-1.24012.1)
6
+ version: 1.10.0
7
+ tracks-harness: cowork-harness 1.10.0 (baseline desktop-1.24012.1)
8
8
  ---
9
9
 
10
10
  # cowork-harness
@@ -22,7 +22,7 @@ flagged with a loud `::warning::`, not silent — auto-answer a gate, observe an
22
22
  allowlist). This skill exists mostly to keep you out of those traps — the Gotchas section below is
23
23
  the highest-value part. Read it.
24
24
 
25
- > **Version note:** the facts and `file:line` pointers here track `cowork-harness 1.8.0` (baseline
25
+ > **Version note:** the facts and `file:line` pointers here track `cowork-harness 1.10.0` (baseline
26
26
  > `desktop-1.24012.1`). If your checkout is newer, prefer the live `--help` and — in a repo checkout —
27
27
  > `SPEC.md` / `docs/*.md` over this snapshot, and re-run the bundled linter.
28
28
 
@@ -39,88 +39,11 @@ Before the first command, confirm the CLI is reachable and **fail loud** (never
39
39
 
40
40
  - **One-shot check.** Run `cowork-harness doctor [--tier <tier>]` first — a read-only prerequisite check that inspects Docker, the staged agent, the token, and the baseline in one pass. The bullets below explain each thing it checks (and how to fix it).
41
41
  - **Replay-only? Skip `doctor`.** Replaying committed cassettes needs no Docker, no staged agent, and no token — and every tier's `doctor` validates the auth token (the live tiers also Docker + the staged agent), so a ✗ there is expected, not a blocker. Go straight to `cowork-harness replay <cassette>`.
42
- - **CLI on PATH, recent enough?** Run `cowork-harness --version` — this skill needs **≥ 1.8.0**. If it's missing or older, prefix every command with the version floor `npx "cowork-harness@>=1.8.0" <cmd>` (Node ≥ 20), or install once with `npm i -g "cowork-harness@>=1.8.0"`. **Pin `@>=1.8.0`, never `@latest`** — `@latest` can silently fetch an older CLI and the new commands fail as "unknown command", whereas the floor **fails loud** if no compatible version is published.
43
-
44
- What the ≥ 1.8.0 floor gates, by release:
45
-
46
- - **1.8.0 (critique hardening + hostloop unpin):** `critique --skill <name>` (multi-skill-plugin
47
- grading — a plugin root with no `--skill` is refused pre-spend; the evidence package gains the
48
- invoked skill's `agents/<name>.md` + bounded `references/*.md` CONTENT), run-dir artifacts
49
- (`critique-report.json` always, `critique-evidence-package.txt` when the evaluator ran,
50
- `critique-salvage.json` on exit 2) + `--out <path>`, per-critique `costUsd` across all four
51
- workloads, `findingFingerprint` per item (cross-INPUT clustering; skillHash stays the cross-FIX
52
- key), gate-answer `--answer` echo in the report, per-item-tolerant evaluator parse
53
- (`droppedEvaluatorItems`), `skillMdTruncated` (a readable-but-oversized SKILL.md is flagged
54
- "graded a cut copy" — distinct from missing/unreadable), `subagents[].webSearches` +
55
- `trace --view subagent-research` (live/record lane), and the hostloop uploads-bullet fix (Read of
56
- an uploaded file at the advertised path works — no copy-into-outputs workaround needed). Also:
57
- **`critique --fidelity hostloop`** (the container-tier pin is lifted — resume-continuity proven
58
- live on the native binary; `microvm`/`protocol`/`cowork` stay refused, each with a tagged reason),
59
- **`skill --allow-host-writes`** (hostloop writable-folder consent; `critique` forwards it to both
60
- turns), `verdictProvenance` stamped on every critique report (advisory self-run, not an
61
- attestation), evidence caps 64KB SKILL.md / 32KB transcript / 144KB package (a flagship-sized
62
- SKILL.md grades untruncated; ~2–2.5× evaluator cost on large skills), pinned sessions stamp their
63
- fidelity and a cross-tier `--resume` fails loud pre-spawn, and `doctor`'s advisory
64
- `image-freshness` check (pulled agent image behind the published GHCR one → warn + re-pull
65
- remedy).
66
-
67
- - **1.7.0 (per-turn run-directory layout, single shape):** every run dir — `run`/`skill`/`chat`,
68
- single-turn or multi-turn (any `--session-id` + `--resume`, and **every `critique`**: task turn +
69
- reflection turn) — writes each turn's `result.json` / `run.jsonl` / `trace.json` / `resources.jsonl`
70
- into **`turns/<N>/`**, written once and never renamed. **There is no root compat copy of anything —
71
- `<run-dir>/result.json` does not exist.** **When a user asks about a run, point them at
72
- `turns/1/result.json`** (the only completion on a single-turn dir) — or on a `critique` dir, better yet
73
- at `result.graded.json` / `trace.graded.json`, the role-stable aliases `critique` writes (the graded
74
- task turn, never the reflection one). `events.jsonl` / `timeline.jsonl` stay cumulative at the run-dir
75
- root, always. A dir written before this layout existed is refused LOUD, by name, naming the shape found
76
- — never silently misread as if it were `turns/1/`. **Convert it in place with
77
- `cowork-harness migrate-run-dir` (dry-run by default); do NOT tell the user to re-run or re-record,
78
- which throws away history the migrator recovers.** Its `events.jsonl` still fully supports `trace`,
79
- which is why every refusal points there. `verify-run` REFUSES a multi-turn dir rather than certifying
80
- the wrong turn, and `trace` shows the latest turn with a `::notice::` when earlier turns exist.
81
-
82
- - **1.7.0 (limitation provenance):** every `critique` limitation in `critique --help` and
83
- docs/critique.md is tagged with WHY it exists — `[structural]` (permanent), `[unverified]` (unproven,
84
- **not** known-impossible), `[deliberate]`, `[not-built]`. **Read the tag before telling a user to
85
- design around a limitation.** Worked example of why the tag matters: `critique`'s container-tier pin
86
- was tagged `[unverified]`, not permanent — and 1.8.0 lifted it (hostloop allowed, see above) exactly
87
- as the tag predicted a proof could.
88
-
89
- - **1.6.0 (`critique`'s skill-flag parity):** `critique` accepts most `skill` flags under the same names,
90
- which is what makes a document-analysis skill (cap table, deck, financial model, transcript)
91
- critiquable at all: `--upload`/`--folder`/`--plugin`/`--marketplace`/`--enable`/`--model`/
92
- `--allow-missing-capability` reach BOTH spawned turns; `--label`/`--timeout`/`--answer`/
93
- `--answer-policy`/`--on-unanswered`/the decider flags reach the graded turn only; `--prompt-file` is
94
- new. Anything that can't work is REFUSED with a reason rather than silently ignored.
95
- **Repeating a flag:** `--upload`/`--folder`/`--plugin`/`--marketplace`/`--enable`/`--answer`
96
- accumulate; every other value-taking flag is single-valued and repeating it is a usage error (exit `2`)
97
- rather than a silent last-wins — do not assume the CLI-conventional "last one wins". Booleans repeat
98
- harmlessly. On `1.5.0` these flags do not exist: `critique --upload x.xlsx` fails as "unknown flag", so
99
- if a user hits that, they are below the floor.
100
-
101
- - **1.5.0:** `critique <skill-folder> --prompt "<probe>"` (EXPERIMENTAL) — runs a skill, asks the agent
102
- what confused it, then grades that self-report against a frozen record of the run: a blinded evaluator
103
- plus mechanical citation checking. Findings NEVER gate (exit 0); exit 2 means no critique was produced.
104
- `skill --repeat N` (2-100) with `--min-pass-rate`/`--stop-on-diverge`/`--max-budget-usd`/
105
- `--allow-budget-stop` brings the variance rollup to the exploratory lane. `result.json` gains
106
- `outcome` (`errored`/`no_deliverable`/`delivered_with_verdict_fail`/`delivered_clean`). The
107
- `skill`/`probe-dispatch` lanes now emit `fingerprint.skillHash`/`skillCommit` at all — before 1.5.0
108
- they emitted NEITHER, so a generation-pairing step over those lanes silently grouped on an absent key.
109
- `diff`'s exit code now honours its own documented gateable signal (tools/artifacts/meta; a
110
- transcript-only difference no longer fails it).
111
- - **core set (pre-0.21.0 vintage, or mixed):** `assertions --list`, `scaffold <run-id>`, `trace --view dispatches`, `artifact_json` incl. the `in:` operator (passes when the resolved value deep-equals one of the listed members — value ∈ your list, not the reverse), `verify-cassettes` incl. the `--allow-domain`/`--allow-email`/`--allow-patterns-file` allows (`--allow-patterns-file <path>` is a FILE of patterns, one regex per line — not a path to allow, unlike `--allow <regex>`), batch `record <dir>`/`--rerecord-stale`, `record --concurrency <N>`, record-time redaction, multiSelect/`answer:`, `verify-run` answer-coverage, `record --max-artifact-bytes`, live record-time deciders, scenario `skills:` staleness scoping with `COWORK_HARNESS_AGENT_SCOPE=skill`, `chat --plugin`, and `/help` in the REPL.
112
- - **0.21.0:** `verify-cassettes --allow-path` (`path` — local absolute filesystem paths — is the scanner's 4th class), and `hostloop`'s native host/VM process split with its `allow_host_writes:` consent field.
113
- - **0.22.0:** `computer_links_resolve`.
114
- - **0.28.0:** `semantic_matches` (an LLM judge grades a fixed `rubric` of claims against the run's answer — **live-only**, so it is evidence-unavailable / skipped-loud on replay, never a vacuous pass), and **glob-matched** `tool_called`/`tool_not_called`/`subagent_tool_used`/`subagent_tool_absent` (a pattern like `mcp__workspace__*` matches any tool in the family; exact names match exactly).
115
- - **0.30.0:** resolved sub-agent identity on dispatch records (`resolvedAgentType`/`resolvedModel`), the five path-gate assertion keys `no_vm_path_file_op`/`vm_path_denied`/`path_denied`/`no_path_denied`/`subagent_file_write`, and the session-level `agent_env` knob.
116
- - **0.31.0:** the `lint-skill` (static host-loop footgun + `subagent_type` resolution linter) / `analyze-skill` (advisory `/sessions`-path static scan, `--strict`, `analyze-skill: ignore` marker) / `probe-dispatch` (single-dispatch mechanics probe) commands, `status --latest-for` (resolve a scenario's newest run dir by run time, not directory mtime), the `subagent_dispatch_healthy` composite assertion, and the persisted `result.json` fields `verdict`, `subagents[].referencesRead`/`subagents[].reasoning`, plus the `toolCounts`/`toolErrors`/`toolDurations` shape distinction.
117
- - **0.32.0:** `analyze-skill`'s directory scan now covering a skill/plugin's full contract surface (recursive `agents/`/`references/`/`commands/`, plugin-root-aware, symlink-following) with line/block-scoped `analyze-skill: ignore-next-line`/`ignore-start`/`ignore-end` markers and multi-path/glob input, and `lint-skill`'s provable in-plugin `subagent_type` typo now a WARN that gates under `--strict`.
118
- - **0.33.0:** the `redacted` marker on display-omitted reasoning — `subagents[].reasoning` and the top-level `thinking[]` now carry `{text:"", redacted:true}` when the model returns a signed-but-empty thought (so "reasoned, text omitted" is distinct from "no thought"), plus the fenced `debug.thinking_display` escape hatch.
119
- - **1.0.0:** first stable release — the SPEC §12 compatibility contract takes effect (covered CLI/schema/env/Action surfaces are now stable; breaking changes need a major bump). No new author-facing command; the floor simply tracks the 1.0 release.
120
- - **1.1.0:** `analyze-skill` now also flags **interactive-artifact write-backs lost under Cowork** — a relative `fetch`/XHR/`sendBeacon`/`<form method=post>` in an emitted `.html` (or its `.py`/`.js` generator) that silently fails when the artifact is served from Cowork's own origin. `artifact-write-back-lost` gates under `--strict`; `artifact-write-back-suspect` is advisory; an unanalyzable candidate is a could-not-verify exit 3. An optional **`analyze-skill --runtime`** drives the artifact in a headless DOM (needs `jsdom`) to *observe* the lost write-back — enrichment only, never changes the exit code. Plus a `lint` check for a container-only assertion key (`no_scratchpad_leak`/`present_files_called`) used off the `container` tier, and the `doctor --output-format json` envelope frozen as a covered SPEC §12 surface (`schema/doctor.json`).
121
- - **1.2.0:** three new assertion keys — `no_lost_write_back: true` (the write-back detector above wired as a per-scenario gate over the run's authored files, live-only) and the regex siblings `tool_result_matches`/`tool_result_not_matches` (case-insensitive per-result, for an error-signature *family* a literal substring can't express). **`microvm` outputs are now observable** — its session tree is snapshotted from the VM into the run dir, so `file_exists`/`artifact_json`/`user_visible_artifact`/`no_unexpected_files`/`input_unmodified`/`no_lost_write_back`/`semantic_matches` all work there, no longer `container`/`hostloop`-only. `status <dir>` also resolves the newest session under a `--run-dir` root; the completion footer prints a `→ result: …/result.json` pointer; the `on_unanswered=fail` error also points to `on_unanswered: llm`. `analyze-skill` hardening: a phantom `<script>` prose block no longer sinks a real verdict, a delete/remove flow claiming success classifies as lost (error) not just suspect, and write-back detection widened (optional-call `?.` spellings, member-spelled/aliased `fetch`/`sendBeacon`, axios instance/config forms).
122
- - **1.3.0:** run-identity for the iterate-across-fixes loop — `skill`/`run` take `--label <tag>` (a generation tag surfaced in `result.json` `runLabel`, the run-index row, `inspect`, and `status.json`) alongside an auto-recorded `skillCommit`, layered on the **authoritative** content-exact `fingerprint.skillHash` a harvest step should pair critiques by (`inspect`/index surface a short `skillHash` prefix). `trace --full-results` captures the full input+result of **every** tool call — successful ones too, not just errors — so an external grader can ground a self-critique finding against the call it cites. `verify-run` now **warns** on skillHash drift for answer-less scenarios (was silent unless the scenario declared scripted `answers`). Plus `skill --allow-missing-capability` (the open-ended-run opt-out for a capability FALSE-NEGATIVE on the lean `core` image), a new **warn-severity `ended_with_question`** verdict signal (the agent's final answer contains a question and the run wrote no `outputs/` deliverable — the lenient sibling of the strict `stalled`), and LLM-decider `OTHER:` free-text answers now marked `[via Other free-text]` in gate provenance.
123
- - **1.4.0:** platform baseline synced to Desktop **1.22209.3** (agent 2.1.215; no prompt/spawn/egress drift), and **`coworkWebFetchDedup` is now enacted** on the hostloop `web_fetch` path — a repeat fetch of the same normalized URL within the TTL (15 min, cap 100) makes **no network request** and returns a marker to re-use the earlier result, with no egress event. Baseline-gated (Desktop ≥ 1.22209.3), keyed under the request + terminal `destination_url`, never caching errors/empty; a hit is assertable via `tool_result_contains: "Already fetched"`.
42
+ - **CLI on PATH, recent enough?** Run `cowork-harness --version` — this skill needs **≥ 1.10.0**. If it's missing or older, prefix every command with the version floor `npx "cowork-harness@>=1.10.0" <cmd>` (Node ≥ 20), or install once with `npm i -g "cowork-harness@>=1.10.0"`. **Pin `@>=1.10.0`, never `@latest`** — `@latest` can silently fetch an older CLI and the new commands fail as "unknown command", whereas the floor **fails loud** if no compatible version is published.
43
+
44
+ This skill documents the CURRENT surface, not release history. If `cowork-harness --version` is
45
+ OLDER than the floor, the per-release record of what you are missing is [CHANGELOG.md](https://github.com/yaniv-golan/cowork-harness/blob/main/CHANGELOG.md)
46
+ — upgrade rather than work around it, since this skill's `file:line` pointers and flag names track the floor.
124
47
  - **Agent binary (sandboxed live tiers — `container`/`microvm`/`hostloop`/`cowork`).** The staged Claude Code agent is **bind-mounted** from a local Claude Desktop install, or point `COWORK_AGENT_BINARY` at a `claude-code-vm/<ver>/claude` ELF. Nothing is bundled. `protocol` (L0) and `replay` need no staged agent; for the sandboxed tiers, no agent → no run; report that, don't skip silently.
125
48
  - **Docker / Lima.** Only `--fidelity protocol` (L0) runs without them. `container` / `microvm` / `hostloop` / `cowork` need Docker (Lima for L2). If they're absent, drop to `--fidelity protocol` and **say so** — a green that never exercised the sandbox is not a sandbox pass.
126
49
  - **Auth.** `CLAUDE_CODE_OAUTH_TOKEN` (preferred), or `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`, via env or `.env`. Minting an OAuth token needs the **`claude` CLI** (`npm i -g @anthropic-ai/claude-code`, then `claude setup-token`).
@@ -436,7 +359,9 @@ cassette — has its own recipe:
436
359
  input + result are captured, not just errored ones. When iterating, tag generations with `--label` and
437
360
  pair a critique only with a `result.json` whose `fingerprint.skillHash` **matches** the skill that
438
361
  produced it (`inspect`/the run-index row surface a short `skillHash` prefix; `verify-run` warns when a
439
- kept run predates the current skill). **On a pre-1.5.0 CLI the `skill` lane emits no `fingerprint.skillHash` at all**, so a
362
+ kept run predates the current skill). **Multi-skill plugin caveat (post-1.7.0 CLIs with `--skill`):**
363
+ skillHash keys the whole MOUNTED plugin, so on a multi-skill plugin the hash alone cross-pairs
364
+ critiques of DIFFERENT skills — pair by the report's `(gradedSkillHash, gradedSkill)` pair. **On a pre-1.5.0 CLI the `skill` lane emits no `fingerprint.skillHash` at all**, so a
440
365
  pairing step there silently groups on an absent key instead of erroring — check the field is present, or
441
366
  require ≥ 1.5.0. See `docs/debugging.md`
442
367
  (repo-only) for the full loop.
@@ -1,6 +1,6 @@
1
1
  # CI recipe — replay vs live lanes
2
2
 
3
- Self-contained reference. Tracks `cowork-harness 1.8.0` (baseline `desktop-1.24012.1`).
3
+ Self-contained reference. Tracks `cowork-harness 1.10.0` (baseline `desktop-1.24012.1`).
4
4
 
5
5
  **Fastest path: the packaged Action.** One step gets you `replay`/`lint`/`verify-cassettes` plus a PR
6
6
  job-summary reporter (verdict table, staleness findings, cost/turns when available):
@@ -13,7 +13,7 @@ job-summary reporter (verdict table, staleness findings, cost/turns when availab
13
13
  ```
14
14
 
15
15
  The Action's `version` input defaults to `latest` — intentional so a copy-pasted recipe tracks the current
16
- release; pin an exact version (e.g. `version: "1.8.0"`) for reproducible CI.
16
+ release; pin an exact version (e.g. `version: "1.10.0"`) for reproducible CI.
17
17
 
18
18
  Reach for the manual multi-step form below only when you need per-step control the Action's inputs don't
19
19
  cover (a custom flag combination, a different runner matrix per step, or `lint`/`verify-cassettes` gated
@@ -57,7 +57,7 @@ sha256-*checked* but not hard-blocking on mismatch — it's advisory for an inte
57
57
  GitHub-hosted runners, no token/Docker/agent:
58
58
 
59
59
  ```yaml
60
- - run: npm i -g "cowork-harness@>=1.8.0"
60
+ - run: npm i -g "cowork-harness@>=1.10.0"
61
61
  - run: cowork-harness lint scenarios/*.yaml # no silent false-greens
62
62
  - run: cowork-harness verify-cassettes cassettes/ # privacy + staleness
63
63
  - run: cowork-harness replay cassettes/ # token-free content/structure
@@ -197,7 +197,7 @@ jobs:
197
197
  with: { node-version: '20' }
198
198
  - uses: actions/setup-python@v5
199
199
  with: { python-version: '3.x' } # python3 only — PyYAML is bundled with the linter
200
- - run: npm i -g "cowork-harness@>=1.8.0"
200
+ - run: npm i -g "cowork-harness@>=1.10.0"
201
201
  - run: cowork-harness lint scenarios/*.yaml # no-silent-false-green (needs python3; PyYAML bundled)
202
202
  - run: cowork-harness verify-cassettes cassettes/ --output-format json # privacy + staleness gate
203
203
  - run: cowork-harness replay cassettes/ --output-format json # token-free content/structure
@@ -226,7 +226,7 @@ jobs:
226
226
  echo "live=true" >> "$GITHUB_OUTPUT"
227
227
  fi
228
228
  - if: steps.guard.outputs.live == 'true'
229
- run: npm i -g "cowork-harness@>=1.8.0"
229
+ run: npm i -g "cowork-harness@>=1.10.0"
230
230
  - if: steps.guard.outputs.live == 'true'
231
231
  run: cowork-harness run scenarios/ --output-format json
232
232
  env:
@@ -1,6 +1,6 @@
1
1
  # Fidelity tiers & answer paths
2
2
 
3
- Self-contained reference. Tracks `cowork-harness 1.8.0` (baseline `desktop-1.24012.1`).
3
+ Self-contained reference. Tracks `cowork-harness 1.10.0` (baseline `desktop-1.24012.1`).
4
4
 
5
5
  ## Fidelity tiers (`fidelity:` in the scenario)
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Scenario & session schema, assertion catalog, web_fetch, full gotchas
2
2
 
3
- Self-contained reference for authoring `cowork-harness` scenarios. Tracks `cowork-harness 1.8.0`
3
+ Self-contained reference for authoring `cowork-harness` scenarios. Tracks `cowork-harness 1.10.0`
4
4
  (baseline `desktop-1.24012.1`). If your checkout is newer, prefer the live `docs/scenario.md`,
5
5
  `docs/session.md`, and `SPEC.md`.
6
6
 
@@ -136,6 +136,8 @@ plugins:
136
136
  remote_plugins: []
137
137
  skills:
138
138
  local: [] # extra host skill dirs
139
+ suggest_enabled: true # gate 245679952 override — `mcp__skills__suggest_skills` on/off (default true)
140
+ proactive_suggest_enabled: false # gate 1598976391 override — proactive description + `trigger` param (default false)
139
141
  mcp:
140
142
  config: null # --mcp-config file (standard mcpServers map)
141
143
  enabled: []
@@ -274,7 +276,7 @@ same set live from the schema.
274
276
  | `no_skill_triggered: <regex>` | no invoked skill id matched — the negative-control / description-collision catcher; evidence-unavailable (never a vacuous pass) if invocation data is absent or the `Skill` tool is unobservable |
275
277
  | `skill_available: <regex>` | a staged skill's id matched the regex (offered, not necessarily invoked — see `skill_triggered` for invocation) — content-class: the id list comes from the agent's init `skills` listing, so it replays from the frozen init event (id-only; the `whenToUse` enrichment is live-disk and thus absent on replay, but the id is what's matched); evidence-unavailable only if `RunResult.context.availableSkills` is absent entirely (an older cassette recorded before the available-skills listing was captured) |
276
278
  | `connector_available: <regex>` | an MCP server/connector's name matched the regex (available, not necessarily used) — evidence-unavailable if `RunResult.context.mcpServers` is absent |
277
- | `tool_available: <regex>` | a tool in the init manifest matched the regex (available, not necessarily called — see `tool_called` for invocation) — evidence-unavailable if `RunResult.context.tools` is absent |
279
+ | `tool_available: <regex>` | a tool in the init manifest matched the regex (available, not necessarily called — see `tool_called` for invocation) — evidence-unavailable if `RunResult.context.tools` is absent. The `mcp__skills__*`/`mcp__plugins__*` discovery tools are modeled (as `alwaysLoad`) on `container`/`hostloop`/`cowork` — a miss there is a real absence; `microvm`/`protocol` declare no such server, so a miss on those two tiers means "not modeled at this tier", not "provably unavailable" |
278
280
 
279
281
  **Name-matching styles differ by key (don't mix them up):** `tool_called`, `tool_not_called`,
280
282
  `subagent_tool_used`, `subagent_tool_absent` are **glob** (anchored, case-sensitive, `*`/`?`). `tool_available`,
@@ -2,7 +2,7 @@
2
2
 
3
3
  Each recipe composes facts that live scattered across SKILL.md and the other references into one
4
4
  decision path. Every one answers a question a real fleet owner had to work out the hard way.
5
- Tracks `cowork-harness 1.8.0` (baseline `desktop-1.24012.1`), same as SKILL.md's front-matter. Recipe 2's `resolved-tier`/`unverifiable-tier` staleness classes and
5
+ Tracks `cowork-harness 1.10.0` (baseline `desktop-1.24012.1`), same as SKILL.md's front-matter. Recipe 2's `resolved-tier`/`unverifiable-tier` staleness classes and
6
6
  Recipe 3's `init-redact` shipped in 0.24.0 and are part of the current feature set — no version gate
7
7
  needed if your CLI meets SKILL.md's version floor.
8
8
 
package/AGENTS.md CHANGED
@@ -21,7 +21,8 @@ protocol layer or run-loop bookkeeping in the CLI.
21
21
  - Tests are **token-free & spawn-free** wherever possible (`cli-json` uses usage-errors + cassette replay).
22
22
  Don't add a test that needs a live model or Docker to the default suite; that's the `pytest -m cowork` /
23
23
  `npm run test:live` lane. Python fast lane (from `python/`): `pytest -m 'not cowork'`.
24
- - CLI binary `cowork-harness`; env vars `COWORK_HARNESS_*` (+ `COWORK_AGENT_BINARY` / `COWORK_AGENT_IMAGE`).
24
+ - CLI binary `cowork-harness`; env vars `COWORK_HARNESS_*` (+ `COWORK_AGENT_BINARY` / `COWORK_AGENT_IMAGE`) —
25
+ see README's [Reproducibility knobs](./README.md#reproducibility-knobs) for the full env-var list.
25
26
  Node ≥ 20.
26
27
  - `cowork-harness sync` is **local-only** (needs Desktop + `app.asar`; not on CI). The committed
27
28
  `baselines/*.json` are CI's source of truth — never hand-edit release facts into source; they come from
package/CHANGELOG.md CHANGED
@@ -6,6 +6,129 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.10.0] — 2026-07-25
10
+
11
+ ### Changed
12
+
13
+ - **The packaged `cowork-harness` skill no longer carries a per-release version history.** SKILL.md's
14
+ "what the floor gates, by release" list had grown to 19 releases / ~15 KB (about a fifth of the file)
15
+ and was self-defeating: every entry described a feature at or below the floor, so anyone meeting the
16
+ floor already had all of it. Because SKILL.md is loaded into an agent's context on every invocation,
17
+ that was pure token cost for content that cannot change behaviour. The version floor (and *why* to pin
18
+ it rather than use `@latest`) stays; the history now lives where it belongs — this changelog — with a
19
+ pointer for anyone diagnosing an older CLI. SKILL.md: 77 KB → 62 KB.
20
+
21
+ ### Added
22
+
23
+ - **Skill/plugin discovery SDK-MCP servers** (`mcp__skills__list_skills`/`suggest_skills`,
24
+ `mcp__plugins__list_plugins`/`search_plugins`/`suggest_plugin_install`) — modeled on `container` and
25
+ `hostloop` (and `cowork`, which resolves to one of those) alongside the existing `cowork`/`workspace`
26
+ servers, via a new `combineSdkMcp` composition helper (`src/agent/session.ts`). Every tool is
27
+ `alwaysLoad` so it surfaces in `system/init.tools` from turn one, matching real Cowork; `list_skills`/
28
+ `list_plugins` are populated deterministically from the session's actually-mounted skills/plugins,
29
+ and `suggest_skills`/`search_plugins`/`suggest_plugin_install` return a deterministic empty-catalog
30
+ advisory result (the real add/install catalog is out of band). Two gates control the `skills` server's
31
+ `suggest_skills` tool — `suggestSkillsEnabled` (default on) and `proactiveSkillSuggestEnabled` (default
32
+ off, adds a `trigger` param) — read from the synced baseline via a new bare-boolean `readGateBool`
33
+ reader, with new session-level overrides `skills.suggest_enabled` / `skills.proactive_suggest_enabled`
34
+ (see [docs/session.md](./docs/session.md)). `tool_available: "mcp__skills__.*"` /
35
+ `"mcp__plugins__.*"` is no longer a false negative on `container`/`hostloop`/`cowork` (still absent on
36
+ `microvm`/`protocol` — see [docs/fidelity-gaps.md](./docs/fidelity-gaps.md)). Fidelity scope: the tool
37
+ inventory, inputSchemas, gating, and the `list_skills`/`list_plugins` envelopes are asar/session-log
38
+ derived; the tool **description strings** and the `search_plugins`/`suggest_plugin_install` envelopes are
39
+ a faithful reconstruction rather than byte-captured — see [docs/fidelity-gaps.md](./docs/fidelity-gaps.md).
40
+ Both catalogs report what the sandbox will actually **receive**: a plugin skill directory that staging
41
+ drops (untracked, under the default git-tracked staging boundary) is omitted from `list_skills` *and*
42
+ `list_plugins`, including on `--resume`, so the two never contradict each other within a run.
43
+
44
+ ### Fixed
45
+
46
+ - **`chat --fidelity container` declared SDK-MCP tools it never served.** The container branch built its
47
+ SDK-MCP bundle and then discarded it, so `mcp__cowork__present_files` was advertised on `--tools`/
48
+ `--allowedTools` while its server was never announced in `initialize` — calling it failed, and
49
+ `context.mcpServers` omitted `cowork`. (`run --fidelity container` was unaffected; this was the
50
+ interactive `chat` lane only.) The bundle is now forwarded, which also serves the new
51
+ `skills`/`plugins` discovery servers on that lane.
52
+
53
+ ## [1.9.0] — 2026-07-24
54
+
55
+ ### Added
56
+
57
+ - **`schema/critique-report.json`** — a descriptive, test-pinned schema for `critique`'s JSON report /
58
+ `critique-report.json` artifact, so automation consumers (budget pacers gating on `costUsd.complete`,
59
+ harvesters) parse field names/shapes from a schema instead of prose. Deliberately **not** a SPEC
60
+ §12-frozen surface (unlike `doctor.json`) — critique is EXPERIMENTAL and additive field changes are
61
+ expected; the schema says so in its own description, and a two-way sync test pins it against the
62
+ actual report builder on every branch (findings / infraFailure / evaluatorError).
63
+ - **`gradedSkill` in the critique report** (text header + JSON): the resolved `skills/<name>` the
64
+ packager graded under `--skill`/auto-selection. Load-bearing for multi-skill plugins:
65
+ `gradedSkillHash` keys the whole mounted plugin, so pairing by hash alone cross-pairs critiques of
66
+ *different* skills — pair by `(gradedSkillHash, gradedSkill)`. Docs updated accordingly.
67
+
68
+ ### Docs
69
+
70
+ - Truncation→DROPPED mechanics: a finding whose `evidence` quotes SKILL.md text past the packaging cap
71
+ fails citation-resolution and lands in DROPPED (the check runs against the *cut* copy) — documented
72
+ next to `skillMdTruncated` so a back-half DROPPED skew on an oversized skill has its cause named.
73
+ - `findingFingerprint` direction-of-inference: high-precision, LOW-RECALL — a match proves
74
+ reproduction; a mismatch does NOT prove non-reproduction (the same finding reworded fingerprints
75
+ differently). The Reproduction section says so before anyone concludes "didn't reproduce".
76
+ - Evaluator cost share: the two evaluator passes were ~3/4 of a measured e2e total — the
77
+ calibrate-then-`--evaluator-model` strategy is now in the cost section and `critique --help`, with
78
+ the armor-verification-is-default-evaluator-only caveat.
79
+ - SPEC §12 now names the critique report explicitly under **NOT covered**: `schema/critique-report.json`
80
+ is descriptive (parse against it, not prose) but not the compatibility contract while critique is
81
+ EXPERIMENTAL — its surface-baseline presence is for change visibility, and it is the promotion
82
+ candidate on the `doctor.json` template once critique stabilizes. README, llms.txt, and the shipped
83
+ skill point at the schema and the `(gradedSkillHash, gradedSkill)` pairing rule.
84
+
85
+ ### Fixed
86
+
87
+ - **critique's "Attached inputs" evidence no longer reports `(none)` when `mounts.json` is corrupt.**
88
+ `listAttachedInputs` derived connected-folder names from `loadVmPathContext`, which returns `null` for
89
+ BOTH an absent `mounts.json` (legitimately no mounts) and a present-but-unparseable one (the folder map
90
+ is UNKNOWN). Uploads already distinguished these (the ENOENT-vs-read-fault split), but folders — which
91
+ have no fixed-layout fallback — silently collapsed to `[]`, so a corrupt `mounts.json` rendered `(none)`,
92
+ telling the evaluator "the agent correctly saw no connected folder" when the truth was unknown. It now
93
+ surfaces a corrupt `mounts.json` as an explicit UNKNOWN note, completing the same
94
+ confabulation-vs-correct guard the uploads path already applies.
95
+ - **`diff` no longer treats two tool inputs that differ only past the ~2000-char cap as the same call.**
96
+ `canonicalizeInput` truncated a tool input's canonical JSON to a 2000-char cap and used that truncated
97
+ string as the tool-sequence equality key, so two `Write`/`Bash` calls sharing a long identical prefix
98
+ but differing only in the dropped tail compared as `op: "same"` in `diffToolSequence` — a false "no
99
+ change" that could flip the advisory `diff` exit code to 0. A truncated key now folds in a
100
+ `#<len>·<sha16>` hash of the full canonical string, so the key depends on the entire content while the
101
+ visible prefix stays readable in the hunk; both diff sides canonicalize identically, so the comparison
102
+ stays consistent.
103
+ - **`COWORK_VM_GATEWAY` is now validated as a canonical IPv4 literal.** The L2-microVM gateway override was
104
+ interpolated verbatim into a root-run guest `iptables -A OUTPUT -d <gateway>` command (via `sh -c`), so a
105
+ malformed or hostile value could inject shell syntax into privileged provisioning — or, more mundanely,
106
+ leave the firewall in an unknown state. `vmGatewayIp()` now rejects anything that isn't a canonical IPv4
107
+ literal (digits-and-dots only, octets 0–255, no leading zeros), failing loud instead of reaching the
108
+ shell. Operator-set env var, so this is defense-in-depth; no valid gateway value is affected.
109
+ - **The `agent.stderr.log` sink is now flushed before the teardown secret-scrub reads it.** The stderr sink
110
+ was piped fire-and-forget and never awaited, so bytes still buffered when `scrubRawRunLogs` read the file
111
+ could land raw *afterwards* — a persisted-secret leak in a narrow teardown window. `LiveAgentSession` now
112
+ pipes it with `{ end: false }` and ends+awaits it in the same session-teardown drain that already flushes
113
+ `events.jsonl` / `control-out.jsonl`, so the session generator resolves only after the sink is fully
114
+ flushed — the scrub always sees the complete log.
115
+ - **`critique` no longer prints raw host paths in its report or diagnostics.** The text report's `run dir:`
116
+ line, the `inspect <dir>` hints, the write-failure diagnostics, and the echoed skill-folder path all
117
+ printed absolute `$HOME`-rooted paths, so a shared report or screenshot leaked the username + filesystem
118
+ layout (it landed in a video frame). critique was the one rendering path in the CLI that never called
119
+ `tildeify`, while `skill`/`run` scrub unconditionally. Every human-facing path is now collapsed to `~`;
120
+ the JSON report and persisted-artifact paths stay raw (machine data a consumer feeds back to a tool). The
121
+ `--demo` rejection is unchanged — it was never the fix — but its reason now notes the report already
122
+ collapses paths.
123
+ - **`critique` fails fast on a missing or non-directory skill folder.** A typo'd/absent positional folder
124
+ previously minted a session and spawned the task turn before infra-failing (exit 2), leaving a stray run
125
+ dir behind. `resolveCritiquedSkillDir` now `existsSync`/`isDirectory`-checks the folder up front — before
126
+ any session is minted or spawned — so a bad path exits 2 immediately with nothing left on disk. A
127
+ present-but-`SKILL.md`-less folder still defers to the packager's degraded flow, unchanged.
128
+ - **`critique`'s report header and stderr diagnostics now say `critique:` (were `skill-critique:`).** A
129
+ leftover label from the `scripts/skill-critique.ts` instrument; the invoked command is `critique`.
130
+ Cosmetic, no schema change.
131
+
9
132
  ## [1.8.0] — 2026-07-23
10
133
 
11
134
  ### Added
package/CONTRIBUTING.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  Thanks for helping make Cowork skill-testing reproducible.
4
4
 
5
- For the full documentation map, see [docs/README.md](./docs/README.md).
5
+ For the full documentation map, see [docs/README.md](./docs/README.md). Read [AGENTS.md](./AGENTS.md) —
6
+ the canonical agent/architecture contract (architecture seams, invariants) — before changing code.
6
7
 
7
8
  ## Development setup
8
9
 
@@ -29,7 +30,8 @@ CI Stage 1 (the `build` job in `.github/workflows/ci.yml`) does **not** invoke `
29
30
  gate steps individually — e.g. `check:versions`, `format:check`, `typecheck`, `build`, a CLI smoke
30
31
  (`node dist/cli.js list`), three token-free `replay` gate fixtures, `verify-cassettes`, `lint`, and
31
32
  source-guard checks — while the unit suite runs separately as the 4-shard `test` job (see `ci.yml` for
32
- the authoritative list). Only `release.yml` calls `npm run ci`.
33
+ the authoritative list). Only `release.yml` calls `npm run ci`. For a copy-paste GitHub Actions setup,
34
+ see [ci-recipe.md](./.claude/skills/cowork-harness/references/ci-recipe.md).
33
35
 
34
36
  > **Cutting a release?** See [RELEASING.md](./RELEASING.md) for the branch → PR → tag → publish flow.
35
37
 
package/README.md CHANGED
@@ -91,7 +91,7 @@ node dist/cli.js replay examples/replays/example-pdf-skill.cassette.json
91
91
 
92
92
  > **Installed globally instead?** Once linked/installed, the same command is `cowork-harness replay
93
93
  > <cassette>` — but the relative path above only resolves from a source checkout's `examples/replays/`.
94
- > From a global install (`npm i -g "cowork-harness@>=1.8.0"`), point at the package root instead:
94
+ > From a global install (`npm i -g "cowork-harness@>=1.10.0"`), point at the package root instead:
95
95
  > `cowork-harness replay "$(npm root -g)/cowork-harness/examples/replays/example-pdf-skill.cassette.json"`
96
96
  > (or copy the cassette into your own project and pass that path).
97
97
 
@@ -101,7 +101,7 @@ Live `run`/`skill` need the prerequisites in the next section — note the `prot
101
101
  > - **Replay only (zero setup):** `cowork-harness replay <cassette>` — no token, no Docker, no agent. The command above.
102
102
  > - **`protocol` (real model, no Docker):** needs only the auth token (item 3 below).
103
103
  > - **Live `container` / `microvm` / `hostloop` / `cowork`:** needs Docker (or Lima for `microvm`), a staged agent, and the token — run `cowork-harness doctor` first.
104
- > - **Invocation:** from a source checkout, `node dist/cli.js <cmd>` (or `npm link` to get the `cowork-harness` command); from a global install, `cowork-harness <cmd>`; the companion skill falls back to `npx "cowork-harness@>=1.8.0"`.
104
+ > - **Invocation:** from a source checkout, `node dist/cli.js <cmd>` (or `npm link` to get the `cowork-harness` command); from a global install, `cowork-harness <cmd>`; the companion skill falls back to `npx "cowork-harness@>=1.10.0"`.
105
105
 
106
106
  Two more worked examples worth knowing about: `examples/scenarios/protocol-smoke.yaml` (zero-Docker smoke
107
107
  test) and `examples/scenarios/skill-loads.yaml` (container-tier acceptance check) — see
@@ -126,7 +126,7 @@ claude plugin marketplace add yaniv-golan/cowork-harness
126
126
  claude plugin install cowork-harness@cowork-harness
127
127
  ```
128
128
 
129
- The skill **self-bootstraps the CLI**: if `cowork-harness` isn't on your PATH it falls back to `npx "cowork-harness@>=1.8.0"` (a version floor that fails loud rather than silently fetching a too-old CLI; Node ≥ 20). Tiers above `protocol` still need Docker/Lima and a Claude Desktop agent binary — see the prerequisites below.
129
+ The skill **self-bootstraps the CLI**: if `cowork-harness` isn't on your PATH it falls back to `npx "cowork-harness@>=1.10.0"` (a version floor that fails loud rather than silently fetching a too-old CLI; Node ≥ 20). Tiers above `protocol` still need Docker/Lima and a Claude Desktop agent binary — see the prerequisites below.
130
130
 
131
131
  It also follows the open [Agent Skills](https://agentskills.io) spec, so it installs cross-editor (Cursor, Codex, OpenCode, …) via [`npx skills`](https://github.com/vercel-labs/skills) (Vercel Labs' CLI implementation of that spec):
132
132
 
@@ -147,7 +147,7 @@ A global install is enough for CI `lint`, reading the teaching skill, and replay
147
147
  To `run` the worked examples live or copy them as a starting point, use a source checkout. (The marketplace
148
148
  skill install itself only pulls `.claude/skills/cowork-harness/` — SKILL.md + `references/` + `scenario.py`/
149
149
  assertion keys, per `.claude-plugin/marketplace.json`'s `source` — not the rest of this table; the full set
150
- above becomes available once the skill's first command self-bootstraps `npx "cowork-harness@>=1.8.0"` — see
150
+ above becomes available once the skill's first command self-bootstraps `npx "cowork-harness@>=1.10.0"` — see
151
151
  [above](#drive-it-from-claude-code-companion-skill) — which pulls the same npm package as the global-install row.)
152
152
 
153
153
  ### Prerequisites for anything above `protocol` fidelity
@@ -363,6 +363,10 @@ L2 microvm parity Optional. Agent inside a real Linux microVM (Lima/Apple-VZ
363
363
  routes via host curl. A PreToolUse path-containment hook is the security boundary
364
364
  for real filesystem access at this tier — see docs/boundary.md.
365
365
 
366
+ Both container and hostloop also declare the skills/plugins skill/plugin-discovery SDK-MCP servers
367
+ (mcp__skills__list_skills/suggest_skills, mcp__plugins__list_plugins/search_plugins/
368
+ suggest_plugin_install) alongside cowork/workspace — see docs/fidelity-gaps.md.
369
+
366
370
  cowork Auto-picks hostloop vs container the way Cowork itself does — decoded from
367
371
  GrowthBook gate 1143815894 (Cowork's internal feature-flag system) in the
368
372
  synced baseline. "Do what real Cowork does."
@@ -396,7 +400,7 @@ Skill testing is the headline use, but the tool is a general harness over the Co
396
400
 
397
401
  | Command | What it does | Reach for it when… |
398
402
  |---|---|---|
399
- | `skill <folder> "<prompt>"` | Run a local skill/plugin folder once against the staged agent; `--compact`/`--demo` trim output for shareable screenshots/GIFs; `--ablate-skill` runs the same prompt with the skill removed (a negative control for skill-lift); `--label <tag>` stamps a generation name for the iterate-across-fixes loop (surfaced in `result.json`/index/`inspect`); `--repeat N` (2-100) runs the same skill+prompt N times and aggregates a variance rollup — "did this finding reproduce, or did it pass once?"; `--allow-missing-capability` stops a capability false-negative on the lean `core` image from failing the verdict (open-ended equivalent of asserting `allow_missing_capability: true`) | ad-hoc "is the skill alive / does it do X?" — the fast inner loop |
403
+ | `skill <folder> "<prompt>"` | Run a local skill/plugin folder once against the staged agent; an unanswered gate's `--on-unanswered` defaults adaptively (`prompt` on a TTY, `fail` in CI/non-TTY); `--compact`/`--demo` trim output for shareable screenshots/GIFs; `--ablate-skill` runs the same prompt with the skill removed (a negative control for skill-lift); `--label <tag>` stamps a generation name for the iterate-across-fixes loop (surfaced in `result.json`/index/`inspect`); `--repeat N` (2-100) runs the same skill+prompt N times and aggregates a variance rollup — "did this finding reproduce, or did it pass once?"; `--allow-missing-capability` stops a capability false-negative on the lean `core` image from failing the verdict (open-ended equivalent of asserting `allow_missing_capability: true`); `--allow-host-writes` consents to a writable `hostloop`-fidelity connected folder (same consent as clicking "connect folder" in Desktop; no effect off `hostloop` or without a writable `--folder`) | ad-hoc "is the skill alive / does it do X?" — the fast inner loop |
400
404
  | `run <scenario.yaml \| dir/>` | Run authored scenarios with `assert:` + a CI-ready exit code; a decider can answer unscripted gates; `--repeat`/`--matrix` add variance runs / a compatibility matrix (detail below); `--ablate-skill` runs the same prompt with the skill removed (a negative control for skill-lift) | you want a repeatable, **asserted regression test** — to **measure flakiness** instead of trusting one green — or to **test a compatibility matrix** (multiple baselines/models/skill variants) in one run |
401
405
  | `chat <folder> [prompt]` | Interactive multi-turn REPL against a skill (TTY); optional seed prompt is sent as the first turn. `--upload <file>` / `--folder <dir>` (repeatable) attach files/project folders; `--verbose` shows thinking blocks + tool inputs; `--fidelity protocol\|container\|hostloop` (no `microvm`/`cowork` in the REPL); `--allow-host-writes` consents to a writable `hostloop`-fidelity connected folder (same consent as clicking "connect folder" in Desktop); `--raw` skips the control protocol for native `docker run -it` (rejects `--upload`/`--folder`/`--plugin`/`--fidelity`) | debugging a multi-turn flow by hand |
402
406
  | `record` / `replay` | **Record a live run once → replay it token-free, Docker-free thereafter** (key flags below; `replay --explain` prints the evidence behind every passing assert) | **token-free, Docker-free CI** from a once-recorded run |
@@ -429,7 +433,7 @@ Skill testing is the headline use, but the tool is a general harness over the Co
429
433
 
430
434
  **Flags worth knowing** (the full list is always `<command> --help`):
431
435
  - `run`: a decider (`--decider-cmd <helper>`/`--decider-dir <dir>`, or a scenario's `on_unanswered: llm` — a scenario-YAML key, not a CLI flag; `run` rejects `--on-unanswered llm`) can answer unscripted gates; `--repeat N` (2-100) runs each scenario N times and aggregates a variance rollup instead of a single pass/fail (`--min-pass-rate`, `--stop-on-diverge`, `--max-budget-usd` tune the batch verdict/loop); `--matrix <matrix.yaml>` runs ONE scenario across the cross-product of baseline/model/skill_dir axes (worked example: `examples/matrices/csv-metrics-matrix.yaml`; `--max-cells`/`--concurrency` tune the cap/pool — any cell failing, assertion or infra, fails the run); `--compact`/`--demo` trim `run`/`skill` output for shareable screenshots/GIFs; `--label <tag>` (both `run` and `skill`) stamps a generation name for the iterate-across-fixes loop — surfaced in `result.json` (`runLabel`), the run-index row, `inspect`, and `status.json`, alongside the auto-recorded `skillCommit` (git provenance) and the authoritative `fingerprint.skillHash` a harvest step should pair critiques by.
432
- - `record`/`replay`: `replay --explain` prints the evidence trail behind every passing assert (the flagship false-green tool — text mode; `--output-format json` already carries `assertions[].evidence`); `--decider-llm`/`--decider-dir` answer gates live during recording; `--rerecord-stale`/`--concurrency <N>` batch re-record; `--assert-from <scenario.yaml>`/`--reassert` re-check the on-disk `assert:` instead of the frozen one; `--strict`/`--fail-on-skill-drift` control staleness handling on replay; `--no-redact` skips record-time redaction; `--allow-failing` relaxes the post-run verdict gate; `--dry-run` resolves without recording; `--force` overrides the refusal to overwrite a default-path cassette that belongs to a *different* scenario (a slug collision) — not a general overwrite-anything flag; `replay --best-effort-future-cassette` lets a cassette from a newer format version replay anyway (warn instead of the default hard error).
436
+ - `record`/`replay`: `replay --explain` prints the evidence trail behind every passing assert (the flagship false-green tool — text mode; `--output-format json` already carries `assertions[].evidence`); `--decider-llm`/`--decider-dir` answer gates live during recording; `record <dir/>` is itself a first-class batch input (recording a whole directory of scenarios), and `--rerecord-stale` re-records everything stale in one pass — `--concurrency <N>` bounds the parallelism for either; `--assert-from <scenario.yaml>`/`--reassert` re-check the on-disk `assert:` instead of the frozen one; `--strict`/`--fail-on-skill-drift` control staleness handling on replay; `--no-redact` skips record-time redaction; `--allow-failing` relaxes the post-run verdict gate; `--dry-run` resolves without recording; `--force` overrides the refusal to overwrite a default-path cassette that belongs to a *different* scenario (a slug collision) — not a general overwrite-anything flag; `replay --best-effort-future-cassette` lets a cassette from a newer format version replay anyway (warn instead of the default hard error).
433
437
  - `verify-cassettes`: privacy scan (email/currency/domain/path/machine-inventory) + staleness — exit 1 when verification RAN and found a real problem (a finding, a genuine drift, or scenario-prompt drift), exit 3 when it could NOT complete (an unverifiable-class staleness finding, a too-new cassette format, or a read error); whole-token allows via `--allow <regex>` (a pattern) / class-scoped `--allow-domain` / `--allow-email` / `--allow-path` / `--allow-machine-inventory` / `--allow-patterns-file <path>` (a **file** of patterns, one regex per line); `--skip-privacy` or `--skip-staleness` runs only part of the gate; a diverged scenario `prompt` vs. the cassette's frozen prompt is also a hard fail (its own `scenarioDrift` bucket), opt out with `--skip-scenario-drift`; `--margins` adds a per-cassette recorded-vs-budget report for count-bound assertions (a single-sample estimate — diagnostic only, never changes the gate verdict).
434
438
  - `stats`: reads `<runsRoot>/index.jsonl`, written automatically at every result; `--since`/`--baseline`/`--branch` filter; `--last <n>` windows per-scenario; `--reindex` rebuilds the index from the physical run-dir tree (the migration path for pre-index runs).
435
439
  - `diff`: `--changelog` renders known-field prose for a baseline diff; `--view tools|transcript|artifacts|meta` narrows a run/cassette diff to one section; normalization (default on) masks per-run noise (ids/timestamps/session markers/host paths) so two runs of the same scenario diff as identical — `--no-normalize` compares raw values.
@@ -690,7 +694,7 @@ jobs:
690
694
  anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
691
695
  ```
692
696
 
693
- Every run writes a Markdown verdict table (scenario, pass/fail, signals, cost/turns when available, staleness findings, and the replay-skipped-assertions honesty line) to the job summary. Inputs: `command`, `path` (required), `version` (npm dist-tag/version, default `latest` — intentional so recipes track the current release; pin an exact version for reproducible CI. The companion skill's `cowork-harness@>=1.8.0` floor guidance applies to ad-hoc CLI installs, not this input), `strict` (applies to `replay` (staleness findings), `lint`/`lint-skill` (WARN/INFO), and `analyze-skill` (any advisory finding); IGNORED — not forwarded — for `verify-cassettes`/`run`, which don't accept the flag), `fail-on-skill-drift` (**`replay`-only** — never forwarded to the analyzers), `extra-args`, `summary` (default `true`), `anthropic-api-key` (live lane only). See [`action.yml`](./action.yml) for the full input reference.
697
+ Every run writes a Markdown verdict table (scenario, pass/fail, signals, cost/turns when available, staleness findings, and the replay-skipped-assertions honesty line) to the job summary. Inputs: `command`, `path` (required), `version` (npm dist-tag/version, default `latest` — intentional so recipes track the current release; pin an exact version for reproducible CI. The companion skill's `cowork-harness@>=1.10.0` floor guidance applies to ad-hoc CLI installs, not this input), `strict` (applies to `replay` (staleness findings), `lint`/`lint-skill` (WARN/INFO), and `analyze-skill` (any advisory finding); IGNORED — not forwarded — for `verify-cassettes`/`run`, which don't accept the flag), `fail-on-skill-drift` (**`replay`-only** — never forwarded to the analyzers), `extra-args`, `summary` (default `true`), `anthropic-api-key` (live lane only). See [`action.yml`](./action.yml) for the full input reference.
694
698
 
695
699
  The provided [GitHub Actions workflow](.github/workflows/ci.yml) runs a **seven-stage pipeline**. The **build** + **test** stages are the token-free gate you can copy into your skill repo; the `action-self-test`, `python`, `boundary`, `scenarios`, and `parity-drift` stages are this repo's own fidelity self-tests and are not directly portable (they build the harness's Docker image and run harness-specific e2e scenarios — see [`ci-recipe.md`](./.claude/skills/cowork-harness/references/ci-recipe.md) for the skill-repo template):
696
700
 
@@ -734,6 +738,8 @@ Most runs need **none** of these — the defaults are correct. They're grouped b
734
738
  - `COWORK_HARNESS_JUDGE_MODEL` — the default model for `semantic_matches`' LLM judge (overridden by a
735
739
  per-assertion `judge_model`; falls back to the pinned default `claude-opus-4-8`). Pin it alongside `judge_model` for a
736
740
  reproducible before/after comparison across re-records.
741
+ - `COWORK_HARNESS_EVALUATOR_MODEL` — the default `critique` grading model (overridden by the `--evaluator-model` flag;
742
+ falls back to the pinned default `claude-opus-4-8`).
737
743
  - `COWORK_HARNESS_RUNS_DIR` (or the `--run-dir <path>` flag — a **global** flag that must precede the subcommand — `--dotenv` follows the same rule everywhere except `critique`, which also takes it per-command) — override the default run-output root `~/.cowork-harness/runs` (kept out of any working tree so sensitive skill inputs/outputs don't land in a repo). Precedence: `--run-dir` > env > default. The root is flat and machine-global (shared across projects); pinned `--session-id` runs are guarded against cross-project overwrite, and `prune` never prunes them. In CI, set it to a workspace path (e.g. `runs`) so artifact upload can collect the runs. `COWORK_HARNESS_ALLOW_FOREIGN_RESUME=1` overrides the guard that blocks `--resume` onto another project's pinned session.
738
744
  - **Networking / loop:** `COWORK_EGRESS_PROXY` overrides the egress-proxy URL injected into the sandbox; `COWORK_PROXY_IMAGE` overrides the egress proxy Docker image name (default `cowork-egress-proxy:2`); `COWORK_DOCKER_NETWORK` pins the Docker network the agent container joins; `CLAUDE_FORCE_HOST_LOOP=1` forces the host-loop path regardless of the baseline's loop decision (the `cowork` tier's auto-pick). `COWORK_LIMACTL` overrides the `limactl` binary path (default `/opt/homebrew/bin/limactl`).
739
745
  - `COWORK_HARNESS_PRERUN_HASH_CAP` — override the default cap on pre-run file hashing (bytes); raise it if `input_unmodified`/`no_unexpected_files` report evidence unavailable on a large connected folder.
@@ -752,7 +758,7 @@ Most runs need **none** of these — the defaults are correct. They're grouped b
752
758
  - **Staleness boundary** (the git-tracked-files-only default and its `git add`/hard-fail rationale are explained in [Test a local skill](#test-a-local-skill-in-one-command)): a **non-repo** source dir falls back to a raw walk instead; OS-junk like `.DS_Store` is always excluded; a partial exclusion emits a `::notice:: [stage]`. `COWORK_HARNESS_GITSET=0` opts out to a raw walk for every dir (and copies untracked files too); `COWORK_HARNESS_DEBUG_SKILLHASH=1` dumps the exact file set feeding the staleness hash on a mismatch (and flags OS-junk) so a drift source is one line. Declare per-plugin non-runtime paths in a `.cowork-hashignore` file (or the session `staleness.hash_ignore`). `COWORK_HARNESS_AGENT_SCOPE=skill` (opt-in) refines a scenario's `skills:` scope so a **skill-named** `agents/<name>.md` re-stales only that skill's cassettes instead of the whole fleet (generic agents stay shared; stamped into the fingerprint, so flipping it is a one-time re-record like `GITSET`).
753
759
  - **`skill` / `chat` defaults:** `COWORK_HARNESS_FIDELITY` sets the default fidelity tier for ad-hoc `skill`/`chat` runs (a `--fidelity` flag or a scenario's `fidelity:` still wins); `COWORK_HARNESS_MODEL` sets the default model; `COWORK_HARNESS_OUTPUT_FORMAT` (`text`|`json`) sets the default output format. Each is overridden by the matching explicit flag. **Caveat:** `chat` only accepts `protocol`/`container`/`hostloop` — `microvm` and `cowork` are rejected (no Lima/auto-pick plumbing in the interactive REPL), so a `COWORK_HARNESS_FIDELITY` set to either is rejected loudly for `chat` even though `skill` accepts the full tier set.
754
760
  - **Secret scrubbing:** `COWORK_HARNESS_SCRUB_KEYS=<KEY1,KEY2>` adds extra env-var names whose values are redacted from logs (beyond the known auth tokens + `ANTHROPIC_CUSTOM_HEADERS`); `COWORK_HARNESS_SCRUB_VALUES=<v1,v2>` redacts literal values regardless of env. **Committed-cassette redaction:** `COWORK_HARNESS_REDACT_PATTERNS=<rx1,rx2>` / `COWORK_HARNESS_REDACT_KEYS=<k1,k2>` extend the privacy layer that scrubs recorded `controlOut` before a cassette is written for commit.
755
- - L2 microVM: `COWORK_VM_GATEWAY` overrides the Lima host-proxy gateway IP (default `192.168.5.2`); `COWORK_VM_PROXY_PORT` pins the egress-proxy port (unset, the host binds an OS-assigned free port and threads that same value into the guest firewall + `HTTP(S)_PROXY`). The Lima instance is named `cowork-vm-<config-hash>` (a config change → a fresh VM); `COWORK_LIMA_INSTANCE` pins a fixed name, and `vm prune` removes orphaned ones.
761
+ - L2 microVM: `COWORK_VM_GATEWAY` overrides the Lima host-proxy gateway IP (default `192.168.5.2`; must be a canonical IPv4 literal — an invalid value is rejected, since it is interpolated into the guest firewall rule); `COWORK_VM_PROXY_PORT` pins the egress-proxy port (unset, the host binds an OS-assigned free port and threads that same value into the guest firewall + `HTTP(S)_PROXY`). The Lima instance is named `cowork-vm-<config-hash>` (a config change → a fresh VM); `COWORK_LIMA_INSTANCE` pins a fixed name, and `vm prune` removes orphaned ones.
756
762
  - **Advanced / internal escape hatches** (rarely needed): `PYTHON` overrides the interpreter for `lint` / scenario tooling (default `python3`); `COWORK_HARNESS_DEBUG=1` surfaces which `.env` files were loaded; `COWORK_HARNESS_CLAUDE_BIN=<path>` points the `--decider-llm` transport at a specific `claude` binary; `COWORK_HARNESS_ALLOW_AGENT_FALLBACK=1` lets the harness use the newest sibling agent binary when the baseline-pinned version is missing (a fidelity compromise — off by default) — a same-major.minor **patch** bump of the staged NATIVE binary is auto-accepted without this flag (the native binary carries no sha256 pin, so a patch drift is safe by default; it prints a loud stderr note naming the pinned and substituted versions). At `hostloop`, and at `cowork` **only when it resolves to host-loop** on the synced baseline, the staged **VM ELF** is auto-accepted on a patch bump too, because on that path it's a non-executed parity mount into the bash sidecar; a `cowork` baseline that resolves to VM-loop instead executes the ELF directly, so it keeps the strict sha-pinned exact-version match, same as `container`/`microvm`, which always keep it (the ELF is the executed agent there), so the flag remains required for any ELF drift on those tiers/paths, and for a major/minor gap everywhere; `COWORK_MANAGED_CONFIG=1` forces the managed-config path on `protocol`; `COWORK_HARNESS_ALLOW_MISSING_PROMPT=1` downgrades a missing prompt asset to a warning; `COWORK_HARNESS_SAFE_STAGING_PREFIX=<a,b>` whitelists prefixes under which a delete in `outputs/` is allowed (otherwise delete-in-outputs fails loud); `COWORK_HARNESS_NO_HEARTBEAT=1` disables the idle-run heartbeat, `COWORK_HARNESS_HEARTBEAT_MS` tunes its interval (default 30000ms / 30s); `COWORK_HARNESS_CLI=/path/to/cli.js` overrides which built CLI the Python `cowork` pytest lane drives (see python/README.md).
757
763
  - Pin `baseline: desktop-<ver>` and `model:` in a session for byte-stable runs; use `latest` to track.
758
764
 
@@ -801,7 +807,7 @@ This repo is built to be driven by agents, not just read by humans:
801
807
  - **[AGENTS.md](./AGENTS.md)** — the canonical agent-instructions file (architecture seams, the build gate, invariants, ethos). Read it before changing code. Also indexed in **[llms.txt](./llms.txt)**.
802
808
  - **Companion skill** — [`.claude/skills/cowork-harness/`](./.claude/skills/cowork-harness/SKILL.md) teaches an agent to drive the harness; install it via the marketplace (see [above](#drive-it-from-claude-code-companion-skill)).
803
809
  - **Machine-readable interfaces** — stable `--output-format json` envelope on stdout, deterministic exit codes (`0`/`1`/`2`/`3`, with a couple of documented per-command exceptions — see [SPEC.md](./SPEC.md) for the full table), and `--help` on every command.
804
- - **JSON Schemas** — [`schema/scenario.schema.json`](./schema/scenario.schema.json) and [`schema/session.schema.json`](./schema/session.schema.json) describe every field of the YAML you author (generated from the source schemas; `npm run schema`). [`schema/protocol.v1.json`](./schema/protocol.v1.json) (hand-authored) schemas the harness's own control-channel wire protocol, with a golden vector pack at [`fixtures/protocol/v1/`](./fixtures/protocol/v1/) — see [docs/protocol.md](./docs/protocol.md).
810
+ - **JSON Schemas** — [`schema/scenario.schema.json`](./schema/scenario.schema.json) and [`schema/session.schema.json`](./schema/session.schema.json) describe every field of the YAML you author (generated from the source schemas; `npm run schema`). [`schema/protocol.v1.json`](./schema/protocol.v1.json) (hand-authored) schemas the harness's own control-channel wire protocol, with a golden vector pack at [`fixtures/protocol/v1/`](./fixtures/protocol/v1/) — see [docs/protocol.md](./docs/protocol.md). [`schema/critique-report.json`](./schema/critique-report.json) describes `critique`'s JSON report / `critique-report.json` artifact for automation consumers (budget pacers, harvesters) — **descriptive, not §12-frozen** while critique is EXPERIMENTAL (see [SPEC.md §12](./SPEC.md#12-versioning--the-10-compatibility-contract)).
805
811
 
806
812
  `AGENTS.md`, `SPEC.md`, and `DESIGN.md` **are** shipped in the npm package (see `package.json` `files`) —
807
813
  a global install has them locally too, not just on GitHub.
@@ -840,6 +846,7 @@ a global install has them locally too, not just on GitHub.
840
846
  | [examples/README.md](./examples/README.md) | The worked examples to copy — sessions, scenarios, and skills you can run end-to-end (from a source checkout — a global npm install's only runnable `examples/` subtree is `replays/`; see the "What ships" table). |
841
847
  | [SECURITY.md](./SECURITY.md) | Threat model — the sandbox is a fidelity fixture, not a security boundary. |
842
848
  | [RELEASING.md](./RELEASING.md) | The release flow — branch → PR → tag → npm publish. |
849
+ | [llms.txt](./llms.txt) | The AI-agent index — a machine-readable map of this repo's docs for an agent bootstrapping context. |
843
850
 
844
851
  ## Versioning
845
852
 
package/RELEASING.md CHANGED
@@ -50,14 +50,14 @@ Never push the tag before CI is green for the exact commit you intend to tag. Th
50
50
  enforces this (`Require ci.yml success for this commit` step), but don't rely on it — tag a green
51
51
  SHA.
52
52
 
53
- **Tag the MERGE COMMIT (main HEAD after the merge), never the release-branch head — and here's why.**
54
- The publish gate (`require-ci-success`) queries `ci.yml` runs with `--event push` for the tagged SHA.
55
- `ci.yml` only triggers `on: push` for **`main`** (plus `pull_request` / `workflow_dispatch`), so a
56
- release-branch/PR head has *only* a `pull_request` run — which the `--event push` filter ignores.
57
- Tagging that SHA makes the gate poll ~30 min and then FAIL. Only the merge commit (produced by
58
- `gh pr merge`, then `git pull`ed onto `main`) has a push-event `ci.yml` run. This is why Phase 3 tags
59
- `main` HEAD after the merge — do **not** "optimize" by tagging the branch commit whose PR CI you just
60
- watched go green.
53
+ > **Tag the MERGE COMMIT (main HEAD after the merge), never the release-branch head — and here's why.**
54
+ > The publish gate (`require-ci-success`) queries `ci.yml` runs with `--event push` for the tagged SHA.
55
+ > `ci.yml` only triggers `on: push` for **`main`** (plus `pull_request` / `workflow_dispatch`), so a
56
+ > release-branch/PR head has *only* a `pull_request` run — which the `--event push` filter ignores.
57
+ > Tagging that SHA makes the gate poll ~30 min and then FAIL. Only the merge commit (produced by
58
+ > `gh pr merge`, then `git pull`ed onto `main`) has a push-event `ci.yml` run. This is why Phase 3 tags
59
+ > `main` HEAD after the merge — do **not** "optimize" by tagging the branch commit whose PR CI you just
60
+ > watched go green.
61
61
 
62
62
  When you query runs by SHA, use the **full 40-char SHA** (`git rev-parse HEAD`) —
63
63
  `gh run list --commit <short-sha>` silently returns empty. If you mis-tag: `git push origin
package/SPEC.md CHANGED
@@ -793,6 +793,13 @@ Covered-surface changes follow semver as of `1.0.0` — see [RELEASING.md](./REL
793
793
  the `doctor`/`verify-cassettes`/RunResult envelopes above, these have no `schema/*.json` and may change
794
794
  (fields, rule ids, the artifact-write-back finding shape) while the analyzers stabilize. Parse at your
795
795
  own risk until they are promoted to a covered surface.
796
+ - **The `critique` report** (`--output-format json` / the `critique-report.json` run-dir artifact) —
797
+ NOT yet frozen, and — uniquely — it DOES have a schema file: `schema/critique-report.json` is
798
+ **descriptive** (authoritative field names/shapes, test-pinned against the builder) so automation can
799
+ parse against a schema rather than prose, but it is NOT this section's compatibility contract —
800
+ critique is EXPERIMENTAL and additive field changes may land in any minor release (the schema's own
801
+ `description` says so). Its presence in the surface-drift baseline is for change *visibility*, not
802
+ coverage. It is the promotion CANDIDATE once critique stabilizes, on the `doctor.json` template.
796
803
  - **`docs/internal/**`** — untracked working notes.
797
804
  - **The reconstructed system-prompt append text** — a paraphrase by design (see
798
805
  [docs/fidelity-gaps.md](./docs/fidelity-gaps.md)); behaviorally equivalent, not byte-stable.