khive-pack-session 0.3.0

Session verb pack — store/list/get over the notes substrate (ADR-080)
Documentation

khive-pack-session

Session pack: registers the session note kind and a background transcript mirror that live-tails Claude Code and Codex CLI session logs into two auxiliary SQL tables (ADR-080).

What ships this milestone

The pack declares three verbs — session.store, session.list, session.get — over the notes substrate, but all three are Visibility::Subhandler: reachable via the runtime and kkernel exec (a trusted operator surface), but withheld from the agent-facing MCP request surface until the session-continuity query UX is designed. They add zero verbs to what an agent sees in the request tool's catalog today.

The pack's active feature this milestone is the mirror service, started from the warm() pack hook independent of verb visibility. It polls $HOME/.claude/projects/**/*.jsonl (Claude Code) and $HOME/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl (Codex CLI), tracks a byte-offset cursor per file (session_mirror_cursor), and appends parsed messages into sessions / session_messages — both idempotent (INSERT OR IGNORE) so restarting the daemon or double-processing a file is safe. The service is opt-in via environment variable, disabled by default.

KHIVE_MIRROR_ENABLED=true        # enable the Claude Code mirror
KHIVE_MIRROR_PROJECTS_DIR=...    # default: $HOME/.claude/projects
KHIVE_MIRROR_CODEX_ENABLED=true  # enable the Codex CLI mirror (independent flag)
KHIVE_MIRROR_CODEX_DIR=...       # default: $HOME/.codex/sessions
KHIVE_MIRROR_POLL_SECS=2         # polling interval
KHIVE_MIRROR_BACKFILL=true       # mirror existing files from byte 0 vs. current EOF

Usage

The mirror is started automatically by the runtime's pack warm-up; it is not called directly. The parse and config surfaces are public for testing and alternate embedding:

use khive_pack_session::mirror::{parse_cc_line, MirrorConfig};

let config = MirrorConfig::from_env();
if config.enabled {
    // a JSONL line from a Claude Code transcript; returns None on a blank or
    // unparseable line
    if let Some(event) = parse_cc_line(line) {
        // event: ParsedEvent — session_id, uuid, role, text, raw JSON, ...
    }
}

Once the session.store/list/get verbs are flipped to Visibility::Verb, the intended call shape is:

request(ops="session.list(agent_id=\"lambda:khive\", limit=20)")

Storage

Three tables, applied idempotently via the pack's schema_plan hook: sessions (one row per transcript: provider_session_id, source, cwd, git_branch, message_count, first/last-seen timestamps), session_messages (one row per parsed message: seq, parent_uuid, role, msg_type, text, raw JSON), and session_mirror_cursor (byte-offset bookkeeping per file).

Where this sits

khive-pack-session sits in the pack tier alongside khive-pack-kg (a REQUIRES dependency for the session note kind's substrate) and is one of the eight packs loaded by default in khive-mcp. This crate is the OSS storage mechanism half of session continuity — the design record explicitly moved the scope boundary so the mirror mechanism (this crate, the session.* verb surface, and the note-kind registration) ships in the open-source repository rather than staying a deployment-only concern. Governing ADR: ADR-080 (Session Pack — OSS Storage Mechanism).

License

Apache-2.0.