Skip to content

fix(store): record which agent a decline was made for - #774

Merged
fajarhide merged 4 commits into
mainfrom
fix/773-passthrough-agent-id
Sep 4, 2026
Merged

fix(store): record which agent a decline was made for#774
fajarhide merged 4 commits into
mainfrom
fix/773-passthrough-agent-id

Conversation

@fajarhide

@fajarhide fajarhide commented Sep 4, 2026

Copy link
Copy Markdown
Owner

passthrough_events recorded the command, the byte count and the reason, and no agent, so
OMNI could say what it handed back and never what share of it was handed back to a model
rather than to a shell. The ledger and the distillers exist to serve one of those two.

The obvious proxy is wrong, and this repository's own data disproves it. host output cap fires only when agent_id == "claude_code" (post_tool.rs:366), so every one of its
rows is a hook row, and 20 of its 39 carry no session id. An empty session is not a
door test.

What that was costing. #608 proposes changing the format-safety gate on the strength of
structured payload with no downstream reader. Re-measured today that pool is 12.33 MB over
576 calls, and splitting it by session id gives 11.99 MB with none against 0.34 MB with
one. If the session-less rows are pipe, the proposal is 36 times smaller than it reads. If
they are not, it is not. Nothing in the store could tell, so a gate change was queued
behind a number nobody could attribute, which is #760's shape exactly.

agent_id is written from normalized.agent_id on the hook path and from the resolved
agent on the pipe path, the same value distillations already stores.
passthrough_bytes_by_agent(days) reports the split. Rows written before the column read
unknown rather than being backfilled: the door cannot be recovered after the fact, and
guessing it is inventing the number this exists to stop inventing.

The first test I wrote was toothless, and the break step found it. It called
record_passthrough with the agent passed in by hand, so blanking the agent at every
post_tool call site left it green. The test runs through process_payload now, and the
same break is red:

assertion `left == right` failed: the hook has to record the agent it resolved, not a
default: [("unknown", 1, 1111)]

That is the third test this session that passed for the wrong reason and was caught by
breaking it rather than by reading it.

Verification: make ci green, smoke_test.sh 70/70.

Closes #773

Greptile Summary

The PR adds durable agent attribution to passthrough events and exposes per-agent byte aggregation.

  • Migrates existing SQLite stores with an agent_id column whose legacy rows remain unknown.
  • Records resolved agent identity for hook declines and pipe passthroughs.
  • Adds regression and aggregation coverage for the new attribution.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported decline-attribution defect is fixed across the current hook recording paths.

Important Files Changed

Filename Overview
src/hooks/post_tool.rs All hook passthrough-recording paths now use the same resolved reporting identity used by distillation, resolving the prior misattribution.
src/hooks/pipe.rs Pipe passthrough paths now persist the resolved pipe agent identity.
src/store/sqlite.rs Adds backward-compatible agent attribution storage and a parameterized per-agent aggregation query.
changelog.d/773.fixed.md Documents the new attribution field, query, and intentional treatment of legacy rows.

Reviews (2): Last reviewed commit: "test(hooks): say what the Codex arm does..." | Re-trigger Greptile

Context used (3)

passthrough_events carried the command, the bytes and the reason, and no
agent, so the table could say what OMNI handed back and never what share of
it was handed back to a model rather than to a shell.

The obvious proxy is wrong, and this repository's own data disproves it: host
output cap only fires for claude_code, so every one of its rows is a hook row,
and 20 of its 39 carry no session id. An empty session is not a door test.

This is what makes #608 unsizeable. Its case rests on 12.33 MB of structured
payload with no downstream reader, and only the part that entered a model's
context was ever the ledger's to take. Today that share is somewhere between
337 KB and 12.33 MB, and nothing in the store can narrow it.

Old rows read unknown. The door cannot be recovered after the fact, so any
query about the split windows itself, which is the same rule the reason column
already needs.

Closes #773
…test did not

Break-testing found it: blanking the agent at the post_tool call sites left
the first test green, because it passed the agent to record_passthrough by
hand. The column is only worth having if the hook writes it.

Driven through process_payload now. Third toothless test this session, all
three found by the break step rather than by review.
Comment thread src/hooks/post_tool.rs
…s shape

Greptile on #774. Codex sends a Claude-Code-shaped payload, which is why
stats_agent_id exists and why distillation already books under it. The decline
sites were using normalized.agent_id, so a Codex decline would have been filed
under Claude Code: a column added for attribution, attributing wrongly.

The test gained the arm that catches it, in Codex's own payload shape.
Break-testing it showed the arm stays green when the decline sites go back to
the payload's own id, and the reason is worth writing down rather than papering
over: for a Codex-shaped payload both ids agree. They diverge when a
Claude-Code-shaped payload arrives under an environment naming another host,
which is resolve_agent_id's own rule and needs a process-wide variable no test
here may set.

The rule is tested on the pure function where it can be. The arm holds the
weaker property, and now says so.
@fajarhide
fajarhide merged commit eac93e1 into main Sep 4, 2026
14 checks passed
@fajarhide
fajarhide deleted the fix/773-passthrough-agent-id branch September 4, 2026 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

passthrough_events cannot say which door a decline came through, so #608 cannot be sized

1 participant