# 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.

```text
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:

```rust
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:

```text
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`](https://crates.io/crates/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)](https://github.com/ohdearquant/khive/blob/main/docs/adr/ADR-080-session-pack-oss-storage-mechanism.md).

## License

Apache-2.0.
