Documentation
¶
Overview ¶
Package activity is the append-only record of what an agent actually did.
Everything else in this system is something a model chose to tell us. A memory is stored because the agent called `remember`; a checkpoint exists because it called `checkpoint`. That works right up until the model does not call them, and the failure is silent — a session that decided three things and wrote none of them down looks, from the vault, exactly like a session that never happened.
This is the other half. It records events the agent does not get a vote on: the prompts a person typed, the tools that ran, the turns that ended. The host reports them through hooks whether or not the model would have thought to mention them, which makes this the one part of the record that cannot be forgotten.
Three deliberate choices:
JSONL on disk, one file per month. Not SQLite. The log is meant to be read by things that are not us — grep, jq, a script, the user at three in the morning wondering what happened. A database would be faster to query and would make that impossible, and "your history is a file you own" stops being true the moment the only reader is our binary. Monthly files keep `activity/2026-09.jsonl` a name a person can guess and keep any single file bounded.
Append-only, never rewritten. A log that edits itself is not evidence.
No model, ever. Recording is arithmetic and string handling. A summariser here would turn a factual record into a plausible one, and cost a hook budget it does not have.
Index ¶
Constants ¶
const ( KindPrompt = "prompt" // a person typed something KindTool = "tool" // a tool ran and returned KindTurnEnd = "turn-end" // the agent finished a turn KindNotice = "notice" // the host wants the user's attention KindStart = "session-start" KindEnd = "session-end" KindBlocked = "permission" // the agent asked to do something and is waiting KindWarn = "warning" // something the hook path could not report any louder )
Event kinds. Plain strings, so a host that grows a new event type does not need a migration here — an unknown kind still records, still greps, and still prints. The set below is what Claude Code's hooks can currently tell us.
const Dir = "activity"
Dir is the vault subdirectory holding the log.
Variables ¶
This section is empty.
Functions ¶
func Append ¶
Append writes one event. It creates the month's file if needed and never rewrites what is already there.
Errors are returned rather than swallowed, but callers on a hook path should treat them as advisory: a log that fails to write must not break the session it is recording. The hook decides that; this function only reports.
Types ¶
type Event ¶
type Event struct {
TS int64 `json:"ts"`
Kind string `json:"kind"`
Project string `json:"project,omitempty"`
Session string `json:"session,omitempty"`
// Agent is the host that reported the event ("claude-code"), not the model.
Agent string `json:"agent,omitempty"`
// Tool is the tool's name for KindTool, empty otherwise.
Tool string `json:"tool,omitempty"`
// Summary is one line a human can read without expanding anything. It is
// the whole point of the row; a log whose rows need decoding is a log
// nobody scans.
Summary string `json:"summary,omitempty"`
// Extra carries whatever the host sent that we did not model. Kept so the
// record stays lossless even where our schema is not.
Extra map[string]any `json:"extra,omitempty"`
}
Event is one line of the log.
The fields are small and fixed on purpose. This file is appended to from a hook on the critical path of every tool call, so it holds what is cheap to know and true without interpretation — and puts anything host-specific in Extra rather than growing a column per host.
func FromHook ¶
FromHook builds an Event from a host hook payload. event is the host's event name; raw is the JSON it wrote to the hook's stdin. project is used when the payload does not carry enough to work one out.
type Query ¶
type Query struct {
Project string
Kind string
Tool string
Since time.Time
Until time.Time
// Limit caps the result, keeping the *newest* matches. A limit applied
// while scanning would keep the oldest, which is the opposite of what
// anyone asking for "the last 20" means.
Limit int
}
Query narrows the log. A zero value matches everything.