cowork-harness 0.27.0 → 0.29.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 +210 -110
- 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 +81 -29
- package/.claude/skills/cowork-harness/references/task-recipes.md +60 -7
- package/.claude/skills/cowork-harness/scripts/assertion-keys.json +3 -0
- package/.claude/skills/cowork-harness/scripts/scenario.py +241 -3
- package/AGENTS.md +5 -4
- package/CHANGELOG.md +392 -0
- package/CONTRIBUTING.md +12 -5
- package/DESIGN.md +4 -4
- package/README.md +73 -46
- package/RELEASING.md +123 -0
- package/SPEC.md +32 -17
- package/baselines/desktop-1.18286.2.json +296 -0
- package/baselines/desktop-1.19367.0.json +373 -0
- package/baselines/desktop-1.20186.0.json +373 -0
- package/baselines/prompts/cowork-system-prompt-fingerprints.json +38 -0
- package/dist/agent/session.js +125 -29
- package/dist/agent/timeline.js +18 -10
- package/dist/assert.js +325 -57
- package/dist/baseline.js +1 -1
- package/dist/cli.js +109 -69
- package/dist/critique/evaluator.js +321 -0
- package/dist/critique/evidence.js +175 -0
- package/dist/critique/package-evidence.js +198 -0
- package/dist/decide/external-channel.js +8 -6
- package/dist/decide/llm-transport.js +26 -3
- package/dist/decide/semantic-judge.js +156 -0
- package/dist/egress/sidecar.js +9 -8
- package/dist/hostloop/workspace-handler.js +1 -2
- package/dist/prompt.js +16 -0
- package/dist/redact.js +2 -3
- package/dist/run/artifacts.js +341 -35
- package/dist/run/cassette.js +544 -169
- package/dist/run/chat-result.js +24 -3
- package/dist/run/chat.js +21 -1
- package/dist/run/computer-links.js +54 -3
- package/dist/run/diff.js +1 -1
- package/dist/run/doctor.js +22 -5
- package/dist/run/execute.js +200 -35
- package/dist/run/matrix.js +1 -1
- package/dist/run/pre-run-manifest.js +132 -32
- package/dist/run/renderer.js +9 -1
- package/dist/run/run-index.js +10 -1
- package/dist/run/run-status.js +64 -8
- package/dist/run/run.js +124 -19
- package/dist/run/runs-gc.js +48 -3
- package/dist/run/scenario-tool.js +4 -8
- package/dist/run/skill-files.js +7 -2
- package/dist/run/skill-hash.js +2 -2
- package/dist/run/trace-view.js +180 -16
- package/dist/run/verdict.js +10 -1
- package/dist/runtime/argv.js +30 -28
- package/dist/runtime/container.js +2 -4
- package/dist/runtime/hostloop-stage.js +15 -2
- package/dist/runtime/hostloop.js +33 -24
- package/dist/runtime/microvm.js +2 -4
- package/dist/runtime/protocol.js +15 -0
- package/dist/runtime/resource-sampler.js +84 -21
- package/dist/runtime/stage.js +7 -0
- package/dist/scan.js +6 -1
- package/dist/session.js +171 -40
- package/dist/sync/cowork-sync.js +410 -34
- package/dist/types.js +93 -13
- package/dist/usage-limit.js +40 -0
- package/docs/README.md +15 -11
- package/docs/boundary.md +6 -1
- package/docs/cassette.md +75 -18
- package/docs/chat.md +8 -0
- package/docs/cowork-spawn-contract-1.12603.1.md +2 -2
- package/docs/debugging.md +20 -5
- package/docs/discovery.md +6 -4
- package/docs/gotchas.md +12 -3
- package/docs/invariants.md +14 -7
- package/docs/maintenance.md +27 -7
- package/docs/plugin-root.md +78 -0
- package/docs/protocol.md +28 -7
- package/docs/run-status.md +8 -2
- package/docs/scenario.md +74 -24
- package/docs/session.md +23 -7
- package/docs/stats.md +20 -7
- package/examples/README.md +1 -1
- package/examples/replays/README.md +7 -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 +50 -36
- package/llms.txt +1 -1
- package/package.json +11 -2
- package/python/README.md +3 -2
- package/python/cowork_harness.py +40 -1
- package/schema/cassette.v10.json +328 -0
- package/schema/run-result.json +793 -127
- package/schema/scenario.schema.json +81 -18
- package/schema/session.schema.json +45 -37
|
@@ -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.29.0
|
|
7
|
+
tracks-harness: cowork-harness 0.29.0 (baseline desktop-1.20186.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.29.0` (baseline
|
|
26
|
+
> `desktop-1.20186.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,21 +38,36 @@ 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.29.0**. If it's missing or older, prefix every command with the version floor `npx "cowork-harness@>=0.29.0" <cmd>` (Node ≥ 20), or install once with `npm i -g "cowork-harness@>=0.29.0"`. **Pin `@>=0.29.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.29.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
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.
|
|
46
46
|
|
|
47
|
-
##
|
|
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:
|
|
48
53
|
|
|
49
54
|
- **"Is it even alive?"** (inner loop) → `cowork-harness skill <folder> "<prompt>"`. Fastest; no
|
|
50
55
|
scenario file.
|
|
51
56
|
- **Repeatable, asserted regression** → author a `scenarios/*.yaml` and run `cowork-harness run`.
|
|
52
57
|
This is the CI-grade path and most of this skill.
|
|
53
|
-
- **
|
|
54
|
-
|
|
55
|
-
|
|
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.29.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**).
|
|
56
71
|
|
|
57
72
|
Full command set: `skill · run · chat · record · replay · verify-cassettes · rehash · prune · lint ·
|
|
58
73
|
verify-run · trace · inspect · diff · stats · decide · gates · answer · scaffold · assertions --list · sync ·
|
|
@@ -60,10 +75,17 @@ list · boundary-check · status · vm <init|status|delete|prune> · doctor · i
|
|
|
60
75
|
|
|
61
76
|
**Two different `scaffold` tools — don't confuse them.** The native `cowork-harness scaffold <run-id>`
|
|
62
77
|
above turns an already-*recorded* run into a scenario (needs a run to exist first). The bundled
|
|
63
|
-
`scripts/scenario.py scaffold --name … --skill …`
|
|
64
|
-
|
|
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
|
|
65
83
|
|
|
66
|
-
|
|
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.
|
|
87
|
+
|
|
88
|
+
### Two files: session vs scenario
|
|
67
89
|
|
|
68
90
|
- **`sessions/*.yaml`** — pre-prompt setup: `model`, mounts (`folders`), and discovery
|
|
69
91
|
(marketplaces / plugins / skills / mcp). One session is reused by many scenarios. A scenario that
|
|
@@ -74,7 +96,7 @@ This split matters: release ground truth (`baseline:` / `baselines/`, produced b
|
|
|
74
96
|
**separate** from authored setup (`session:` / `sessions/`). "profile" is retired vocabulary — do
|
|
75
97
|
not use it. See `references/scenario-schema.md` for every field.
|
|
76
98
|
|
|
77
|
-
|
|
99
|
+
### Discovery: how the skill-under-test gets mounted
|
|
78
100
|
|
|
79
101
|
The skill is **copied fresh into the sandbox each run**. Wire it via `plugins.local_plugins` +
|
|
80
102
|
`plugins.enabled: [<plugin>@local]` in the session (or `--marketplace` / `--plugin` flags on
|
|
@@ -84,13 +106,17 @@ the folder basename (collision-resolved); there is no `to:` override. See `refer
|
|
|
84
106
|
|
|
85
107
|
> **`git add` a brand-new skill before testing it.** Inside a git repo the harness stages the
|
|
86
108
|
> **git-tracked** files (the fidelity boundary — real Cowork installs from a repo and sees only committed
|
|
87
|
-
> 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
|
|
88
114
|
> installed" then did the work itself — a green-looking run where the skill never loaded. That now
|
|
89
115
|
> **hard-fails** (`BoundaryError`, exit 3) naming the dir, and a partially-tracked folder emits a loud
|
|
90
116
|
> `::notice:: [stage]` listing the excluded files. Fix: `git add` the skill, or `COWORK_HARNESS_GITSET=0`
|
|
91
117
|
> to copy untracked files (won't reflect what ships). A folder **outside** any repo is copied raw (no guard).
|
|
92
118
|
|
|
93
|
-
|
|
119
|
+
### Choose a fidelity tier
|
|
94
120
|
|
|
95
121
|
| Tier | What it gives you | Use when |
|
|
96
122
|
|---|---|---|
|
|
@@ -103,7 +129,7 @@ Set the tier in the **scenario's `fidelity:` field**, not a flag — `run` rejec
|
|
|
103
129
|
(it's a `skill`/`chat` flag; `run` takes fidelity only from the scenario). See
|
|
104
130
|
`references/fidelity-and-answers.md`.
|
|
105
131
|
|
|
106
|
-
|
|
132
|
+
### Choose an answer path (gates: AskUserQuestion + tool-permission)
|
|
107
133
|
|
|
108
134
|
Default to **deterministic**: scripted `answers:` + `on_unanswered: fail`. Anything that brings a
|
|
109
135
|
live model into answering flags the run `nonDeterministic` — keep those out of deterministic
|
|
@@ -132,7 +158,7 @@ anchor. (When label *order* is stable but the text drifts, a positional `choose`
|
|
|
132
158
|
linter flags positional `choose` as order-dependent, so use it deliberately.) The caution stands: `first`
|
|
133
159
|
*masks* an unanswered gate, so don't use it for a gate you actually need answered a specific way.
|
|
134
160
|
|
|
135
|
-
|
|
161
|
+
#### External deciders and the "first" shorthand
|
|
136
162
|
|
|
137
163
|
When using `--decider-cmd` or `--decider-dir`, the helper's output is passed through
|
|
138
164
|
`coerceLabel` **with the "first" shorthand disabled**. This means a helper that returns the literal
|
|
@@ -143,28 +169,7 @@ The `"first"` shorthand remains active only for the built-in `--on-unanswered fi
|
|
|
143
169
|
write an external helper, return a label name or option index — never the bare word `"first"` unless
|
|
144
170
|
your gate actually has a label called `"first"`.
|
|
145
171
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Never use `ps aux` to check on a `cowork-harness` run you launched in the background — it only sees
|
|
149
|
-
processes in your OWN PID namespace, which is frequently NOT the harness process's namespace (e.g. when
|
|
150
|
-
you're a sandboxed subagent). An empty `ps aux` match tells you nothing about whether the run is still
|
|
151
|
-
going.
|
|
152
|
-
|
|
153
|
-
Use **`cowork-harness status <dir> [--follow]`** instead — reads `<outDir>/status.json`, a file the
|
|
154
|
-
harness writes/updates throughout the run's lifecycle (including a crash-safety net for a thrown
|
|
155
|
-
error/`SIGTERM`, AND staleness detection for a hard `SIGKILL`/OOM-kill that no exit handler can catch —
|
|
156
|
-
either way you get `"error"`/`stale` instead of a permanently-trusted `"running"`), so liveness is
|
|
157
|
-
checkable regardless of PID namespace. The harness prints `[status] <outDir>` to stderr as soon as the
|
|
158
|
-
run starts, so capture stderr to get the exact directory. `--follow` fails loud on a timeout/staleness
|
|
159
|
-
rather than hanging forever. See `docs/run-status.md` for the full recipe.
|
|
160
|
-
|
|
161
|
-
**A multi-minute `record`/`run` outlives a short-lived wrapper.** Don't launch a long record from a
|
|
162
|
-
subagent that returns before it finishes — the returning agent tears down its process tree and kills the
|
|
163
|
-
in-flight run mid-artifact-write. Run it foreground, or detached from any process that will exit first.
|
|
164
|
-
(The `status.json` liveness above is exactly what surfaces such a teardown as `"error"`/`stale` rather
|
|
165
|
-
than a stuck `"running"`.)
|
|
166
|
-
|
|
167
|
-
## 6. Assertions: two orthogonal axes
|
|
172
|
+
### Assertions: two orthogonal axes
|
|
168
173
|
|
|
169
174
|
Conflating these is the **biggest landmine**. An assertion key has two independent properties:
|
|
170
175
|
|
|
@@ -180,11 +185,11 @@ Conflating these is the **biggest landmine**. An assertion key has two independe
|
|
|
180
185
|
|
|
181
186
|
Getting Axis B wrong means a check that **does nothing in CI** — the harness warns loudly when it skips
|
|
182
187
|
(an `::warning::` annotation, not a silent no-op — see the Axis B bullet above), and the bundled linter
|
|
183
|
-
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).
|
|
184
189
|
|
|
185
190
|
See `references/scenario-schema.md` for the full assertion catalog with each key's replay class.
|
|
186
191
|
|
|
187
|
-
|
|
192
|
+
#### Which assertion for which question (goal → key)
|
|
188
193
|
|
|
189
194
|
Beyond the outcome/content keys most scenarios reach for first (`result`, `transcript_*`,
|
|
190
195
|
`file_exists`/`user_visible_artifact`, `artifact_json`), the harness surfaces the agent's *behavior*
|
|
@@ -201,7 +206,7 @@ them by what you're trying to prove:
|
|
|
201
206
|
| a skill actually **ran** (or must NOT) | `skill_triggered: <regex>`, `no_skill_triggered: <regex>` |
|
|
202
207
|
| a tool ran **inside** a skill's scope | `skill_tool_used: {skill, tool}` |
|
|
203
208
|
| a sub-agent did the work | `subagent_output_contains: {contains}`, `subagent_dispatched: <regex>`, `dispatch_count_max: <N>` |
|
|
204
|
-
| a pre-existing input wasn't mutated | `input_unmodified: [<glob
|
|
209
|
+
| a pre-existing input wasn't mutated (incl. `uploads/**`) | `input_unmodified: <glob>` or `[<glob>, …]` (live/verify-run; not microvm) |
|
|
205
210
|
| a resource ceiling held | `max_peak_rss_bytes: <N>` (**live-only**) |
|
|
206
211
|
| a hook blocked / didn't block a tool | `hook_blocked: <regex>`, `no_hook_blocked: true` (replay needs a `controlOut` cassette) |
|
|
207
212
|
| every MCP round-trip succeeded | `no_mcp_error: true` (**live-only**) |
|
|
@@ -213,7 +218,7 @@ cassette on replay, so check the catalog's replay class before putting one on a
|
|
|
213
218
|
straight from the schema — treat it (and the catalog) as the source of truth; this map is a
|
|
214
219
|
goal-oriented index into it, not a second catalog.
|
|
215
220
|
|
|
216
|
-
|
|
221
|
+
### web_fetch (fail-closed, two-path)
|
|
217
222
|
|
|
218
223
|
`web_fetch` behaves unlike `curl`. A URL is gated by **provenance**, not the egress allowlist:
|
|
219
224
|
|
|
@@ -229,7 +234,47 @@ goal-oriented index into it, not a second catalog.
|
|
|
229
234
|
Surprise to remember: adding a host to `egress.extra_allow` is a **no-op** for a provenanced fetch.
|
|
230
235
|
Full model in `references/scenario-schema.md`.
|
|
231
236
|
|
|
232
|
-
|
|
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
|
|
233
278
|
|
|
234
279
|
Read the verdict and the inline failing transcript. To pin a flaky-because-stochastic gate, paste
|
|
235
280
|
the echoed `--answer "<q>=<choice>"` footer lines back into the scenario's `answers:` for a
|
|
@@ -254,7 +299,7 @@ launched from a repo root never drops sensitive skill inputs/outputs into it. Pa
|
|
|
254
299
|
`COWORK_HARNESS_RUNS_DIR`) to relocate; in CI point it at a workspace path so an artifact-upload step can
|
|
255
300
|
collect the runs.
|
|
256
301
|
|
|
257
|
-
|
|
302
|
+
#### Validate a skill against real documents (not a cassette)
|
|
258
303
|
|
|
259
304
|
The loops above build **deterministic regressions**. A different job — drive a skill against *real* input
|
|
260
305
|
documents to judge whether it actually does the work (extraction, analysis), with no intent to record a
|
|
@@ -290,7 +335,7 @@ cassette — has its own recipe:
|
|
|
290
335
|
proceeds. Rebuild with `--build-arg COWORK_FULL_PARITY=1` and point `COWORK_AGENT_IMAGE` at it for those
|
|
291
336
|
skills.
|
|
292
337
|
|
|
293
|
-
|
|
338
|
+
#### Interpreting verdict signals
|
|
294
339
|
|
|
295
340
|
The run verdict may include `WARN`-severity signals in addition to pass/fail. One to watch for:
|
|
296
341
|
|
|
@@ -299,66 +344,111 @@ The run verdict may include `WARN`-severity signals in addition to pass/fail. On
|
|
|
299
344
|
the run can still green. If you see it, fix the asset path — a green with a missing asset is
|
|
300
345
|
not a valid pass.
|
|
301
346
|
|
|
302
|
-
###
|
|
303
|
-
|
|
304
|
-
A verdict is only the top of what a run records. Beyond pass/fail, every `run`/`skill`/`chat` writes a
|
|
305
|
-
`result.json` and a trace you can read without a re-record — use these to *observe* behavior (and to decide
|
|
306
|
-
which assertions from §6 are worth adding), and to diagnose a failure:
|
|
347
|
+
### Checking whether a background run is alive
|
|
307
348
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
`tokens`, `cache-tokens`, `model-cost`, `turns`, `pass-rate`.
|
|
313
|
-
- **`result.json` carries the raw fields** the assertions read: `toolDurations`, `models`, `toolErrors`,
|
|
314
|
-
`redundantToolCalls`, `modelUsage`, `thinking`, `skillActivity`, `subagents[]` (prompt/model/output/
|
|
315
|
-
`attributedSkillId`), `context` (tools/mcpServers/availableSkills), `tasks`, `workspaceFiles`,
|
|
316
|
-
`presentedFiles`, `hookEvents`, `mcpErrors`, `contextEvents`, `resources` — see the README's
|
|
317
|
-
"Observability fields" for the full field semantics.
|
|
318
|
-
- **Opaque failure?** A failed run also records **`errorSource`** (where the failure originated) and
|
|
319
|
-
**`stderrLogPath`** (the captured agent stderr) — read those and `trace <run-dir>` *before* re-running;
|
|
320
|
-
a re-record rarely tells you more than the captured stderr already does.
|
|
321
|
-
|
|
322
|
-
## 9. Scaffold a valid scenario, lint before you push, then place in CI
|
|
323
|
-
|
|
324
|
-
Don't hand-write the YAML from memory — that's how invented keys (`assertions:` vs `assert:`,
|
|
325
|
-
`json_file`, `answer_policy`) creep in. Start from the bundled generator, which emits the
|
|
326
|
-
known-good skeleton (right tier, scripted `answers:` + `on_unanswered: fail`, content assertions
|
|
327
|
-
separated from live-only ones, one concern per item) and **self-lints its own output**:
|
|
328
|
-
|
|
329
|
-
```bash
|
|
330
|
-
S="${CLAUDE_PLUGIN_ROOT}/scripts/scenario.py"
|
|
331
|
-
# Working from a repo checkout instead of an installed plugin?
|
|
332
|
-
# S=".claude/skills/cowork-harness/scripts/scenario.py"
|
|
333
|
-
python3 "$S" scaffold --name report-check --skill ./skills/report-gen \
|
|
334
|
-
--prompt "Generate the weekly report to outputs/report.md." \
|
|
335
|
-
--content 'weekly report' --artifact outputs/report.md \
|
|
336
|
-
--egress-allowed api.weather.example.com --out scenarios/report-check.yaml
|
|
337
|
-
```
|
|
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.
|
|
338
353
|
|
|
339
|
-
|
|
340
|
-
|
|
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.)
|
|
341
362
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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"`.)
|
|
345
368
|
|
|
346
|
-
|
|
347
|
-
quoting, an egress assert on `protocol` fidelity, `transcript_no_host_path` on `hostloop`/`protocol`
|
|
348
|
-
(ERROR — fails by design at those tiers; WARN on `fidelity: cowork`, whose tier resolves per the
|
|
349
|
-
baseline's host-loop gate), non-empty `requires_capabilities` on `protocol` without
|
|
350
|
-
`allow_missing_capability` (ERROR — the capability probe can't run there, so the run hard-fails as
|
|
351
|
-
unverifiable), a `controlOut`-gated key on a non-`controlOut` replay, mixed-class assertion items,
|
|
352
|
-
and hallucinated schema (`assertions:` vs `assert:`, unknown keys). Exit code is non-zero on errors
|
|
353
|
-
(CI-friendly). `scaffold` auto-upgrades the tier if you ask for egress on `protocol`, so it never
|
|
354
|
-
emits a scenario `lint` would reject.
|
|
369
|
+
### Place assertions in the right CI lane
|
|
355
370
|
|
|
356
371
|
CI placement: a **token-free `replay` PR gate** (content/structure only) + a **nightly live `run`**
|
|
357
372
|
(filesystem/egress). Fastest setup: `uses: yaniv-golan/cowork-harness@v1` (a packaged GitHub Action with a
|
|
358
373
|
PR job-summary reporter). See `references/ci-recipe.md` for the Action, the manual step-by-step form, and
|
|
359
374
|
the four-stage pipeline.
|
|
360
375
|
|
|
361
|
-
##
|
|
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`, `outputTruncated`), `context` (tools/mcpServers/availableSkills), `tasks`,
|
|
425
|
+
`workspaceFiles`, `presentedFiles`, `hookEvents`, `mcpErrors`, `contextEvents`, `resources`
|
|
426
|
+
(`probeFailures` distinguishes a failed sample from a tier that was never sampleable). Provenance/
|
|
427
|
+
evidence-health fields: `command` (`run`/`skill`/`record`/`chat`/`replay` — finer than `mode`),
|
|
428
|
+
`gateProvenance` (per-gate `scripted`/`decided(llm|external)`/`first-option`/`prompt` with a
|
|
429
|
+
`bySource` histogram), `evidenceErrors` (dropped/malformed telemetry lines per stream, incl.
|
|
430
|
+
`egressParse`), `fingerprint.frozen` (replay only — marks the shown staleness fingerprint as the
|
|
431
|
+
cassette's record-time value, not a fresh recompute), and `assertTextTruncated` (companion to
|
|
432
|
+
`outputTruncated` on a matched tool result). (Full per-field semantics: the README's "Observability
|
|
433
|
+
fields" section — repo-only; `schema/run-result.json` is the machine source.)
|
|
434
|
+
- **Opaque failure?** A failed run also records **`errorSource`** (where the failure originated) and
|
|
435
|
+
**`stderrLogPath`** (the captured agent stderr) — read those and `trace <run-dir>` *before* re-running;
|
|
436
|
+
a re-record rarely tells you more than the captured stderr already does. Also check
|
|
437
|
+
**`resultErrorKind`** (`"transport" | "agent" | "usage_limit"`) before spending another paid run: a
|
|
438
|
+
`"usage_limit"` failure is a quota exhaustion, not a skill bug — retry after the limit resets rather
|
|
439
|
+
than debugging; `"transport"`/`"agent"` means something actually broke, worth localizing before
|
|
440
|
+
re-running.
|
|
441
|
+
- **Attributing cost to sub-agent work.** `subagents[]` gives the dispatch tree — each sub-agent's
|
|
442
|
+
`model`, `toolsUsed`, `prompt`/`output`, and `attributedSkillId` — but **not** its own token/cost;
|
|
443
|
+
aggregate cost is per-**model** in `modelUsage` (and `trace --view usage`), not per-sub-agent. So a
|
|
444
|
+
cost spike from fan-out reads as `trace --view dispatches` (how many, which agent) against that model's
|
|
445
|
+
per-model usage — the harness doesn't line-item each sub-agent's tokens.
|
|
446
|
+
- **Debugging a wrong Cowork UI panel.** Each panel is reconstructed in `result.json`: **Progress** =
|
|
447
|
+
`tasks[]`, **Working folder** = `workspaceFiles[]` (classified output/mount/input, with a
|
|
448
|
+
`trace --view files` diff), **Context / Connectors** = `context` (tools / mcpServers / availableSkills),
|
|
449
|
+
**Scratch-pad → outputs** = `presentedFiles[]`. If a panel looks wrong in a run, read its field.
|
|
450
|
+
|
|
451
|
+
### Debugging with `chat`
|
|
362
452
|
|
|
363
453
|
`cowork-harness chat` opens an interactive multi-turn REPL against a live Cowork session. It is
|
|
364
454
|
**not** an asserted test — no `assert:` block, no cassette. Use it to explore behavior, reproduce a
|
|
@@ -392,8 +482,8 @@ The startup banner now reads `type your message (/help for commands)` as a remin
|
|
|
392
482
|
|
|
393
483
|
## Gotchas — the "✓ passed ≠ correct" landmines
|
|
394
484
|
|
|
395
|
-
Stated as *symptom → why → fix*.
|
|
396
|
-
|
|
485
|
+
Stated as *symptom → why → fix*. **This is the full landmine catalog;** `references/scenario-schema.md`
|
|
486
|
+
repeats the assertion/replay-relevant ones alongside the schema (a scoped subset, not a fuller list).
|
|
397
487
|
|
|
398
488
|
1. **An assertion passed but tested nothing on the PR gate.** *Why:* on a manifest-less cassette
|
|
399
489
|
`replay` skips filesystem/egress keys (`file_exists`, `user_visible_artifact`, `artifact_json`,
|
|
@@ -430,9 +520,9 @@ are the ones that bite hardest.
|
|
|
430
520
|
6. **`dispatch_count_max` is an author-chosen budget, not a production cap.** It's a post-hoc count
|
|
431
521
|
assertion: passing means "happened to dispatch ≤N this run," nothing more. Cowork imposes **no**
|
|
432
522
|
in-conversation `Task`-dispatch cap — gate `1648655587`'s `{perTask:1, global:3}` governs the
|
|
433
|
-
separate scheduled/cron-task session scheduler, not the `Task` tool (binary-verified;
|
|
434
|
-
§10). So there is no "skip-on-cap" for the harness to reproduce; use this key
|
|
435
|
-
fan-out you don't want.
|
|
523
|
+
separate scheduled/cron-task session scheduler, not the `Task` tool (binary-verified; details in
|
|
524
|
+
`SPEC.md` §10 — repo-only). So there is no "skip-on-cap" for the harness to reproduce; use this key
|
|
525
|
+
only to catch a fan-out you don't want.
|
|
436
526
|
|
|
437
527
|
7. **`protocol` is rejected (not silently passed) if the scenario asserts egress** — boundary
|
|
438
528
|
assertions need a sandboxed tier (`container`+). Good: this one fails loud by design.
|
|
@@ -455,13 +545,14 @@ are the ones that bite hardest.
|
|
|
455
545
|
content assertion and gate on `file_exists` on the live lane instead.
|
|
456
546
|
|
|
457
547
|
11. **An external decider returning `"first"` does not select option 1.** The `"first"` keyword
|
|
458
|
-
shorthand is disabled for `--decider-cmd` / `--decider-dir` helpers (see
|
|
548
|
+
shorthand is disabled for `--decider-cmd` / `--decider-dir` helpers (see *Choose an answer path*
|
|
549
|
+
→ External deciders). If your helper
|
|
459
550
|
accidentally emits `"first"` and no label named `"first"` exists, the gate fails — it does
|
|
460
551
|
**not** silently pick the first option. This is intentional: a helper bug should fail loud, not
|
|
461
552
|
green wrong. *Fix:* have helpers return a label name or numeric index.
|
|
462
553
|
|
|
463
554
|
12. **`prompt_asset_missing` is a WARN, not a hard failure — greens can hide it.** The
|
|
464
|
-
`prompt_asset_missing` verdict signal (see
|
|
555
|
+
`prompt_asset_missing` verdict signal (see *Interpreting verdict signals*) does not block a green verdict. Scan the verdict
|
|
465
556
|
signals section after every run; a run that greened with this signal ran against an incomplete
|
|
466
557
|
prompt. *Fix:* treat `prompt_asset_missing` as a blocking error in CI by checking the signals
|
|
467
558
|
array.
|
|
@@ -505,6 +596,11 @@ are the ones that bite hardest.
|
|
|
505
596
|
any failure) — a failing scenario does NOT abort the batch. So a single `cowork-harness record cassettes/
|
|
506
597
|
--rerecord-stale` surfaces ALL stale anchors in one pass (add `--concurrency <N>` to parallelize); a shell
|
|
507
598
|
wrapper that loops one cassette at a time with `set -e` defeats this and rediscovers stale anchors serially.
|
|
599
|
+
Two durability properties make the batch safe to trust: each cassette is written **atomically** (a
|
|
600
|
+
same-directory temp file + rename), so an interrupted or OOM-killed batch never leaves a partial/corrupt
|
|
601
|
+
cassette — a failed scenario simply produces none; and under `--concurrency <N>` each scenario runs **fully
|
|
602
|
+
isolated** (its own egress sidecar network + proxy, its own per-session run dir), so parallel records don't
|
|
603
|
+
cross-talk — the concurrency bound exists only for the Docker address pool + API rate limits, not correctness.
|
|
508
604
|
|
|
509
605
|
17. **Editing `scenarios/*.yaml` `assert:` does NOT change a plain `replay`.** *Why:* `replay` evaluates the
|
|
510
606
|
assertions **frozen in the cassette** by default — it is byte-deterministic and ignores the working tree (so
|
|
@@ -546,10 +642,11 @@ are the ones that bite hardest.
|
|
|
546
642
|
`resolved-tier` finding (re-record — the recording now exercises the wrong tier); a cassette with no
|
|
547
643
|
`effectiveFidelity` at all, or an unloadable pinned `baseline:`, reports `unverifiable-tier` instead
|
|
548
644
|
(couldn't check — also re-record). Both are `fidelity: cowork`-only; an explicit-tier scenario never
|
|
549
|
-
produces them.
|
|
645
|
+
produces them. (Details: `docs/cassette.md` § tier staleness — repo-only.)
|
|
550
646
|
|
|
551
|
-
For the
|
|
552
|
-
|
|
647
|
+
For the assertion catalog, the YAML schema, the fidelity/answer tables, and the CI recipe, read the
|
|
648
|
+
files in `references/` (the gotchas above are the full list; the references repeat only the
|
|
649
|
+
assertion/replay-relevant ones).
|
|
553
650
|
|
|
554
651
|
## References
|
|
555
652
|
|
|
@@ -558,10 +655,13 @@ and the CI recipe, read the files in `references/`.
|
|
|
558
655
|
redaction before the first hostloop/protocol record, derive budget assertions without a
|
|
559
656
|
two-pass record. Start here when the question is "how do I do X", not "what does flag Y mean".
|
|
560
657
|
- `references/scenario-schema.md` — scenario/session YAML schema, full assertion catalog (with each
|
|
561
|
-
key's replay class), the web_fetch model, and
|
|
658
|
+
key's replay class), the web_fetch model, and an assertion/replay-scoped gotcha subset (the full
|
|
659
|
+
landmine catalog lives in this SKILL's Gotchas section above).
|
|
562
660
|
- `references/fidelity-and-answers.md` — fidelity tiers, answer paths, the determinism contract.
|
|
563
661
|
- `references/ci-recipe.md` — the packaged GitHub Action, replay-vs-live lane split, and the four-stage
|
|
564
662
|
GitHub Actions pipeline.
|
|
565
663
|
- `scripts/scenario.py` — `scaffold` a valid scenario skeleton and `lint` scenarios for the
|
|
566
664
|
no-silent-false-green invariants (both usable as CI steps).
|
|
567
|
-
-
|
|
665
|
+
- Checking a background run's status without `ps aux` — covered in *Checking whether a background run is
|
|
666
|
+
alive* (Part II) above; the fuller recipe is in `docs/run-status.md` (repo-only, not shipped with the
|
|
667
|
+
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.29.0` (baseline `desktop-1.20186.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.29.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.29.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.29.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.29.0` (baseline `desktop-1.20186.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
|