cowork-harness 0.26.0 → 0.28.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/.claude/skills/cowork-harness/SKILL.md +241 -82
- package/.claude/skills/cowork-harness/references/ci-recipe.md +7 -6
- package/.claude/skills/cowork-harness/references/fidelity-and-answers.md +2 -2
- package/.claude/skills/cowork-harness/references/scenario-schema.md +114 -29
- package/.claude/skills/cowork-harness/references/task-recipes.md +62 -7
- package/.claude/skills/cowork-harness/scripts/assertion-keys.json +25 -1
- package/.claude/skills/cowork-harness/scripts/scenario.py +231 -4
- package/.env.example +7 -1
- package/CHANGELOG.md +427 -0
- package/CONTRIBUTING.md +12 -8
- package/DESIGN.md +4 -4
- package/README.md +190 -86
- package/RELEASING.md +123 -0
- package/SPEC.md +45 -26
- package/baselines/desktop-1.18286.2.json +296 -0
- package/baselines/desktop-1.19367.0.json +373 -0
- package/baselines/prompts/cowork-system-prompt-fingerprints.json +22 -0
- package/dist/agent/session.js +119 -19
- package/dist/agent/timeline.js +141 -0
- package/dist/assert.js +570 -73
- package/dist/boundary-paths.js +7 -6
- package/dist/cli.js +225 -108
- package/dist/critique/evaluator.js +199 -0
- package/dist/critique/evidence.js +60 -0
- package/dist/critique/package-evidence.js +141 -0
- package/dist/decide/external-channel.js +8 -6
- package/dist/decide/semantic-judge.js +146 -0
- package/dist/egress/proxy.js +23 -8
- package/dist/egress/sidecar.js +46 -8
- package/dist/errors.js +1 -1
- package/dist/hostloop/cowork-handler.js +224 -0
- package/dist/hostloop/workspace-handler.js +1 -2
- package/dist/io.js +1 -1
- package/dist/redact.js +2 -3
- package/dist/run/artifacts.js +218 -10
- package/dist/run/assemble-run-result.js +17 -0
- package/dist/run/cassette.js +1130 -228
- package/dist/run/chat-result.js +122 -0
- package/dist/run/chat.js +40 -7
- package/dist/run/computer-links.js +97 -28
- package/dist/run/diff.js +5 -5
- package/dist/run/display-translate.js +3 -3
- package/dist/run/doctor.js +23 -6
- package/dist/run/envelope.js +30 -11
- package/dist/run/execute.js +401 -47
- package/dist/run/matrix.js +21 -12
- package/dist/run/pre-run-manifest.js +157 -15
- package/dist/run/renderer.js +10 -3
- package/dist/run/repeat.js +10 -6
- package/dist/run/run-index.js +21 -3
- package/dist/run/run-status.js +43 -15
- package/dist/run/run.js +466 -34
- package/dist/run/runs-gc.js +48 -3
- package/dist/run/skill-files.js +7 -2
- package/dist/run/skill-hash.js +2 -2
- package/dist/run/skill-metadata.js +82 -0
- package/dist/run/timeline-fold.js +85 -0
- package/dist/run/trace-view.js +248 -23
- package/dist/run/verdict.js +15 -3
- package/dist/run/vm-path-ctx-file.js +2 -2
- package/dist/runtime/argv.js +35 -31
- package/dist/runtime/container.js +23 -6
- package/dist/runtime/hostloop-stage.js +15 -2
- package/dist/runtime/hostloop.js +44 -37
- package/dist/runtime/microvm.js +2 -4
- package/dist/runtime/protocol.js +15 -0
- package/dist/runtime/resource-sampler.js +252 -0
- package/dist/runtime/stage.js +7 -0
- package/dist/scan.js +6 -1
- package/dist/session.js +241 -47
- package/dist/sync/baseline-diff.js +1 -1
- package/dist/sync/cowork-sync.js +208 -27
- package/dist/types.js +200 -18
- package/docs/README.md +15 -11
- package/docs/boundary.md +1 -1
- package/docs/cassette.md +102 -23
- package/docs/cowork-spawn-contract-1.12603.1.md +2 -2
- package/docs/debugging.md +20 -5
- package/docs/decisions/2026-07-07-verification-strictness-and-fidelity.md +74 -0
- package/docs/discovery.md +6 -4
- package/docs/gotchas.md +19 -0
- package/docs/invariants.md +10 -7
- package/docs/maintenance.md +17 -8
- package/docs/plugin-root.md +78 -0
- package/docs/protocol.md +4 -0
- package/docs/run-status.md +5 -1
- package/docs/scenario.md +100 -23
- package/docs/session.md +25 -7
- package/docs/stats.md +13 -7
- package/examples/README.md +18 -4
- package/examples/replays/README.md +34 -4
- package/examples/replays/example-multiselect-gate.cassette.json +111 -53
- package/examples/replays/example-pdf-skill.cassette.json +184 -31
- package/examples/replays/hostloop-computer-links.cassette.json +97 -23
- package/llms.txt +2 -2
- package/package.json +11 -2
- package/python/README.md +7 -3
- package/python/cowork_harness.py +40 -1
- package/schema/cassette.v10.json +328 -0
- package/schema/cassette.v9.json +182 -0
- package/schema/run-result.json +866 -73
- package/schema/scenario.schema.json +220 -14
- package/schema/session.schema.json +48 -35
- package/schema/verify-cassettes.json +17 -5
|
@@ -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. 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: 0.
|
|
7
|
-
tracks-harness: cowork-harness 0.
|
|
6
|
+
version: 0.28.0
|
|
7
|
+
tracks-harness: cowork-harness 0.28.0 (baseline desktop-1.19367.0)
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# cowork-harness
|
|
@@ -22,11 +22,11 @@ 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 0.
|
|
26
|
-
> `desktop-1.
|
|
27
|
-
> `docs/*.md` over this snapshot, and re-run the bundled linter.
|
|
25
|
+
> **Version note:** the facts and `file:line` pointers here track `cowork-harness 0.28.0` (baseline
|
|
26
|
+
> `desktop-1.19367.0`). If your checkout is newer, prefer the live `--help` and — in a repo checkout —
|
|
27
|
+
> `SPEC.md` / `docs/*.md` over this snapshot, and re-run the bundled linter.
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Preflight — make sure the harness can actually run
|
|
30
30
|
|
|
31
31
|
The 10-second inner loop, once the CLI is on PATH:
|
|
32
32
|
|
|
@@ -38,31 +38,54 @@ cowork-harness skill ./my-skill "do X" # run the skill once against the sta
|
|
|
38
38
|
Before the first command, confirm the CLI is reachable and **fail loud** (never fake a pass) when a tier's dependencies are missing:
|
|
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
|
-
- **CLI on PATH, recent enough?** Run `cowork-harness --version` — this skill needs **≥ 0.
|
|
41
|
+
- **CLI on PATH, recent enough?** Run `cowork-harness --version` — this skill needs **≥ 0.28.0**. If it's missing or older, prefix every command with the version floor `npx "cowork-harness@>=0.28.0" <cmd>` (Node ≥ 20), or install once with `npm i -g "cowork-harness@>=0.28.0"`. **Pin `@>=0.28.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. (≥ 0.28.0 is what gates the commands/assertions this skill teaches: `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`, 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, `verify-cassettes --allow-domain`/`--allow-email`/`--allow-path`/`--allow-patterns-file` (`path` — local absolute filesystem paths — is the scanner's 4th class, new in 0.21.0; `--allow-patterns-file <path>` is a FILE of patterns, one regex per line — not a path to allow, unlike `--allow <regex>`), scenario `skills:` staleness scoping with `COWORK_HARNESS_AGENT_SCOPE=skill`, `chat --plugin`, `/help` in the REPL, `hostloop`'s native host/VM process split with its `allow_host_writes:` consent field, `computer_links_resolve` (new in 0.22.0), and `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; new in 0.27.0), 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; new in 0.28.0).)
|
|
42
42
|
- **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.
|
|
43
43
|
- **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.
|
|
44
44
|
- **Auth.** `CLAUDE_CODE_OAUTH_TOKEN` (preferred) or `ANTHROPIC_API_KEY`, via env or `.env`. Minting an OAuth token needs the **`claude` CLI** (`npm i -g @anthropic-ai/claude-code`, then `claude setup-token`).
|
|
45
|
+
- **`--dotenv` is a GLOBAL flag — put it BEFORE the subcommand.** `cowork-harness --dotenv .env record …`, never `cowork-harness record … --dotenv .env`. Every *other* flag is subcommand-level, so muscle memory fights this one; the harness rejects the misplaced form with an exact-fix error, but placing it first avoids the round-trip.
|
|
45
46
|
|
|
46
|
-
##
|
|
47
|
+
## Orient — the three loops
|
|
48
|
+
|
|
49
|
+
Everything you do with the harness is one of **three loops**, and the rest of this skill is organized
|
|
50
|
+
into three Parts to match: **author** a scenario (Part I), **run / record / lock** it into a
|
|
51
|
+
reproducible regression (Part II), and **debug** a run that misbehaved or greened when it shouldn't
|
|
52
|
+
(Part III — reachable straight from here in one hop). Pick the loop you're in:
|
|
47
53
|
|
|
48
54
|
- **"Is it even alive?"** (inner loop) → `cowork-harness skill <folder> "<prompt>"`. Fastest; no
|
|
49
55
|
scenario file.
|
|
50
56
|
- **Repeatable, asserted regression** → author a `scenarios/*.yaml` and run `cowork-harness run`.
|
|
51
57
|
This is the CI-grade path and most of this skill.
|
|
52
|
-
- **
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
- **Regression-test your skill's ANSWER quality** (not just its behavior — does its guidance still lead to
|
|
59
|
+
correct answers after you edit it?) → author `semantic_matches` scenarios and gate on the per-claim
|
|
60
|
+
profile. See **Recipe 5** in `references/task-recipes.md` (validity, N≥3, discrimination — the traps).
|
|
61
|
+
- **A run failed — or greened and you don't trust it** (the debugging loop) → don't re-run and hope.
|
|
62
|
+
The run already wrote its evidence to a **kept run dir** (`~/.cowork-harness/runs/…`; `--keep` prints
|
|
63
|
+
the path, `trace <run-id>` finds it). **Localize the failure post-hoc** from that evidence:
|
|
64
|
+
`cowork-harness trace <run-dir>`'s views + the emitted `result.json` to see what the run actually did,
|
|
65
|
+
then `verify-run` to re-check a suspect assertion — all token-free, no Docker, no re-record. This is
|
|
66
|
+
the loop 0.28.0's observability is built for; the *Triage* and *Inspecting a run's observability
|
|
67
|
+
output* sections in **Part III — Debug** are the detail (the fuller human-facing map lives in
|
|
68
|
+
`docs/debugging.md` — repo-only, not shipped with the installed skill).
|
|
69
|
+
- **Multi-turn / interactive reproduction** → `cowork-harness chat` (interactive; gates answered at the
|
|
70
|
+
TTY, **not** an asserted test — see *Debugging with `chat`* in **Part III — Debug**).
|
|
55
71
|
|
|
56
72
|
Full command set: `skill · run · chat · record · replay · verify-cassettes · rehash · prune · lint ·
|
|
57
73
|
verify-run · trace · inspect · diff · stats · decide · gates · answer · scaffold · assertions --list · sync ·
|
|
58
|
-
list · boundary-check · status · vm <init|status|delete|prune> · doctor`. Always check `cowork-harness <cmd> --help`.
|
|
74
|
+
list · boundary-check · status · vm <init|status|delete|prune> · doctor · init-redact`. Always check `cowork-harness <cmd> --help`.
|
|
59
75
|
|
|
60
76
|
**Two different `scaffold` tools — don't confuse them.** The native `cowork-harness scaffold <run-id>`
|
|
61
77
|
above turns an already-*recorded* run into a scenario (needs a run to exist first). The bundled
|
|
62
|
-
`scripts/scenario.py scaffold --name … --skill …`
|
|
63
|
-
|
|
78
|
+
`scripts/scenario.py scaffold --name … --skill …` — see *Scaffold a valid scenario, then lint before
|
|
79
|
+
you push* in **Part I** — builds a scenario from flags alone, no run required. Passing that section's
|
|
80
|
+
flag set to the native command fails with `unknown flag: --name` (exit 2).
|
|
81
|
+
|
|
82
|
+
## Part I — AUTHOR a scenario
|
|
83
|
+
|
|
84
|
+
Everything below composes one deterministic, asserted `scenarios/*.yaml`: the session/scenario split,
|
|
85
|
+
how the skill mounts, the fidelity tier, the answer path, the two assertion axes, `web_fetch`
|
|
86
|
+
provenance, and the scaffold/lint tools that keep the YAML honest.
|
|
64
87
|
|
|
65
|
-
|
|
88
|
+
### Two files: session vs scenario
|
|
66
89
|
|
|
67
90
|
- **`sessions/*.yaml`** — pre-prompt setup: `model`, mounts (`folders`), and discovery
|
|
68
91
|
(marketplaces / plugins / skills / mcp). One session is reused by many scenarios. A scenario that
|
|
@@ -73,7 +96,7 @@ This split matters: release ground truth (`baseline:` / `baselines/`, produced b
|
|
|
73
96
|
**separate** from authored setup (`session:` / `sessions/`). "profile" is retired vocabulary — do
|
|
74
97
|
not use it. See `references/scenario-schema.md` for every field.
|
|
75
98
|
|
|
76
|
-
|
|
99
|
+
### Discovery: how the skill-under-test gets mounted
|
|
77
100
|
|
|
78
101
|
The skill is **copied fresh into the sandbox each run**. Wire it via `plugins.local_plugins` +
|
|
79
102
|
`plugins.enabled: [<plugin>@local]` in the session (or `--marketplace` / `--plugin` flags on
|
|
@@ -83,13 +106,17 @@ the folder basename (collision-resolved); there is no `to:` override. See `refer
|
|
|
83
106
|
|
|
84
107
|
> **`git add` a brand-new skill before testing it.** Inside a git repo the harness stages the
|
|
85
108
|
> **git-tracked** files (the fidelity boundary — real Cowork installs from a repo and sees only committed
|
|
86
|
-
> files).
|
|
109
|
+
> files). *Tracked* means **in the git index** (committed **or** `git add`-staged); the **content** staged
|
|
110
|
+
> is your **working tree**, so an uncommitted edit to an already-tracked file *is* tested — you needn't
|
|
111
|
+
> commit to iterate. Only brand-new (untracked) files must be `git add`-ed to appear. Commit before you
|
|
112
|
+
> record the **locking cassette**, though: real Cowork ships the *committed* tree, so a green on
|
|
113
|
+
> uncommitted edits isn't yet a green on what installs. An **all-untracked** skill folder used to mount *empty* and the agent reported "the skill isn't
|
|
87
114
|
> installed" then did the work itself — a green-looking run where the skill never loaded. That now
|
|
88
115
|
> **hard-fails** (`BoundaryError`, exit 3) naming the dir, and a partially-tracked folder emits a loud
|
|
89
116
|
> `::notice:: [stage]` listing the excluded files. Fix: `git add` the skill, or `COWORK_HARNESS_GITSET=0`
|
|
90
117
|
> to copy untracked files (won't reflect what ships). A folder **outside** any repo is copied raw (no guard).
|
|
91
118
|
|
|
92
|
-
|
|
119
|
+
### Choose a fidelity tier
|
|
93
120
|
|
|
94
121
|
| Tier | What it gives you | Use when |
|
|
95
122
|
|---|---|---|
|
|
@@ -102,7 +129,7 @@ Set the tier in the **scenario's `fidelity:` field**, not a flag — `run` rejec
|
|
|
102
129
|
(it's a `skill`/`chat` flag; `run` takes fidelity only from the scenario). See
|
|
103
130
|
`references/fidelity-and-answers.md`.
|
|
104
131
|
|
|
105
|
-
|
|
132
|
+
### Choose an answer path (gates: AskUserQuestion + tool-permission)
|
|
106
133
|
|
|
107
134
|
Default to **deterministic**: scripted `answers:` + `on_unanswered: fail`. Anything that brings a
|
|
108
135
|
live model into answering flags the run `nonDeterministic` — keep those out of deterministic
|
|
@@ -131,7 +158,7 @@ anchor. (When label *order* is stable but the text drifts, a positional `choose`
|
|
|
131
158
|
linter flags positional `choose` as order-dependent, so use it deliberately.) The caution stands: `first`
|
|
132
159
|
*masks* an unanswered gate, so don't use it for a gate you actually need answered a specific way.
|
|
133
160
|
|
|
134
|
-
|
|
161
|
+
#### External deciders and the "first" shorthand
|
|
135
162
|
|
|
136
163
|
When using `--decider-cmd` or `--decider-dir`, the helper's output is passed through
|
|
137
164
|
`coerceLabel` **with the "first" shorthand disabled**. This means a helper that returns the literal
|
|
@@ -142,22 +169,7 @@ The `"first"` shorthand remains active only for the built-in `--on-unanswered fi
|
|
|
142
169
|
write an external helper, return a label name or option index — never the bare word `"first"` unless
|
|
143
170
|
your gate actually has a label called `"first"`.
|
|
144
171
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
Never use `ps aux` to check on a `cowork-harness` run you launched in the background — it only sees
|
|
148
|
-
processes in your OWN PID namespace, which is frequently NOT the harness process's namespace (e.g. when
|
|
149
|
-
you're a sandboxed subagent). An empty `ps aux` match tells you nothing about whether the run is still
|
|
150
|
-
going.
|
|
151
|
-
|
|
152
|
-
Use **`cowork-harness status <dir> [--follow]`** instead — reads `<outDir>/status.json`, a file the
|
|
153
|
-
harness writes/updates throughout the run's lifecycle (including a crash-safety net for a thrown
|
|
154
|
-
error/`SIGTERM`, AND staleness detection for a hard `SIGKILL`/OOM-kill that no exit handler can catch —
|
|
155
|
-
either way you get `"error"`/`stale` instead of a permanently-trusted `"running"`), so liveness is
|
|
156
|
-
checkable regardless of PID namespace. The harness prints `[status] <outDir>` to stderr as soon as the
|
|
157
|
-
run starts, so capture stderr to get the exact directory. `--follow` fails loud on a timeout/staleness
|
|
158
|
-
rather than hanging forever. See `docs/run-status.md` for the full recipe.
|
|
159
|
-
|
|
160
|
-
## 6. Assertions: two orthogonal axes
|
|
172
|
+
### Assertions: two orthogonal axes
|
|
161
173
|
|
|
162
174
|
Conflating these is the **biggest landmine**. An assertion key has two independent properties:
|
|
163
175
|
|
|
@@ -173,11 +185,40 @@ Conflating these is the **biggest landmine**. An assertion key has two independe
|
|
|
173
185
|
|
|
174
186
|
Getting Axis B wrong means a check that **does nothing in CI** — the harness warns loudly when it skips
|
|
175
187
|
(an `::warning::` annotation, not a silent no-op — see the Axis B bullet above), and the bundled linter
|
|
176
|
-
catches it before you push — run it (
|
|
188
|
+
catches it before you push — run it (see *Scaffold a valid scenario, then lint before you push* below).
|
|
177
189
|
|
|
178
190
|
See `references/scenario-schema.md` for the full assertion catalog with each key's replay class.
|
|
179
191
|
|
|
180
|
-
|
|
192
|
+
#### Which assertion for which question (goal → key)
|
|
193
|
+
|
|
194
|
+
Beyond the outcome/content keys most scenarios reach for first (`result`, `transcript_*`,
|
|
195
|
+
`file_exists`/`user_visible_artifact`, `artifact_json`), the harness surfaces the agent's *behavior*
|
|
196
|
+
— tool health, sub-agent work, panels, skill attribution, resources — as assertable keys. Reach for
|
|
197
|
+
them by what you're trying to prove:
|
|
198
|
+
|
|
199
|
+
| You want to check that… | Reach for |
|
|
200
|
+
|---|---|
|
|
201
|
+
| the skill didn't error out of a tool | `tool_no_error: <regex>`, `max_tool_errors: <N>` |
|
|
202
|
+
| it didn't waste repeated identical calls | `max_redundant_tool_calls: <N>` |
|
|
203
|
+
| a deliverable reached the user | `user_visible_artifact: <path>` (+ `no_scratchpad_leak: true` if it delivers via `present_files` — **`container` only**) |
|
|
204
|
+
| a to-do workflow finished | `all_tasks_completed: true`, `task_status: {match, status}` |
|
|
205
|
+
| a skill / connector / tool was **offered** | `skill_available`, `connector_available`, `tool_available` (all `<regex>`) |
|
|
206
|
+
| a skill actually **ran** (or must NOT) | `skill_triggered: <regex>`, `no_skill_triggered: <regex>` |
|
|
207
|
+
| a tool ran **inside** a skill's scope | `skill_tool_used: {skill, tool}` |
|
|
208
|
+
| a sub-agent did the work | `subagent_output_contains: {contains}`, `subagent_dispatched: <regex>`, `dispatch_count_max: <N>` |
|
|
209
|
+
| a pre-existing input wasn't mutated | `input_unmodified: [<glob>]` (live/verify-run; not microvm) |
|
|
210
|
+
| a resource ceiling held | `max_peak_rss_bytes: <N>` (**live-only**) |
|
|
211
|
+
| a hook blocked / didn't block a tool | `hook_blocked: <regex>`, `no_hook_blocked: true` (replay needs a `controlOut` cassette) |
|
|
212
|
+
| every MCP round-trip succeeded | `no_mcp_error: true` (**live-only**) |
|
|
213
|
+
| a context compaction happened | `compaction_occurred: true` |
|
|
214
|
+
|
|
215
|
+
Every one of these still obeys the two axes above — several are live-only or need a `controlOut`
|
|
216
|
+
cassette on replay, so check the catalog's replay class before putting one on a PR gate.
|
|
217
|
+
`cowork-harness assertions --list` prints the full, always-current key set with one-line semantics
|
|
218
|
+
straight from the schema — treat it (and the catalog) as the source of truth; this map is a
|
|
219
|
+
goal-oriented index into it, not a second catalog.
|
|
220
|
+
|
|
221
|
+
### web_fetch (fail-closed, two-path)
|
|
181
222
|
|
|
182
223
|
`web_fetch` behaves unlike `curl`. A URL is gated by **provenance**, not the egress allowlist:
|
|
183
224
|
|
|
@@ -193,7 +234,47 @@ See `references/scenario-schema.md` for the full assertion catalog with each key
|
|
|
193
234
|
Surprise to remember: adding a host to `egress.extra_allow` is a **no-op** for a provenanced fetch.
|
|
194
235
|
Full model in `references/scenario-schema.md`.
|
|
195
236
|
|
|
196
|
-
|
|
237
|
+
### Scaffold a valid scenario, then lint before you push
|
|
238
|
+
|
|
239
|
+
Don't hand-write the YAML from memory — that's how invented keys (`assertions:` vs `assert:`,
|
|
240
|
+
`json_file`, `answer_policy`) creep in. Start from the bundled generator, which emits the
|
|
241
|
+
known-good skeleton (right tier, scripted `answers:` + `on_unanswered: fail`, content assertions
|
|
242
|
+
separated from live-only ones, one concern per item) and **self-lints its own output**:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
S="${CLAUDE_PLUGIN_ROOT}/scripts/scenario.py"
|
|
246
|
+
# Working from a repo checkout instead of an installed plugin?
|
|
247
|
+
# S=".claude/skills/cowork-harness/scripts/scenario.py"
|
|
248
|
+
python3 "$S" scaffold --name report-check --skill ./skills/report-gen \
|
|
249
|
+
--prompt "Generate the weekly report to outputs/report.md." \
|
|
250
|
+
--content 'weekly report' --artifact outputs/report.md \
|
|
251
|
+
--egress-allowed api.weather.example.com --out scenarios/report-check.yaml
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Then lint every scenario — it encodes the no-silent-false-green invariants. Use the CLI wrapper
|
|
255
|
+
`cowork-harness lint` (it runs the same bundled `scenario.py lint`):
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
cowork-harness lint scenarios/*.yaml
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`lint` flags: filesystem/egress-only assertions on a `replay` gate (silent no-op), bad regex
|
|
262
|
+
quoting, an egress assert on `protocol` fidelity, `transcript_no_host_path` on `hostloop`/`protocol`
|
|
263
|
+
(ERROR — fails by design at those tiers; WARN on `fidelity: cowork`, whose tier resolves per the
|
|
264
|
+
baseline's host-loop gate), non-empty `requires_capabilities` on `protocol` without
|
|
265
|
+
`allow_missing_capability` (ERROR — the capability probe can't run there, so the run hard-fails as
|
|
266
|
+
unverifiable), a `controlOut`-gated key on a non-`controlOut` replay, mixed-class assertion items,
|
|
267
|
+
and hallucinated schema (`assertions:` vs `assert:`, unknown keys). Exit code is non-zero on errors
|
|
268
|
+
(CI-friendly). `scaffold` auto-upgrades the tier if you ask for egress on `protocol`, so it never
|
|
269
|
+
emits a scenario `lint` would reject.
|
|
270
|
+
|
|
271
|
+
## Part II — RUN, RECORD & LOCK
|
|
272
|
+
|
|
273
|
+
You have an authored scenario. This Part runs it, reads the verdict, locks it into a
|
|
274
|
+
byte-deterministic cassette, checks a background run's liveness, and places the assertions in the
|
|
275
|
+
right CI lane.
|
|
276
|
+
|
|
277
|
+
### Run, then lock determinism
|
|
197
278
|
|
|
198
279
|
Read the verdict and the inline failing transcript. To pin a flaky-because-stochastic gate, paste
|
|
199
280
|
the echoed `--answer "<q>=<choice>"` footer lines back into the scenario's `answers:` for a
|
|
@@ -218,7 +299,7 @@ launched from a repo root never drops sensitive skill inputs/outputs into it. Pa
|
|
|
218
299
|
`COWORK_HARNESS_RUNS_DIR`) to relocate; in CI point it at a workspace path so an artifact-upload step can
|
|
219
300
|
collect the runs.
|
|
220
301
|
|
|
221
|
-
|
|
302
|
+
#### Validate a skill against real documents (not a cassette)
|
|
222
303
|
|
|
223
304
|
The loops above build **deterministic regressions**. A different job — drive a skill against *real* input
|
|
224
305
|
documents to judge whether it actually does the work (extraction, analysis), with no intent to record a
|
|
@@ -254,7 +335,7 @@ cassette — has its own recipe:
|
|
|
254
335
|
proceeds. Rebuild with `--build-arg COWORK_FULL_PARITY=1` and point `COWORK_AGENT_IMAGE` at it for those
|
|
255
336
|
skills.
|
|
256
337
|
|
|
257
|
-
|
|
338
|
+
#### Interpreting verdict signals
|
|
258
339
|
|
|
259
340
|
The run verdict may include `WARN`-severity signals in addition to pass/fail. One to watch for:
|
|
260
341
|
|
|
@@ -263,51 +344,111 @@ The run verdict may include `WARN`-severity signals in addition to pass/fail. On
|
|
|
263
344
|
the run can still green. If you see it, fix the asset path — a green with a missing asset is
|
|
264
345
|
not a valid pass.
|
|
265
346
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
Don't hand-write the YAML from memory — that's how invented keys (`assertions:` vs `assert:`,
|
|
269
|
-
`json_file`, `answer_policy`) creep in. Start from the bundled generator, which emits the
|
|
270
|
-
known-good skeleton (right tier, scripted `answers:` + `on_unanswered: fail`, content assertions
|
|
271
|
-
separated from live-only ones, one concern per item) and **self-lints its own output**:
|
|
347
|
+
### Checking whether a background run is alive
|
|
272
348
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
python3 "$S" scaffold --name report-check --skill ./skills/report-gen \
|
|
278
|
-
--prompt "Generate the weekly report to outputs/report.md." \
|
|
279
|
-
--content 'weekly report' --artifact outputs/report.md \
|
|
280
|
-
--egress-allowed api.weather.example.com --out scenarios/report-check.yaml
|
|
281
|
-
```
|
|
349
|
+
Never use `ps aux` to check on a `cowork-harness` run you launched in the background — it only sees
|
|
350
|
+
processes in your OWN PID namespace, which is frequently NOT the harness process's namespace (e.g. when
|
|
351
|
+
you're a sandboxed subagent). An empty `ps aux` match tells you nothing about whether the run is still
|
|
352
|
+
going.
|
|
282
353
|
|
|
283
|
-
|
|
284
|
-
|
|
354
|
+
Use **`cowork-harness status <dir> [--follow]`** instead — reads `<outDir>/status.json`, a file the
|
|
355
|
+
harness writes/updates throughout the run's lifecycle (including a crash-safety net for a thrown
|
|
356
|
+
error/`SIGTERM`, AND staleness detection for a hard `SIGKILL`/OOM-kill that no exit handler can catch —
|
|
357
|
+
either way you get `"error"`/`stale` instead of a permanently-trusted `"running"`), so liveness is
|
|
358
|
+
checkable regardless of PID namespace. The harness prints `[status] <outDir>` to stderr as soon as the
|
|
359
|
+
run starts, so capture stderr to get the exact directory. `--follow` fails loud on a timeout/staleness
|
|
360
|
+
rather than hanging forever. (Fuller recipe in `docs/run-status.md` — repo-only, not in the installed
|
|
361
|
+
payload; `cowork-harness status --help` has the flags.)
|
|
285
362
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
363
|
+
**A multi-minute `record`/`run` outlives a short-lived wrapper.** Don't launch a long record from a
|
|
364
|
+
subagent that returns before it finishes — the returning agent tears down its process tree and kills the
|
|
365
|
+
in-flight run mid-artifact-write. Run it foreground, or detached from any process that will exit first.
|
|
366
|
+
(The `status.json` liveness above is exactly what surfaces such a teardown as `"error"`/`stale` rather
|
|
367
|
+
than a stuck `"running"`.)
|
|
289
368
|
|
|
290
|
-
|
|
291
|
-
quoting, an egress assert on `protocol` fidelity, `transcript_no_host_path` on `hostloop`/`protocol`
|
|
292
|
-
(ERROR — fails by design at those tiers; WARN on `fidelity: cowork`, whose tier resolves per the
|
|
293
|
-
baseline's host-loop gate), non-empty `requires_capabilities` on `protocol` without
|
|
294
|
-
`allow_missing_capability` (ERROR — the capability probe can't run there, so the run hard-fails as
|
|
295
|
-
unverifiable), a `controlOut`-gated key on a non-`controlOut` replay, mixed-class assertion items,
|
|
296
|
-
and hallucinated schema (`assertions:` vs `assert:`, unknown keys). Exit code is non-zero on errors
|
|
297
|
-
(CI-friendly). `scaffold` auto-upgrades the tier if you ask for egress on `protocol`, so it never
|
|
298
|
-
emits a scenario `lint` would reject.
|
|
369
|
+
### Place assertions in the right CI lane
|
|
299
370
|
|
|
300
371
|
CI placement: a **token-free `replay` PR gate** (content/structure only) + a **nightly live `run`**
|
|
301
372
|
(filesystem/egress). Fastest setup: `uses: yaniv-golan/cowork-harness@v1` (a packaged GitHub Action with a
|
|
302
373
|
PR job-summary reporter). See `references/ci-recipe.md` for the Action, the manual step-by-step form, and
|
|
303
374
|
the four-stage pipeline.
|
|
304
375
|
|
|
305
|
-
##
|
|
376
|
+
## Part III — Debug
|
|
377
|
+
|
|
378
|
+
A run misbehaved, or greened when you don't trust it. Debugging is a first-class loop, not an
|
|
379
|
+
afterthought: the run already wrote its evidence, so you **localize the failure post-hoc** rather than
|
|
380
|
+
re-run and hope. Start at the triage below, then use the observability output and, when you need to
|
|
381
|
+
reproduce interactively, `chat`.
|
|
382
|
+
|
|
383
|
+
### Triage — a run misbehaved, or a green looks wrong
|
|
384
|
+
|
|
385
|
+
<!-- BEGIN triage-canonical -->
|
|
386
|
+
Two situations need different tools — figure out which one you're in first, then reach for the tool
|
|
387
|
+
instead of re-running and hoping. The run already wrote its evidence to a kept run dir (`--keep` prints
|
|
388
|
+
the path; `trace <run-id>` finds it), and every tool below reads that evidence **token-free** — no
|
|
389
|
+
Docker, no re-record.
|
|
390
|
+
|
|
391
|
+
| Situation | Symptom | Reach for (in order) |
|
|
392
|
+
|---|---|---|
|
|
393
|
+
| **The skill misbehaved** | wrong output, an unexpected gate, a denied tool, an opaque crash | `inspect` — what did it produce? · `trace <run-dir> --view <view>` — what did it actually do (tools, gates, sub-agent tree)? · `verify-run` — re-assert cheaply when only an assertion is wrong · `diff <old-run> <new-run>` — what changed since it worked · `chat` — reproduce it by hand |
|
|
394
|
+
| **A green you don't trust** | an assert that may have tested nothing, a stale cassette, an auto-answered or decided gate | `replay --explain` — the evidence trail behind each *passing* assert · `lint` — assertions on the wrong CI lane / mixed-class keys · `verify-cassettes` — privacy + staleness over committed cassettes · the Gotchas landmine catalog — how a check passes vacuously · `run --repeat N` — did it pass, or pass once? · `stats` — flaky or expensive over time |
|
|
395
|
+
|
|
396
|
+
A failed run also records `errorSource` (where the failure originated) and `stderrLogPath` (the captured
|
|
397
|
+
agent stderr) — read those before re-running; a re-record rarely tells you more than the captured stderr
|
|
398
|
+
already does.
|
|
399
|
+
<!-- END triage-canonical -->
|
|
400
|
+
|
|
401
|
+
**Is it your skill's bug, or a known harness gap?** Before deep-debugging a wrong behavior, rule out a
|
|
402
|
+
**deliberate fidelity gap** — the harness intentionally does *not* reproduce a few real-Cowork behaviors,
|
|
403
|
+
so a "bug" you see here that real Cowork also has isn't yours to fix. The tier semantics are in
|
|
404
|
+
`references/fidelity-and-answers.md` (shipped); the specific deltas vs. real Cowork and the sandbox
|
|
405
|
+
boundary model live in `docs/fidelity-gaps.md` / `docs/boundary.md` (repo-only, not in the installed
|
|
406
|
+
payload). If the behavior is on that gap list, it's expected — stop debugging your skill.
|
|
407
|
+
|
|
408
|
+
### Inspecting a run's observability output
|
|
409
|
+
|
|
410
|
+
A verdict is only the top of what a run records, and the run dir persists after the verdict
|
|
411
|
+
(`~/.cowork-harness/runs/…`). Beyond pass/fail, every `run`/`skill`/`chat` writes a `result.json` and a
|
|
412
|
+
trace you read back without a re-record — the debugging loop is *localize the failure from that
|
|
413
|
+
already-written evidence*, not re-run-and-hope. Use them to diagnose a failure (and, secondarily, to
|
|
414
|
+
decide which assertions from *Assertions: two orthogonal axes* are worth adding):
|
|
415
|
+
|
|
416
|
+
- **`cowork-harness trace <run-dir> --view <view>`** — focuses one of the run's rollups (the per-tool
|
|
417
|
+
call-count/timing table, the sub-agent dispatch tree, the gate lifecycle, the tool/error rollups, …);
|
|
418
|
+
bare `trace` digests the whole run. The view set is actively being extended — run `trace --help` for
|
|
419
|
+
the current list rather than relying on a fixed enumeration here.
|
|
420
|
+
- **`cowork-harness stats [--metric <m>]`** — aggregate across the run index: `cost`, `duration`,
|
|
421
|
+
`tokens`, `cache-tokens`, `model-cost`, `turns`, `pass-rate`.
|
|
422
|
+
- **`result.json` carries the raw fields** the assertions read: `toolDurations`, `models`, `toolErrors`,
|
|
423
|
+
`redundantToolCalls`, `modelUsage`, `thinking`, `skillActivity`, `subagents[]` (prompt/model/output/
|
|
424
|
+
`attributedSkillId`), `context` (tools/mcpServers/availableSkills), `tasks`, `workspaceFiles`,
|
|
425
|
+
`presentedFiles`, `hookEvents`, `mcpErrors`, `contextEvents`, `resources`. (Full per-field semantics:
|
|
426
|
+
the README's "Observability fields" section — repo-only; `schema/run-result.json` is the machine
|
|
427
|
+
source.)
|
|
428
|
+
- **Opaque failure?** A failed run also records **`errorSource`** (where the failure originated) and
|
|
429
|
+
**`stderrLogPath`** (the captured agent stderr) — read those and `trace <run-dir>` *before* re-running;
|
|
430
|
+
a re-record rarely tells you more than the captured stderr already does.
|
|
431
|
+
- **Attributing cost to sub-agent work.** `subagents[]` gives the dispatch tree — each sub-agent's
|
|
432
|
+
`model`, `toolsUsed`, `prompt`/`output`, and `attributedSkillId` — but **not** its own token/cost;
|
|
433
|
+
aggregate cost is per-**model** in `modelUsage` (and `trace --view usage`), not per-sub-agent. So a
|
|
434
|
+
cost spike from fan-out reads as `trace --view dispatches` (how many, which agent) against that model's
|
|
435
|
+
per-model usage — the harness doesn't line-item each sub-agent's tokens.
|
|
436
|
+
- **Debugging a wrong Cowork UI panel.** Each panel is reconstructed in `result.json`: **Progress** =
|
|
437
|
+
`tasks[]`, **Working folder** = `workspaceFiles[]` (classified output/mount/input, with a
|
|
438
|
+
`trace --view files` diff), **Context / Connectors** = `context` (tools / mcpServers / availableSkills),
|
|
439
|
+
**Scratch-pad → outputs** = `presentedFiles[]`. If a panel looks wrong in a run, read its field.
|
|
440
|
+
|
|
441
|
+
### Debugging with `chat`
|
|
306
442
|
|
|
307
443
|
`cowork-harness chat` opens an interactive multi-turn REPL against a live Cowork session. It is
|
|
308
444
|
**not** an asserted test — no `assert:` block, no cassette. Use it to explore behavior, reproduce a
|
|
309
445
|
bug interactively, or test a prompt before committing it to a scenario.
|
|
310
446
|
|
|
447
|
+
Each session still writes an informational `result.json` (`mode: "chat"`, no `assertions`) plus a
|
|
448
|
+
trace and index row under its run dir — the same telemetry (tool durations, model usage, resources,
|
|
449
|
+
etc.) that `run`/`skill` produce — so `cowork-harness trace <chat-run-dir>` / `stats` work on a chat
|
|
450
|
+
session too, even though it never yields a verdict.
|
|
451
|
+
|
|
311
452
|
**`--plugin <dir>` flag (repeatable).** Load additional skill folders alongside the primary session
|
|
312
453
|
plugin. Each `--plugin <dir>` appends the folder to `local_plugins`. Useful when the skill-under-test
|
|
313
454
|
depends on a sibling plugin:
|
|
@@ -331,8 +472,8 @@ The startup banner now reads `type your message (/help for commands)` as a remin
|
|
|
331
472
|
|
|
332
473
|
## Gotchas — the "✓ passed ≠ correct" landmines
|
|
333
474
|
|
|
334
|
-
Stated as *symptom → why → fix*.
|
|
335
|
-
|
|
475
|
+
Stated as *symptom → why → fix*. **This is the full landmine catalog;** `references/scenario-schema.md`
|
|
476
|
+
repeats the assertion/replay-relevant ones alongside the schema (a scoped subset, not a fuller list).
|
|
336
477
|
|
|
337
478
|
1. **An assertion passed but tested nothing on the PR gate.** *Why:* on a manifest-less cassette
|
|
338
479
|
`replay` skips filesystem/egress keys (`file_exists`, `user_visible_artifact`, `artifact_json`,
|
|
@@ -369,9 +510,9 @@ are the ones that bite hardest.
|
|
|
369
510
|
6. **`dispatch_count_max` is an author-chosen budget, not a production cap.** It's a post-hoc count
|
|
370
511
|
assertion: passing means "happened to dispatch ≤N this run," nothing more. Cowork imposes **no**
|
|
371
512
|
in-conversation `Task`-dispatch cap — gate `1648655587`'s `{perTask:1, global:3}` governs the
|
|
372
|
-
separate scheduled/cron-task session scheduler, not the `Task` tool (binary-verified;
|
|
373
|
-
§10). So there is no "skip-on-cap" for the harness to reproduce; use this key
|
|
374
|
-
fan-out you don't want.
|
|
513
|
+
separate scheduled/cron-task session scheduler, not the `Task` tool (binary-verified; details in
|
|
514
|
+
`SPEC.md` §10 — repo-only). So there is no "skip-on-cap" for the harness to reproduce; use this key
|
|
515
|
+
only to catch a fan-out you don't want.
|
|
375
516
|
|
|
376
517
|
7. **`protocol` is rejected (not silently passed) if the scenario asserts egress** — boundary
|
|
377
518
|
assertions need a sandboxed tier (`container`+). Good: this one fails loud by design.
|
|
@@ -394,13 +535,14 @@ are the ones that bite hardest.
|
|
|
394
535
|
content assertion and gate on `file_exists` on the live lane instead.
|
|
395
536
|
|
|
396
537
|
11. **An external decider returning `"first"` does not select option 1.** The `"first"` keyword
|
|
397
|
-
shorthand is disabled for `--decider-cmd` / `--decider-dir` helpers (see
|
|
538
|
+
shorthand is disabled for `--decider-cmd` / `--decider-dir` helpers (see *Choose an answer path*
|
|
539
|
+
→ External deciders). If your helper
|
|
398
540
|
accidentally emits `"first"` and no label named `"first"` exists, the gate fails — it does
|
|
399
541
|
**not** silently pick the first option. This is intentional: a helper bug should fail loud, not
|
|
400
542
|
green wrong. *Fix:* have helpers return a label name or numeric index.
|
|
401
543
|
|
|
402
544
|
12. **`prompt_asset_missing` is a WARN, not a hard failure — greens can hide it.** The
|
|
403
|
-
`prompt_asset_missing` verdict signal (see
|
|
545
|
+
`prompt_asset_missing` verdict signal (see *Interpreting verdict signals*) does not block a green verdict. Scan the verdict
|
|
404
546
|
signals section after every run; a run that greened with this signal ran against an incomplete
|
|
405
547
|
prompt. *Fix:* treat `prompt_asset_missing` as a blocking error in CI by checking the signals
|
|
406
548
|
array.
|
|
@@ -444,6 +586,11 @@ are the ones that bite hardest.
|
|
|
444
586
|
any failure) — a failing scenario does NOT abort the batch. So a single `cowork-harness record cassettes/
|
|
445
587
|
--rerecord-stale` surfaces ALL stale anchors in one pass (add `--concurrency <N>` to parallelize); a shell
|
|
446
588
|
wrapper that loops one cassette at a time with `set -e` defeats this and rediscovers stale anchors serially.
|
|
589
|
+
Two durability properties make the batch safe to trust: each cassette is written **atomically** (a
|
|
590
|
+
same-directory temp file + rename), so an interrupted or OOM-killed batch never leaves a partial/corrupt
|
|
591
|
+
cassette — a failed scenario simply produces none; and under `--concurrency <N>` each scenario runs **fully
|
|
592
|
+
isolated** (its own egress sidecar network + proxy, its own per-session run dir), so parallel records don't
|
|
593
|
+
cross-talk — the concurrency bound exists only for the Docker address pool + API rate limits, not correctness.
|
|
447
594
|
|
|
448
595
|
17. **Editing `scenarios/*.yaml` `assert:` does NOT change a plain `replay`.** *Why:* `replay` evaluates the
|
|
449
596
|
assertions **frozen in the cassette** by default — it is byte-deterministic and ignores the working tree (so
|
|
@@ -479,8 +626,17 @@ are the ones that bite hardest.
|
|
|
479
626
|
`RunResult.artifacts`). A `mode: rw`/`rwd` folder's contents are captured with a full body, same as
|
|
480
627
|
`outputs/`.
|
|
481
628
|
|
|
482
|
-
|
|
483
|
-
|
|
629
|
+
21. **A `fidelity: cowork` cassette can go stale in a way `skill`/`format` drift won't catch.** Its recorded
|
|
630
|
+
`effectiveFidelity` field pins which concrete tier (`hostloop` or `container`) the baseline resolved to
|
|
631
|
+
AT RECORD TIME. If a later Desktop baseline flips that resolution, `verify-cassettes` reports it as a
|
|
632
|
+
`resolved-tier` finding (re-record — the recording now exercises the wrong tier); a cassette with no
|
|
633
|
+
`effectiveFidelity` at all, or an unloadable pinned `baseline:`, reports `unverifiable-tier` instead
|
|
634
|
+
(couldn't check — also re-record). Both are `fidelity: cowork`-only; an explicit-tier scenario never
|
|
635
|
+
produces them. (Details: `docs/cassette.md` § tier staleness — repo-only.)
|
|
636
|
+
|
|
637
|
+
For the assertion catalog, the YAML schema, the fidelity/answer tables, and the CI recipe, read the
|
|
638
|
+
files in `references/` (the gotchas above are the full list; the references repeat only the
|
|
639
|
+
assertion/replay-relevant ones).
|
|
484
640
|
|
|
485
641
|
## References
|
|
486
642
|
|
|
@@ -489,10 +645,13 @@ and the CI recipe, read the files in `references/`.
|
|
|
489
645
|
redaction before the first hostloop/protocol record, derive budget assertions without a
|
|
490
646
|
two-pass record. Start here when the question is "how do I do X", not "what does flag Y mean".
|
|
491
647
|
- `references/scenario-schema.md` — scenario/session YAML schema, full assertion catalog (with each
|
|
492
|
-
key's replay class), the web_fetch model, and
|
|
648
|
+
key's replay class), the web_fetch model, and an assertion/replay-scoped gotcha subset (the full
|
|
649
|
+
landmine catalog lives in this SKILL's Gotchas section above).
|
|
493
650
|
- `references/fidelity-and-answers.md` — fidelity tiers, answer paths, the determinism contract.
|
|
494
651
|
- `references/ci-recipe.md` — the packaged GitHub Action, replay-vs-live lane split, and the four-stage
|
|
495
652
|
GitHub Actions pipeline.
|
|
496
653
|
- `scripts/scenario.py` — `scaffold` a valid scenario skeleton and `lint` scenarios for the
|
|
497
654
|
no-silent-false-green invariants (both usable as CI steps).
|
|
498
|
-
-
|
|
655
|
+
- Checking a background run's status without `ps aux` — covered in *Checking whether a background run is
|
|
656
|
+
alive* (Part II) above; the fuller recipe is in `docs/run-status.md` (repo-only, not shipped with the
|
|
657
|
+
installed skill).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CI recipe — replay vs live lanes
|
|
2
2
|
|
|
3
|
-
Self-contained reference. Tracks `cowork-harness 0.
|
|
3
|
+
Self-contained reference. Tracks `cowork-harness 0.28.0` (baseline `desktop-1.19367.0`).
|
|
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):
|
|
@@ -45,15 +45,16 @@ jobs:
|
|
|
45
45
|
Why this is a step *you* write, not an Action input the harness provides for you: pulling Anthropic's
|
|
46
46
|
binary is a call about your own relationship with their distribution terms — keeping it in your own
|
|
47
47
|
version-controlled workflow keeps that decision and its execution yours, auditable, and outside any
|
|
48
|
-
third-party action's code.
|
|
49
|
-
the full recovery/verification story (including why
|
|
48
|
+
third-party action's code. The agent-binary provenance runbook (`docs/maintenance.md` in the repo —
|
|
49
|
+
not shipped with the installed skill) has the full recovery/verification story (including why
|
|
50
|
+
`COWORK_AGENT_BINARY` substitutions are
|
|
50
51
|
sha256-*checked* but not hard-blocking on mismatch — it's advisory for an intentional substitution).
|
|
51
52
|
|
|
52
53
|
**Minimal token-free PR gate (manual form)** — the smallest thing worth committing; runs on stock
|
|
53
54
|
GitHub-hosted runners, no token/Docker/agent:
|
|
54
55
|
|
|
55
56
|
```yaml
|
|
56
|
-
- run: npm i -g "cowork-harness@>=0.
|
|
57
|
+
- run: npm i -g "cowork-harness@>=0.28.0"
|
|
57
58
|
- run: cowork-harness lint scenarios/*.yaml # no silent false-greens
|
|
58
59
|
- run: cowork-harness verify-cassettes cassettes/ # privacy + staleness
|
|
59
60
|
- run: cowork-harness replay cassettes/ # token-free content/structure
|
|
@@ -187,7 +188,7 @@ jobs:
|
|
|
187
188
|
with: { node-version: '20' }
|
|
188
189
|
- uses: actions/setup-python@v5
|
|
189
190
|
with: { python-version: '3.x' } # python3 only — PyYAML is bundled with the linter
|
|
190
|
-
- run: npm i -g "cowork-harness@>=0.
|
|
191
|
+
- run: npm i -g "cowork-harness@>=0.28.0"
|
|
191
192
|
- run: cowork-harness lint scenarios/*.yaml # no-silent-false-green (needs python3; PyYAML bundled)
|
|
192
193
|
- run: cowork-harness verify-cassettes cassettes/ --output-format json # privacy + staleness gate
|
|
193
194
|
- run: cowork-harness replay cassettes/ --output-format json # token-free content/structure
|
|
@@ -216,7 +217,7 @@ jobs:
|
|
|
216
217
|
echo "live=true" >> "$GITHUB_OUTPUT"
|
|
217
218
|
fi
|
|
218
219
|
- if: steps.guard.outputs.live == 'true'
|
|
219
|
-
run: npm i -g "cowork-harness@>=0.
|
|
220
|
+
run: npm i -g "cowork-harness@>=0.28.0"
|
|
220
221
|
- if: steps.guard.outputs.live == 'true'
|
|
221
222
|
run: cowork-harness run scenarios/ --output-format json
|
|
222
223
|
env:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Fidelity tiers & answer paths
|
|
2
2
|
|
|
3
|
-
Self-contained reference. Tracks `cowork-harness 0.
|
|
3
|
+
Self-contained reference. Tracks `cowork-harness 0.28.0` (baseline `desktop-1.19367.0`).
|
|
4
4
|
|
|
5
5
|
## Fidelity tiers (`fidelity:` in the scenario)
|
|
6
6
|
|
|
@@ -58,7 +58,7 @@ regressions.
|
|
|
58
58
|
| **Spawned helper** | `--decider-cmd '<helper>'` | depends on the helper |
|
|
59
59
|
| **In-band (driving agent)** | `--decider-dir <dir>` (+ a Monitor that writes responses) | depends |
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Scripted `answers:` and a terminal (`--decider-llm` / `--decider-cmd` / `--decider-dir` / `on_unanswered`) compose as a normal Chain — scripted rules resolve matched gates, the terminal answers whatever's left. What's mutually exclusive is picking **two terminals** at once (e.g. `--decider-llm` with `--decider-cmd`, or `--decider-llm` with an explicit `--on-unanswered`) — the CLI rejects that combination. The in-band path's Monitor drives two
|
|
62
62
|
subcommands: `gates <dir>` (stream the pending questions) and `answer <dir> --gate N …` (reply to one).
|
|
63
63
|
|
|
64
64
|
**The `--decider-llm` answer protocol.** The answering model is shown the gate's options **numbered** and
|