fix(skill-use): a research phase writing markdown is not a TDD failure
The first live scoring of run 3 reported `cargo-test-driven-development` and `tdd-red-green-refactor` as compliance=FAIL: files were written and no test ever ran. Wrong, and wrong in the way this module exists to prevent. The phase wrote fifteen markdown notes and a helper script; there was no code to test-drive. Reporting it as an agent failure is a system defect wearing an agent's name — and it would have buried the actual finding, which is that a repo-less `research_only` mission is staffed with a Rust SDLC crew whose coder, tester, reviewer and committer have nothing to do. The check is now scoped to files with a source extension in the languages the skill itself names. Shell is deliberately excluded: a helper script written during a research turn is not behaviour-adding code, and the false failure costs more than the missed one. Recorded in SKILL-USE-BASELINE.md as finding 8 rather than quietly corrected. A measurement that hides its own false positives cannot be trusted about anyone else's. Also in the doc: the Trigger reason is half false now (the transport can surface a tool call; we simply still inline), and the architecture doc's observe/gate table said the container tier was ungated and unobserved, which shipped work has made wrong. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_018i9Ten1LU4jUr5d7TAWda9
This commit is contained in:
co-authored by
Claude Opus 5
parent
4a6d0dfe01
commit
c209e654d9
@@ -420,10 +420,9 @@ fn red_before_green(ev: &Evidence<'_>) -> Verdict {
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let first_write = ev
|
let first_write = ev.tools.iter().position(|t| {
|
||||||
.tools
|
WRITE_TOOLS.contains(&t.tool.as_str()) && t.path.as_deref().is_some_and(is_source)
|
||||||
.iter()
|
});
|
||||||
.position(|t| WRITE_TOOLS.contains(&t.tool.as_str()) && t.path.is_some());
|
|
||||||
let first_test = ev
|
let first_test = ev
|
||||||
.tools
|
.tools
|
||||||
.iter()
|
.iter()
|
||||||
@@ -432,9 +431,11 @@ fn red_before_green(ev: &Evidence<'_>) -> Verdict {
|
|||||||
// Nothing was written, so there was no implementation to test-drive.
|
// Nothing was written, so there was no implementation to test-drive.
|
||||||
(None, _) => Verdict::NotApplicable,
|
(None, _) => Verdict::NotApplicable,
|
||||||
(Some(_), None) => Verdict::Fail(format!(
|
(Some(_), None) => Verdict::Fail(format!(
|
||||||
"wrote {} file(s) and never ran a test — the loop is red, green, \
|
"wrote {} source file(s) and never ran a test — the loop is red, \
|
||||||
refactor, and a test that never ran cannot have been red",
|
green, refactor, and a test that never ran cannot have been red",
|
||||||
ev.writes().count()
|
ev.writes()
|
||||||
|
.filter(|w| w.path.as_deref().is_some_and(is_source))
|
||||||
|
.count()
|
||||||
)),
|
)),
|
||||||
(Some(w), Some(t)) if t < w => Verdict::Pass,
|
(Some(w), Some(t)) if t < w => Verdict::Pass,
|
||||||
_ => Verdict::NotObservable(
|
_ => Verdict::NotObservable(
|
||||||
@@ -446,6 +447,28 @@ fn red_before_green(ev: &Evidence<'_>) -> Verdict {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this file behaviour-adding code, in the languages the skill names?
|
||||||
|
///
|
||||||
|
/// The check is scoped to source because the first live run scored `fail`
|
||||||
|
/// against a **research** phase: its agents wrote fifteen markdown notes and
|
||||||
|
/// never ran a test, which is not a violation of anything — there was no code
|
||||||
|
/// to test-drive. Reporting that as an agent failure would be a system defect
|
||||||
|
/// wearing an agent's name, and the real finding it obscures is that a research
|
||||||
|
/// role is pinned TDD skills at all.
|
||||||
|
///
|
||||||
|
/// Extensions the skill itself names — "Rust, TypeScript, Python, anywhere
|
||||||
|
/// tests can run cheap" — plus their immediate neighbours. Prose, config and
|
||||||
|
/// data are excluded. Shell is excluded deliberately: a helper script written
|
||||||
|
/// during a research turn is not the behaviour-adding code this loop is about,
|
||||||
|
/// and the false failure costs more than the missed one.
|
||||||
|
fn is_source(path: &str) -> bool {
|
||||||
|
const SOURCE: [&str; 10] = [
|
||||||
|
".rs", ".ts", ".tsx", ".py", ".js", ".jsx", ".go", ".java", ".rb", ".kt",
|
||||||
|
];
|
||||||
|
let p = path.to_ascii_lowercase();
|
||||||
|
SOURCE.iter().any(|e| p.ends_with(e))
|
||||||
|
}
|
||||||
|
|
||||||
/// Commands that run a test suite, across the languages the skills name.
|
/// Commands that run a test suite, across the languages the skills name.
|
||||||
fn is_test_command(cmd: &str) -> bool {
|
fn is_test_command(cmd: &str) -> bool {
|
||||||
const RUNNERS: [&str; 9] = [
|
const RUNNERS: [&str; 9] = [
|
||||||
@@ -991,6 +1014,28 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A phase that wrote nothing had no implementation to test-drive.
|
/// A phase that wrote nothing had no implementation to test-drive.
|
||||||
|
/// The verdict the first live run got wrong.
|
||||||
|
///
|
||||||
|
/// A research phase wrote fifteen markdown notes and ran no test, and the
|
||||||
|
/// check called it a TDD failure. There was no code to test-drive. The
|
||||||
|
/// real finding is that a research role is pinned TDD skills at all, and
|
||||||
|
/// scoring the agent for it would have buried that.
|
||||||
|
#[test]
|
||||||
|
fn writing_prose_is_not_a_tdd_failure() {
|
||||||
|
let prompt = rendered(&[("tdd-red-green-refactor", "Red, green, refactor.")]);
|
||||||
|
let tools = acted(&[
|
||||||
|
("Write", Some("/mission/repo/research/notes.md"), json!({"file_path": "/mission/repo/research/notes.md"})),
|
||||||
|
("Edit", Some("/mission/repo/research/notes.md"), json!({"file_path": "/mission/repo/research/notes.md"})),
|
||||||
|
("Write", Some("/mission/repo/research/check.sh"), json!({"file_path": "/mission/repo/research/check.sh"})),
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
score(&prompt, &Evidence::new("", &tools), &builtin)[0].compliance,
|
||||||
|
Verdict::NotApplicable,
|
||||||
|
"markdown and a helper script are not the behaviour-adding code \
|
||||||
|
this loop is about"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_phase_that_wrote_nothing_is_not_a_tdd_failure() {
|
fn a_phase_that_wrote_nothing_is_not_a_tdd_failure() {
|
||||||
let prompt = rendered(&[("tdd-red-green-refactor", "Red, green, refactor.")]);
|
let prompt = rendered(&[("tdd-red-green-refactor", "Red, green, refactor.")]);
|
||||||
|
|||||||
+179
-112
@@ -1,7 +1,7 @@
|
|||||||
# Skill-Use baseline
|
# Skill-Use baseline
|
||||||
|
|
||||||
*First measurement of whether ClawMates' skills change what agents do.
|
*Whether ClawMates' skills change what agents do. First measured 2026-08-19;
|
||||||
2026-08-19.*
|
re-measured 2026-08-21 against tool evidence rather than agent prose.*
|
||||||
|
|
||||||
Scored on the three axes from `Skill-Use` (arXiv, 2026-08-05): **Trigger** (did
|
Scored on the three axes from `Skill-Use` (arXiv, 2026-08-05): **Trigger** (did
|
||||||
the agent reach for the skill), **Compliance** (did it follow the procedure),
|
the agent reach for the skill), **Compliance** (did it follow the procedure),
|
||||||
@@ -10,165 +10,232 @@ the agent reach for the skill), **Compliance** (did it follow the procedure),
|
|||||||
Read the method before the numbers. A measurement whose limits are not stated
|
Read the method before the numbers. A measurement whose limits are not stated
|
||||||
is worse than none, because it gets quoted without them.
|
is worse than none, because it gets quoted without them.
|
||||||
|
|
||||||
## Why there was no baseline before today
|
## What changed on 2026-08-21
|
||||||
|
|
||||||
Not because nobody ran it. Because **it could not have returned anything but
|
The 2026-08-19 measurement scored Compliance and Boundary from the
|
||||||
zero**, for two structural reasons that had nothing to do with agent behaviour:
|
`reasoning` events — the agent's own account of its turn. Since then the
|
||||||
|
container tier records what agents actually **do**
|
||||||
|
(`container_tool_hooks`, `PostToolUse`), and `vm_tool_tap` stopped throwing the
|
||||||
|
tool's arguments away, so `Bash` commands and `Write` paths are on the record.
|
||||||
|
|
||||||
1. 55 of 85 role skill bindings resolved to skills that were never authored.
|
`skill_use` now reads those. The difference is not cosmetic:
|
||||||
2. Even resolved skills had no delivery channel to a mission agent — the
|
|
||||||
catalogue's only route was an MCP server that mission claws cannot reach.
|
|
||||||
|
|
||||||
Both were fixed in the two commits preceding this document. Anyone who had run
|
- `workspace-repo-commit-protocol`'s Boundary was a substring search for
|
||||||
this measurement in July would have concluded "our agents ignore their skills",
|
`/workspace/repo` in the narrative. **An agent that wrote to the wrong root
|
||||||
which would have been false and expensive.
|
without narrating it scored a clean pass.** It now reads the write paths.
|
||||||
|
- `arxiv-daily`'s Boundary read a URL in prose, which may be the agent
|
||||||
|
explaining that it did *not* fetch it. It now reads the `curl` that ran.
|
||||||
|
- `tdd-red-green-refactor`, `cargo-test-driven-development` and
|
||||||
|
`small-focused-commits` gained their first checks at all.
|
||||||
|
|
||||||
|
Two verdicts changed for honesty rather than coverage. **Silence used to score
|
||||||
|
`Pass`** — a mission with no evidence scored identically to one checked and
|
||||||
|
found clean; it is now `NotObservable`. And a test that ran *after* the first
|
||||||
|
write is `NotObservable`, not a failure, because a Rust unit test lives in the
|
||||||
|
file under test.
|
||||||
|
|
||||||
|
### Trigger: the reason changed, and only half of it went away
|
||||||
|
|
||||||
|
The 2026-08-19 document said Trigger was unobservable because `claude_cli`
|
||||||
|
"cannot surface a tool call — there is nothing to retrieve *with*."
|
||||||
|
|
||||||
|
**That half is now false.** Mission tool calls are recorded on both tiers; a
|
||||||
|
retrieval would be as visible as any other call.
|
||||||
|
|
||||||
|
The other half still holds and is the one that decides the verdict: **we still
|
||||||
|
inline**. `pinned_skills_text` puts full skill bodies in the prompt, so the
|
||||||
|
agent never reaches for anything — it is simply holding one. Trigger is now
|
||||||
|
*instrumentable* and still not *observable*, and the blocker has moved from the
|
||||||
|
transport to the delivery model.
|
||||||
|
|
||||||
|
Making it real is one change and no scorer work: serve skills through the door
|
||||||
|
(`TOOL-CALL-ARCHITECTURE.md` §3) so retrieval becomes a tool call.
|
||||||
|
|
||||||
## Method, and what it cannot see
|
## Method, and what it cannot see
|
||||||
|
|
||||||
Scored by `cm_api::skill_use` from what the platform records: the
|
Scored by `cm_api::skill_use` from what the platform records: `prompt.composed`
|
||||||
`prompt.composed` event (the exact bytes an agent received) and the `reasoning`
|
(the exact bytes an agent received), `reasoning` (what it said it did), and
|
||||||
events (what it said it did). No re-derivation from the catalogue — the
|
`tool.call` (what it did). No re-derivation from the catalogue — the catalogue
|
||||||
catalogue changes, and now that agents author their own skills it changes by
|
changes, and now that agents author their own skills it changes by itself.
|
||||||
itself.
|
|
||||||
|
|
||||||
### Trigger is not observable here, and that is a finding
|
**Every tool-backed check is one-sided.** It reports a violation it can see and
|
||||||
|
never infers compliance from silence: the recorded stream is capped per phase
|
||||||
The paper measures agents under **progressive disclosure**: the agent sees a
|
(`PER_PHASE_CAP = 400`), so an absent call is not proof of an absent action.
|
||||||
name and description and must decide to retrieve the body. That retrieval is a
|
|
||||||
tool call, which makes Trigger observable.
|
|
||||||
|
|
||||||
We do not deliver skills that way. `pinned_skills_text` inlines full bodies into
|
|
||||||
the prompt, because mission claws run on `claude_cli`, which cannot surface a
|
|
||||||
tool call — there is nothing to retrieve *with*. The agent never reaches for a
|
|
||||||
skill; it is simply holding one.
|
|
||||||
|
|
||||||
So Trigger is reported as `not_observable` with the reason attached, **never as
|
|
||||||
zero**. Scoring it zero would report a delivery-model property as an agent
|
|
||||||
failure — the same confusion that kept 55 empty bindings invisible.
|
|
||||||
|
|
||||||
### Compliance and Boundary are checked mechanically, or not at all
|
|
||||||
|
|
||||||
Only skills whose procedure has a machine-checkable consequence are scored.
|
Only skills whose procedure has a machine-checkable consequence are scored.
|
||||||
Everything else returns `not_applicable` rather than a guess: a heuristic that
|
Everything else returns `not_applicable` rather than a guess — a heuristic that
|
||||||
scores prose by keyword overlap produces a number that looks like a measurement
|
scores prose by keyword overlap produces a number that looks like a measurement
|
||||||
and is not one.
|
and is not one.
|
||||||
|
|
||||||
Compliance for `int-xx-marker-protocol` is checked by running the **real**
|
Compliance for `int-xx-marker-protocol` is checked by running the **real**
|
||||||
`task_card_parser`, not a copy of its rules — a second implementation would
|
`task_card_parser`, not a copy of its rules; a second implementation would
|
||||||
drift, and then the score would pass while the mission loop still stalled.
|
drift, and then the score would pass while the mission loop still stalled.
|
||||||
|
|
||||||
## The runs
|
## The runs
|
||||||
|
|
||||||
Two missions on the container/ZeroClaw tier, local stack, `research_only`.
|
Three missions on the container/ZeroClaw tier, local stack, `research_only`.
|
||||||
|
Run 3 uses the **same task text as run 2**, so the only variable is the scorer.
|
||||||
|
|
||||||
| | run 1 | run 2 |
|
| | run 1 | run 2 | run 3 |
|
||||||
|---|---|---|
|
|---|---|---|---|
|
||||||
| distinct skills delivered | 3 | **9** |
|
| date | 08-19 | 08-19 | 08-21 |
|
||||||
| total deliveries (per role prompt) | 3 | 14 |
|
| distinct skills delivered | 3 | 9 | 9 |
|
||||||
| phantom "skills" scored | **2** | 0 |
|
| total deliveries (per role prompt) | 3 | 14 | 14 |
|
||||||
|
| phantom "skills" scored | **2** | 0 | 0 |
|
||||||
|
| tool calls recorded | 0 | 0 | **49** |
|
||||||
|
| axes scored from actions | 0 | 0 | **3 skills** |
|
||||||
|
|
||||||
Run 1's phantom entries are the finding of the run, described below.
|
Run 3, per skill (all `source_kind=builtin`; no agent-authored skill has been
|
||||||
|
|
||||||
Run 2, per skill (all `source_kind=builtin`; no agent-authored skill has been
|
|
||||||
delivered yet):
|
delivered yet):
|
||||||
|
|
||||||
| skill | deliveries | compliance | boundary |
|
| skill | deliveries | compliance | boundary |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `int-xx-marker-protocol` | 1 | **pass** | n/a |
|
| `int-xx-marker-protocol` | 1 | **pass** | n/a |
|
||||||
| `small-focused-commits` | 4 | n/a | n/a |
|
| `workspace-repo-commit-protocol` | 2 | n/a | **pass** *(from 12 write paths)* |
|
||||||
|
| `small-focused-commits` | 4 | n/a | n/a *(no commit ran)* |
|
||||||
| `cargo-test-driven-development` | 2 | n/a | n/a |
|
| `cargo-test-driven-development` | 2 | n/a | n/a |
|
||||||
| `workspace-repo-commit-protocol` | 2 | n/a | n/a |
|
| `tdd-red-green-refactor` | 1 | n/a | n/a |
|
||||||
| `decompose-int-items` | 1 | n/a | n/a |
|
| `decompose-int-items` | 1 | n/a | n/a |
|
||||||
| `write-rust-current-edition` | 1 | n/a | n/a |
|
| `write-rust-current-edition` | 1 | n/a | n/a |
|
||||||
| `code-review-checklist` | 1 | n/a | n/a |
|
| `code-review-checklist` | 1 | n/a | n/a |
|
||||||
| `criterion-benchmarking` | 1 | n/a | n/a |
|
| `criterion-benchmarking` | 1 | n/a | n/a |
|
||||||
| `tdd-red-green-refactor` | 1 | n/a | n/a |
|
|
||||||
|
|
||||||
**n = 2 runs. No spread is reported because two runs cannot establish one.**
|
**n = 3 runs. No spread is reported because three cannot establish one.** This
|
||||||
This is a baseline in the sense of "the first honest number", not in the sense
|
is a baseline in the sense of "the first honest number", not in the sense of
|
||||||
of `metrics-baseline-comparison.md`, which requires enough runs to see the noise
|
`metrics-baseline-comparison.md`, which requires enough runs to see the noise
|
||||||
floor before any change is judged against it. Do not compare a future number to
|
floor before any change is judged against it.
|
||||||
this one without first establishing that floor.
|
|
||||||
|
`workspace-repo-commit-protocol`'s pass is the one score that materially
|
||||||
|
improved: it now rests on twelve recorded `Write`/`Edit` paths, every one under
|
||||||
|
`/mission/repo`, instead of on the absence of a string in prose.
|
||||||
|
|
||||||
## What the measurement found
|
## What the measurement found
|
||||||
|
|
||||||
Four defects, none of which any test or log would have surfaced.
|
### 1–4: the 2026-08-19 findings
|
||||||
|
|
||||||
### 1. The prompt format made its own record unparseable
|
Four defects, none of which any test or log would have surfaced: the prompt
|
||||||
|
format made its own record unparseable (`## <name>` against markdown bodies);
|
||||||
|
a prompt was recorded that was never sent; a pinned skill taught
|
||||||
|
`/workspace/repo`, a path the platform does not mount; and
|
||||||
|
`int-xx-marker-protocol` documented a `PLAN_COMPLETE` marker the parser had
|
||||||
|
never implemented. All four are fixed, with guards in
|
||||||
|
`skills_loader::contradiction_tests` and `topology_exec`. The detail is in this
|
||||||
|
file's git history.
|
||||||
|
|
||||||
Skills were introduced with `## <name>`, and skill bodies are markdown full of
|
### 5. The tool tap recorded the name and discarded the argument
|
||||||
`##` headings. Run 1 duly scored **"Sizing heuristic"** and **"The output
|
|
||||||
shape"** — both subheadings inside `decompose-int-items` — as skills with no
|
|
||||||
catalogue row.
|
|
||||||
|
|
||||||
Fixed with an unambiguous `--- SKILL: <name> ---` marker, and both writers now
|
The first container-tier mission with telemetry recorded `Bash × 6` and not one
|
||||||
share one renderer so the reader cannot drift from the writer.
|
of them said what it ran. `vm_tool_tap::parse` read `tool_input` to pull the
|
||||||
|
path out of it and dropped the rest.
|
||||||
|
|
||||||
### 2. A prompt was recorded that was never sent
|
Every behavioural question was therefore unanswerable from a record that looked
|
||||||
|
complete — which is the recurring shape, not a new one. Fixed host-side: the
|
||||||
|
arguments were always in the tap file.
|
||||||
|
|
||||||
The phase prompt was recorded at the dispatch fork, before the tier was chosen.
|
### 6. Two more skills contradicted the platform
|
||||||
The container tier does not send that text — it sends the bare task and appends
|
|
||||||
skills per turn. So every container mission logged a `solo` prompt that reached
|
|
||||||
no agent.
|
|
||||||
|
|
||||||
A provenance record of something that did not happen is worse than no record: it
|
Same class as finding 3, and both found by reading the source of truth before
|
||||||
is the wrong answer, delivered confidently. Recording now happens inside each
|
writing a check against it.
|
||||||
tier, and a test asserts every launcher records the prompt it actually sends.
|
|
||||||
|
|
||||||
### 3. A pinned skill contradicted the platform in the same prompt
|
- **`decompose-int-items` taught `PLAN_COMPLETE: INT-01..05`.** An id is
|
||||||
|
strictly `INT-` plus digits, so the range form is rejected outright: the plan
|
||||||
|
pass records nothing while every item stays open. A live planner emitted
|
||||||
|
exactly that line.
|
||||||
|
- **`workspace-repo-commit-protocol` claimed the task-card parser advances
|
||||||
|
mission state on the INT id in your commit subject.** Nothing in the platform
|
||||||
|
reads commit messages. `task_card_parser::apply_for_run` reads `run_events` —
|
||||||
|
the agent's turn output. An agent that believed this would commit with the id,
|
||||||
|
never emit `COMPLETED: INT-NN`, and leave the mission open on an item it had
|
||||||
|
already finished.
|
||||||
|
|
||||||
`workspace-repo-commit-protocol` told agents that **`/workspace/repo`** was "the
|
`no_skill_shows_a_marker_the_parser_would_reject` now runs the real parser over
|
||||||
ONLY path where source-modifying edits belong". The platform mounts and
|
every marker in every skill's fenced blocks, negative-controlled against the
|
||||||
advertises **`/mission/repo`** — 26 references in the code; `/workspace/repo`
|
range form.
|
||||||
appears in none. The skill is bound on **29 role bindings** and was delivered
|
|
||||||
twice in run 2, so agents received the real path in the tool preamble and a
|
|
||||||
skill contradicting it a few hundred tokens later.
|
|
||||||
|
|
||||||
It also instructed `file_read` / `file_write` / `shell` — ZeroClaw's tool names,
|
### 7. A repo-less research mission is staffed with a Rust SDLC crew
|
||||||
the exact ones `phase_task_text` was fixed to stop advertising after five agents
|
|
||||||
on one mission spent 7.4k tokens describing the mismatch instead of working.
|
|
||||||
|
|
||||||
An agent that obeyed this skill wrote source into a directory nothing collects,
|
This is the finding of run 3, and it explains most of the `not_applicable`
|
||||||
and reached for tools its subprocess does not expose. Rewritten against what the
|
column above.
|
||||||
code actually does, with two guards in `skills_loader::contradiction_tests`: no
|
|
||||||
skill may name a repo path the platform does not mount, and none may instruct a
|
|
||||||
tool the agent does not have. Both negative-controlled.
|
|
||||||
|
|
||||||
This is the same shape as the finding below and it is worth stating as a class:
|
`templates/workflows/research_only.toml` declares `requires_repo = false` and a
|
||||||
**the skills were never checked against the platform they describe.** Nothing
|
single `research` phase — and `default_team_template = "rust_sdlc"`. So the
|
||||||
compared them, so a skill could contradict the prompt it ships inside and stay
|
mission was staffed with **planner, coder, tester, reviewer, committer**, and
|
||||||
that way indefinitely.
|
each received the skills its role is bound to:
|
||||||
|
|
||||||
### 4. The skill documents a marker the platform never implemented
|
```
|
||||||
|
coder :: write-rust-current-edition, cargo-test-driven-development,
|
||||||
|
workspace-repo-commit-protocol, small-focused-commits,
|
||||||
|
int-xx-marker-protocol
|
||||||
|
tester :: cargo-test-driven-development, criterion-benchmarking,
|
||||||
|
tdd-red-green-refactor
|
||||||
|
committer :: workspace-repo-commit-protocol, small-focused-commits
|
||||||
|
reviewer :: code-review-checklist, small-focused-commits
|
||||||
|
planner :: decompose-int-items, small-focused-commits
|
||||||
|
```
|
||||||
|
|
||||||
`int-xx-marker-protocol` lists `PLAN_COMPLETE: INT-NN` in its ladder.
|
There is no repository, nothing to test, nothing to review and nothing to
|
||||||
`task_card_parser` has **no such kind** and never has. An agent following the
|
commit. Four of the five roles have no work, and 50KB of prompt (~12.6k tokens)
|
||||||
skill exactly emits a marker that is silently ignored.
|
is spent staffing them.
|
||||||
|
|
||||||
Observed live: run 2's planner emitted `PLAN_COMPLETE: INT-01..02`, which is
|
**The skills are correctly bound to the roles. The roles are wrong for the
|
||||||
also the range form — on the kinds that *are* parsed, that yields the id
|
workflow.** That distinction matters: a reader who saw only "7 of 9 skills
|
||||||
`INT-01..02`, a task card for an item that does not exist while the two real
|
scored not_applicable" would conclude the skills are useless, when what the
|
||||||
items stay open.
|
number actually measures is a staffing default.
|
||||||
|
|
||||||
**This is a skill/implementation mismatch, not an agent failure**, and it is
|
`continuous_research` names its own team; the other four recipes all default to
|
||||||
precisely what this measurement exists to find: the agent did what it was told,
|
`rust_sdlc`. `research_only` has no correct existing template to point at —
|
||||||
and what it was told was wrong. Both shapes are now scored as failures; the
|
`papers_research` is arXiv-shaped, `insight_research` is vault-shaped, and
|
||||||
underlying reconciliation — implement `PLAN_COMPLETE` or remove it from the
|
`codebase_research` needs a repo — so the fix is an operator decision, not a
|
||||||
skill — is deliberately left as a decision rather than guessed at here.
|
one-line edit, and is deliberately left open.
|
||||||
|
|
||||||
|
### 8. The check that got it wrong first
|
||||||
|
|
||||||
|
Run 3's first scoring reported `cargo-test-driven-development` and
|
||||||
|
`tdd-red-green-refactor` as **compliance = fail**: files were written and no
|
||||||
|
test ever ran.
|
||||||
|
|
||||||
|
That verdict was wrong, and wrong in the way this whole document exists to
|
||||||
|
prevent. The phase wrote fifteen markdown notes and a helper script. There was
|
||||||
|
no code to test-drive. Reporting it as an agent failure would have been a system
|
||||||
|
defect wearing an agent's name — and it would have buried the real finding,
|
||||||
|
which is finding 7 above.
|
||||||
|
|
||||||
|
The check is now scoped to files with a source extension in the languages the
|
||||||
|
skill itself names. It is recorded here rather than quietly corrected, because
|
||||||
|
a measurement that hides its own false positives cannot be trusted about
|
||||||
|
anyone else's.
|
||||||
|
|
||||||
## Honest limits
|
## Honest limits
|
||||||
|
|
||||||
- **Two runs, one tier, one workflow.** Nothing here generalises to the microVM
|
- **Three runs, one tier, one workflow.** Nothing here generalises to the
|
||||||
or session tiers yet, and this document should not be read as if it does.
|
microVM or session tiers.
|
||||||
- **Most skills score `not_applicable`** on both observable axes. That is not a
|
- **The new checks are not yet exercised live.** `research_only` writes no code
|
||||||
pass. It means we cannot currently tell whether those skills changed anything.
|
and makes no commits, so the TDD and commit checks are proven by unit tests
|
||||||
`workspace-repo-commit-protocol` now has a Boundary check (writing outside
|
and negative controls, not by a mission that could have violated them. A
|
||||||
`/mission/repo`); `small-focused-commits` and `tdd-red-green-refactor` remain
|
coding run against a real repository is the next measurement.
|
||||||
candidates, and both need the repository diff rather than the turn text.
|
- **Tool calls carry no agent attribution.** `record_vm_tools` writes
|
||||||
- **No agent-authored skill has been measured.** Self-authoring shipped in the
|
`agent_id: None` — the container tier's tap is per-container, and all five
|
||||||
same pass; `source_kind` is carried through the scorer specifically so a
|
roles share one container. Every score above is therefore per-**mission**,
|
||||||
rising score on agent-authored skills is visible rather than averaged in.
|
not per-role, and the World's per-agent view gets nothing from it. Mapping
|
||||||
|
the hook payload's `session_id` back to a turn would fix it.
|
||||||
|
- **Most skills still score `not_applicable`** on both observable axes. That is
|
||||||
|
not a pass. See finding 7 for why the number is what it is.
|
||||||
|
- **No agent-authored skill has been measured.** `source_kind` is carried
|
||||||
|
through the scorer specifically so a rising score on agent-authored skills is
|
||||||
|
visible rather than averaged in.
|
||||||
- **Evidence expires.** Mission events are reaped after 7 days unless
|
- **Evidence expires.** Mission events are reaped after 7 days unless
|
||||||
`retain_events_until` is set. Both runs here are held for 90 days. An empty
|
`retain_events_until` is set; `scripts/skill-use-run.sh` holds every run for
|
||||||
score means "no evidence", never "no compliance", and the API says so in its
|
90 days so it stays re-scorable when the scorer changes again — which is
|
||||||
payload rather than leaving the caller to infer it.
|
exactly what happened to run 3. An empty score means "no evidence", never "no
|
||||||
|
compliance", and the API says so in its payload.
|
||||||
|
|
||||||
|
## Reproducing
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/skill-use-run.sh "<title>" "<task>" # run and score
|
||||||
|
scripts/skill-use-run.sh --score <mission-id> # re-score, no new run
|
||||||
|
```
|
||||||
|
|
||||||
|
Local stack only. Production auth is Clerk and a mission cannot be launched
|
||||||
|
from a terminal there — which is also why, as of 2026-08-21, **production has
|
||||||
|
never run a mission at all** (`select count(*) from missions` → 0).
|
||||||
|
|||||||
@@ -26,17 +26,32 @@ a security posture, and it is the one we have.
|
|||||||
|
|
||||||
## Observe versus gate — they are different, and both are partial
|
## Observe versus gate — they are different, and both are partial
|
||||||
|
|
||||||
|
As of 2026-08-21, with the container-tier hooks shipped:
|
||||||
|
|
||||||
| Path | Has tools | We observe | We gate |
|
| Path | Has tools | We observe | We gate |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Solo microVM | yes | **yes** — `vm_tool_tap` | no |
|
| Solo microVM | yes | **yes** — `vm_tool_tap` | **yes** — `vm_tool_gate` |
|
||||||
| Composed microVM | yes | **yes** — same tap | no |
|
| Composed microVM | yes | **yes** — same tap | **yes** — same gate |
|
||||||
| Direct session | yes | **no mechanism at all** | no |
|
| Direct session | yes | **no mechanism at all** | no |
|
||||||
| Container / ZeroClaw | yes (see below) | mechanism exists, receives nothing | no |
|
| Container / ZeroClaw | yes (see below) | **yes** — `container_tool_hooks` | **yes** — same |
|
||||||
|
|
||||||
|
The direct-session row is the only remaining gap, and it is dormant:
|
||||||
|
`CLAWMATES_MISSION_EXECUTOR` is unset in production, so that tier never runs.
|
||||||
|
Checking that before building a tap for it is the reason there is no tap for it.
|
||||||
|
|
||||||
`vm_tool_tap` installs a **`PostToolUse`** hook, which fires *after* the tool has
|
`vm_tool_tap` installs a **`PostToolUse`** hook, which fires *after* the tool has
|
||||||
already run, and `exit 0`s unconditionally because a non-zero `PostToolUse`
|
already run, and `exit 0`s unconditionally because a non-zero `PostToolUse`
|
||||||
talks back to the model. It is telemetry and says so. It is structurally
|
talks back to the model. It is telemetry and says so. It is structurally
|
||||||
incapable of gating.
|
incapable of gating — which is why the gate is a separate `PreToolUse` hook
|
||||||
|
rather than a stricter version of this one.
|
||||||
|
|
||||||
|
**What the tap records.** Until 2026-08-21 it kept the tool's name and the path
|
||||||
|
its arguments named, and threw the arguments themselves away. A phase that
|
||||||
|
recorded `Bash × 6` could not answer whether it ran the tests, whether it
|
||||||
|
committed, or whether it called an API a skill forbids. It now keeps the
|
||||||
|
arguments, bounded: file bodies become a byte count, over-long strings are
|
||||||
|
truncated with a marker. That is what makes `skill_use` able to score behaviour
|
||||||
|
rather than the agent's own account of it.
|
||||||
|
|
||||||
The §15 `GatePolicy` has exactly **one** enforcement site — `Runtime::drive`,
|
The §15 `GatePolicy` has exactly **one** enforcement site — `Runtime::drive`,
|
||||||
the chat loop — and its approvals are keyed to `(session_id, message_id)`, which
|
the chat loop — and its approvals are keyed to `(session_id, message_id)`, which
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
# PG local postgres ctr (default clawmates-postgres-1)
|
# PG local postgres ctr (default clawmates-postgres-1)
|
||||||
# OWNER account to mint for (default [email protected])
|
# OWNER account to mint for (default [email protected])
|
||||||
# TEMPLATE workflow recipe (default research_only)
|
# TEMPLATE workflow recipe (default research_only)
|
||||||
|
# REPO_ID repository to check out; required by the coding recipes, and
|
||||||
|
# the only way the TDD and commit checks can ever fire — a
|
||||||
|
# repo-less run writes markdown and commits nothing
|
||||||
# TIMEOUT seconds to wait (default 1800)
|
# TIMEOUT seconds to wait (default 1800)
|
||||||
# RETAIN_DAYS hold events this long so the run stays re-scorable (default 90)
|
# RETAIN_DAYS hold events this long so the run stays re-scorable (default 90)
|
||||||
|
|
||||||
@@ -87,13 +90,16 @@ fi
|
|||||||
TITLE="${1:?title}"
|
TITLE="${1:?title}"
|
||||||
TASK="${2:?task description}"
|
TASK="${2:?task description}"
|
||||||
|
|
||||||
body=$(python3 - "$TITLE" "$TASK" "$TEMPLATE" <<'PY'
|
body=$(python3 - "$TITLE" "$TASK" "$TEMPLATE" "${REPO_ID:-}" <<'PY'
|
||||||
import json, sys
|
import json, sys
|
||||||
print(json.dumps({
|
req = {
|
||||||
"title": sys.argv[1],
|
"title": sys.argv[1],
|
||||||
"description": sys.argv[2],
|
"description": sys.argv[2],
|
||||||
"template_kind": sys.argv[3],
|
"template_kind": sys.argv[3],
|
||||||
}))
|
}
|
||||||
|
if sys.argv[4]:
|
||||||
|
req["repo_id"] = sys.argv[4]
|
||||||
|
print(json.dumps(req))
|
||||||
PY
|
PY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user