Emit a structured investigation_notes.json artifact at terminal state. Load when you are an investigation agent finishing a fix or unfixable verdict. The dispatcher reads this file before worktree teardown and persists it into qa_findings.structured_evidence.investigation_notes.
Install
npx skillscat add tzeusy/butlers/investigation-notes Install via the SkillsCat registry.
Investigation Notes Emission
When your investigation reaches a terminal step — either because you have
produced a commit ready for a PR, or because you have determined the issue is
unfixable — you MUST write a JSON artifact at ./.qa/investigation_notes.json
inside your worktree before signalling completion.
Required File Location
./.qa/investigation_notes.jsonCreate the .qa/ directory if it does not already exist. The path is relative
to the root of your worktree.
JSON Schema
The file must contain a single JSON object that conforms to theInvestigationNotes model defined insrc/butlers/core/qa/notes.py. The authoritative field list:
{
"schema_version": 1,
"headline": "<one-line summary of the root cause, anonymized>",
"hypothesis": "<one or two sentences stating the root-cause claim>",
"blurb_segments": [
{ "claim": "c1", "text": "<prose sentence anchored to claim c1>" },
" ",
{ "claim": "c2", "text": "<prose sentence anchored to claim c2>" },
"<additional free text>"
],
"claims": {
"c1": {
"evidence_ids": ["e1"],
"note": "<one sentence rationale for this claim>"
},
"c2": {
"evidence_ids": ["e1", "e2"],
"note": "<one sentence rationale for this claim>"
}
},
"evidence_lines": [
{
"id": "e1",
"ts": "HH:MM:SS",
"lvl": "ERROR",
"butler": "<butler-name>",
"msg": "<raw log line — operator-only surface, never reaches GitHub>"
}
],
"counter_evidence": [
{
"hypothesis": "<alternative root cause you considered>",
"verdict": "rejected",
"reason": "<why you ruled it out>"
}
],
"why_this_fix": "<one sentence explaining why this code change resolves the root cause>",
"diff_snapshot": []
}Field contract
| Field | Type | Notes |
|---|---|---|
schema_version |
Literal[1] |
Always 1. |
headline |
str |
One line, anonymized. Renders as the case title in the dossier rail. |
hypothesis |
str |
Root-cause claim in 1–2 sentences. |
blurb_segments |
list[str | {claim, text}] |
Mixed list: plain strings for free text, objects with claim key to anchor prose to a claim id. |
claims |
dict[str, {evidence_ids, note}] |
Keys are claim ids referenced in blurb_segments. evidence_ids is a list of ids from evidence_lines. |
evidence_lines |
list[{id, ts, lvl, butler, msg}] |
Raw log lines. Operator-only — do NOT sanitize msg; anonymization-on-egress is enforced by the dispatcher for any GitHub-bound paths. |
counter_evidence |
list[{hypothesis, verdict, reason}] |
verdict must be one of "rejected", "accepted", "pending". |
why_this_fix |
str |
One sentence. Renders below the diff preview. |
diff_snapshot |
list[{kind, text}] |
Leave as []. The dispatcher overwrites this field by running git diff HEAD~1..HEAD after your commit. |
Tolerant parsing
The dispatcher uses a best-effort parser: if some fields are malformed or
missing, it recovers what it can. An empty object {} is preferable to no
file at all. The dispatcher never fails the investigation because of a
malformed artifact — it increments qa_investigation_notes_parse_total withstatus="partial" or "failed" but preserves your terminal state.
Emission Steps
Create the
.qa/directory:mkdir -p .qaWrite the JSON object to
.qa/investigation_notes.json. Usejq -nor a direct file write — plain JSON, no trailing newline required.Verify the file exists and is valid JSON before signalling completion:
python3 -c "import json, sys; json.load(open('.qa/investigation_notes.json'))" \ && echo "valid" || echo "invalid — fix before continuing"Continue to your commit step or unfixable signal. The dispatcher reads
the file after the runtime exits; you do not need to do anything else with it.
Anonymization Rules
headline,hypothesis,blurb_segments,claims,counter_evidence,
andwhy_this_fixare narrative fields you author. They are shown to the
operator and may eventually appear in external PR descriptions.
Do not include PII, credentials, or raw personal identifiers in these fields.evidence_lines[].msgis an operator-only field. Include the raw log line
exactly as observed — do not sanitize it. The dispatcher enforces
anonymization-on-egress and will never forwardevidence_linesto GitHub.diff_snapshotis populated by the dispatcher; leave it as[].