- 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)
6.3 KiB
name, description, allowed-tools
| name | description | allowed-tools | |||
|---|---|---|---|---|---|
| obelisk | Search and query past Claude Code session history. Reactive: when the user asks "how did I fix X", "what did we do last time", "find the session where", "上次怎么修的", "之前的session", "历史记录". Proactive: when the user references past work you lack context for, when you're about to modify a file with complex edit history, when the user says "继续之前的" or "continue where we left off", or when understanding prior decisions would improve your current response. |
|
obelisk
Searches and queries your Claude Code session history stored in ~/.claude/.
A SQLite index with FTS5 full-text search covers all sessions, subagent conversations, and workflow agent runs.
You write JS query snippets that run in a sandboxed VM against the indexed data, then parse the JSON output.
Quick Start
The base directory for this skill is provided as $SKILL_DIR at invocation time (shown as "Base directory for this skill: ...").
Fast keyword search (no script needed):
node $SKILL_DIR/scripts/runtime.mjs --search "keyword"
Custom query (write a JS snippet, run it):
- Write a query to a temp file (e.g.
/tmp/q.mjs) - Run:
node $SKILL_DIR/scripts/runtime.mjs --query /tmp/q.mjs - Parse the JSON stdout and answer the user
The query file body is executed inside (async () => { ... })() with the API below available as globals. The last expression is returned as JSON. Use return to emit results.
API
search(text, opts?)
Full-text search across all messages (user, assistant, subagent, workflow agent).
Returns: [{ message: {uuid, text, role, timestamp, model}, session: {id, title, project, started_at}, context: [...surrounding messages] }]
opts: { limit, sessionId, project, after, before }
context(uuid)
Full story around a message: the message itself, parent chain, session info, subagent/workflow metadata.
Returns: { message, parentChain, session, subagent, workflow }
recent(n?)
Latest n sessions (default 10). Returns session rows with title, project, started_at, ended_at.
sql(query, ...params)
Raw SQL. Use ? placeholders. Returns array of row objects.
Before writing your first SQL query, read references/schema.md for the full table schema, column names, and relationships. Don't guess column names — the schema is your source of truth.
Tables: sessions, messages, tool_calls, tool_results, subagents, workflows, workflow_agents, messages_fts
Other APIs
trace(uuid)-- full parent chain from root to messagethread(sessionId)-- all messages in a session, ordered by timesubagents(sessionId)-- subagent metadata + message countsworkflows(sessionId?)-- workflow runs (all if no sessionId)workflowTree(runId)-- workflow + its agents + all their messagesfileHistory(filePath)-- every Edit/Write/Read on a file across sessionsfailures(sessionId?)-- tool calls that returned errors, with surrounding contextsummaries(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:
summaries()— read session summaries to judge relevance (cheapest)search()— find specific messages matching a query- When you find a relevant message and want more context, expand from that point:
- Horizontally: use
sql()to fetch neighboring messages by timestampsql('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, orcontext(uuid)to see subagent/workflow relationships
- Horizontally: use
raw(uuid, opts?)— recover truncated content from a specific messagethread(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.
Returns: { text, totalLength, offset, limit, hasMore }
opts: { offset: 0, limit: 10000 } — character window into the raw JSONL line.
// First window
const r = raw(messageUuid)
// r.text = first 10k chars of the original JSONL line
// r.totalLength = full line length
// r.hasMore = true if more content remains
// Scroll forward
const r2 = raw(messageUuid, { offset: 10000, limit: 10000 })
Examples
"上次怎么修 auth 的"
const hits = search('auth fix')
return hits.slice(0, 5).map(h => ({
session: h.session.title,
date: h.session.started_at,
message: h.message.text?.slice(0, 200)
}))
"最近在做什么"
return recent(10).map(s => ({ title: s.title, project: s.project, date: s.started_at }))
"哪些文件被反复修改"
return sql(`
SELECT file_path, COUNT(*) as n FROM tool_calls
WHERE name IN ('Edit','Write') AND file_path IS NOT NULL
GROUP BY file_path HAVING n > 3 ORDER BY n DESC LIMIT 20
`)
"那个 review workflow 的结果是什么"
const wfs = workflows()
const review = wfs.find(w =>
w.run_id.includes('review') ||
JSON.parse(w.result_json || '{}').synthesis
)
return review ? JSON.parse(review.result_json) : 'No review workflow found'
"上次跑 experiment 用了多少 token"
const hits = search('experiment')
if (!hits.length) return 'No experiment sessions found'
const sid = hits[0].session.id
return sql('SELECT SUM(input_tokens) as input, SUM(output_tokens) as output FROM messages WHERE session_id = ?', sid)
"追踪一下那个决策是怎么做的"
const hits = search('the decision query here')
if (!hits.length) return 'Nothing found'
return context(hits[0].message.uuid)
Notes
- First run builds the index (~5s for ~100 sessions). Subsequent runs are incremental.
- DB location:
~/.claude/obelisk.sqlite - Subagent and workflow agent conversations are fully indexed and searchable.
- Query scripts run in a sandboxed VM context -- no file system or network access from inside scripts.
- Text is truncated to 10k chars per message during indexing.
- FTS5 search supports standard SQLite FTS syntax:
"exact phrase",term1 AND term2,term1 OR term2,term1 NOT term2.