feat(obelisk): index session summaries and guide incremental retrieval
- Add generic summaries table (source-agnostic, ready for Codex)
- Index Claude Code away_summary events as session-level recaps
- Add summaries() query API
- Rewrite retrieval strategy in SKILL.md: never pull entire sessions,
navigate horizontally (by timestamp) or vertically (by parent chain)
This commit is contained in:
@@ -71,8 +71,24 @@ Tables: `sessions`, `messages`, `tool_calls`, `tool_results`, `subagents`, `work
|
||||
- `workflowTree(runId)` -- workflow + its agents + all their messages
|
||||
- `fileHistory(filePath)` -- every Edit/Write/Read on a file across sessions
|
||||
- `failures(sessionId?)` -- tool calls that returned errors, with surrounding context
|
||||
- `summaries(sessionId?)` -- session summaries (away recaps, compaction summaries when available)
|
||||
- `raw(uuid, opts?)` -- windowed access to the original JSONL line (bypasses index truncation)
|
||||
|
||||
### Retrieval strategy
|
||||
|
||||
**Never pull an entire session.** Navigate incrementally:
|
||||
|
||||
1. `summaries()` — read session summaries to judge relevance (cheapest)
|
||||
2. `search()` — find specific messages matching a query
|
||||
3. When you find a relevant message and want more context, expand from that point:
|
||||
- **Horizontally**: use `sql()` to fetch neighboring messages by timestamp
|
||||
```js
|
||||
sql('SELECT uuid,role,text FROM messages WHERE session_id=? AND timestamp>? ORDER BY timestamp LIMIT 5', sid, msg.timestamp)
|
||||
```
|
||||
- **Vertically**: use `trace(uuid)` to walk up the parent chain, or `context(uuid)` to see subagent/workflow relationships
|
||||
4. `raw(uuid, opts?)` — recover truncated content from a specific message
|
||||
5. `thread(sessionId)` — full session dump, **last resort only**
|
||||
|
||||
### raw(uuid, opts?)
|
||||
|
||||
Some indexed fields (tool call inputs, tool results) are truncated to 10k chars. `raw()` reads the original JSONL line to recover the full content.
|
||||
|
||||
@@ -36,6 +36,9 @@ CREATE TABLE IF NOT EXISTS workflow_agents (
|
||||
agent_type TEXT, description TEXT);
|
||||
CREATE TABLE IF NOT EXISTS index_state (
|
||||
jsonl_path TEXT PRIMARY KEY, mtime REAL, lines_processed INTEGER);
|
||||
CREATE TABLE IF NOT EXISTS summaries (
|
||||
id TEXT PRIMARY KEY, session_id TEXT, timestamp TEXT,
|
||||
source TEXT, content TEXT);
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
||||
uuid UNINDEXED, session_id UNINDEXED, text, content=messages, content_rowid=rowid);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
|
||||
@@ -46,6 +49,7 @@ CREATE INDEX IF NOT EXISTS idx_tc_file ON tool_calls(file_path);
|
||||
CREATE INDEX IF NOT EXISTS idx_sa_session ON subagents(session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_wf_session ON workflows(session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_wa_run ON workflow_agents(run_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_summaries_session ON summaries(session_id);
|
||||
`;
|
||||
|
||||
function openDb() {
|
||||
|
||||
@@ -62,6 +62,7 @@ function indexJsonl(db, fi) {
|
||||
msg: db.prepare('INSERT OR REPLACE INTO messages (uuid,session_id,type,parent_uuid,timestamp,role,text,model,is_sidechain,agent_id,input_tokens,output_tokens) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)'),
|
||||
tc: db.prepare('INSERT OR REPLACE INTO tool_calls (id,message_uuid,session_id,name,input_json,file_path) VALUES (?,?,?,?,?,?)'),
|
||||
tr: db.prepare('INSERT OR REPLACE INTO tool_results (tool_use_id,message_uuid,session_id,content,file_path) VALUES (?,?,?,?,?)'),
|
||||
sum: db.prepare('INSERT OR REPLACE INTO summaries (id,session_id,timestamp,source,content) VALUES (?,?,?,?,?)'),
|
||||
idx: db.prepare('INSERT OR REPLACE INTO index_state (jsonl_path,mtime,lines_processed) VALUES (?,?,?)'),
|
||||
};
|
||||
|
||||
@@ -85,6 +86,10 @@ function indexJsonl(db, fi) {
|
||||
const ts = obj.timestamp || null;
|
||||
|
||||
if (obj.type === 'ai-title' && obj.aiTitle) { sm.title = obj.aiTitle; return; }
|
||||
if (obj.type === 'system' && obj.subtype === 'away_summary' && obj.content) {
|
||||
ins.sum.run(obj.uuid || `${sid}-away-${ts}`, sid, ts, 'away_summary', obj.content);
|
||||
return;
|
||||
}
|
||||
if (obj.type !== 'user' && obj.type !== 'assistant') return;
|
||||
|
||||
if (ts && (!sm.started_at || ts < sm.started_at)) sm.started_at = ts;
|
||||
|
||||
+5
-1
@@ -104,6 +104,10 @@ function createQueryApi(db) {
|
||||
|
||||
const recent = (n = 10) => db.prepare('SELECT * FROM sessions ORDER BY ended_at DESC LIMIT ?').all(n);
|
||||
|
||||
const summaries = (sessionId) => sessionId
|
||||
? db.prepare('SELECT * FROM summaries WHERE session_id=? ORDER BY timestamp').all(sessionId)
|
||||
: db.prepare('SELECT su.*, s.title as session_title, s.project FROM summaries su LEFT JOIN sessions s ON s.id=su.session_id ORDER BY su.timestamp DESC').all();
|
||||
|
||||
const resolveJsonlPath = (messageUuid) => {
|
||||
const msg = db.prepare('SELECT session_id, agent_id FROM messages WHERE uuid=?').get(messageUuid);
|
||||
if (!msg) return null;
|
||||
@@ -149,7 +153,7 @@ function createQueryApi(db) {
|
||||
};
|
||||
};
|
||||
|
||||
return { sql: q, search, context, trace, thread, subagents, workflows, workflowTree, fileHistory, failures, recent, raw };
|
||||
return { sql: q, search, context, trace, thread, subagents, workflows, workflowTree, fileHistory, failures, recent, summaries, raw };
|
||||
}
|
||||
|
||||
export { createQueryApi };
|
||||
|
||||
Reference in New Issue
Block a user