How attribution works
The sequence
-
The agent works. Claude Code, Codex and
agyeach write a session transcript for their own purposes — session resume, history. whodunit only reads them; it does not ask the agent for anything and does not change how the agent runs. -
dun ingestreads them into a local journal. One row per edit: timestamp, agent, session, tool, file path, lines added and removed, a hash per produced line, and the outcome. This happens automatically at commit time, and continuously if you rundun daemon run. -
You commit. The
prepare-commit-msghook looks up which staged files were touched by a recent session, compares the staged lines against the hashes the agent produced, and writes the trailer. -
commit-msgvalidates what was written, so a malformed trailer fails at commit rather than at analysis time.
How the confidence level is decided
staged files match a recent session?
├── no → undetermined
└── yes → observed
└── staged lines match lines the agent produced?
└── yes → intersected
observed means the agent edited these files recently. intersected means
the exact text it produced is what got staged — the text was not rewritten
in between.
declared and inferred sit below observed on the ladder and are
reserved for agents whose stores cannot support file-level or line-level
evidence.
Why hashes rather than commit SHAs
Attribution is matched by content hash, never by commit SHA.
A commit does not exist when the observation is recorded, and once it does it may be amended, rebased, squashed or cherry-picked. Hashing what changed, rather than where it landed, keeps attribution correct across all four.
The hashes are one-way. They confirm that a line the agent produced is the line that shipped; they cannot reconstruct the line.
The lookback window
A journal entry counts toward a commit for 30 days.
Seven days was the original value and lost anything that sat over a holiday or a long-running branch — which is exactly the work most likely to be agent-heavy. The wider window costs about 26ms per commit, roughly 2% of the hook's budget, because the query is indexed.
Line-hash retention is derived from the same constant rather than
configured separately. Pruning hashes the hook would still have matched
would silently turn an intersected commit into an observed one, so
retention_days cannot be set below the lookback.
What happens when it fails
Hooks never fail a commit. A hook that blocks work because it could not
attribute it would be uninstalled within a day, so every failure path exits
zero and stamps undetermined.
That silence is deliberate but not total — the errors are recorded:
dun log
shows what the hooks did and every error they swallowed.
Verifying it end to end
dun verify
Checks the install, the hooks, the journal, the agents it can find, and the sync target if one is configured — and names what to fix rather than reporting a bare pass or fail.