v0.2: Session Store And Recall [FIRST SLICE]¶
Implementation Status¶
First slice shipped in 3708d7e:
- Added
SessionSQLiteStoreatbackend/agent/g_agent/session/sqlite_store.pywithsessions,messages,tool_calls,media_refs, external-content FTS5, WAL, schema idempotency, and write retry. - Kept JSONL readable and added dual-write through
SessionManager.save(). - Added SQLite cleanup for
/new/archive/delete so reused session keys do not retain stale searchable rows. - Added
session_searchand/historyrecall while preserving/searchas web search. - Added URL/path/command fallback search for punctuation-heavy terms that FTS5 tokenization can miss.
- Added tests in
test_session_sqlite_store.pyand updatedtest_session_new_command.py.
Remaining follow-up:
- Backfill/import historical JSONL sessions into SQLite explicitly.
- Add richer context windows around each hit instead of raw excerpts only.
- Add owner-facing migration/status commands if the JSONL-to-SQLite transition needs manual inspection.
Goal¶
Give G-Agent a real memory substrate before adding self-improvement.
User Outcome¶
The owner can ask what happened in old sessions, search old channel messages, and recover decisions, paths, commands, URLs, and unresolved work.
Scope¶
- Replace scattered/per-channel transcript assumptions with one SQLite session store.
- Store sessions, messages, source channel, chat id, user id, model, token/cost counters, tool calls, media refs, and titles.
- Enable WAL and write-lock retry for concurrent gateway/channel use.
- Add FTS5 search across messages.
- Add
session_searchtool. - Add
/search,/history, and/sessionscommand aliases. - Add tests for persistence, FTS search, concurrent writes, and channel/source filtering.
Module Targets¶
backend/agent/g_agent/session/sqlite_store.pybackend/agent/g_agent/session/manager.pybackend/agent/g_agent/agent/tools/session_search.pybackend/agent/g_agent/channels/backend/agent/g_agent/cli/commands.pybackend/agent/tests/test_session_sqlite_store.pybackend/agent/tests/test_session_new_command.py
Data Model¶
sessions: id, title, character id, channel, chat id, user id, parent session id, created at, updated at, metadata.messages: id, session id, role, content, content type, provider, model, token counters, timestamps, metadata.message_fts: full-text index for message content.tool_calls: tool name, arguments, result summary, status, timestamps.media_refs: kind, path, mime type, hash, metadata.
Acceptance Criteria¶
- CLI and at least one chat channel write inbound and outbound messages into SQLite.
- Search returns grouped results by session with short context windows.
- Search preserves commands, paths, URLs, decisions, and unresolved items.
- Media is referenced by path/hash, not stored as raw blobs.
- Concurrent writes do not corrupt the database.
Tests¶
- Schema creation and migration are idempotent.
- WAL is enabled.
- Message writes and reads are stable.
- FTS search finds exact terms, paths, URLs, and command text.
- Search filters by channel and session.
- Lock retry works under concurrent writes.
References¶
hermes-agent-ref/hermes_state.pyhermes-agent-ref/tools/session_search_tool.py
Agent Handoff¶
Current G-Agent State¶
backend/agent/g_agent/session/manager.pykeeps the public JSONL-compatible session API and dual-writes saved sessions intobackend/agent/g_agent/session/sqlite_store.py.SessionManager.get_or_create(session_key)returns aSessionwith in-memorymessagesand a SQLite session id when available.AgentLoop._process_message()currently calls:session = self.sessions.get_or_create(msg.session_key)session.get_history()session.add_message("user", clean_user_content)session.add_message("assistant", log_content)self.sessions.save(session)/newand/compactinchannels/slash_commands.pyinstantiateSessionManagerdirectly and expect archive/clear behavior./newnow also removes matching SQLite rows before a reused key writes new history.test_session_new_command.pyincludes SQLite consistency coverage; preserve that behavior when changing archive/reset flows.
Reference Details To Inspect¶
hermes_state.pyCREATE TABLE IF NOT EXISTS sessionsCREATE TABLE IF NOT EXISTS messages- FTS5 table/triggers
append_messagelist_sessions_rich- write retry handling
session_search_tool.py- recent sessions mode when query is empty
- current session exclusion
- parent chain resolution
- grouped search result summaries
- raw preview fallback
Implementation Strategy¶
Do not delete JSONL sessions immediately. Add SQLite behind a new store and keep
the old SessionManager public API stable until callers migrate.
Recommended shape:
session/sqlite_store.py: low-level SQLite schema and queries.session/search.py: FTS query and result formatting.agent/tools/session_search.py: tool wrapper around search.session/manager.py: keep existing API, optionally delegate persistence to SQLite or dual-write during migration.
Implementation Slices¶
- Add SQLite store class.
SessionSQLiteStore(path: Path)init_schema()append_message(...)get_history(session_key, max_messages)list_sessions(...)search_messages(query, limit, channel=None)- Add FTS5.
- Use external content table and triggers, like Hermes.
- If FTS5 is unavailable, fail with a clear error in tests.
- Add dual-write from
SessionManager.save()orAgentLoop. - Prefer dual-write in
SessionManager.save()first to minimize caller changes. - Keep JSONL files during v0.2 for rollback.
- Add session search tool.
- Name:
session_search. - Query empty means recent sessions.
- Query non-empty searches FTS.
- Return session id/key, channel, chat id, timestamps, excerpts.
- Summarization can be deferred; raw grouped excerpts are enough for first slice.
- Add command routing.
- Existing
/searchis web search. Do not silently change that behavior. - Add
/history <query>for session search first. - Later decide whether
/searchbecomes local recall and/webbecomes web search. - Add tests and migration docs.
Data Model Details¶
Required minimum:
sessions(id TEXT PRIMARY KEY, key TEXT UNIQUE, channel TEXT, chat_id TEXT, user_id TEXT, title TEXT, parent_session_id TEXT, created_at TEXT, updated_at TEXT, message_count INTEGER, metadata_json TEXT)messages(id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, role TEXT, content TEXT, content_type TEXT, provider TEXT, model TEXT, created_at TEXT, input_tokens INTEGER, output_tokens INTEGER, metadata_json TEXT)tool_calls(id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, message_id INTEGER, tool_name TEXT, arguments_json TEXT, result_summary TEXT, status TEXT, created_at TEXT)media_refs(id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, message_id INTEGER, kind TEXT, path TEXT, mime_type TEXT, sha256 TEXT, metadata_json TEXT)
Tests To Add¶
test_session_sqlite_store.py- schema creation
- WAL enabled
- append/read history
- list sessions sorted by updated time
- write retry on locked DB if feasible
test_session_search.py- FTS finds path, URL, command, Indonesian text
- current session can be excluded
- channel filter works
- empty query returns recent sessions
- Update
test_session_new_command.py /newarchives or resets without losing SQLite consistency.
Migration Guardrails¶
- Existing JSONL session files must remain readable.
- First implementation may dual-write; do not require migration before runtime starts.
- Search should include only SQLite-backed sessions until migration is explicit.
- Do not store raw media blobs in SQLite.
First PR Boundary¶
Build the smallest vertical slice:
- SQLite schema + append/read.
- Dual-write assistant/user messages from existing session save path.
/history <query>against SQLite.- Tests for schema, append, search, and command output.