Panout prototype: commit-boundary record and contracts
This spike tests a narrow Panout product: collect metadata from several coding-agent runs, preserve it in one tamper-evident local log, and answer three questions:
- What did each agent session touch?
- Which checkpointed work has no commit-level evidence yet?
- Which files did more than one session change?
It does not infer intent, score agents, or call a surviving commit "good." Those need better evidence than a local activity log contains.
Commit-boundary MVP (2026-09-03)
Panout is the record of what your team stopped reading. init installs two reversible git hooks that run the team's contracts on each commit, print only failures, write a Panout-Work and Panout-Contracts trailer into the commit (trailers survive squash merges; SHAs do not), and record a metadata-only work.evaluated event. A commit made after a printed failure is an override and is recorded as one; commits are never blocked.
python3 prototype/panout.py init # ledger, .git/info/exclude, contracts.json, hook shims
python3 prototype/panout.py status # contracts, evaluations, overrides, exposure vs 30% target
python3 prototype/panout.py audit # agent-attributed commits with contract results and a 30-day rework proxy
python3 prototype/panout.py inject # fault injection on a scratch worktree; prints catch rates per contract
python3 prototype/panout.py verify # hash-chain integrity
python3 prototype/panout.py uninstall # remove shims, restore chained hooks (--purge deletes .panout/)
Built-in contracts: tests-touch (source staged implies a test staged), no-secrets (high-precision secret shapes in added lines; only the pattern name and path are recorded), size-guard (added-line budget, default 400), agent-attribution (agent trailer implies a work id). A command contract runs a repo command with a timeout and records only exit code and duration. Edit .panout/contracts.json to tune.
Unit of policy is the contract, never the task class. Autonomy levels are not implemented; inject only measures and prints. The rework column in audit is a line-identity proxy, not a defect label. Existing hooks are chained (moved to <hook>.panout-chained) and restored on uninstall; core.hooksPath is honored.
Try it
The script uses Python 3.10+ and the standard library on macOS or Linux. Keep the generated ledger out of Git; it can contain repository-relative filenames.
python3 prototype/panout.py init
python3 prototype/panout.py collect
python3 prototype/panout.py brief
python3 prototype/panout.py verify
collect reads normal commits from HEAD and T3 Code's refs/t3/checkpoints/* refs. Checkpoint refs are shared-worktree recovery snapshots. They do not count toward agent attribution or overlap warnings, including turn-over-turn diffs, because other sessions can change the same worktree between snapshots.
Claude Code lifecycle adapter
record-claude accepts the JSON that Claude Code sends to a command hook. On SessionEnd, it reads the hook-provided transcript and extracts repository-relative paths from known write calls with a successful tool result. It stores aggregate tool counts and confirmed filenames, then discards the working directory, transcript path, session identifier, prompt text, final answer, tool arguments, and file contents. The session identifier is salted and hashed locally.
Example:
printf '%s' '{"session_id":"demo","hook_event_name":"SessionStart","model":"example"}' \
| python3 prototype/panout.py record-claude
A real installation should merge SessionStart and SessionEnd command hooks into the user's existing Claude Code settings. The prototype does not edit those settings.
Why a hash chain?
Each JSONL row contains the prior row's hash and its own SHA-256 hash. File locking makes concurrent local appends serial. verify detects deletion, reordering, and editing within the retained chain. It does not prove who created an event and is not a signature system.
Run the tests
python3 -m unittest -v prototype/test_panout.py