Capture evidence trails for AI-generated code commits
Docket turns an AI coding agent's transcript into a per-commit evidence record, so code review attention goes where nothing was verified.
0.0.7Add to Favorites
Why it matters
Create auditable evidence records for every commit written by coding agents, capturing what the agent tried, what tests passed or failed, and which lines have coverage-so code reviewers can focus attention on changes that lack verification evidence.
Outcomes
What it gets done
Record per-hunk evidence showing agent intent, superseded attempts, and test results
Generate risk-ordered code review summaries highlighting unverified changes
Verify commit integrity with cryptographic signatures and digest validation
Trace code lineage by explaining why specific lines exist via git blame
Source
Get it from source
Spark does not host a copy of it.
Open sourceReports
Agent outcome reports
No reports yet
Overview
Docket
Docket reads a coding agent's own session transcript, replays its edits against the committed diff, and produces a signed per-hunk evidence record - origin, task, intent, abandoned attempts, and correlated test/coverage evidence - stored locally on an orphan git ref. It adds one trailer line per commit and directs reviewer attention to the hunks nothing verified. Use it on repositories where agent-written commits need evidence-based review, especially with Claude Code, Codex CLI, or opencode; not for orchestration, task routing, or agent spawning.
What it does
Docket is a per-commit evidence record for agent-written code. Coding agents produce diffs faster than anyone can verify them, and the reviewer normally receives a finished diff with no implementation journey - no record of what the agent tried first, what it verified, or which lines nobody ever looked at. Docket reads the harness's own transcript (or observes edits directly when the transcript can't see them), folds that journey against the committed diff, and produces a per-hunk evidence record, so review attention lands where there is no evidence. Every commit gets one extra trailer line alongside Signed-off-by/Reviewed-by, naming a signed record stored on an orphan refs/docket/records ref inside the repository itself - no account, no network, and the digest doesn't depend on any service staying up. Three fields carry the reasoning: task is the request the edit descends from, intent is what the agent said it was doing immediately before making the edit, and attempt is code written into the same region and then reverted, with whichever check failed in between - the abandoned approaches that are otherwise lost within hours.
When to use - and when NOT to
Use it in any repository where agent-written commits need reviewer attention directed at the parts nobody verified, particularly with Claude Code, Codex CLI, or opencode, whose session transcripts docket reads directly; any other agent can be wired in via docket collect pre/docket collect post hooks. It is explicitly not orchestration, task assignment, or agent spawning; not agent-to-agent handoff or provider routing; not a live dashboard; and not something that asks for an account - it only annotates the review surface (terminal, or a GitHub Action PR comment) you already use. Rebases and cherry-picks are a known gap: git does not run the prepare-commit-msg hook for either, so a trailer can travel onto a diff it wasn't built from, and docket verify reports that mismatch rather than trusting the trailer.
Inputs and outputs
Install is a single static binary with no Go toolchain or runtime required:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
Every release also publishes SHA256SUMS, which the installer checks automatically, and go install github.com/Dillonsmart/docket/cmd/docket@latest builds from source. Running docket init in a repository installs a prepare-commit-msg hook (writes the trailer), a post-commit hook (stores the record), the refs/docket/* refspec, a local signing key, and Claude Code hooks that let docket observe shell-made edits directly. scripts/demo.sh builds a disposable temp-directory repo with a recorded session to show the output without touching your own repositories. Day to day, docket show HEAD prints the risk-ordered record for a commit, docket explain <file:line> finds why a line exists via git blame, docket review --format md produces the pull request comment, docket verify HEAD checks digest/signature/commit binding, docket push sends records to your git remote, docket doctor reports what docket can and can't see, and docket gate --commits 20 self-measures attribution coverage against real history. Records store short redacted excerpts, never full prompts or files, and an aggressive redaction pass strips known credential shapes, private keys, JWTs, auth headers, connection-string credentials, and any high-entropy token before anything is written.
Integrations
Docket reads Claude Code sessions from ~/.claude/projects/**/*.jsonl (Edit/Write calls, shell commands, prompts, narration), Codex CLI from ~/.codex/sessions/**/rollout-*.jsonl (apply_patch calls with exit codes), and opencode from its local sqlite database (write/edit calls with diffs); everything downstream of ingestion - replay, attribution, evidence, the record itself - is agent-agnostic. A PreToolUse/PostToolUse hook pair also snapshots and diffs the working tree directly for edits no transcript can see (a heredoc, sed -i, a generator), storing both images as git blobs and marking those edits observed rather than transcript. Coverage reports (istanbul coverage-final.json, lcov) are matched line by line against each hunk and ignored if older than the code they'd cover; a bundled GitHub Action downloads the same binary and posts a PR comment reordering hunks so uncovered ones surface first. Each hunk gets a 0-1 evidence density score, published in the spec rather than tuned in private: coverage of the lines is worth up to 0.5, a check that passed after the edit 0.3 (0.35 if the edit turned it green), type and static checks 0.1 together, and recorded human contact 0.1; unexecuted code caps at 0.15, unattributed code at 0.5, and a locally-claimed record scores 0.9 of what a CI-attested one would.
Who it's for
Teams and reviewers who want agent-written commits reviewed by evidence rather than trust - directing attention to unverified hunks instead of re-reviewing everything equally. On docket's own self-measurement (docket gate), a 4-commit/98-edit Claude Code session hit 98.1% hunk attribution and 100% content-verification, a 4-commit/256-edit Codex CLI session hit 94.7%, a 100-edit uncommitted opencode session hit 92.9% of added lines, and a 12-commit/79-edit Claude Code session built largely through the shell (before the observer hooks existed) hit only 59.2% - the gap the shell-edit collector exists to close. Across whole diffs the headline rate is lower still (docket measured 40%, 94%, and 37% on its own history), because real commits also contain lockfiles, scaffolded models, and generated code no agent edit ever touched - reported as unknown, never guessed at. Two trust tiers are tracked from the schema up: local_claimed (signed on a developer machine - a claim) and ci_attested (signed by a CI runner holding DOCKET_SIGNING_KEY). The record format, the Commit Evidence Record, is specified separately under Apache 2.0 so other tools can implement it too; docket is its reference implementation. Not yet built: Gemini CLI and ACP-native readers, coverage correlation beyond istanbul/lcov, GitLab support, cross-repository aggregation, path-based policy gates, and a hosted team tier.
Source README
A per-commit evidence record for agent-written code.
Why
More and more of the code in a repository is written by a coding agent, often running in auto mode. The agent reads the code, picks an approach, edits, runs the tests, fixes what failed and hands back a finished diff. Along the way it made decisions and ran checks that the developer never saw.
The developer still owns that code. They have to explain it in review, fix it when it breaks and decide whether to trust it. Without knowing what the agent tried, what it verified and which lines nobody ever looked at, that is hard to do.
The agent harness already records all of this. It is thrown away when you commit. Docket keeps it: it reads the session, matches it against the diff, and stores a signed evidence record for each commit. You can then see, for any hunk or any line, who wrote it, why, what was tried before it, and what tested it.
$ docket show HEAD
docket 7a9da95a2a748c2fb99706f48f2ddf64760ffab2
record sha256:046c5278e93cb516e3ba72400fd1a69648c85b93833d31d57648e9ed8e535ebf
trust local_claimed (signed on local)
verified digest and signature check out (ed25519:f889a26cbc34c95e)
1 hunks in 1 files, 4 added lines
100% of added lines attributed to a recorded edit
mean evidence density 0.77
src/auth.js:3-6 (4 lines) covered density 0.77
origin claude-code/main via Edit (reported by the harness)
claude-opus-5
task Fix session fixation on login
intent The raw UUID is reused across logins, so minting a prefixed id instead.
attempt Stamping a creation time on the session so expiry can be checked. [superseded]
vitest failed after this change: npx vitest run
evidence coverage: 4 of 4 lines executed (coverage/coverage-final.json)
evidence test_execution: pass (npx vitest run --coverage) — failed before this change
human no recorded human contact with these lines — the edit was written without a prompt (claude-code acceptEdits)
There is no account and no network access. The records live in your repository on an orphan ref, refs/docket/records.
Install
Docket is a single static binary. On macOS or Linux:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
On Windows, or if you would rather not pipe to sh, download the archive for your platform from the releases page (macOS, Linux and Windows, arm64 and x86-64), unpack it and put docket on your PATH. Each release publishes SHA256SUMS, and the installer checks the download against it.
To build from source:
go install github.com/Dillonsmart/docket/cmd/docket@latest
Try it first
scripts/demo.sh
This creates a throwaway repository in a temp directory with a recorded agent session in it. The agent fixes a session fixation bug, gets it wrong once, sees a test fail, fixes it properly, and also edits a second file through the shell, which the transcript does not record. It then prints the docket for the commit so you can see both kinds of hunk. Your own repositories are not touched and the directory is deleted on exit. It needs docket on your PATH, or DOCKET=/path/to/docket.
Or read this repository's own records
Docket records its own development. Clone it, fetch the records, and read the history of the tool from the tool:
git clone https://github.com/Dillonsmart/docket && cd docket
docket fetch # records live on refs/docket/records, which git does not fetch by default
docket show HEAD # the last commit, riskiest hunks first
docket show --all HEAD # including the hunks with evidence behind them
# a commit the agent made through the shell: docket watched the files, and the tests ran in the same call
docket show --all $(git log --format=%h --grep='Join an observed edit' -1)
# why does this function exist? git blame finds the commit, the record explains the hunk
docket explain internal/build/build.go:$(grep -n 'func link(' internal/build/build.go | cut -d: -f1)
docket review --range v0.0.5..v0.0.6 --format md # a release reviewed as one change, least-verified hunks first
docket verify HEAD # the record is intact, signed, and describes this commit
docket doctor # what docket can see on this machine
The records were made on the author's machine, so they read trust local_claimed. docket gate and --rebuild need the session transcripts, which stay on that machine; everything else works from the clone. A record is built once, at commit time, with whatever docket could do then - so older commits carry thinner records than newer ones, and that is the history, not a bug.
Set up a repository
docket init
This installs:
- a
prepare-commit-msggit hook, which builds the record and writes its digest into the commit message as a trailer, - a
post-commitgit hook, which stores the record onrefs/docket/records, - a fetch refspec for
refs/docket/*onorigin, sogit fetchbrings records down (docket pushsends them up; if there is no remote yet, rundocket initagain once there is), - a local signing key in
.git/docket/, - Claude Code
PreToolUse/PostToolUsehooks in.claude/settings.local.json, so docket can see edits the agent makes through the shell. That is the per-machine file, not the committedsettings.json: the hook names the path to the binary on this machine.
The hooks never block a commit. If docket is missing or the record cannot be built, the commit goes through and the error is written to .git/docket/docket.log.
From then on each commit gets one extra trailer line, alongside Signed-off-by and Reviewed-by:
Add the session helper
The API needs a stable id per session, and the obvious place is here
rather than in the middleware.
Reviewed-by: Someone Else <someone@example.com>
Docket: sha256:db77fdb4b0c1fc6cc7644d3fa2204267e25799d74a6f8f665aff3d32b12c39af
That is the only change to your history. Subject lines and git log --oneline are untouched, and the trailer names a record in the repository rather than a URL, so it does not depend on any service staying up.
Some cases to know about:
- Amend rebuilds the record and replaces the trailer, so a commit never points at a record for content it no longer has.
- Merges get no trailer, and neither does a commit with nothing attributable in it.
- Rebase and cherry-pick do not run
prepare-commit-msg, so the trailer is copied onto a diff it was not built from.docket verifyreports this rather than trusting the trailer.
To stop using docket, delete the two hooks from .git/hooks. Existing trailers stay in the history as plain text.
Upgrading
Re-run the installer and it replaces the binary in place:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
docket version prints what you have. To pin a version, or to roll back, set DOCKET_VERSION on the sh side of the pipe:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | DOCKET_VERSION=v0.0.7 sh
From source, go install github.com/Dillonsmart/docket/cmd/docket@latest again. In CI, the action's default version: latest picks up the newest release on each run; pin it to a tag if you want to control when that happens.
Nothing needs migrating. Stored records carry the schema version they were written against and stay readable, and the only local state is the signing key, which upgrades do not touch. The hooks call the binary by absolute path and fall back to docket on PATH, so you only need to re-run docket init if you install to a new location and delete the old binary.
Use it
docket show HEAD # the record for a commit, riskiest hunks first
docket build --staged # build a record for the staged change without committing
docket explain src/auth.js:51 # why does this line exist? (found via git blame)
docket review --format md # the pull request comment
docket verify HEAD # check digest, signature and commit binding
docket push # send records to the remote
docket fetch # get records from the remote
docket doctor # what docket can and cannot see in this repo
docket gate --commits 20 # measure attribution against your own history
For pull requests, the GitHub Action reads the records that came with the commits and posts one comment. It puts the hunks with no evidence first and collapses the well-covered ones underneath. It downloads the same binary, so nothing needs installing on the runner.
Reading the reasoning back
Three fields answer "why does this code look like this":
- task: the request this edit came from, in the human's words.
- intent: what the agent said it was about to do, in the message immediately before the edit. This is the agent's reasoning in its own words.
- attempt: code that was written into this region and then removed, with the check that failed in between if there was one. These abandoned approaches are the part that is normally lost within hours.
$ docket explain database/migrations/0001_01_01_000000_create_players_table.php:54
database/migrations/…:54 was last written by be66f35c5977
origin claude-code/main via Edit
task Look at the engine plan for this project, challenge any assumptions then start implementing
intent Postgres `jsonb` normalises key order, so the replayed response wasn't byte-identical
to the original. For a stored response we only ever return verbatim, `json` is the
right column type.
attempt Now the schema. Replacing the default `users` table with `players` as the
authenticatable model. [superseded]
docket explain uses git blame to find the commit, so you can start from the line in front of you rather than needing to know which commit to look in. docket show <sha> --all --json prints the whole record as JSON.
Docket stores short, redacted excerpts, not the conversation. That is enough to reconstruct the decision, and whole transcripts contain secrets and grow without bound.
How it works
Attribution. Docket parses the agent transcript into an ordered stream of events, replays every recorded edit per file, and tracks an origin for each line through later edits. At commit time it lines up the committed file against that replay and resolves each hunk.
A line is only attributed to an edit when its text is found at the aligned position and in that edit's recorded output. Timestamps are used to order events, never to justify an attribution.
If an edit's recorded before-image disagrees with the replay, because a shell command, an editor or a person changed the file in between, docket marks the affected lines unknown and restarts the replay from the recorded content. It does not carry a guess forward. unknown is always an available answer and always comes with a reason, because a confidently wrong attribution is worse than none.
Agents. Docket reads sessions from Claude Code, Codex CLI and opencode. Everything after the reader is agent-agnostic, so adding an agent means adding a reader:
| Agent | Where its session lives | What docket gets from it |
|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl (or $CLAUDE_CONFIG_DIR) |
Edit/Write calls with before and after images, shell commands, prompts, the agent's narration |
| Codex CLI | ~/.codex/sessions/**/rollout-*.jsonl |
apply_patch calls, shell commands with exit codes, prompts, narration |
| opencode | ~/.local/share/opencode/opencode.db (needs sqlite3 installed) |
write/edit calls with diffs, shell commands with exit codes, prompts, narration |
| anything else | - | point its hooks at docket collect pre and docket collect post and the edits are observed directly |
Codex and opencode send patches rather than whole files, so their edits have no before-image. Docket seeds the replay from the base revision and applies each patch to the content it was written against. When a patch does not fit, it re-seeds and marks what it cannot explain rather than guessing where the hunk goes.
Observation. Reading the transcript recovers Edit and Write calls. An agent that writes files through the shell (a heredoc, sed -i, a generator, a formatter) leaves nothing in the transcript to recover. So docket also watches the working tree: a PreToolUse hook snapshots the content, a PostToolUse hook diffs it, and both images are stored as git blobs. These edits are marked observed rather than transcript, because docket saw them itself.
Evidence. Test runs, type checks and static analysis are matched to the edits they followed. A check that ran before the code was written is not evidence about it. Coverage reports (istanbul coverage-final.json and lcov) are matched line by line against each hunk, and ignored when the report is older than the code.
Human contact. A terminal has no read receipts, so docket only claims what a harness recorded. edited means the harness saw the file change underneath it and these lines are among the ones that differed - line-accurate, from a recorded before-image. approved means the edit that wrote these lines went through a permission prompt: Claude Code's default mode, Codex's untrusted policy or a read-only sandbox. That is inferred from the setting in force, not observed, and a per-tool allow rule can silence a prompt the mode would otherwise show, so every record says what the claim rests on (contact_basis) and a reader can discount it. Everything else is none, with the basis saying whether the harness wrote without asking or the transcript simply does not say. opencode records nothing about permissions per call, so its edits are always the latter.
Density. Each hunk gets a score between 0 and 1. The formula is published in the spec: coverage of the hunk's lines is worth up to 0.5, a check that passed after the edit 0.3 (0.35 if this change turned it green), type and static checks up to 0.1 together, recorded human contact up to 0.1. Then the caps: if nothing executed the code the score is capped at 0.15, if nobody can say who wrote it at 0.5, and a locally-claimed record scores 0.9 of a CI-attested one.
The caps matter. This number will end up being used as a target, the same way coverage was, and a score you can raise without running anything would be worthless.
How well does attribution work
docket gate replays real sessions against real commits and reports what it could and could not explain. Measured so far:
| Session | Agent | Hunks in files the session edited | Added lines | Content-verified |
|---|---|---|---|---|
| A Laravel engine, Edit/Write tools (4 commits, 98 edits) | Claude Code | 98.1% | 99.8% | 100% |
A Python app, apply_patch (4 commits, 256 edits) |
Codex CLI | 94.7% | 95.7% | 99.8% |
| A FastAPI project, 100 edits (uncommitted, measured against the working tree) | opencode | - | 92.9% | - |
| A PHP framework built largely through the shell (12 commits, 79 edits) | Claude Code | 59.2% | 82.1% | 100% |
Two things to take from this. Every attribution was checked against the crediting edit's own recorded output, so docket never credited a line to an edit that did not write it. And the last row is why the shell-edit hooks exist: those sessions wrote files with heredocs and sed, which no transcript records, before docket could observe them. With docket init in place, those edits are captured.
Across whole diffs the rate is lower (40%, 94% and 37% for the three committed sessions), because real commits also contain composer.lock, scaffolded models and generated code that no agent edit touched. Docket reports those as unknown, listing the commands that ran nearby as candidates. It does not count them as attributed.
Run it on your own history:
docket gate --commits 20
What it is not
- Not orchestration, task assignment or agent spawning.
- Not agent-to-agent handoff or provider routing.
- Not a UI you live in. It annotates the review surface you already use.
- Not a live dashboard.
- Not something that asks for an account.
Trust
| Tier | Meaning |
|---|---|
local_claimed |
Built on a developer machine and signed with a key that machine holds. It is a claim. |
ci_attested |
Built by a CI runner and signed with a key the developer does not hold. Set DOCKET_SIGNING_KEY in CI. |
The tier is part of the schema and shown wherever a record is rendered.
The format
The record format is specified separately as the Commit Evidence Record, with a JSON Schema. Docket is the reference implementation. The spec is Apache 2.0 and written to be implemented by other tools.
Status
Working: attribution, the shell-edit hooks, redaction, signed records on an orphan ref, coverage and test correlation, the terminal viewer, explain, the pull request comment, the GitHub Action, and gate.
Not built yet: readers for Gemini CLI and anything ACP-native, coverage formats beyond istanbul and lcov, GitLab, cross-repository aggregation, policy gates on paths, and a hosted team tier.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.