Skip to content

[G2] Control events as typed evidence: slash-command triads, task notifications, hook outputs, system reminders, Codex control wrappers #180

Closed

Description

@willwashburn

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 by parentUuid. burn collapses them into a synthetic ActivityCategory::Skill (crates/relayburn-sdk/src/reader/claude/incremental.rs:666-689, reader/classifier/slash_triads.rs) by reading type, 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 of history via CLAUDE_CONTROL_PREFIXES (crates/ai-hist/src/discover.rs:650-661) and keeps the rows as ordinary session_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; fixture task-notification.jsonl). relayhistory stores them as user text events (they are not in CLAUDE_CONTROL_PREFIXES).
  • Hook outputs (<user-prompt-submit-hook>), <bash-input/stdout/stderr>, Caveat: The messages below…: prefix-filtered from history only.
  • 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's codex::is_control_context (crates/ai-hist/src/codex.rs:69) rejects them from history but they remain in session_events.
  • Meta rows (isMeta: true) and /resume, /continue marker 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

  1. 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, and session_markers rows of kind = "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}.
  2. Claude parser: read origin.kind and attachment.{type, commandMode}; implement the triad walk over parentUuid exactly as burn's slash_triads.rs (port its tests; fixture slash-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 separate system_reminder events (text kept, so overhead analysis can attribute them) rather than merged into the prompt text.
  3. Codex parser: tag codex_context_wrapper rows using codex::is_control_context (single implementation; discovery and hydration must agree, which is why that module exists).
  4. history rows: unchanged semantics (control rows still excluded), but derive the exclusion from control_kind instead of the prefix list, and delete CLAUDE_CONTROL_PREFIXES once tests prove parity on the corpus.
  5. 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, and first_prompt derivation) skips control rows via control_kind.
  6. Expose controlKind on SessionEvent in 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 a slash_command marker in SessionEvidence.markers ([G2] SessionStore facade: the single public entry point (seven methods, typed evidence, no raw connections, no contract constant) #178).

Acceptance

  • Corpus slash-command-triad: one slash_command marker with command_name populated; the three rows carry the three slash_command_* control kinds; history has no row for them.
  • task-notification: the row is control_kind = task_notification, not in history, and not the parent_prompt for any usage attribution.
  • A user content array containing a <system-reminder> block yields one text event (the prompt) and one system_reminder event with the same message_id.
  • Codex simple-turn with an <environment_context> wrapper: tagged, excluded from history, present in session_events.
  • burn's classifier/slash_triads.rs tests, 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).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions