Activity Log
June 22, 2026 · View on GitHub
Status: M0–M4 shipped. ExecPlan
crux-dual-surface-activity-log-2026-06-18. Capture + agent lane + human page + verify cross-walk, all behindCORECRUXD_FEATURE_ACTIVITY_LOG.
The activity log gives every session a rolling, chronological record of what agents do — captured once per turn-event and read two ways off one source of truth:
- Agent lane — a cheap, token-budgeted pull (
GET /v1/activity+ MCPactivity_recent) answering "what just happened in this session" in ~500 tokens. - Human lane (M3, pending) — a console Activity tab rendering the same rows with verbatim text + a ✓verify badge.
Both lanes join on turn_id and reference the same append id, so the human
view is the agent view with prose rehydrated — they can never disagree.
Enabling it
Off by default. Set the flag in the daemon's environment:
CORECRUXD_FEATURE_ACTIVITY_LOG=1
With the flag unset the routes return 404 and the daemon behaves exactly as
before. Optional retention override (default 24h):
CORECRUXD_FEATURE_ACTIVITY_LOG_TTL_SECS=86400
The seven categories (kind)
question · answer · reasoning · command · fact · execplan ·
handoff · error
(reasoning is best-effort — see OD-15 on harness reasoning capture.)
Capture — POST /v1/activity
Append a journal entry. Requires a write scope (facts:write or
admin:write) for the body's tenant_id. The actor is bound to the
authenticated passport (the body cannot spoof it).
POST /v1/activity
{
"tenant_id": "acme",
"session_id": "sess-abc",
"turn_id": "turn-12", // optional; the join key to receipts + the human lane
"kind": "command",
"text": "ran cargo test -p corecruxd",
"refs": { "fact_ids": [], "receipt_ids": [], "event_ids": [] }, // optional cross-refs
"meta": { "tool": "Bash", "intent": "test", "confidence": null }, // optional
"private": false // PII: never syncs remote, author-scoped reads
}
Response 201 is the persisted crux.activity.journal_entry.v1 entry, with a
content-addressed entry_id that is also recorded in refs.receipt_ids (the
append's audit reference). Each append broadcasts an activity.appended event
on /v1/events/stream (ids + kind only — never the verbatim text).
Privacy: reserved-prefix tokens (__agent::, __ops::,
__bootstrap__::) are stripped from text on persist and on every read.
private:true entries are returned only to the authoring passport and never
sync to a remote.
Agent lane — GET /v1/activity
GET /v1/activity?tenant_id=acme&session=sess-abc&token_budget=500
[&since=<seq>] [&kinds=command,error]
token_budgetis mandatory (QC.2) — a missing/zero value is a400.- Returns compact rows newest-first, privacy-scoped, trimmed to fit the
budget (using the same estimator as
tool_trace_recent).
{
"session_id": "sess-abc",
"token_budget": 500,
"returned": 3,
"truncated": false,
"rows": [
{ "turn_id": "turn-12", "seq": 41, "ts": "...", "kind": "command",
"tool": "Bash", "intent": "test", "confidence": null,
"fact_refs": [], "receipt_ids": ["act_…"], "preview": "ran cargo test …" }
]
}
preview is truncated — never the verbatim text. To get the full text for a
turn, deref it:
GET /v1/activity/turn/{turn_id}?tenant_id=acme&session=sess-abc
returns the full entries[] with verbatim text and refs.
MCP — activity_recent
Same pull from chat; applies a default token_budget of 500.
activity_recent({ "session_id": "sess-abc", "kinds": ["error","command"], "token_budget": 500 })
Human lane — /console/activity
A new page on the embedded console (linked from the console header). It is a self-contained, dependency-free page that:
- streams live rows via
EventSource('/v1/events/stream?types=activity.appended'), - backfills via
GET /v1/activity(tenant / session / token_budget inputs), - colour-codes rows by kind, filters client-side by free text,
- expands a row to its verbatim
textviaGET /v1/activity/turn/{turn_id}with a ✓verify badge per receipt ref.
Deep-link with ?session=<id>&tenant_id=<t>. The page is inert (its API calls
return 404) unless CORECRUXD_FEATURE_ACTIVITY_LOG=1. Iterate without a
rebuild by pointing CORECRUXD_CONSOLE_DEV_PATH at a dir containing
activity.html.
Verification cross-walk (M4)
The agent-lane row's receipt_ids for a turn are byte-identical to the
human-lane deref's refs.receipt_ids for the same turn_id — guaranteed by
the parity test activity_turn_id_parity_agent_vs_human_lane. The console
✓verify badge (M3) reuses the existing receipt_verify path against those
ids.
What's not here yet
- Reasoning capture (category 3) — best-effort, pending OD-15.
- Full CROWN signing of each append — today the append id + the
activity.appendedprojection row satisfy the audit-trail requirement (T.4); CROWN co-signing is a tracked follow-up. - Hook clients —
POST /v1/activityis the ingestion contract; per-workstation Claude Code hooks (prompt/stop/posttooluse → append) are an opt-in step, not shipped enabled.