Docket – 针对代理编写代码的逐提交证据记录
Docket – Per-commit evidence records for agent-written code

原始链接: https://github.com/Dillonsmart/docket

**Docket** 是一款开源工具,旨在为 AI 智能体编写的代码提供按提交(per-commit)划分的“证据记录”。由于智能体生成代码的速度往往超过人工审核的速度,审核人员目前缺乏对智能体实现过程的了解,包括其失败的尝试或已验证的测试。 Docket 通过捕获智能体的过程(任务、意图和中间尝试)并将其整合进 git 提交中来弥补这一差距。它会生成一份逐段代码(per-hunk)的证据记录,突出显示未经验证的代码,使审核人员能够将注意力集中在最需要的地方。 **主要功能包括:** * **证据关联**:自动将代码修改与测试结果、覆盖率报告和静态分析相关联,并为每一段代码分配一个“证据密度”评分。 * **透明度**:使用钩子(hooks)观察修改(包括通过 shell 命令进行的修改),并将其记录在存储库本身,确保历史记录独立于外部服务。 * **审核集成**:GitHub Action 会汇总这些记录,重新排序合并请求(Pull Request)的差异,优先显示未经验证的代码。 * **隐私与信任**:记录在本地进行签名,并经过严格脱敏以移除敏感凭据。 Docket 与具体的智能体无关,它作为现有 git 工作流之上的一层透明且可自托管的工具运行。

Hacker News 最新 | 往日 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Docket – 针对智能体编写代码的逐次提交证据记录 (github.com/dillonsmart) 5 分,由 dillonsmartdev 发布于 2 小时前 | 隐藏 | 往日 | 收藏 | 讨论 | 帮助 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Docket

A per-commit evidence record for agent-written code.


Coding agents produce code faster than anyone can verify it. The reviewer 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. The harness already emits that journey and then throws it away at commit time.

Docket captures it, folds it against the diff, and produces a per-hunk evidence record — so review attention lands where there is no evidence.

$ 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

No account. No network. The records live in the repository, on an orphan ref.


A single static binary. No Go toolchain, no runtime, nothing to compile.

curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh

Or take the archive for your platform from the releases page — macOS and Linux on arm64 and x86-64, Windows on both — unpack it, and put docket on your PATH. Every release publishes SHA256SUMS; the installer checks them for you.

Building from source stays available for anyone who wants it:

go install github.com/Dillonsmart/docket/cmd/docket@latest

Then, in a repository:

See it before instrumenting anything

It builds a throwaway repository in a temp directory with a recorded session in it — an agent fixes a session fixation bug, gets it wrong once, watches a test fail, fixes it properly — and prints the docket for the commit. Your own repositories are not touched, and the directory is deleted on exit.

Re-run the installer. It overwrites the binary in place with the newest release:

curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh

docket version says what you have; the releases page says what is current. To pin, or to go back, name the tag:

curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | DOCKET_VERSION=v0.0.2 sh

(The variable goes on the sh side of the pipe. In front of curl it would be set for the download and not for the script.)

From source, it is go install github.com/Dillonsmart/docket/cmd/docket@latest. In CI, the action's default version: latest takes the newest release on every run; pin it to a tag if you would rather decide when that happens.

Nothing needs migrating. Records already stored stay readable — each one carries the schema version it was written against — and there is no local state beyond the signing key, which upgrades never touch.

You only need to re-run docket init if the binary lands somewhere new. The hooks call it by absolute path, falling back to whatever docket is on PATH, so an upgrade in place needs nothing; installing to a different directory and deleting the old copy is the case where the recorded path goes stale.

That installs a prepare-commit-msg hook (which writes the trailer — the audited agent never writes its own audit record), a post-commit hook (which stores the record), the refs/docket/* refspec, a local signing key, and the Claude Code hooks that let docket observe edits made through the shell.

From then on, every commit gets one extra line, in the trailer block where Signed-off-by and Reviewed-by live:

    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 <[email protected]>
    Docket: sha256:db77fdb4b0c1fc6cc7644d3fa2204267e25799d74a6f8f665aff3d32b12c39af

That is the whole footprint in your history. git log --oneline is unchanged, your subject line is untouched, and the digest names a signed record on refs/docket/records rather than a URL — so nothing in the permanent history depends on a service still existing.

Amending rebuilds the record and replaces the trailer, so a commit never points at the content it used to have. A merge gets no trailer, and neither does a commit with nothing attributable in it. Rebases and cherry-picks are the awkward case: git does not run prepare-commit-msg for either, so the trailer travels onto a diff it was not built from — docket verify reports that plainly rather than trusting the trailer.

To stop: delete the two hooks in .git/hooks. Existing trailers stay in the history as inert text.

docket show HEAD                      # the record for a commit, risk-ordered
docket explain src/auth.js:51         # why does this line exist? (via git blame)
docket review --format md             # the pull request comment
docket verify HEAD                    # digest, signature and commit binding
docket push                           # send the records to the remote
docket doctor                         # what can docket see here, and what can't it
docket gate --commits 20              # measure attribution against real history

For pull requests, the GitHub Action reads the records the commits brought with them and posts one comment, reordering the diff so the hunks nothing can vouch for come first and the well-covered boilerplate is collapsed underneath. It downloads the same binary, so nothing needs installing on the runner either.

Reading someone's reasoning back

The fields that answer why does this code look like this are task, intent and attempt:

  • task — the request this edit descends from, i.e. what the human actually asked for.
  • intent — what the agent said it was doing in the sentence immediately before it made the edit. This is where the reasoning lands, in the agent's own words.
  • attempt — code written into this same region and then taken out again, with the check that failed in between when there was one. The abandoned approaches are the part that is otherwise 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 finds the commit through git blame, so you can start from the code in front of you rather than having to know which commit to look in. docket show <sha> --all --json gives the whole record if you would rather read it as data.

Docket stores short redacted excerpts, not the conversation: enough to reconstruct the decision, never the whole transcript, because whole transcripts carry secrets and grow without bound.


Attribution. Docket parses the agent transcript into an ordered event stream, replays every recorded edit per file, and carries a provenance vector — one origin per line — forward through subsequent edits. At commit time it aligns the committed file against that replay and resolves each hunk.

A line is attributed only when its text is found at the aligned position and in the recorded output of the edit being credited. Timestamps only order events; they never justify an attribution.

When an edit's recorded pre-image disagrees with the replay — because a shell command, an editor or a person changed the file in between — the disagreement is detected, the affected lines become unknown, and the replay re-seeds from the recorded truth. Carrying a reconstruction past a disagreement is how tools produce confident, wrong answers. An attribution engine that is confidently wrong is worse than no product, so unknown is always an available answer, and it always comes with a reason.

Agents. Docket reads Claude Code, Codex CLI and opencode. Everything downstream — replay, attribution, evidence, the record — is agent-agnostic, so supporting another agent is a reader, not a redesign:

Agent Where its session lives What docket gets from it
Claude Code ~/.claude/projects/**/*.jsonl Edit/Write calls with before and after images, shell commands, prompts, the agent's own 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) write/edit calls with diffs, shell commands with exit codes, prompts, narration
anything else wire its hooks to docket collect pre / docket collect post and the edits are observed directly

Codex and opencode send patches rather than whole files, so their edits carry no before-image. Docket seeds the replay from the base revision and applies the patch to the content it was actually written against; when that does not fit, it re-seeds and marks what it cannot explain rather than placing the hunk by guesswork.

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 there to recover. So docket watches the working tree itself: a PreToolUse hook snapshots content, a PostToolUse hook diffs it, and both images are stored as git blobs. Those edits are marked observed rather than transcript, because docket read them itself.

Evidence. Test runs, type checks and static analysis are correlated with the edits they followed — a check that ran before the code was written is not evidence about it. Coverage reports (istanbul coverage-final.json, lcov) are matched line by line against the hunk, and ignored when the report is older than the code it would otherwise appear to cover.

Density. One number per hunk, between 0 and 1, published in full rather than tuned in private: coverage of these lines is worth at most 0.5, a check that passed after the edit 0.3 (0.35 if this change turned it green), type and static checks 0.1 together, recorded human contact 0.1. If nothing executed the code, the score is capped at 0.15. If nobody can say who wrote it, at 0.5. A locally-claimed record scores 0.9 of what a CI-attested one would.

Those caps are the point. This number will be turned into a target, exactly as coverage was, and a metric you can raise without running anything is worse than no metric.


How well does attribution actually work

Docket measures itself. docket gate replays real sessions against real commits and reports what it could and could not explain:

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 verified against the crediting edit's own recorded output — docket did not credit a line to an edit that did not write it. And the last row is why the collector exists: those sessions wrote files with heredocs and sed, which no transcript records, and that work happened before docket could observe it. With docket init in place, those edits are observed directly.

Across whole diffs the headline rate is lower — 40%, 94%, 37% — because real commits also contain composer.lock, scaffolded models and generated code that no agent edit ever touched. Docket reports those as unknown with the commands that ran nearby listed as candidates, never as attributions. That is the honest answer, and it is deliberately not smoothed into the number.

Run it on your own history:


  • Not orchestration, task assignment or agent spawning.
  • Not agent-to-agent handoff or provider routing.
  • Not a window you live in. It annotates the review surface you already use.
  • Not a live dashboard.
  • Not something that asks for an account before it does anything.

Traces contain raw prompts, file contents and terminal output. So:

  • Everything that reaches a record goes through an aggressive redaction pass: known credential shapes, private keys, JWTs, authorization headers, connection-string credentials, KEY=/SECRET=/PASSWORD= assignments — and, failing closed, any long token whose character distribution looks random.
  • A record stores short redacted excerpts, never whole files or whole prompts.
  • Nothing is sent anywhere. The records live in your repository, and docket push sends them to the git host you already trust.
  • The signing key lives in .git/docket/ and never leaves the machine.
Tier Meaning
local_claimed Built on a developer machine, signed with a key that machine holds. It is a claim.
ci_attested Built by a runner, signed with a key the developer does not hold. Set DOCKET_SIGNING_KEY in CI.

The distinction is in the schema from day one and is shown everywhere a record is rendered.

The record format is specified separately as the Commit Evidence Record, with a JSON Schema. Docket is its reference implementation. The specification is Apache 2.0 and deliberately boring: it is meant to be implemented by other tools, including ones that compete with this one.

Built and working: attribution, the shell-edit collector, redaction, signed records on an orphan ref, coverage and test correlation, the terminal viewer, explain, the pull request comment, the GitHub Action, and the self-measurement gate.

Not built yet: readers for Gemini CLI and anything ACP-native, coverage correlation beyond istanbul/lcov, GitLab, cross-repository aggregation, policy gates on paths, and the hosted team tier.

Apache 2.0. See LICENSE and NOTICE.

联系我们 contact @ memedata.com