Hooks install globally, by design: hook_bootstrap.py says as much, and removes
per-project Cursor hooks because "global covers all workspaces". That is the
right default for a recorder, since an agent you forgot to instrument is the one
that matters.
But the canonical event carries nothing to say which project a tool call
belonged to. Grepping canonical.py and external.py for cwd, workspace or
project returns nothing. Every session on the machine lands in one
undifferentiated trail.
Three consequences, in rising order of seriousness.
The dashboard cannot answer the most natural question. "What did the agent
do in this repo today" is probably the first thing any developer asks a flight
recorder, and there is no way to express it. Filters offer source app, event
type, outcome, and time, but not place.
Detection noise crosses projects. A session-tool-burst or an off-hours hit
from unrelated work sits in the same queue as one from the repo you care about,
with nothing to tell them apart until you open each session and read the
commands.
Evidence packs leak. export --evidence covers a date range, so a pack
produced for a reviewer of one project includes every command, path and file
name from every other project worked on that week. For a tool sold on producing
compliance evidence, an export with no way to scope it to a subject is a real
obstacle, and the person who discovers it will be the person who least wants to.
The data is already arriving
Claude Code sends cwd in its hook payload. The client half-uses it: line 802
of scripts/agentmetry_ingest.py normalises Antigravity's Cwd into the tool
args, where it is folded into input_hash and stops being queryable. So the
value is on the wire and gets thrown away.
Proposed shape
- Extract
cwd in the hook client and put it on the event as a first-class
field rather than inside tool.input.
- Derive a stable
project label from it, preferring the git toplevel of the
cwd and falling back to the directory name, so worktrees and subdirectories of
one repo group together.
- Index it in
trail_db alongside session_id and source_app.
- Add a project filter to the dashboard, and a
--project flag to
export --evidence.
Constraints
Additive only. The canonical schema is a contract and existing trails must keep
opening: events recorded before this lands simply have no project, and the UI
should show them as unscoped rather than hiding them.
Absolute paths are more revealing than most fields already in the trail, since
they carry usernames and client names. Store the derived label as well as the
raw path, and let redaction apply to the path the way it does elsewhere.
Do not turn this into per-project opt-in capture. The recorder's value is that
it sees everything it orchestrates; this is about reading the trail, not about
narrowing what reaches it.
Hooks install globally, by design:
hook_bootstrap.pysays as much, and removesper-project Cursor hooks because "global covers all workspaces". That is the
right default for a recorder, since an agent you forgot to instrument is the one
that matters.
But the canonical event carries nothing to say which project a tool call
belonged to. Grepping
canonical.pyandexternal.pyforcwd,workspaceorprojectreturns nothing. Every session on the machine lands in oneundifferentiated trail.
Three consequences, in rising order of seriousness.
The dashboard cannot answer the most natural question. "What did the agent
do in this repo today" is probably the first thing any developer asks a flight
recorder, and there is no way to express it. Filters offer source app, event
type, outcome, and time, but not place.
Detection noise crosses projects. A
session-tool-burstor an off-hours hitfrom unrelated work sits in the same queue as one from the repo you care about,
with nothing to tell them apart until you open each session and read the
commands.
Evidence packs leak.
export --evidencecovers a date range, so a packproduced for a reviewer of one project includes every command, path and file
name from every other project worked on that week. For a tool sold on producing
compliance evidence, an export with no way to scope it to a subject is a real
obstacle, and the person who discovers it will be the person who least wants to.
The data is already arriving
Claude Code sends
cwdin its hook payload. The client half-uses it: line 802of
scripts/agentmetry_ingest.pynormalises Antigravity'sCwdinto the toolargs, where it is folded into
input_hashand stops being queryable. So thevalue is on the wire and gets thrown away.
Proposed shape
cwdin the hook client and put it on the event as a first-classfield rather than inside
tool.input.projectlabel from it, preferring the git toplevel of thecwd and falling back to the directory name, so worktrees and subdirectories of
one repo group together.
trail_dbalongsidesession_idandsource_app.--projectflag toexport --evidence.Constraints
Additive only. The canonical schema is a contract and existing trails must keep
opening: events recorded before this lands simply have no project, and the UI
should show them as unscoped rather than hiding them.
Absolute paths are more revealing than most fields already in the trail, since
they carry usernames and client names. Store the derived label as well as the
raw path, and let redaction apply to the path the way it does elsewhere.
Do not turn this into per-project opt-in capture. The recorder's value is that
it sees everything it orchestrates; this is about reading the trail, not about
narrowing what reaches it.