Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/B2.7.mp4Without the motivating input, root cause cannot be established at all; without the principal, nothing can be attributed.
Per-hop attribution written to an append-only store outside the agent's reach.
Answer 'which user caused this deletion' from the trace, then try the same on a trace missing one field.
A trace that records tool calls is not evidence. Evidence answers who asked, which agent acted, what authority it held, and what input made it act — and an auditor will ask all four in that order.
db.audit takes the provenance.Span that B2.6 made it possible to have, and records its origin plus a digest — not the text, because a refund request quoted in full puts traveller prose in a long-lived store.Recording what motivated an action, not only who took it — the fourth investigation question, answerable only once ingress marks where text came from, and recorded as a digest rather than the text.
Without the motivating input, root cause cannot be established at all; without the principal, nothing can be attributed.
Per-hop attribution written to an append-only store outside the agent's reach.
Actions with a complete chain. The share below 1.00 is precisely the part of the estate you cannot audit.
Measure this
| what to measure | denominator or target |
|---|---|
| actions with a complete delegation chain | share; the shortfall below 1.00 is the part of the estate you cannot audit |
the four fields an auditor asks for, in order 1. principal dana@corp who asked 2. agent patch-agent#7 who acted 3. authority repo:write, exp+90s under what 4. motivating input PR #412 body, [data] why a trace with 1-3 and not 4 cannot distinguish authorised from injected
Mitigates: T8 Repudiation & Untraceability · T13 Rogue Agents.
B1.14 showed a log that was complete for debugging and empty for investigation. This is the record that is not.
Four fields, each answering a question the tool-call log could not:
The human principal — who caused this. From B2.1.
The agent identity and instance — what performed it, and which run. From B2.2, so it is attested rather than claimed.
The delegation chain — how authority got from the human to this action. From B2.3, which is also what makes B1.17's laundering path visible.
The motivating input, with its origin — what made the agent decide. From B2.6. This is the field that establishes root cause, and the one most often missing, because logging tool calls feels like logging decisions.
Then the structural property, which is not a field: the store must be outside the agent's reach. An agent with broad credentials can usually touch the logging stack, and a record the actor can edit is not evidence. Append-only, different credential, ideally different trust domain.
The test is not whether the log looks thorough. It is whether you can answer "which user caused this, and what made it happen" without asking anyone.
What this control closes. Makes the incident answerable. Also the control an auditor asks for first, because a system that cannot attribute an action cannot be defended even on a quiet day.
The four questions an auditor asks about that $5,000 refund have to be answerable from one entry. The skill puts them to a single ledger record, and then tries to amend the record as the agent — because a complete log its subject can edit records what the agent wanted you to see.
The skill — skills/identity/attribution-ledger-check/SKILL.md
name: attribution-ledger-check
description: >-
Check that one record answers all four investigation questions — human
principal, attested workload and run, delegation chain, and the motivating
input with its origin — and that the agent cannot amend it. Use when
designing agent audit records rather than reading them.
allowed-tools: Read, Grep, GlobAn audit trail is not "we log tool calls". It is a record that answers, from a single entry, the four questions an investigation asks — and that the subject of the record cannot edit. Both halves are required: a complete record the agent can rewrite is a record of what the agent wanted you to see.
While designing the record. Retrofitting attribution after an incident means reconstructing it from four services' timestamps, which does not happen at 2am.
1 — Write the four questions down first. Which human. Which workload and which run. Through what delegation chain. On what motivating input, from what origin. Design the entry to answer them; do not collect fields and hope.
2 — Map each question to a field. Principal from the delegated token, not from a header the agent set. Workload and instance from the attestation. Chain from the token's act nesting. Motivating input from the ingress record that caused this step, with its origin tag.
3 — Test each question against one entry. Alone. If answering needs a join across services, that question is unanswered for practical purposes and should be recorded as such.
4 — Attempt the amendment as the agent. Append, overwrite, delete, reorder. Every one must be refused by something the agent does not control — an append-only store, a separate writer identity, a downstream sink it cannot reach.
5 — Check the write path's own identity. If the agent's role can write to the log destination, the ledger is advisory whatever the API says.
Input — the fixture committed at the top of scripts/attribution_ledger_check.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — from a real run, the fixture summary and the fourth question's verdict:
the fixture this run is derived from
LEDGER 3 item(s)
STORE 6 item(s)
"answered_from_single_entry": true,
"question": "On what motivating input, from what origin, did the agent
decide? ('wiki/473: retire invoice 8812 when the customer
disputes a duplicate charge' from origin 'knowledge', a
trust-0 source, not the user's request about a double charge)"
That last clause is the whole lesson: the traveller asked about a double charge, and a retrieved document is what produced the DELETE. A record without question 4 closes this incident as "the agent misbehaved".
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"entry": {"principal": "str", "workload": "str", "instance": "str",
"chain": ["str"], "motivating_input": {"ref": "str", "origin": "str"}},
"questions": [{"question": "str", "answered_from_single_entry": true}],
"amendment_attempts": [{"operation": "append|overwrite|delete|reorder", "refused_by": "str"}],
"writer_identity": {"agent_can_write_destination": false}
}
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons B2.7Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick b2-7-attribution-an-audit-trail-that-answers:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons B2.7 | type /b2-7-attribution-an-audit-trail-that-answers |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons B2.7 | type /b2-7-attribution-an-audit-trail-that-answers in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons B2.7 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons B2.7 | type $b2-7-attribution-an-audit-trail-that-answers, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py B2.7Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at B2.7 --out work/cybertravels
python3 scripts/checkpoint.py --at B2.7 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
One ledger entry answers all four investigation questions — the human principal, the attested workload and run, the delegation chain, and the motivating input with its origin — and the agent's attempt to amend the record is refused.
Take the last significant action one of your agents performed and try to fill in these four fields from what you actually logged. The missing one is almost always the motivating input.
Tools used
Skill source: skills/identity/attribution-ledger-check