Tokening Adds a Flight Recorder for AI Agent Actions
A grant says what an agent may do. After the task, you want to know what it actually did, and you want a record that can’t be quietly tidied up afterwards. Version 0.1.2 adds that record.
Tokening now writes every grant, every call the agent makes, every decision and every upstream result into an append-only log. Each entry carries the SHA-256 hash of the entry before it. Change one, delete one, swap two or cut the end off, and the chain breaks at that spot. tokening flight verify tells you which entry and why.
Grant checking itself hasn’t changed. Same matcher, same single-use spending, same vault. This is the part that comes after.
What a replay looks like
Say an agent got a grant to fix the title of post 2. It tried post 3 first, then did the job. This is the replay:
$ tokening flight replay --db tokening.db --agent editor-bot
#1 2026-10-31T10:02:03.120Z grant g_1a2b issued to editor-bot by operator: wp.edit-post-text on wp_update_post, 1 use, expires 2026-10-31T10:17:03.120Z, bound id, reason "fix title"
#2 2026-10-31T10:02:04.880Z request g_1a2b editor-bot asked for wp_update_post on demo-wp with arguments id, title (digest 8b07c1d2e94f)
#3 2026-10-31T10:02:04.880Z decision g_1a2b refused (arg_mismatch): Grant is bound to id 2; call sent id 3. 1 use left.
#4 2026-10-31T10:02:05.410Z request g_1a2b editor-bot asked for wp_update_post on demo-wp with arguments id, title (digest 3f2a91c07d4e)
#5 2026-10-31T10:02:05.410Z decision g_1a2b allowed: Call fits this grant. One use was consumed before forwarding. 0 uses left.
#6 2026-10-31T10:02:05.494Z result g_1a2b upstream answered HTTP 200 in 84 ms
Chain intact through entry 6.
The log keeps argument names and a digest of each call. It never keeps the values the agent sent, the real credential or the grant secret. If you still have the original call, you can show it matches the digest. If you don’t, the log can’t leak it.
Now someone deletes entry 3, because a refused attempt on the wrong post looks bad:
$ tokening flight verify --db tokening.db
Broken at entry 4: entry 3 is missing between entries 2 and 4.
Entries 1 to 2 verify. Last good head: 2:5d0c...
Saving a head
There’s a limit every hash chain has, and it’s better said plainly. Someone who can write to the database file could rebuild the whole chain from the first change onward. The hashes would line up again.
So keep a small piece of the chain somewhere else. tokening flight head prints the last entry’s number and hash. Paste it into a ticket, a commit message or a note to a colleague. Later, tokening flight verify --head 6:9c1e... checks that this exact entry is still in the log. A rebuilt chain won’t have it. Entries cut from the end are caught too, by the saved head and by a counter SQLite keeps of the highest entry number it ever handed out.
Compliance reviews tend to ask two things: who was allowed to do this, and could the record have been changed since? For anything that went through Tokening, the log answers both. It says nothing about what an agent did by other routes. That’s why the real key should only ever live in Tokening.
Exporting
tokening flight export writes the log as JSON lines, filtered by agent, grant or time range. A filtered export skips entries on purpose, so verify it with --partial. Every entry’s own hash is still checked, and links are checked wherever entries are next to each other. The hash format is written down in the flight recorder reference, so you can recompute it in any language without Tokening.
How it was tested
Entries go into the database in the same transaction as the change they describe. If the process dies halfway, nothing half-written is left behind. Several writers can’t fork the chain, because each one takes SQLite’s write lock before it reads the previous hash.
The tests try to break exactly those promises. The numbers below come from the latest release test results (v0.1.4), measured on Linux. The same tests passed on macOS and on each of the five platforms the release ships for, with the same counts. Only the size of the log left after the kill test changes, because faster machines write more before each kill.
- A real log of 42 entries was built through the normal grant and call paths, then attacked directly in SQLite with the protective triggers dropped: every entry edited, every entry but the last deleted (removing the last one is a truncation), every neighbouring pair swapped, every possible truncation, and every truncation again with SQLite’s counter reset to hide it. That’s 208 attacks. Verify caught all 208, and named the right entry for every edit, deletion and swap. The last group was only caught with a saved head, which is the point of saving one.
- Eight database connections wrote at the same time, then four separate processes on one file. 800 and 400 entries came out, every chain intact, and every request sat directly next to its own decision.
- A writer process was killed at random moments, 20 times in a row. After each kill the log verified to its last complete entry, and every committed decision had both of its entries. In the v0.1.4 release run the Linux log on Intel held 964 entries at the end.
Every release archive also runs flight verify and flight replay on a real log as part of its smoke test before anything is published.
Case studies and recipes
Three case studies show the recorder on real runs of the 0.1.4 binary in front of a stand-in CMS: replaying an agent’s publish session, catching edited and deleted log rows, and a full rebuild caught by a saved head, and exporting one agent’s session for a reviewer. The recipes post has the commands for saving heads, checking the log in CI, exporting and replaying.
Getting it
Download 0.1.2 and run tokening serve with your existing database. It moves to the new schema on first start and records from then on. Older decisions stay in the regular audit log. The commands are on the reference page and in the quick start.