Skip to content

feat: session history searchable by agents — live+archived, retention (#491) - #495

Merged
thedancingdeveloper merged 3 commits into
devfrom
feat/491-history-search
Sep 1, 2026
Merged

feat: session history searchable by agents — live+archived, retention (#491)#495
thedancingdeveloper merged 3 commits into
devfrom
feat/491-history-search

Conversation

@thedancingdeveloper

Copy link
Copy Markdown
Contributor

Closes #491.

Operators could search archived terminal output in the History GUI, but agents could not reach any of it, and "history" meant dead sessions only. This makes all session history — live and archived — searchable by agents (MCP), scripts (HTTP/REST), and operators (GUI), and stops the store growing unbounded.

Built in three layers, one commit each.

Engine (Rust)

  • GET /api/history/search gains include_live (default true): on top of the archived FTS hits, each running session's scrollback is scanned on-demand (last history_live_scan_bytes, ANSI-stripped, same AND-of-terms match) and appended with live: true. Zero DB writes — chosen over incremental FTS indexing, which is ~O(n²) write amplification for ~30 MB sessions (see design). Combined list held to limit.
  • GET /api/history/{id}/log gains strip_ansi (default false) so an agent reads plain text; byte counters still describe the raw tail window.
  • history_retention_days config (default 30, 0 = forever) + a daily retention sweeper mirroring the assistant-log sweeper, bounding both history.db and session-logs/. history_live_scan_bytes (default 256 KiB) tunes the scan.
  • Tests: unit tests for the live-scan helpers + integration tests for live-before-exit search and strip_ansi.

vogt-core (Python)

Three registry read ops (scope=read, no reason), generating MCP + CLI + REST at once:

Operation MCP tool CLI REST
session.search_output session_search_output session search GET /sessions/history/search
session.log_tail session_log_tail session log GET /sessions/log
session.history_list session_history_list session history GET /sessions/history

Static GET paths with the id as a query field (the house convention — the registry has no path params). Each result carries the FR-E9 engine field: no engine, or an unreachable one, sets it and returns an empty view rather than an error that reads as "no history". Tests: parity (CLI/REST/MCP agree) + unit tests for mapping, query-param forwarding, missing-log, and degrade paths.

GUI (web)

The live-list/badge/live-replay already landed on dev (#477); the remaining gap was search. History search now passes include_live=true, badges a live hit Live, and the "not yet in the search index" caveat is retired.

Deliberately out of scope

The readable-transcript replay renderer (historyReplay.ts) is PR #493 (#490), not duplicated here to avoid a merge collision; #491 uses the engine strip_ansi for the agent path instead.

Verification

  • Engine: cargo test --all green (234 lib + 87 integration + others), fmt + clippy clean.
  • vogt-core: uv run pytest 1313 passed / 91% coverage; mypy + ruff clean.
  • Web: pnpm typecheck clean; 849 tests; pnpm build clean.

Design doc (operator-local, git-ignored): docs/local/SESSION_HISTORY_SEARCH_DESIGN.md. Docs updated: ENGINE.md (config table + history API), AGENT_GUIDE.md (history-search section).

🤖 Generated with Claude Code

thedancingdeveloper and others added 3 commits September 1, 2026 10:39
…n sweep (#491)

Make session history cover live sessions and stop it growing unbounded:

- `GET /api/history/search` gains `include_live` (default true): on top of the
  archived FTS hits, each running session's scrollback is scanned on-demand
  (last `history_live_scan_bytes`, ANSI-stripped, same AND-of-terms match) and
  appended with `live: true`. Zero DB writes; the combined list is held to
  `limit`. `SearchResult` gains a `live` flag (archived rows default false).
- `GET /api/history/{id}/log` gains `strip_ansi` (default false) so an agent
  can read plain text; the byte counters still describe the raw tail window.
- `history_retention_days` config (default 30, 0 = forever) + a daily
  retention sweeper mirroring the assistant-log sweeper, bounding both
  `history.db` and `session-logs/`. `history_live_scan_bytes` (default 256 KiB)
  tunes the live scan.

Tests: pure unit tests for the live-scan helpers (query_tokens, live_match,
truncate_chars) + integration tests for live-before-exit search and strip_ansi.
Docs: ENGINE.md config table and history API section.

Refs #491

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7eWQbHi8HyLyhaRA2piEn
…, list (#491)

Agents could not reach any session history: the engine adapter is metadata-only
and there were no history read ops. Add three, generated onto MCP/CLI/REST from
the registry (scope=read, no reason):

- session.search_output (mcp session_search_output, cli `session search`) —
  full-text over session output, live sessions included by default; each hit
  carries `live`.
- session.log_tail (`session log`) — the tail of one session's output log,
  ANSI-stripped by default; works for live sessions too.
- session.history_list (`session history`) — the archived-session listing.

Static GET paths with the id as a query field (the house convention — the
registry has no path params). Each result carries the FR-E9 `engine` field:
no engine, or an unreachable one, sets it and returns an empty view rather than
an error that reads as "no history". Engine client gains history_sessions /
search_history / history_log plus their dataclasses; the "six things" narrative
becomes nine.

Tests: parity SCRIPT + stand-in engine history routes (CLI/REST/MCP agree);
unit tests for mapping, query-param forwarding, missing-log, and the
no-engine / dead-engine degrade paths. Docs: AGENT_GUIDE history-search section.

Refs #491

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7eWQbHi8HyLyhaRA2piEn
… hits (#491)

History search was archive-only, so a running session's output could not be
found even though its rows already appear in the list (#477). Pass
`include_live=true` to the search endpoint, add the `live` flag to
`HistorySearchResult`, badge a live hit "Live" in the results, and retire the
"not yet in the search index" caveat — it now says running sessions are
searched too. Pin the result date hard-right so the badge groups with the name.

Tests: a live hit badges and the search carries include_live=true; the caveat
assertion updated.

Refs #491

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7eWQbHi8HyLyhaRA2piEn
@thedancingdeveloper
thedancingdeveloper merged commit e9a0f9c into dev Sep 1, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant