docs(skills): require a proven positive before trusting a negative result - #2755
docs(skills): require a proven positive before trusting a negative result#2755tpavay wants to merge 4 commits into
Conversation
|
Speaking as Kun's firstmate: VISION verdict: align. An unproven empty result is a query failure, not evidence of absence. That keeps the interface honest under load. The load-trigger widening is scoped to query-derived absence, so digest Class: default-behavior. Reviewed the full diff vs base. Docs-only: a short section on Security: no. Waiting on CI including no-mistakes, not on the captain. Not merge-eligible yet. Not a captain-flag. |
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Reviews (3): Last reviewed commit: "no-mistakes: apply CI fixes" | Re-trigger Greptile |
A zero, an empty result, or a "not found" is a claim about the method as much as about the world. Reporting absence from a method never shown to find what is there produces confident false statements, and two checks that share a failure mode read as corroboration while being wrong together. Add a short section to the diagnostic-reasoning skill: prove the method against data known to exist before trusting a negative, distinguish found / confirmed absent / the query failed rather than only present or absent, require an independent method for corroboration, and hold captain-facing absence claims to the same burden as any other load-bearing finding. Widen the skill's load trigger, in its own description and in AGENTS.md sections 7 and 13, to cover reporting that something is absent, empty, missing, or not found, so the rule is loaded where that class of claim is actually made rather than only during bug scoping.
…itive-control fallback
e36148e to
3b857c1
Compare
Intent
Add one rule to firstmate's diagnostic reasoning: a negative result is not evidence until the method has been shown to produce a positive.
Evidence this comes from: in one session, firstmate told the captain six false things, all the same shape - reporting that something was ABSENT using a method never shown capable of detecting PRESENCE. An invoked subcommand that does not exist, counted with grep -c, returned 0 and was reported as "no leftover data" (the data existed). An API answering "which subcollections does this document have" returned none when three existed. A URL with literal parentheses inside a shell loop broke every request, and the empty output was reported to the captain as an empty production leaderboard that actually held 13 records. A grep pattern that also matched a second unintended path shape reported 19 where the truth was 6. A production storage bucket was reported as holding zero objects when it held 264. A tree comparison keyed by a name that seventeen nodes shared silently dropped all but one, producing 19 false differences. Two of these were presented to the captain as corroborating each other, which made the wrong answer more convincing. The only check that did not go wrong was the one first run against data known to be present.
The change: add a short section to .agents/skills/diagnostic-reasoning/SKILL.md, because that skill already owns how firstmate reasons about evidence and already separates observed facts from hypotheses; this is the missing rule about a class of observed "fact" that is really a tooling artifact. The rule, in the skill's own voice and register: a zero, an empty result, or a "not found" is a claim about the method as much as about the world, so before reporting absence run the same method against data known to exist and treat it as having established nothing if it cannot find that; distinguish three outcomes rather than two (found, confirmed absent, the query failed), with tooling that renders a failure as an empty result being the trap, so an unproven empty is treated as a failure until shown otherwise; two agreeing checks are not corroboration when they share a failure mode, since a wrong pattern and a wrong endpoint can agree with each other, so corroboration requires an independent method rather than a repeated one; and this binds hardest on captain-facing statements, because "there is nothing there" is a conclusion the captain will act on and carries the same burden as any other load-bearing finding.
Deliberate constraints, chosen by the requester and not accidents of the diff:
One decision made while doing the work, beyond the original request, and already flagged to the requester for possible reversion: the skill's load trigger was widened. The original trigger fired only "before scoping a reported bug and before acting on a diagnostic report", so firstmate stating "the bucket is empty" would never have loaded the skill and the new rule would not be read at the moment it matters. The trigger now also reads "before reporting that something is absent, empty, missing, or not found". This was patched in place in four existing lines rather than added as new ones: the SKILL.md frontmatter description, the SKILL.md opening body line, and the two existing trigger lines in AGENTS.md (section 7's intake line and section 13's agent-only skill registry line). AGENTS.md grew by zero lines, which is deliberate under the repo's size-discipline rule that every AGENTS.md line is paid by every session of every fleet member.
Also in scope per the request: a pointer from anywhere that genuinely needs one. The widened trigger is that pointer; no new pointer file or section was added.
Repo conventions followed deliberately: one full sentence per line in tracked Markdown, plain dash rather than em dash, no agent name as commit co-author. No new test was added: the change is prose in an agent-loaded skill with no executable interface, and the repo's own testing rule forbids tests that assert implementation-source bytes. bin/fm-doc-audience-check.sh passes, tests/fm-documentation-audiences.test.sh passes, and bin/fm-lint.sh reports no changed lint targets because no scripts were touched.
What Changed
.agents/skills/diagnostic-reasoning/SKILL.md, placed between the causal-explanation and scoping sections. It requires running the same method against data known to exist before reporting absence, splits outcomes into three (found / confirmed absent / the query failed) with an unproven empty treated as a failure, rules out corroboration between two checks that share a failure mode, and binds hardest on captain-facing statements. The failure examples are deliberately tool-agnostic, and the section stays at roughly a dozen lines so it does not get skimmed.AGENTS.md- leavingAGENTS.mdat zero net line growth.ABSENTmarkers and healthy-empty-queue relays out of it, and added a fallback for targets with no known-present sample: run the control against the nearest comparable populated location, or state the missing control explicitly rather than claiming confirmed absence.No new tests: the change is prose in an agent-loaded skill with no executable interface.
bin/fm-doc-audience-check.shandtests/fm-documentation-audiences.test.shpass; the remaining failures in the changed-file test set are pre-existing onmainper issue #2753 (three red specs, plus the two lint scripts exiting 127 from a missingactionlint), which is also the source of the Lint stage's exit-code-127 warning.Risk Assessment
✅ Low: Documentation-only prose change to one agent-loaded skill plus two in-place AGENTS.md trigger lines, with both prior findings fixed exactly as instructed, all four trigger surfaces verified identical and no stale wording left anywhere, zero net AGENTS.md line growth, and no executable interface touched.
Testing
I rendered the edited skill as an agent reads it and screenshotted it, confirming the new twelve-line section sits between "Test the causal explanation" and "Scope and act on the result" and that the widened trigger reaches all four intended lines (SKILL.md frontmatter and opening line, AGENTS.md sections 7 and 13) with AGENTS.md unchanged at 573 lines. To show the rule does real work rather than just reading well, I reproduced all six failure shapes it exists to catch against this repo's own data - a subcommand that does not exist returning 0 while 20 skills are checked in, a parenthesized URL in a shell loop returning silently empty, a listing that cannot see one level down, a pattern matching an unintended second path, a name key collapsing 21 rows to 1, and two broken checks agreeing on 0 - and showed each false absence being caught by the positive control the new section requires. bin/fm-doc-audience-check.sh and the targeted documentation-audiences and ensure-agents-md tests pass; the changed-file suite's 5 failures are the pre-existing reds and missing-actionlint 127s documented in issue #2753, all unrelated to this Markdown-only diff. The worktree is clean and no transient artifacts remain.
/var/folders/m3/qgj4gq85293_kqsxz93qlp3m0000gn/T/no-mistakes-evidence/01M0JSR3NFAJEC5DNGNNNW77JG/skill-rendered.png)Evidence: HTML source of the rendered skill surface
Evidence: Negative-result drill: the six incident shapes reproduced against this repo, each caught by the required positive control
1. A subcommand that does not exist, counted with grep -c $ git ls-file --cached '.agents/skills//SKILL.md' | grep -c SKILL.md -> 0 naive claim to the captain: "no agent-only skills are checked in" positive control required by the rule: run the same method against data known to exist $ git ls-file --cached AGENTS.md | grep -c AGENTS.md # AGENTS.md certainly exists -> 0 (the method cannot find a file that is definitely there) verdict: THE QUERY FAILED, not confirmed absent. truth = 20 skills. 2. A malformed request inside a shell loop (literal parentheses) $ for u in "$url"; do bash -c "curl -s $u"; done 2>/dev/null -> 0 lines of output, and the command printed no error the caller could see naive claim to the captain: "the production leaderboard is empty" positive control: run the same fetch against a record set known to be populated -> 0 lines (the fetch returns empty even where rows are known to exist) verdict: THE QUERY FAILED. truth = 3 records. 5. A key collision that silently drops rows $ git ls-files '/SKILL.md' | awk -F/ '{print $NF}' | sort -u | wc -l -> 1 verdict: 21 rows collapsed into 1 because every row shares the key 'SKILL.md'. 6. Two agreeing checks that share a failure mode check A -> 0 ; check B -> 0 ; independent method -> 20 verdict: corroboration requires an independent method, not a repeated one.Evidence: Drill script (read-only, runs against the checkout)
Evidence: Intent constraint checks: AGENTS.md zero growth, section placement, section length, repo prose conventions
### AGENTS.md size discipline base 573 lines target 573 lines ### Section order in SKILL.md 18:## Establish the observed behavior 34:## Test the causal explanation 46:## Trust a negative only after the method has produced a positive 63:## Scope and act on the result ### em dash in changed files: none (plain dash only) ### one sentence per line: none violatedEvidence: Trigger wiring: the widened clause present in all four lines firstmate reads
--- SKILL.md opening body line: Use this procedure before scoping a reported bug, before acting on a diagnostic report, and before reporting that a search, query, or scan found nothing. --- AGENTS.md section 7 intake line: 280:Loaddiagnostic-reasoningbefore scoping a reported bug, before acting on a diagnostic report, and before reporting that a search, query, or scan found nothing. --- AGENTS.md section 13 skill registry line: 528:-diagnostic-reasoning- load before scoping a reported bug, before acting on a diagnostic report, and before reporting that a search, query, or scan found nothing. lines containing the absence clause: 4 (expect 4) prose lines added to the skill, excluding heading and blanks: 11Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
AGENTS.md:280- The widened load trigger "before reporting that something is absent, empty, missing, or not found" has no carve-out for routine operational absence, which firstmate reports on nearly every turn: the session-start digest prints explicitABSENTmarkers (AGENTS.md:174), firstmate relays "no registered secondmates" / "captain.md absent" (AGENTS.md:147), and "an empty queue is healthy" (AGENTS.md:237, :368). Read literally, each of those relays now requires loadingdiagnostic-reasoning, which conflicts with AGENTS.md:526 ("load them only at their precise triggers") and dilutes the trigger precision that makes the agent-only registry cheap. Consider scoping the clause to absence established by a query firstmate ran - e.g. "before reporting that something searched for or queried is absent, empty, missing, or not found" - so digest-sourced ABSENT markers and queue state stay out of it. This is the decision the author already flagged for possible reversion; the specific conflict with the digest's own absence vocabulary is the part worth deciding on..agents/skills/diagnostic-reasoning/SKILL.md:48- The rule requires running the method against "data known to exist" before reporting absence, and line 57 says to treat an unproven empty as a failure until shown otherwise. When no known-present sample exists - a newly created bucket, an empty namespace, a collection that genuinely never had rows - there is nothing to run the positive control against, so "confirmed absent" becomes permanently unreachable and every true absence is classified as a query failure. The file already models the escape valve for exactly this shape at line 21 ("If a faithful reproduction is not feasible, record the exact limitation and use the closest representative path without presenting it as equivalent evidence"). A half-line - fall back to a second independent method, or state the absence with the missing control disclosed - would close it without breaking the deliberate ~12-line budget.🔧 Fix: narrow absence trigger to query results; add positive-control fallback
✅ Re-checked - no issues remain.
fm-lint-workflows.sh: actionlint not found; install actionlint 1.7.12 for CI parity), also issue firstmate repo: bin/fm-lint.sh fails 127 on main (actionlint missing) + 3 pre-existing red specs #2753. Installing actionlint would be a system-level change outside this worktree, so I left the environment alone.bin/fm-doc-audience-check.sh- passes, 70 surfaces and 258 local links validatedbin/fm-test-run.sh tests/fm-documentation-audiences.test.sh- passes, including the owner-pointer and local-link assertions that cover the edited AGENTS.md and SKILL.mdbin/fm-test-run.sh --changed- 32 scripts, 27 pass; the 5 failures are pre-existing per issue #2753 (3 specs red on main, 2 lint scripts exit 127 from missing actionlint)Manual: rendered.agents/skills/diagnostic-reasoning/SKILL.mdto HTML and captured a full-page screenshot viachrome-devtools-axi screenshot --full-page, showing the new section highlighted between "Test the causal explanation" and "Scope and act on the result"Manual:bash negative-result-drill.sh- reproduced all six incident shapes from the intent against this repo's real data (nonexistent subcommand +grep -c, parenthesized URL in a shell loop, listing endpoint that cannot see one level down, pattern matching a second path shape,SKILL.mdkey collision collapsing 21 rows to 1, two broken checks falsely corroborating), each paired with the positive control that exposes itManual constraint checks:git show 3d125ad:AGENTS.md | wc -lvswc -l < AGENTS.md(573 = 573, zero growth);grep -n '^## 'on SKILL.md for section placement; count of prose lines in the new section (11);git diff | grep '^+.*—'(no em dashes);grep -cof the widened trigger clause across both files (4 of 4 expected lines).agents/skills/diagnostic-reasoning/SKILL.md:65- The skill's closing checklist at .agents/skills/diagnostic-reasoning/SKILL.md:65 enumerates what a diagnosis brief must ask for (reproduction, trigger/mask/symptom separation, divergent and proven path comparison, relevant history, smallest counterfactual, disconfirming evidence) and was not extended to cover the new negative-result rule. This may be deliberate: the intent explicitly asks for a tight section that does not restate existing procedure, and the new rule binds mainly on the reporter's own captain-facing statements rather than on brief contents. But a delegated investigation that reports "no occurrences found" is exactly the case the new section governs, and the brief spec currently does not ask for the positive control behind such a claim. Resolving it either way is a scope call for the author: extend that one sentence with the positive-control requirement, or leave the checklist as the pre-existing procedure summary only.✅ **Push** - passed
✅ No issues found.