cowork-harness 0.25.0 → 0.27.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 +74 -5
- package/.claude/skills/cowork-harness/references/ci-recipe.md +4 -4
- package/.claude/skills/cowork-harness/references/fidelity-and-answers.md +1 -1
- package/.claude/skills/cowork-harness/references/scenario-schema.md +43 -9
- package/.claude/skills/cowork-harness/references/task-recipes.md +4 -2
- package/.claude/skills/cowork-harness/scripts/assertion-keys.json +22 -1
- package/.claude/skills/cowork-harness/scripts/scenario.py +35 -2
- package/.env.example +7 -1
- package/CHANGELOG.md +213 -0
- package/CONTRIBUTING.md +0 -3
- package/DESIGN.md +2 -2
- package/README.md +157 -63
- package/SPEC.md +38 -20
- package/dist/agent/session.js +105 -7
- package/dist/agent/timeline.js +137 -0
- package/dist/assert.js +346 -44
- package/dist/boundary-paths.js +7 -6
- package/dist/cli.js +148 -51
- package/dist/egress/proxy.js +23 -8
- package/dist/egress/sidecar.js +43 -7
- package/dist/errors.js +1 -1
- package/dist/hostloop/cowork-handler.js +224 -0
- package/dist/io.js +1 -1
- package/dist/run/artifacts.js +42 -1
- package/dist/run/assemble-run-result.js +17 -0
- package/dist/run/cassette.js +747 -127
- package/dist/run/chat-result.js +111 -0
- package/dist/run/chat.js +40 -7
- package/dist/run/computer-links.js +43 -25
- package/dist/run/diff.js +4 -4
- package/dist/run/display-translate.js +3 -3
- package/dist/run/doctor.js +1 -1
- package/dist/run/envelope.js +30 -11
- package/dist/run/execute.js +251 -33
- package/dist/run/matrix.js +21 -12
- package/dist/run/pre-run-manifest.js +111 -10
- package/dist/run/renderer.js +11 -4
- package/dist/run/repeat.js +10 -6
- package/dist/run/run-index.js +21 -3
- package/dist/run/run-status.js +28 -13
- package/dist/run/run.js +416 -24
- package/dist/run/skill-hash.js +14 -4
- package/dist/run/skill-metadata.js +82 -0
- package/dist/run/timeline-fold.js +85 -0
- package/dist/run/trace-view.js +77 -8
- package/dist/run/verdict.js +15 -3
- package/dist/run/vm-path-ctx-file.js +2 -2
- package/dist/runtime/argv.js +9 -5
- package/dist/runtime/container.js +21 -2
- package/dist/runtime/hostloop.js +11 -13
- package/dist/runtime/resource-sampler.js +246 -0
- package/dist/session.js +71 -8
- package/dist/sync/baseline-diff.js +1 -1
- package/dist/sync/cowork-sync.js +3 -3
- package/dist/types.js +116 -8
- package/docs/README.md +1 -1
- package/docs/cassette.md +70 -20
- package/docs/decisions/2026-07-07-verification-strictness-and-fidelity.md +74 -0
- package/docs/gotchas.md +10 -0
- package/docs/maintenance.md +1 -1
- package/docs/run-status.md +5 -1
- package/docs/scenario.md +39 -10
- package/docs/session.md +2 -0
- package/examples/README.md +17 -3
- package/examples/replays/README.md +28 -1
- package/examples/replays/example-pdf-skill.cassette.json +30 -24
- package/examples/replays/hostloop-computer-links.cassette.json +83 -23
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/python/README.md +4 -1
- package/schema/cassette.v8.json +166 -0
- package/schema/cassette.v9.json +182 -0
- package/schema/run-result.json +209 -1
- package/schema/scenario.schema.json +162 -7
- package/schema/session.schema.json +5 -0
- 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.27.0
|
|
7
|
+
tracks-harness: cowork-harness 0.27.0 (baseline desktop-1.18286.0)
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# cowork-harness
|
|
@@ -22,7 +22,7 @@ flagged with a loud `::warning::`, not silent — auto-answer a gate, observe an
|
|
|
22
22
|
allowlist). This skill exists mostly to keep you out of those traps — the Gotchas section below is
|
|
23
23
|
the highest-value part. Read it.
|
|
24
24
|
|
|
25
|
-
> **Version note:** the facts and `file:line` pointers here track `cowork-harness 0.
|
|
25
|
+
> **Version note:** the facts and `file:line` pointers here track `cowork-harness 0.27.0` (baseline
|
|
26
26
|
> `desktop-1.18286.0`). If your checkout is newer, prefer the live `--help`, `SPEC.md`, and
|
|
27
27
|
> `docs/*.md` over this snapshot, and re-run the bundled linter.
|
|
28
28
|
|
|
@@ -38,10 +38,11 @@ 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.27.0**. If it's missing or older, prefix every command with the version floor `npx "cowork-harness@>=0.27.0" <cmd>` (Node ≥ 20), or install once with `npm i -g "cowork-harness@>=0.27.0"`. **Pin `@>=0.27.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.27.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, and `computer_links_resolve` (new in 0.22.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
|
## 1. Pick the loop
|
|
47
48
|
|
|
@@ -55,7 +56,7 @@ Before the first command, confirm the CLI is reachable and **fail loud** (never
|
|
|
55
56
|
|
|
56
57
|
Full command set: `skill · run · chat · record · replay · verify-cassettes · rehash · prune · lint ·
|
|
57
58
|
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`.
|
|
59
|
+
list · boundary-check · status · vm <init|status|delete|prune> · doctor · init-redact`. Always check `cowork-harness <cmd> --help`.
|
|
59
60
|
|
|
60
61
|
**Two different `scaffold` tools — don't confuse them.** The native `cowork-harness scaffold <run-id>`
|
|
61
62
|
above turns an already-*recorded* run into a scenario (needs a run to exist first). The bundled
|
|
@@ -157,6 +158,12 @@ checkable regardless of PID namespace. The harness prints `[status] <outDir>` to
|
|
|
157
158
|
run starts, so capture stderr to get the exact directory. `--follow` fails loud on a timeout/staleness
|
|
158
159
|
rather than hanging forever. See `docs/run-status.md` for the full recipe.
|
|
159
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
|
+
|
|
160
167
|
## 6. Assertions: two orthogonal axes
|
|
161
168
|
|
|
162
169
|
Conflating these is the **biggest landmine**. An assertion key has two independent properties:
|
|
@@ -177,6 +184,35 @@ catches it before you push — run it (§9).
|
|
|
177
184
|
|
|
178
185
|
See `references/scenario-schema.md` for the full assertion catalog with each key's replay class.
|
|
179
186
|
|
|
187
|
+
### Which assertion for which question (goal → key)
|
|
188
|
+
|
|
189
|
+
Beyond the outcome/content keys most scenarios reach for first (`result`, `transcript_*`,
|
|
190
|
+
`file_exists`/`user_visible_artifact`, `artifact_json`), the harness surfaces the agent's *behavior*
|
|
191
|
+
— tool health, sub-agent work, panels, skill attribution, resources — as assertable keys. Reach for
|
|
192
|
+
them by what you're trying to prove:
|
|
193
|
+
|
|
194
|
+
| You want to check that… | Reach for |
|
|
195
|
+
|---|---|
|
|
196
|
+
| the skill didn't error out of a tool | `tool_no_error: <regex>`, `max_tool_errors: <N>` |
|
|
197
|
+
| it didn't waste repeated identical calls | `max_redundant_tool_calls: <N>` |
|
|
198
|
+
| a deliverable reached the user | `user_visible_artifact: <path>` (+ `no_scratchpad_leak: true` if it delivers via `present_files` — **`container` only**) |
|
|
199
|
+
| a to-do workflow finished | `all_tasks_completed: true`, `task_status: {match, status}` |
|
|
200
|
+
| a skill / connector / tool was **offered** | `skill_available`, `connector_available`, `tool_available` (all `<regex>`) |
|
|
201
|
+
| a skill actually **ran** (or must NOT) | `skill_triggered: <regex>`, `no_skill_triggered: <regex>` |
|
|
202
|
+
| a tool ran **inside** a skill's scope | `skill_tool_used: {skill, tool}` |
|
|
203
|
+
| 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>]` (live/verify-run; not microvm) |
|
|
205
|
+
| a resource ceiling held | `max_peak_rss_bytes: <N>` (**live-only**) |
|
|
206
|
+
| a hook blocked / didn't block a tool | `hook_blocked: <regex>`, `no_hook_blocked: true` (replay needs a `controlOut` cassette) |
|
|
207
|
+
| every MCP round-trip succeeded | `no_mcp_error: true` (**live-only**) |
|
|
208
|
+
| a context compaction happened | `compaction_occurred: true` |
|
|
209
|
+
|
|
210
|
+
Every one of these still obeys the two axes above — several are live-only or need a `controlOut`
|
|
211
|
+
cassette on replay, so check the catalog's replay class before putting one on a PR gate.
|
|
212
|
+
`cowork-harness assertions --list` prints the full, always-current key set with one-line semantics
|
|
213
|
+
straight from the schema — treat it (and the catalog) as the source of truth; this map is a
|
|
214
|
+
goal-oriented index into it, not a second catalog.
|
|
215
|
+
|
|
180
216
|
## 7. web_fetch (fail-closed, two-path)
|
|
181
217
|
|
|
182
218
|
`web_fetch` behaves unlike `curl`. A URL is gated by **provenance**, not the egress allowlist:
|
|
@@ -263,6 +299,26 @@ The run verdict may include `WARN`-severity signals in addition to pass/fail. On
|
|
|
263
299
|
the run can still green. If you see it, fix the asset path — a green with a missing asset is
|
|
264
300
|
not a valid pass.
|
|
265
301
|
|
|
302
|
+
### Inspecting a run's observability output
|
|
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:
|
|
307
|
+
|
|
308
|
+
- **`cowork-harness trace <run-dir> --view <view>`** — `tool-durations` (per-tool call count + timing),
|
|
309
|
+
`dispatches` (the sub-agent tree, each dispatch's prompt / model / output), `questions` (gates +
|
|
310
|
+
sub-question totals), `tools`. Default digests the run.
|
|
311
|
+
- **`cowork-harness stats [--metric <m>]`** — aggregate across the run index: `cost`, `duration`,
|
|
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
|
+
|
|
266
322
|
## 9. Scaffold a valid scenario, lint before you push, then place in CI
|
|
267
323
|
|
|
268
324
|
Don't hand-write the YAML from memory — that's how invented keys (`assertions:` vs `assert:`,
|
|
@@ -308,6 +364,11 @@ the four-stage pipeline.
|
|
|
308
364
|
**not** an asserted test — no `assert:` block, no cassette. Use it to explore behavior, reproduce a
|
|
309
365
|
bug interactively, or test a prompt before committing it to a scenario.
|
|
310
366
|
|
|
367
|
+
Each session still writes an informational `result.json` (`mode: "chat"`, no `assertions`) plus a
|
|
368
|
+
trace and index row under its run dir — the same telemetry (tool durations, model usage, resources,
|
|
369
|
+
etc.) that `run`/`skill` produce — so `cowork-harness trace <chat-run-dir>` / `stats` work on a chat
|
|
370
|
+
session too, even though it never yields a verdict.
|
|
371
|
+
|
|
311
372
|
**`--plugin <dir>` flag (repeatable).** Load additional skill folders alongside the primary session
|
|
312
373
|
plugin. Each `--plugin <dir>` appends the folder to `local_plugins`. Useful when the skill-under-test
|
|
313
374
|
depends on a sibling plugin:
|
|
@@ -479,6 +540,14 @@ are the ones that bite hardest.
|
|
|
479
540
|
`RunResult.artifacts`). A `mode: rw`/`rwd` folder's contents are captured with a full body, same as
|
|
480
541
|
`outputs/`.
|
|
481
542
|
|
|
543
|
+
21. **A `fidelity: cowork` cassette can go stale in a way `skill`/`format` drift won't catch.** Its recorded
|
|
544
|
+
`effectiveFidelity` field pins which concrete tier (`hostloop` or `container`) the baseline resolved to
|
|
545
|
+
AT RECORD TIME. If a later Desktop baseline flips that resolution, `verify-cassettes` reports it as a
|
|
546
|
+
`resolved-tier` finding (re-record — the recording now exercises the wrong tier); a cassette with no
|
|
547
|
+
`effectiveFidelity` at all, or an unloadable pinned `baseline:`, reports `unverifiable-tier` instead
|
|
548
|
+
(couldn't check — also re-record). Both are `fidelity: cowork`-only; an explicit-tier scenario never
|
|
549
|
+
produces them. See `docs/cassette.md` § tier staleness.
|
|
550
|
+
|
|
482
551
|
For the complete gotcha list, the assertion catalog, the YAML schema, the fidelity/answer tables,
|
|
483
552
|
and the CI recipe, read the files in `references/`.
|
|
484
553
|
|
|
@@ -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.27.0` (baseline `desktop-1.18286.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):
|
|
@@ -53,7 +53,7 @@ sha256-*checked* but not hard-blocking on mismatch — it's advisory for an inte
|
|
|
53
53
|
GitHub-hosted runners, no token/Docker/agent:
|
|
54
54
|
|
|
55
55
|
```yaml
|
|
56
|
-
- run: npm i -g "cowork-harness@>=0.
|
|
56
|
+
- run: npm i -g "cowork-harness@>=0.27.0"
|
|
57
57
|
- run: cowork-harness lint scenarios/*.yaml # no silent false-greens
|
|
58
58
|
- run: cowork-harness verify-cassettes cassettes/ # privacy + staleness
|
|
59
59
|
- run: cowork-harness replay cassettes/ # token-free content/structure
|
|
@@ -187,7 +187,7 @@ jobs:
|
|
|
187
187
|
with: { node-version: '20' }
|
|
188
188
|
- uses: actions/setup-python@v5
|
|
189
189
|
with: { python-version: '3.x' } # python3 only — PyYAML is bundled with the linter
|
|
190
|
-
- run: npm i -g "cowork-harness@>=0.
|
|
190
|
+
- run: npm i -g "cowork-harness@>=0.27.0"
|
|
191
191
|
- run: cowork-harness lint scenarios/*.yaml # no-silent-false-green (needs python3; PyYAML bundled)
|
|
192
192
|
- run: cowork-harness verify-cassettes cassettes/ --output-format json # privacy + staleness gate
|
|
193
193
|
- run: cowork-harness replay cassettes/ --output-format json # token-free content/structure
|
|
@@ -216,7 +216,7 @@ jobs:
|
|
|
216
216
|
echo "live=true" >> "$GITHUB_OUTPUT"
|
|
217
217
|
fi
|
|
218
218
|
- if: steps.guard.outputs.live == 'true'
|
|
219
|
-
run: npm i -g "cowork-harness@>=0.
|
|
219
|
+
run: npm i -g "cowork-harness@>=0.27.0"
|
|
220
220
|
- if: steps.guard.outputs.live == 'true'
|
|
221
221
|
run: cowork-harness run scenarios/ --output-format json
|
|
222
222
|
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.27.0` (baseline `desktop-1.18286.0`).
|
|
4
4
|
|
|
5
5
|
## Fidelity tiers (`fidelity:` in the scenario)
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Scenario & session schema, assertion catalog, web_fetch, full gotchas
|
|
2
2
|
|
|
3
|
-
Self-contained reference for authoring `cowork-harness` scenarios. Tracks `cowork-harness 0.
|
|
3
|
+
Self-contained reference for authoring `cowork-harness` scenarios. Tracks `cowork-harness 0.27.0`
|
|
4
4
|
(baseline `desktop-1.18286.0`). If your checkout is newer, prefer the live `docs/scenario.md`,
|
|
5
5
|
`docs/session.md`, and `SPEC.md`.
|
|
6
6
|
|
|
@@ -209,6 +209,11 @@ that accidentally returns `"first"` will fail the gate rather than silently gree
|
|
|
209
209
|
Each list item under `assert:` is one assertion. **An item with multiple keys is an AND** — it
|
|
210
210
|
passes only if every key passes. Keep one concern per item unless you mean conjunction.
|
|
211
211
|
|
|
212
|
+
Looking for a key *by what you want to prove* (tool health, sub-agent work, panels, skill
|
|
213
|
+
attribution, resources, diagnostics)? SKILL.md §6's "goal → key" map is the by-purpose index into this
|
|
214
|
+
table; the table below is the full per-key reference, and `cowork-harness assertions --list` prints the
|
|
215
|
+
same set live from the schema.
|
|
216
|
+
|
|
212
217
|
| Assertion | Passes when |
|
|
213
218
|
|---|---|
|
|
214
219
|
| `result: success \| error` | the run ended with that status |
|
|
@@ -220,6 +225,7 @@ passes only if every key passes. Keep one concern per item unless you mean conju
|
|
|
220
225
|
| `user_visible_artifact: <path>` | exists **and** under a user-visible root (`outputs/` + each connected folder's mount name) — the right primitive for a workspace deliverable when a folder is connected |
|
|
221
226
|
| `no_delete_in_outputs: true` | no delete op touched `mnt/outputs` — **only `true` is valid**; `false` is rejected (omit to allow deletes) |
|
|
222
227
|
| `no_unexpected_files: [<glob>, …]` | every **newly created** file under a user-visible root matches ≥1 glob (workRoot-relative paths; `**` = whole path segment for any depth — use `outputs/handoff/**` for per-run subdirs); `[]` = no new files; **new-files-only** — overwriting a pre-existing file in place is invisible (use content-level producer stamping); live/verify-run without a pre-run manifest ⇒ evidence-unavailable hard-fail (live runs capture the baseline only when this key is asserted; recordings always capture); **microvm cannot capture** (use container/hostloop); replay needs `cassette.preRunPaths` (≥0.24 container/hostloop recordings) — cassettes without it **exclude** the key with a loud warning |
|
|
228
|
+
| `input_unmodified: [<glob>, …]` | every **pre-existing** file whose workRoot-relative path matches ≥1 glob keeps an unchanged content hash after the run — the in-place-mutation companion to `no_unexpected_files`'s new-files check (`[]` is rejected by the schema — list at least one glob); a matched file that was deleted counts as a content change (fails); live/verify-run without a pre-run hash manifest ⇒ evidence-unavailable hard-fail; **microvm cannot capture**; replay needs `cassette.preRunHashes` — cassettes without it **exclude** the key with a loud warning; on replay it compares against the manifest's recorded `sha256`, never a re-hash of the materialized tree |
|
|
223
229
|
| `self_heal_ran: <bool>` | a plugin-root self-heal script was (not) invoked |
|
|
224
230
|
| `tool_called: <Tool>` | the agent invoked the tool (actually ran it) |
|
|
225
231
|
| `tool_not_called: <Tool>` | the agent never invoked it |
|
|
@@ -229,18 +235,34 @@ passes only if every key passes. Keep one concern per item unless you mean conju
|
|
|
229
235
|
| `subagent_tool_absent: <Tool>` | no sub-agent used the tool |
|
|
230
236
|
| `subagent_dispatched: <regex>` | a sub-agent whose `agentType` **or dispatch description** matches |
|
|
231
237
|
| `subagent_declared_but_unused: <Tool>` | a sub-agent declared the tool but never used **that** tool (even if it used others) |
|
|
238
|
+
| `subagent_output_contains: {match?, contains}` | a dispatched sub-agent's own output contains the substring `contains` — `match` (optional regex over `agentType`/`description`) narrows to specific dispatch(es); omitted, checks whether ANY dispatch's output contains it (existence check, not "all") |
|
|
232
239
|
| `dispatch_count_max: <N>` | at most N sub-agents dispatched — an author-chosen budget (Cowork imposes no in-conversation Task-dispatch cap; records only, enforces nothing — see gotcha 12) |
|
|
233
240
|
| `skill_triggered: <regex>` | a skill matching the regex (invoked id, e.g. `"plugin:skill"`) was invoked via the `Skill` tool — evidence-unavailable (not a normal fail) if the agent's init tools have no `Skill` tool |
|
|
234
241
|
| `no_skill_triggered: <regex>` | no invoked skill id matched — the negative-control / description-collision catcher; evidence-unavailable (never a vacuous pass) if invocation data is absent or the `Skill` tool is unobservable |
|
|
242
|
+
| `skill_available: <regex>` | a staged skill's id matched the regex (offered, not necessarily invoked — see `skill_triggered` for invocation) — content-class: the id list comes from the agent's init `skills` listing, so it replays from the frozen init event (id-only; the `whenToUse` enrichment is live-disk and thus absent on replay, but the id is what's matched); evidence-unavailable only if `RunResult.context.availableSkills` is absent entirely (an older cassette recorded before the available-skills listing was captured) |
|
|
243
|
+
| `connector_available: <regex>` | an MCP server/connector's name matched the regex (available, not necessarily used) — evidence-unavailable if `RunResult.context.mcpServers` is absent |
|
|
244
|
+
| `tool_available: <regex>` | a tool in the init manifest matched the regex (available, not necessarily called — see `tool_called` for invocation) — evidence-unavailable if `RunResult.context.tools` is absent |
|
|
245
|
+
| `skill_tool_used: {skill, tool}` | a tool whose name matches `tool` ran inside a skill-activation window whose `skillId` matches `skill` (`RunResult.skillActivity`) — evidence-unavailable if skill-activity telemetry is absent; heuristic for inline skills (a sticky, sequential window matching the agent's `activeSkill` scope, not an exact per-tool boundary) |
|
|
235
246
|
| `max_cost_usd: <N>` | the run's SDK-reported cost is ≤ N USD — evidence-unavailable if cost telemetry is absent. **Replay asserts the frozen recording's cost, not fresh spend** — a real regression needs a live `run` |
|
|
236
247
|
| `max_tokens: <N>` | `usage.input_tokens + usage.output_tokens` ≤ N (cache tokens excluded) — same replay caveat as `max_cost_usd` |
|
|
237
248
|
| `tool_calls_max: <N>` | total top-level tool calls (sum of `toolCounts`) ≤ N — meaningfully replay-checkable (re-drive recomputes `toolCounts` deterministically) |
|
|
249
|
+
| `tool_no_error: <regex>` | no tool whose name matches the regex recorded any error (`RunResult.toolErrors[name].errors === 0` for every match) — **requires ≥1 matching tool call** (a regex matching nothing fails, so a typo can't silently pass); evidence-unavailable if tool-error telemetry is absent |
|
|
250
|
+
| `tool_no_error_if_called: <regex>` | like `tool_no_error` but passes vacuously when no tool matches the regex — the presence-free variant |
|
|
251
|
+
| `max_tool_errors: <N>` | total tool errors across all tools (sum of `RunResult.toolErrors[*].errors`) ≤ N — evidence-unavailable if tool-error telemetry is absent |
|
|
252
|
+
| `max_redundant_tool_calls: <N>` | total WASTED repeated tool calls (sum of `(count-1)` across every redundant `{name,args}` group in `RunResult.redundantToolCalls`) ≤ N — not the raw count of redundant groups; evidence-unavailable if redundant-call telemetry is absent |
|
|
238
253
|
| `max_turns: <N>` | the SDK-reported (or fallback-counted) turn count ≤ N — meaningfully replay-checkable (re-drive recounts turns deterministically, same as `tool_calls_max`) |
|
|
254
|
+
| `compaction_occurred: true` | a context-compaction boundary occurred (a `compact_boundary` system event was recorded) — lives in the stdout stream, so meaningfully replay-checkable; evidence-unavailable if context-event telemetry is absent. **Only `true` is valid** — omit to not require it |
|
|
255
|
+
| `all_tasks_completed: true` | every task in `RunResult.tasks[]` reached status `"completed"` — **requires ≥1 task** (a zero-task run fails; assert `task_count_min` for presence); evidence-unavailable if tasks telemetry is absent |
|
|
256
|
+
| `task_count_min: <N>` | at least N tasks were created (`RunResult.tasks.length >= N`) — the presence companion for task assertions |
|
|
257
|
+
| `task_status: {match, status}` | a task whose `subject` OR `id` matches the regex `match` reached `status` — evidence-unavailable if tasks telemetry is absent |
|
|
258
|
+
| `no_scratchpad_leak: true` | every file presented via `present_files` that was in the scratchpad was successfully promoted to `mnt/outputs` (none left behind) — vacuously passes if nothing was presented (pair with a presence check to require a delivery); content-class: both the `present_files` tool_use and its own tool_result live in the ordinary events stream, so `RunResult.presentedFiles` re-derives identically on replay (meaningfully replay-checkable, same as `skill_triggered`); evidence-unavailable if `presentedFiles` telemetry is absent (an older run predating the feature). **`fidelity: container` only** — `present_files` is not served on hostloop/microvm, so a scratchpad-delivered file is neither promoted to `mnt/outputs` nor detected there (a skill that delivers via write-to-cwd→`present_files` will false-red `user_visible_artifact` on those tiers; use `container`, or write directly to `outputs/`). **Only `true` is valid** |
|
|
239
259
|
| `question_asked: <regex>` | the agent asked an AskUserQuestion whose text matches |
|
|
240
260
|
| `questions_count_max: <N>` | at most N **sub-questions** asked — a bundled `AskUserQuestion` with K sub-questions counts as K, not 1; `trace --view questions`'s footer total uses the same definition |
|
|
241
261
|
| `gate_answers_delivered: true` | every answered gate's answer reached the model (observed `tool_result`; unobserved = fail); **zero gates fired passes vacuously** — pair with `gate_answer_count_min` to also require a gate |
|
|
242
262
|
| `gate_answers_delivered: false` | asserts at least one answered gate's answer was **confirmed not delivered** (an observed delivery failure); an unobserved/null delivery does **not** satisfy this — for negative-path delivery tests |
|
|
243
263
|
| `gate_answer_count_min: <N>` | at least N AskUserQuestion gates fired AND were delivered non-error — presence companion to `gate_answers_delivered`'s vacuous-pass |
|
|
264
|
+
| `hook_blocked: <regex>` | a PreToolUse hook blocked a tool whose name matches the regex (`RunResult.hookEvents`) — evidence-unavailable if hook telemetry is absent. Replay: needs a `controlOut` cassette (a custom hook's decision lives only there, not the recorded stream) |
|
|
265
|
+
| `no_hook_blocked: true` | no tool was hook-blocked during the run (distinguishes a real tool crash from an intentional hook block) — evidence-unavailable if hook telemetry is absent. Replay: needs a `controlOut` cassette. **Only `true` is valid** |
|
|
244
266
|
| `allow_permissive_auto_allow: true` | verdict modifier — suppresses the default-fail when the run recorded a cowork-parity permissive auto-allow; for tests that deliberately assert Cowork's permissive behavior |
|
|
245
267
|
| `allow_missing_capability: true` | verdict modifier — suppresses the default-fail when the (partial "core") agent image omits a capability the skill used but real Cowork ships (OCR/LibreOffice/markitdown/opencv/PDF-tables). Assert only when the skill's fallback is genuinely equivalent; otherwise rebuild full parity (`--build-arg COWORK_FULL_PARITY=1`). Also opts out of the `requires_capabilities` declared-need check. Live tiers only |
|
|
246
268
|
| `allow_l0_plugin_divergence: true` | verdict modifier — opt into L0/protocol plugin divergence: suppresses the default-fail when a plugin behaves differently at `protocol` (L0) fidelity than under a sandboxed tier. Live tiers only |
|
|
@@ -248,8 +270,11 @@ passes only if every key passes. Keep one concern per item unless you mean conju
|
|
|
248
270
|
| `transcript_no_host_path: true` | no host path (`/Users/`, `/opt/cowork/`, `/home/`, `/root/`) leaked into model-visible text — **incompatible with `hostloop` AND `protocol`**: hostloop's native file tools legitimately expose real host paths, and protocol (L0) runs the agent's file tools on the real host cwd with no sealed filesystem, so this fails BY DESIGN on both (the harness warns at run start if asserted anyway); use `container`/`microvm` for this check |
|
|
249
271
|
| `egress_denied: <host>` | the host was blocked by the egress proxy |
|
|
250
272
|
| `egress_allowed: <host>` | the host was allowed through |
|
|
273
|
+
| `no_mcp_error: true` | no MCP round-trip failed (`RunResult.mcpErrors` is empty — no unhandled server, no handler throw) — live-only: MCP round-trips are harness-computed, not in the SDK stdout stream, so evidence-unavailable on replay (never a vacuous pass). **Only `true` is valid** |
|
|
274
|
+
| `max_peak_rss_bytes: <N>` | peak sampled RSS of the agent sandbox ≤ N bytes (`RunResult.resources.peakRssBytes`) — live-only: replay never spawns a sandbox to sample, so evidence-unavailable on replay/protocol (never a vacuous pass); also evidence-unavailable when sampling captured no RSS value |
|
|
251
275
|
| `artifact_json: {artifact, path, …}` | assert a JSON artifact's contents — `equals`/`gt`/`in`/`exists`/`absent`/`is_null` over a dotted `path` (`in` = membership in a list, for a stochastic/LLM value; `absent` ≠ `is_null`; an unresolved intermediate fails loud) |
|
|
252
|
-
| `computer_links_resolve: true` | every `computer://` link in the model-visible transcript resolves to an artifact that exists in the run's collected outputs/mounts — a dangling link fails, naming which target was checked (a live host path, the collected work tree, or the replay manifest).
|
|
276
|
+
| `computer_links_resolve: true` | every `computer://` link in the model-visible transcript resolves to an artifact that exists in the run's collected outputs/mounts — a dangling link fails, naming which target was checked (a live host path, the collected work tree, or the replay manifest). **Requires ≥1 link** (zero links fails — use `computer_links_resolve_if_present` for the presence-free variant). **Only `true` is valid** (`false` is rejected by the schema) |
|
|
277
|
+
| `computer_links_resolve_if_present: true` | like `computer_links_resolve` but passes vacuously when the transcript has zero `computer://` links — the presence-free variant. **Only `true` is valid** |
|
|
253
278
|
|
|
254
279
|
`expect_denied: [host, …]` adds one `egress_denied` per host. Run `cowork-harness assertions --list` for this
|
|
255
280
|
table from the live schema. Example: `artifact_json: { artifact: outputs/cap.json, path: me.run_id, equals: "r1" }`.
|
|
@@ -288,16 +313,21 @@ sourcing ≠ evaluation (replay warns when you edit one). `verify-run` is the on
|
|
|
288
313
|
*run dir*; `--assert-from` is the equivalent for a *cassette*.
|
|
289
314
|
|
|
290
315
|
**Evaluated on replay (content):** `transcript_*`, `tool_*`, `subagent_*`, `dispatch_count_max`,
|
|
291
|
-
`skill_triggered`, `no_skill_triggered`, `
|
|
316
|
+
`skill_triggered`, `no_skill_triggered`, `skill_available`, `connector_available`, `tool_available`,
|
|
317
|
+
`skill_tool_used`, `max_cost_usd`, `max_tokens`, `tool_calls_max`, `tool_no_error`,
|
|
318
|
+
`max_tool_errors`, `max_redundant_tool_calls`, `max_turns`, `compaction_occurred`, `all_tasks_completed`, `task_status`, `result`
|
|
292
319
|
(`max_cost_usd`/`max_tokens` assert the frozen recording's spend on replay, not fresh spend). The verdict
|
|
293
320
|
modifiers `allow_permissive_auto_allow` / `allow_missing_capability` / `allow_l0_plugin_divergence` /
|
|
294
321
|
`allow_stall` are also kept on replay, evaluated as no-op passes.
|
|
295
322
|
|
|
296
323
|
**Gate keys — replay only with a `controlOut` cassette:** `question_asked`, `questions_count_max`,
|
|
297
|
-
`gate_answers_delivered`, `gate_answer_count_min`. With `controlOut` present they evaluate; on an old
|
|
324
|
+
`gate_answers_delivered`, `gate_answer_count_min`, `hook_blocked`, `no_hook_blocked`. With `controlOut` present they evaluate; on an old
|
|
298
325
|
cassette without it, a **loud warning** fires and they are **excluded** (not vacuously passed). Re-record to enable them.
|
|
299
326
|
`questions_count_max` counts sub-questions, not gates/tool-calls — see the catalog row above and
|
|
300
|
-
`trace --view questions`.
|
|
327
|
+
`trace --view questions`. `hook_blocked`/`no_hook_blocked` need `controlOut` for a different reason than
|
|
328
|
+
the question keys: a custom hook's block/allow decision is an opaque async reply recorded only in
|
|
329
|
+
`control-out.jsonl`, not the `events` stream — reconstructing from the stream alone would show only the
|
|
330
|
+
built-in Task hook's view and could vacuously pass `no_hook_blocked` even if a custom hook genuinely blocked.
|
|
301
331
|
|
|
302
332
|
**Filesystem — replay-checkable WITH an artifact manifest:** `file_exists`, `user_visible_artifact`,
|
|
303
333
|
`artifact_json`, `computer_links_resolve` run on replay when the cassette carries an `artifacts` snapshot
|
|
@@ -308,13 +338,16 @@ by first normalizing it to a mount-relative path (recorded connected-folder pref
|
|
|
308
338
|
mounts) — replay has no live filesystem to check a host path against directly (that only happens on a live
|
|
309
339
|
`run`/`verify-run`). Without a manifest (older cassettes) all five are skipped; `no_unexpected_files` also
|
|
310
340
|
needs `preRunPaths` (≥0.24 recordings) — without it the key is excluded with a loud warning (live/verify-run
|
|
311
|
-
hard-fails evidence-unavailable instead).
|
|
341
|
+
hard-fails evidence-unavailable instead). `input_unmodified` is the same shape but needs `preRunHashes`
|
|
342
|
+
(the pre-run per-path sha256 baseline) instead of `preRunPaths`; without it, likewise excluded with a loud
|
|
343
|
+
warning. A green replay re-confirms
|
|
312
344
|
*record-time* artifacts, not that the current skill still produces them — `replay --strict` fails when the
|
|
313
345
|
staleness `fingerprint` shows ANY skill/baseline drift, or `replay --fail-on-skill-drift` only on
|
|
314
346
|
skill-source drift; every replay result also reports it class-tagged in `staleness[]` for a JSON gate.
|
|
315
347
|
|
|
316
348
|
**Egress + other filesystem — still skipped on replay (live-only):** `no_delete_in_outputs`,
|
|
317
|
-
`self_heal_ran`, `transcript_no_host_path`, `egress_*` / `expect_denied
|
|
349
|
+
`self_heal_ran`, `transcript_no_host_path`, `egress_*` / `expect_denied`, `no_mcp_error`, `max_peak_rss_bytes`.
|
|
350
|
+
These run only on a live `run`/`record`.
|
|
318
351
|
|
|
319
352
|
**Mixed assertions on replay:** before evaluating, `replay` strips each assertion to its replay-checkable
|
|
320
353
|
keys and drops any left empty. So `{result, egress_denied}` evaluates on replay as `{result}` alone — its
|
|
@@ -377,8 +410,9 @@ at the top of this file.
|
|
|
377
410
|
concern per item; run the linter. (`contentKeys` in `src/run/cassette.ts`.)
|
|
378
411
|
|
|
379
412
|
2. **Gate keys need a `controlOut` cassette.** `question_asked`, `questions_count_max`,
|
|
380
|
-
`gate_answers_delivered`, `gate_answer_count_min` only evaluate on
|
|
381
|
-
old cassette they warn and are excluded (not passed).
|
|
413
|
+
`gate_answers_delivered`, `gate_answer_count_min`, `hook_blocked`, `no_hook_blocked` only evaluate on
|
|
414
|
+
replay with `controlOut`; on an old cassette they warn and are excluded (not passed).
|
|
415
|
+
`gate_answers_delivered` **fails on
|
|
382
416
|
unobserved delivery** (`delivered: null`) — absence of evidence is failure — but **passes
|
|
383
417
|
vacuously when zero gates fired**; use `gate_answer_count_min: 1` to also require a gate to have
|
|
384
418
|
fired. A **header-only gate** (empty `question`, only `header`) can never be keyed and is rejected
|
|
@@ -47,11 +47,11 @@ what production would do. Two layers of defense:
|
|
|
47
47
|
|
|
48
48
|
### Cassette anatomy (what you're looking at when you open one)
|
|
49
49
|
|
|
50
|
-
Top-level fields of a `*.cassette.json` (schema `schema/cassette.
|
|
50
|
+
Top-level fields of a `*.cassette.json` (schema `schema/cassette.v9.json`):
|
|
51
51
|
|
|
52
52
|
| Field | What it is |
|
|
53
53
|
|---|---|
|
|
54
|
-
| `$schema`, `generator`, `cassetteVersion` | Provenance: schema URL, producing tool, format version (current:
|
|
54
|
+
| `$schema`, `generator`, `cassetteVersion` | Provenance: schema URL, producing tool, format version (current: 9) |
|
|
55
55
|
| `scenario` | The embedded scenario snapshot at record time |
|
|
56
56
|
| `events` | The recorded agent event stream (the replay source) |
|
|
57
57
|
| `controlOut` | Driver→agent control responses — presence unlocks gate asserts on replay |
|
|
@@ -63,6 +63,8 @@ Top-level fields of a `*.cassette.json` (schema `schema/cassette.v7.json`):
|
|
|
63
63
|
| `preRunPaths` | Pre-run file-path baseline for `no_unexpected_files` (workRoot-relative; co-present with `userVisibleRoots`) |
|
|
64
64
|
| `scenarioSource` | Relative path to the authored YAML this was recorded from |
|
|
65
65
|
| `authoring` | Present iff a live decider answered ≥1 gate during recording (`nonDeterministic: true`) |
|
|
66
|
+
| `sessionFingerprint` | v9+: hash of the session's content-relevant SHAPE (folders/plugins/skills/mcp/egress). Checked ONLY by `verify-cassettes`, never the default replay verdict; absent on a pre-v9 cassette → not checked |
|
|
67
|
+
| `folderPrefixMap` | v9+: the record-time connected-folder host-path → mount-name map. Replay's `computer_links_resolve` uses THIS (never the current session file) for a v9+ cassette; a pre-v9 cassette keeps reconstructing from the current session |
|
|
66
68
|
|
|
67
69
|
## Recipe 3 — Set up redaction BEFORE your first hostloop/protocol record
|
|
68
70
|
|
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "GENERATED from the Zod schemas (src/types.ts) by scripts/gen-schema.ts — do not edit; run `npm run schema`.",
|
|
3
3
|
"keys": [
|
|
4
|
+
"all_tasks_completed",
|
|
4
5
|
"allow_l0_plugin_divergence",
|
|
5
6
|
"allow_missing_capability",
|
|
6
7
|
"allow_permissive_auto_allow",
|
|
7
8
|
"allow_stall",
|
|
8
9
|
"artifact_json",
|
|
10
|
+
"compaction_occurred",
|
|
9
11
|
"computer_links_resolve",
|
|
12
|
+
"computer_links_resolve_if_present",
|
|
13
|
+
"connector_available",
|
|
10
14
|
"dispatch_count_max",
|
|
11
15
|
"egress_allowed",
|
|
12
16
|
"egress_denied",
|
|
13
17
|
"file_exists",
|
|
14
18
|
"gate_answer_count_min",
|
|
15
19
|
"gate_answers_delivered",
|
|
20
|
+
"hook_blocked",
|
|
21
|
+
"input_unmodified",
|
|
16
22
|
"max_cost_usd",
|
|
23
|
+
"max_peak_rss_bytes",
|
|
24
|
+
"max_redundant_tool_calls",
|
|
17
25
|
"max_tokens",
|
|
26
|
+
"max_tool_errors",
|
|
18
27
|
"max_turns",
|
|
19
28
|
"no_delete_in_outputs",
|
|
29
|
+
"no_hook_blocked",
|
|
30
|
+
"no_mcp_error",
|
|
31
|
+
"no_scratchpad_leak",
|
|
20
32
|
"no_skill_triggered",
|
|
21
33
|
"no_unexpected_files",
|
|
22
34
|
"question_asked",
|
|
@@ -24,13 +36,21 @@
|
|
|
24
36
|
"replay_protocol_fidelity",
|
|
25
37
|
"result",
|
|
26
38
|
"self_heal_ran",
|
|
39
|
+
"skill_available",
|
|
40
|
+
"skill_tool_used",
|
|
27
41
|
"skill_triggered",
|
|
28
42
|
"subagent_declared_but_unused",
|
|
29
43
|
"subagent_dispatched",
|
|
44
|
+
"subagent_output_contains",
|
|
30
45
|
"subagent_tool_absent",
|
|
31
46
|
"subagent_tool_used",
|
|
47
|
+
"task_count_min",
|
|
48
|
+
"task_status",
|
|
49
|
+
"tool_available",
|
|
32
50
|
"tool_called",
|
|
33
51
|
"tool_calls_max",
|
|
52
|
+
"tool_no_error",
|
|
53
|
+
"tool_no_error_if_called",
|
|
34
54
|
"tool_not_called",
|
|
35
55
|
"tool_result_contains",
|
|
36
56
|
"tool_result_not_contains",
|
|
@@ -53,7 +73,8 @@
|
|
|
53
73
|
"prompt",
|
|
54
74
|
"requires_capabilities",
|
|
55
75
|
"session",
|
|
56
|
-
"skills"
|
|
76
|
+
"skills",
|
|
77
|
+
"timeout_ms"
|
|
57
78
|
],
|
|
58
79
|
"verdictModifierKeys": [
|
|
59
80
|
"allow_l0_plugin_divergence",
|
|
@@ -63,21 +63,50 @@ CONTENT_KEYS = {
|
|
|
63
63
|
"subagent_tool_absent",
|
|
64
64
|
"subagent_dispatched",
|
|
65
65
|
"subagent_declared_but_unused",
|
|
66
|
+
"subagent_output_contains",
|
|
66
67
|
"dispatch_count_max",
|
|
67
68
|
"skill_triggered",
|
|
68
69
|
"no_skill_triggered",
|
|
70
|
+
"skill_available",
|
|
71
|
+
"connector_available",
|
|
72
|
+
"tool_available",
|
|
73
|
+
"skill_tool_used",
|
|
69
74
|
"max_cost_usd",
|
|
70
75
|
"max_tokens",
|
|
71
76
|
"tool_calls_max",
|
|
77
|
+
"tool_no_error",
|
|
78
|
+
"tool_no_error_if_called",
|
|
79
|
+
"max_tool_errors",
|
|
80
|
+
"max_redundant_tool_calls",
|
|
72
81
|
"max_turns",
|
|
82
|
+
"compaction_occurred",
|
|
83
|
+
"all_tasks_completed",
|
|
84
|
+
"task_count_min",
|
|
85
|
+
"task_status",
|
|
86
|
+
"no_scratchpad_leak",
|
|
73
87
|
}
|
|
74
88
|
# content keys, but only evaluated on replay when the cassette carries controlOut
|
|
75
|
-
GATE_KEYS = {
|
|
89
|
+
GATE_KEYS = {
|
|
90
|
+
"question_asked",
|
|
91
|
+
"questions_count_max",
|
|
92
|
+
"gate_answers_delivered",
|
|
93
|
+
"gate_answer_count_min",
|
|
94
|
+
"hook_blocked",
|
|
95
|
+
"no_hook_blocked",
|
|
96
|
+
}
|
|
76
97
|
# manifest-backed: replay-checkable when the cassette carries an `artifacts` manifest (record snapshots one);
|
|
77
98
|
# a manifest-less cassette skips them. Since the 0.3.0 artifact-manifest these are NOT always live-only.
|
|
78
99
|
# computer_links_resolve joins this bucket (not CONTENT_KEYS): resolving a non-empty link set needs
|
|
79
100
|
# either a live filesystem or the cassette's artifacts manifest — see cassette.ts's manifestKeys comment.
|
|
80
|
-
MANIFEST_KEYS = {
|
|
101
|
+
MANIFEST_KEYS = {
|
|
102
|
+
"file_exists",
|
|
103
|
+
"user_visible_artifact",
|
|
104
|
+
"artifact_json",
|
|
105
|
+
"computer_links_resolve",
|
|
106
|
+
"computer_links_resolve_if_present",
|
|
107
|
+
"no_unexpected_files",
|
|
108
|
+
"input_unmodified",
|
|
109
|
+
}
|
|
81
110
|
# live-only: ALWAYS skipped on replay, with a loud warning (no filesystem, no network on the token-free lane)
|
|
82
111
|
LIVE_ONLY_KEYS = {
|
|
83
112
|
"egress_denied",
|
|
@@ -85,6 +114,8 @@ LIVE_ONLY_KEYS = {
|
|
|
85
114
|
"no_delete_in_outputs",
|
|
86
115
|
"self_heal_ran",
|
|
87
116
|
"transcript_no_host_path",
|
|
117
|
+
"no_mcp_error",
|
|
118
|
+
"max_peak_rss_bytes",
|
|
88
119
|
}
|
|
89
120
|
EGRESS_KEYS = {"egress_denied", "egress_allowed"}
|
|
90
121
|
# verdict modifiers — don't verify anything themselves (e.g. suppress a default-fail)
|
|
@@ -141,6 +172,7 @@ _EMBEDDED_TOP_LEVEL_KEYS = {
|
|
|
141
172
|
"fidelity",
|
|
142
173
|
"on_unanswered",
|
|
143
174
|
"prompt",
|
|
175
|
+
"timeout_ms", # wall-clock budget → kill + errorSource:timeout on expiry
|
|
144
176
|
"answers",
|
|
145
177
|
"expect_denied",
|
|
146
178
|
"assert",
|
|
@@ -178,6 +210,7 @@ REGEX_KEYS = {
|
|
|
178
210
|
"when_question",
|
|
179
211
|
"subagent_dispatched",
|
|
180
212
|
"question_asked",
|
|
213
|
+
"hook_blocked",
|
|
181
214
|
}
|
|
182
215
|
VALID_ON_UNANSWERED = {"fail", "prompt", "first", "llm"}
|
|
183
216
|
VALID_TIERS = ("protocol", "container", "microvm", "hostloop", "cowork")
|
package/.env.example
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# cowork-harness host-side credentials.
|
|
2
2
|
#
|
|
3
|
-
# Copy to `.env
|
|
3
|
+
# Copy to `.env`. Resolution order: exported env > `--dotenv <path>` > `./.env` (cwd) >
|
|
4
|
+
# `<install-root>/.env` — so a global/npm-linked install still finds this file at the package
|
|
5
|
+
# root even when you run from an unrelated directory. Already-exported vars always win.
|
|
4
6
|
# `.env` is gitignored. SECURITY: keep this at your repo/working-dir ROOT — never inside a
|
|
5
7
|
# mounted skill/project folder, or its contents would be copied into the agent's sandbox.
|
|
6
8
|
# The token value is also scrubbed from all persisted run logs regardless of source.
|
|
@@ -12,5 +14,9 @@ CLAUDE_CODE_OAUTH_TOKEN=
|
|
|
12
14
|
# token is present, mirroring the desktop).
|
|
13
15
|
# ANTHROPIC_API_KEY=
|
|
14
16
|
|
|
17
|
+
# Alternative: a raw Anthropic auth token. Same precedence as ANTHROPIC_API_KEY (used only when
|
|
18
|
+
# no OAuth token is set) — set at most one of the two.
|
|
19
|
+
# ANTHROPIC_AUTH_TOKEN=
|
|
20
|
+
|
|
15
21
|
# Optional: timezone passed through to the agent.
|
|
16
22
|
# TZ=America/New_York
|