feat(journal): per-harness pane history, with Codex and pi adapters - #44
Open
AltanS wants to merge 2 commits into
Open
feat(journal): per-harness pane history, with Codex and pi adapters#44AltanS wants to merge 2 commits into
AltanS wants to merge 2 commits into
Conversation
Pane history was Claude-only and hardcoded: one parser, one filesystem
source, both wired straight into the route. Every other harness Herdr can
detect had no journal and no way to get one short of forking the reader.
History is now an adapter keyed on the pane's agent (bridge/journal/), the
same shape the frontend already uses for block grammars. An adapter is
{agent, source, parse}: the fs half finds the log, the pure half reads the
grammar, and the harness-blind store owns caching and paging. The registry
is built FROM each adapter's own `agent` field, so a key can't drift from
what it points at, and an agent with no adapter simply has no journal.
Three adapters ship, each verified against real logs on disk:
claude — unchanged behaviour, moved behind the seam.
codex — ~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl. Rows are
double-booked (response_item AND event_msg carry the same turns);
we take response_item, the only family with tool results. Rows
have no id, so the paging cursor is synthesised from row content
rather than position, which a tail-read window would shift.
pi — ~/.pi/agent/sessions/--<cwd>--/<ts>_<uuid>.jsonl. Every row has
its own id, so paging is free.
Two live findings from probing real panes, both fixed here:
* pi reports `agent_session` as kind "path" — an ABSOLUTE PATH chosen by a
process we don't control — and state-engine kept only kind "id", so pi
could never have had history at all. Both kinds are kept now, and which
are meaningful is the adapter's call. A path ref is treated as hostile
input: confined to that harness's root after symlink resolution, with
failure indistinguishable from "no log" so containment isn't probeable.
* Herdr keeps reporting the last session announced for a pane, so
relaunching its agent as a different harness leaves the old ref behind —
a pane running pi still advertised a herdr:claude id. The ref is now
dropped unless its own `agent` matches the pane's.
Because a session ref can be a filesystem path, it no longer goes on the
wire: /api/snapshot strips it to `hasSession`, which is what the History
affordance always actually wanted, and is now also gated on the harness
having an adapter at all.
scripts/journal-probe.ts probes every adapter against this machine's real
logs — the check unit tests can't make, since it's format drift that breaks
a journal. It caught codex 0.145 adding a `developer` message role that the
parser would have rendered as something the operator said.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #40.
Pane history was Claude-only and hardcoded — one parser and one filesystem source wired straight into the route. This makes it a per-harness adapter, and ships three:
claude(unchanged behaviour, moved behind the seam),codex, andpi.An adapter is
{agent, source, parse}: the fs half finds the log, the pure half reads the grammar, and a harness-blind store owns caching and paging. The registry is built from each adapter's ownagentfield, so a key can't drift from what it points at — the same shapeweb/src/lib/harness/registry.tsalready uses for block grammars (a different seam: that one is the live mirror, this one is the on-disk log). An agent with no adapter simply has no journal.Grammars, verified against real logs
~/.claude/projects/<cwd>/<uuid>.jsonl~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonlresponse_itemandevent_msgcarry the same turns) — we takeresponse_item, the only family with tool results~/.pi/agent/sessions/--<cwd>--/<ts>_<uuid>.jsonlCodex rows carry no id at all, so the paging cursor is synthesised from row content rather than position — a tail-read window over a >32 MB log starts somewhere other than the file's first row. The accepted failure mode is documented next to
codexCursor.Two live findings, both fixed here
agent_sessionas kindpath— an absolute path chosen by a process we don't control — and the bridge kept only kindid. Both kinds are kept now, and which are meaningful is the adapter's call. A path ref is treated as hostile input: confined to that harness's root after symlink resolution, with failure indistinguishable from "no log" so containment isn't probeable.piwas observed still advertising aherdr:claudeid. The ref is now dropped unless its ownagentmatches the pane's.Wire change
A session ref can be a filesystem path, so it no longer leaves the bridge:
/api/snapshotsendshasSessioninstead, which is all the History affordance ever wanted. It's now also gated on the harness having an adapter, so a pane can't advertise history that always comes back empty.Verification
bun run test— 439 backend,cd web && bun run test— 1084 frontend, both green; new suites for the codex/pi grammars, the registry, and the store.scripts/journal-probe.ts(new) resolves + parses every adapter against the host's real logs — the format-drift check unit tests can't make. It caught codex 0.145 adding adevelopermessage role that would have rendered as operator speech.collie-demoherd, real codex and pi panes, through a bridge on a spare port: all three harnesses returnavailable:truewith real turns, shell panes stayno-session, and no path appears anywhere in the snapshot payload. Claude re-verified on a 233-turn journal (178 tool results folded).Version: 0.19.0 (new capability, no breaking config change —
COLLIE_TRANSCRIPT_ROOTkeeps meaning Claude's root;CODEX_HOME/PI_CODING_AGENT_DIRare honoured, withCOLLIE_CODEX_ROOT/COLLIE_PI_ROOToverrides).🤖 Generated with Claude Code