Repository navigation
[G2] Control events as typed evidence: slash-command triads, task notifications, hook outputs, system reminders, Codex control wrappers #180
Copy link
Copy link
Closed
Description
Part of #160 (group 2 — depends on #165 markers table; parallel with #178/#179/#181/#182).
Problem
Harness transcripts contain rows that are not human prompts or model output but are essential for classification and correct prompt attribution. Both repos handle them, differently, and burn's handling reads raw JSONL fields that relayhistory currently discards, so a burn built on relayhistory would lose its activity classifier:
- Slash-command triads (Claude): caveat row →
<command-name>/<command-message>/<command-args>invocation row →<local-command-stdout>row, chained byparentUuid. burn collapses them into a syntheticActivityCategory::Skill(crates/relayburn-sdk/src/reader/claude/incremental.rs:666-689,reader/classifier/slash_triads.rs) by readingtype,uuid,parentUuid,message.content,origin.kind,attachment.type,attachment.commandMode— six raw fields, two of which (origin.kind,attachment.*) relayhistory never reads. relayhistory only prefix-filters these out ofhistoryviaCLAUDE_CONTROL_PREFIXES(crates/ai-hist/src/discover.rs:650-661) and keeps the rows as ordinarysession_events(user, text). - Task notifications (
<task-notification>synthetic user rows): burn excludes them from user text, user turns and prompt roots with a 3-clause detector (classifier/slash_triads.rs:28-62; fixturetask-notification.jsonl). relayhistory stores them as user text events (they are not inCLAUDE_CONTROL_PREFIXES). - Hook outputs (
<user-prompt-submit-hook>),<bash-input/stdout/stderr>,Caveat: The messages below…: prefix-filtered fromhistoryonly. - System reminders injected into user content arrays: not modelled by either repo as a distinct kind, but they inflate
payload_bytes/prompt text. - Codex boilerplate:
<environment_context…>,<permissions…>,<collaboration_mode…>,<INSTRUCTIONS>,# AGENTS.md,<app-context,<skills_instructions,<user_instructions— burn strips before classification (reader/codex.rs:364-385); relayhistory'scodex::is_control_context(crates/ai-hist/src/codex.rs:69) rejects them fromhistorybut they remain insession_events. - Meta rows (
isMeta: true) and/resume,/continuemarker prompts.
A consumer needs (a) to know a row is control, (b) which kind, (c) the linkage (which prompt it belongs to), and (d) the parsed payload (command name, args) — without re-reading JSONL.
Scope
- Add
session_events.control_kind TEXT(null for genuine prompts/output) with values:slash_command_caveat,slash_command_invocation,slash_command_output,task_notification,hook_output,bash_passthrough_input,bash_passthrough_output,system_reminder,codex_context_wrapper,meta,resume_marker, andsession_markersrows ofkind = "slash_command"grouping a triad:payload_json = {command_name, command_message, command_args, command_mode, origin_kind, invocation_event_uid, output_event_uid, stdout_bytes}. - Claude parser: read
origin.kindandattachment.{type, commandMode}; implement the triad walk overparentUuidexactly as burn'sslash_triads.rs(port its tests; fixtureslash-command-triad.jsonl); implement the task-notification detector; tag hook/bash passthrough/caveat rows; detect<system-reminder>blocks inside user content arrays and record them as separatesystem_reminderevents (text kept, so overhead analysis can attribute them) rather than merged into the prompt text. - Codex parser: tag
codex_context_wrapperrows usingcodex::is_control_context(single implementation; discovery and hydration must agree, which is why that module exists). historyrows: unchanged semantics (control rows still excluded), but derive the exclusion fromcontrol_kindinstead of the prefix list, and deleteCLAUDE_CONTROL_PREFIXESonce tests prove parity on the corpus.- Prompt-attribution API ([G1] Usage normalization in core: per-request usage records, session summary, and moving attribute_session_usage out of the cloud plugin (supersedes #99 scope) #172
attribute_usage_to_prompts, andfirst_promptderivation) skips control rows viacontrol_kind. - Expose
controlKindonSessionEventin Rust/napi/TS/MCP ([G2] Expose the new evidence through napi, the TypeScript SDK and MCP (markers, tool-result facts, usage/requests, control kinds, continuity, change feed) with contract bumps #181), and aslash_commandmarker inSessionEvidence.markers([G2]SessionStorefacade: the single public entry point (seven methods, typed evidence, no raw connections, no contract constant) #178).
Acceptance
- Corpus
slash-command-triad: oneslash_commandmarker withcommand_namepopulated; the three rows carry the threeslash_command_*control kinds;historyhas no row for them. task-notification: the row iscontrol_kind = task_notification, not inhistory, and not theparent_promptfor any usage attribution.- A user content array containing a
<system-reminder>block yields onetextevent (the prompt) and onesystem_reminderevent with the samemessage_id. - Codex
simple-turnwith an<environment_context>wrapper: tagged, excluded fromhistory, present insession_events. - burn's
classifier/slash_triads.rstests, translated, pass against the marker payload alone (no raw JSON).
Out of scope
Activity classification itself (burn reader/classifier.rs stays in burn and consumes control_kind + markers).
Reactions are currently unavailable
Activity
fix(ingest): reconcile upgraded databases with typed control rows (#180)
fix: validate control_kind at the evidence boundary and keep publishe…
fix: settle titles only from trusted folds, keep shared prompts, bump…
fix(ingest): recreate reminder rows from the current text on every re…
fix(ingest): retire a record's reminder rows before walking its blocks (