Files
obelisk/SKILL.md
T
tommy0103 dff88bbb37 refactor(skill): extract retrieval-semantics.md, compress pitfalls.md Move query design principles (scope/plan/structure/evidence) and field
semantics (context types, ordering, project scopes) out of pitfalls.md
  into a new retrieval-semantics.md. Pitfalls.md becomes a compact debug
  checklist: missing columns, FTS errors, over-large output, empty results.
  SKILL.md query routing updated to point at the three reference tiers.
2026-06-07 20:31:31 +08:00

7.6 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.
Read
Bash(node:*)
Write

obelisk

Search and query Claude Code session history stored in ~/.claude/. Obelisk indexes sessions, messages, tool calls, tool results, summaries, subagents, workflows, workflow agents, parent chains, and raw JSONL lines into SQLite + FTS5.

Obelisk is a CodeAct memory layer: write a small JS query, run it locally, read the JSON, then answer. Do not turn history into a flat document or browse entire sessions by default.

Quick Start

The skill directory is provided as $SKILL_DIR at invocation time.

Fast keyword search:

node $SKILL_DIR/scripts/runtime.mjs --search "keyword"

Custom query:

  1. Write a bounded JS query to a temp file, for example /tmp/q.mjs.

  2. Run:

    node $SKILL_DIR/scripts/runtime.mjs --query /tmp/q.mjs
    
  3. Parse JSON stdout and answer with concise evidence.

The query file runs inside (async () => { ... })(). Use return to emit JSON.

Query Routing

Before writing a query, classify the task. Progressive disclosure is useful, but skipping the relevant reference usually costs extra query rounds.

  • Read references/schema.md before raw sql() unless the needed table/column relationship is already explicit here. Do this before running the SQL, not after a missing-column error.
  • Read references/retrieval-semantics.md before multi-step retrieval, scoped project/file/session searches, or synthesis/conclusion/history questions. It defines the query design frame.
  • Read references/query-patterns.md when you need copyable query scripts: one-shot synthesis, learned detail passes, workflow trees, failure groups, file history, summaries, subagents, raw windows, or empty results.
  • Read references/pitfalls.md after an error or when helper fields, FTS syntax, aliases, or row shapes are unclear.

If a helper row shape is unclear, first run a tiny scoped query and return Object.keys(row) or a compact sample. Do not invent field names.

Core API

search(text, opts?)

Full-text search across main messages, subagent messages, and workflow-agent messages.

Returns:

[{ message: { uuid, text, role, timestamp, model, cwd },
   session: { id, title, project, started_at },
   rank,
   context }]

context here means temporal neighbors: nearby messages in the same session by timestamp. It is not the parent chain. Use context(uuid) or trace(uuid) for causal/parent-chain context.

Opts: { limit, sessionId, project, after, before, cwd }.

project is a SQL LIKE filter over sessions.project, not an exact project identity. Results are already ordered by FTS5 rank; lower rank sorts earlier. Prefer returned order over manually interpreting numeric rank unless you are deliberately using FTS5 semantics.

context(uuid)

Returns the full story around one indexed message:

{ message, parentChain, session, subagent, workflow }

Use this after search() finds a promising message. It is the usual way to expand vertically from one evidence point without dumping the whole session.

sql(query, ...params)

Raw SQL with ? placeholders. Returns array rows.

Before writing non-trivial SQL, read references/schema.md. Common safe joins:

  • tool_calls does not have timestamps. Join messages m ON m.uuid = tc.message_uuid.
  • tool_results does not have timestamps. Join messages m ON m.uuid = tr.message_uuid.
  • For project/session filters, join sessions s ON s.id = <table>.session_id.
  • Prefer SQL-side GROUP BY, COUNT, MAX, ORDER BY, and LIMIT over hand-counting in the final answer.

Tables: sessions, messages, tool_calls, tool_results, summaries, subagents, workflows, workflow_agents, messages_fts.

Structured Helpers

These helpers are convenience accessors over the same SQLite structure. They do not replace sql(); use sql() when you need an exact aggregation or a join the helper does not expose.

All list helpers accept a bounded limit. Many also accept: { project, after, before, sessionId, sessions, branch }. Check the schema or a tiny sample before relying on less common filters.

  • sessions(opts?) -- session rows, newest first. project is a SQL LIKE pattern.
  • recent(n?) -- shorthand for recent sessions.
  • summaries(opts?) -- summary rows, newest first: { id, session_id, timestamp, source, content, session_title, project }.
  • subagents(opts?) -- subagent metadata plus messageCount.
  • workflows(opts?) -- workflow runs, newest first.
  • workflowTree(runId) -- workflow row plus parsed result and agents; may include bulky script and result_json, so project compact fields.
  • fileHistory(filePath, opts?) -- Read/Edit/Write tool calls for a file, oldest first; includes many Read rows.
  • failures(opts?) -- failed tool results with tool/session context, newest first.
  • trace(uuid) -- parent chain from root to message.
  • thread(sessionId) -- full session messages; last resort only.
  • raw(uuid, opts?) -- windowed access to the original JSONL line.

Retrieval Contract

Keep queries scoped, bounded, and structural.

  • Scope First: classify the locator as scope, artifact, or semantic. Use the narrowest structural locator before FTS; empty scoped results are valid unless the user asks to broaden.
  • Plan Before Probe: for conclusion, broad history, failure investigation, or file evolution, write a bounded retrieval script instead of spending turns on intermediate results.
  • Structure Before Text: compute counts, joins, grouping, dedupe, and projection in SQL or JS; keep runtime JSON compact, ideally under 10k-12k chars for synthesis tasks.
  • Evidence Before Conclusion: return compact evidence with stable IDs (session_id, uuid, tool_call_id, run_id, agent_id) and short snippets, then synthesize in the final answer.

If field, context, ordering, FTS, or helper semantics affect the query, read references/retrieval-semantics.md before coding. If a query errors, read references/pitfalls.md before retrying.

Minimal Patterns

Search, then expand one promising hit:

const hits = search('auth fix', { limit: 5 });
if (!hits.length) return [];
return hits.slice(0, 3).map(h => ({
  session_id: h.session.id,
  session_title: h.session.title,
  uuid: h.message.uuid,
  snippet: h.message.text?.slice(0, 240),
}));

Check helper fields before assuming names:

const rows = summaries({ project: '%quiet-zero%', limit: 1 });
return rows.length ? Object.keys(rows[0]) : [];

Fetch message neighbors without a full thread:

const hit = search('runtime query', { limit: 1 })[0];
return sql(
  `SELECT uuid, role, timestamp, substr(text,1,240) AS snippet
   FROM messages
   WHERE session_id=? AND timestamp>=?
   ORDER BY timestamp LIMIT 6`,
  hit.session.id,
  hit.message.timestamp
);

See references/query-patterns.md for longer recipes.

Notes

  • First run builds the index. Later runs update incrementally.
  • DB location: ~/.claude/obelisk.sqlite.
  • Query scripts run in a sandboxed VM with no filesystem or network access from inside the script.
  • Indexed text and stored tool inputs/results are truncated to 10k chars. Use raw(uuid, { offset, limit }) for specific JSONL windows.