Repository navigation
Session Cartographer 0.8.3
This release is about time spent for nothing. A performance review on
2026-10-07 read the recall telemetry, timed every hook, and profiled the
semantic leg: recall over Turbo was fast, but a filtered Qdrant search was
scanning payloads, the hooks were paying for process starts rather than work,
and the catch-up at session start was inspecting transcripts it had already
indexed. Each is fixed here, and each fix is held by a test that reproduces
the measurement.
0.8.2 was tagged first and never published: its release gate failed on Linux,
where the hook-rows fixture had built its workspace under /tmp (a path the
hooks drop as scratch) and retro-index.sh's mtime helper returned GNU stat's
filesystem block instead of a number. Both are fixed here, so 0.8.3 is the
same change set as a release that actually passes its gate on both platforms.
Filtered semantic search is indexed
The session-cartographer collection had no payload indexes. Every recall
filters on project and timestamp, and Qdrant evaluated both by scanning:
on 157k points a project-filtered search cost 200-420 ms of server time and a
timestamp range 100-170 ms, against 3-6 ms unfiltered. That was the dominant
cost in the semantic stage and why some recalls blew the 4000 ms Turbo budget
and fell back to the portable CLI.
scripts/qdrant-collection.jsowns collection bootstrap: the collection
plus akeywordindex onprojectand adatetimeindex ontimestamp.embed-events.jscreates both with the collection.index-event.sh, the
Explorer's/api/healthand Turbo at boot readpayload_schemaand create
whatever is missing, so an upgraded install is indexed from its first hook
event with no reindex.- The same filtered searches now cost 4-16 ms with identical top-50 results.
Hooks start fewer processes
The two hot hooks spent their time starting processes. log-tool-use.sh
spawned 64 for a plain npm test (19 jq, 12 awk, 8 head, 5 tr, 5 grep); the
per-turn Stop hook spawned 24, including a python3 interpreter to URL-encode
one path, and then logged nothing.
- Each hook reads its payload with one
jq | @sh, reads the command text in
one awk pass, resolves the project only when it writes, and builds both rows
of a pair with one jq. The transcript path is encoded with jq's@uri, so
python is no longer a hook dependency. - Measured: a plain command 64 processes to 7, the Claude Stop no-op 24 to 2,
an Edit 37 to 6, a SessionEnd 38 to 11. - The rows do not change.
tests/fixtures/hook-rows/expected.txtis what the
pre-rewrite hooks wrote for a fixed set of payloads, and
hook-rows-golden.test.jsholds the live hooks to it byte for byte.
Catch-up skips before it reads
The background Codex catch-up at session start took 26 s median even with
nothing to index, because retro-index.sh ran two transcript-wide jq passes
and a node project-inference process on every transcript before reading the
checkpoint that said to skip it. A Codex rollout file carries its session id
in its name, so a prefix match against the progress file now skips a
checkpointed session without opening it. Anything that does not match falls
through to the full derivation and full key check, unchanged.
Standup: SILENT and UNCLAIMED
/standup now reports two things the event log never saw: Codex transcripts
written in the window with no logged events, and tracked files changed in the
window that no logged session claims.
Upgrading
Live hooks run from the plugin cache, so the hook and catch-up changes apply
after claude plugin update session-cartographer@session-cartographer and a
restart. The payload indexes are created by the first hook event, health
poll or Turbo start on the new version; node scripts/qdrant-collection.js ensure-indexes --json does it by hand.