关于 obelisk
obelisk 是面向 Claude Code 和 Codex 用户的历史检索 Skill,可从过往编码会话中查找证据。它将对话及执行记录索引到 SQLite 和 FTS5,并支持有界 JavaScript 查询。
主要功能
- 01索引 Claude Code 和 Codex 会话
- 02搜索消息与工具活动
- 03使用 SQLite 和 FTS5 存储
- 04通过 JavaScript 查询历史
- 05提供会话与因果上下文
适用场景
- 01按关键词查找过往讨论
- 02检查工具调用及结果
- 03追踪消息周边上下文
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tommy0103/ obelisk --skill obelisk安装后在 Claude Code 中输入/obelisk确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CONTEXT.mdPRODUCT.mdREADME.md.gitignoreLICENSE.github/workflows/publish-skill.ymlapp/electron.vite.config.tspackage.jsonpackage-lock.jsonapp/src/main/index.tsrecap-capture-query.tsindexer-worker-client.tsindexer-worker.tsindexer-service.tsindexer.tsapp/src/preload/index.tsapp/src/renderer/index.htmlapp/src/renderer/src/App.vueactivity-ledger.mjsflap-number.mjsmain.jsapp/src/renderer/src/assets/recap-cards.htmlapp/src/renderer/src/components/ActivityLedger.vueActivityLedgerRow.vueFlapNumber.vueapp/src/renderer/src/components/recap/CoverCard.vuearchetypes.jsWorkflowCard.vuecard-base.cssseals.js详细介绍
obelisk
搜索和查询存储在 ~/.claude/ 和 ~/.codex/ 中的 Claude Code 和 Codex 会话历史。
Obelisk 会将会话、消息、工具调用、工具结果、摘要、子 Agent、工作流、工作流 Agent、父级链和原始 JSONL 行索引到 SQLite + FTS5。
Obelisk 有两个记录来源。默认情况下,将二者都视为普通会话:
Claude 记录使用 source='claude';Codex 记录使用 source='codex',且 ID
以 codex: 为前缀。仅当来源信息很重要,或用户要求限定到某个提供方时,才使用 source。Codex 子 Agent 的子线程会映射到同一个 subagents 表;Codex 工作流记录可能不存在,因为 Codex 不会生成 Claude 风格的工作流元数据。
Obelisk 是一个 CodeAct 记忆层:编写一段简短的 JS 查询,在本地运行,读取 JSON,然后作答。不要将历史记录整理成一份扁平文档,也不要默认浏览整个会话。
快速开始
调用时,技能目录会作为 $SKILL_DIR 提供。
快速关键词搜索:
node $SKILL_DIR/scripts/runtime.js --search "keyword"
自定义查询:
-
将有范围限制的 JS 查询写入临时文件,例如
/tmp/q.mjs。 -
运行:
node $SKILL_DIR/scripts/runtime.js --query /tmp/q.mjs -
解析 JSON 标准输出,并根据简洁的证据作答。
查询文件会在 (async () => { ... })() 中运行。使用 return 输出 JSON。
查询脚本为只读脚本:remember() 和 forget() 不可用,且
sql() 只接受只读的 SELECT/WITH 查询。
默认首轮查询
首先使用辅助函数,而不是原始 SQL。对于任务中的首次 Obelisk 查询,通常调用 overview({ limit: 6 }),除非用户已提供确切的 session_id、消息 uuid 或绝对文件路径。
对于语义理解或综合类任务,在判断是否需要进一步查询前,先结合概览、记忆检索和原始会话证据:
const map = overview({ limit: 6 });
const project = map.current.project?.project;
const topic = 'English topic terms translated from the user request';
return {
orientation: map.current_project,
prior_memories: memories({ project, query: topic, limit: 5 }),
session_evidence: search(topic.replace(/[-_]/g, ' '), { project, limit: 8 }),
};
仅在需要精确联接、聚合,或提出辅助函数无法清晰回答的架构问题时,才将 sql() 作为升级查询手段。不要将原始 SQL 作为宽泛检索的通用后备方案。
意图路由
Obelisk 在 /obelisk 之后支持一个简短的意图前缀层。此层用于指定输出意图,而非检索架构。
| 意图 | 描述 | 参考文档 |
|---|---|---|
recap [target] | 生成周度/月度回顾卡片内容,用于交接给应用或分享式输出。 | references/recap/overview.md |
路由规则:
- 如果第一个词是
recap,请在首次查询前阅读references/recap/overview.md。recap之后的所有内容都是回顾目标。 常见的应用生成提示包括/obelisk recap this week、/obelisk recap last week、/obelisk recap this month和/obelisk recap last month;应根据当前日期和时区,将这些内容理解为自然语言表示的时间段目标。 recap不会创建单独的检索层。仍然只在需要时使用overview()、memories()、辅助函数和sql()。- 按照概览中逐张卡片的顺序操作。每张卡片都有自己的检索模式和撰写文件;先检索该卡片的证据,阅读该卡片的撰写文件,更新 JSON,然后再处理下一张卡片。不要在撰写当前卡片之前预先加载所有回顾参考文档。
- 如果第一个词不是
recap,不要加载references/recap/overview.md。继续执行下方的查询路由。不要仅凭宽泛的周度/月度摘要、图表、排名、可分享卡片或类似播放列表的比喻,就推断用户想要回顾内容。
参考资料索引
根据任务选择参考资料,而不是习惯性地查阅:
| 参考资料 | 适用场景 |
|---|---|
references/query-patterns.md | 综合归纳、进度总结、设计历史、每周/每月回顾、经批准的记忆写入/归档/更新脚本,或询问用户做过/学到/决定/尝试/放弃了什么。 |
references/retrieval-semantics.md | 多步骤检索、限定项目/文件/会话范围的搜索,或查询设计受范围/内容/语义边界影响时。 |
references/schema.md | 编写复杂的 sql() 之前,快速查阅原始 SQL 字段和联接信息。 |
references/api-reference.md | 不清楚辅助函数签名、选项名称、返回字段,或 remember()/forget() 的确切参数详情时。 |
references/pitfalls.md | 错误恢复、FTS 语法、别名、排序、行结构意外情况,或紧凑/原始结果权衡。 |
references/recap/overview.md | 仅用于明确的 /obelisk recap ... 请求。 |
查询路由
编写查询前,先对任务分类。渐进式披露很有用,但跳过相关参考资料通常会增加查询轮次。
- 对于综合归纳、进度总结、设计历史、常规每周/每月回顾,或询问用户做过、学到、决定、尝试或放弃了什么的问题,在首次查询前先阅读
references/query-patterns.md。从首轮或单次综合模式开始,然后根据需要进行分面细节查询。 - 对于多步骤检索、限定项目/文件/会话范围的搜索,或综合/结论/历史类问题,在查询前先阅读
references/retrieval-semantics.md。该文件定义了查询设计框架。 - 除非此处已明确所需的表/列关系,否则在使用原始
sql()前先阅读references/schema.md。该文件刻意保持简短,并聚焦 SQL。应在运行 SQL 前阅读,而不是等到出现缺少列的错误后再读。除非辅助函数无法表达所需的聚合或联接,否则不要在综合归纳任务中先使用原始 SQL。 - 当辅助函数的选项名称、返回字段、标量简写行为,或
remember()/forget()的详情不明确时,阅读references/api-reference.md。 - 出现错误后,或不清楚 FTS 语法、别名、排序、行结构,或紧凑/原始结果权衡时,阅读
references/pitfalls.md。
如果不清楚辅助函数返回的行结构,先运行一个范围很小的查询,并返回
Object.keys(row) 或紧凑示例。不要臆造字段名。
对于经批准的记忆变更,先遵循下方的 Memory Layer 部分。
使用 references/query-patterns.md 获取可复制的 --attune 脚本
(Attune Approved Memory、Forget Approved Memory、Update Approved Memory),
仅在需要确认确切参数语义时查阅 references/api-reference.md。
核心 API
search(text, opts?)
跨主消息、子 Agent 消息和工作流 Agent 消息进行全文搜索。
返回:
[{ message: { uuid, text, content_type, is_meta, role, timestamp, model, cwd, source },
session: { id, title, project, started_at, source },
rank,
context }]
这里的 context 指时间上的邻近消息:同一会话中时间戳相近的消息,而不是父级链。请使用 context(uuid) 或 trace(uuid) 获取因果/父级链上下文。
使用 message.content_type 保持证据边界完整:text 是用户/助手可见的语言,thinking 是跟踪/调试材料,tool_use 表示工具调用消息,其详细信息位于 tool_calls 中,tool_result 表示工具结果消息,其详细信息位于 tool_results 中。unknown 是保守的回退值。不要将 thinking 视为助手面向用户的结论。真实用户输入的条件是 type='user' 且 content_type='text';不要臆造单独的 user_message 内容类型。
使用 message.is_meta 将对话记录的控制平面材料与对话证据区分开来。is_meta=1 表示注入的注意事项、命令封装或其他以用户角色内容进入对话记录、但默认不应视为用户请求的消息。除非传入 includeMeta: true,否则 search() 和 thread() 会省略元消息;context() 和 trace() 会保留原始链,并在行中公开 is_meta。
选项:{ limit, sessionId, project, after, before, cwd, source, includeMeta }。
project 是针对 sessions.project 的 SQL LIKE 筛选条件,并非精确的项目标识。结果已按 FTS5 排名排序;排名值越低,排序越靠前。除非你有意使用 FTS5 语义,否则应优先采用返回顺序,而不是手动解读数值排名。
source 可以是 'claude'、'codex',也可以省略。省略时会搜索所有已索引来源。
context(uuid)
返回一条已索引消息的完整上下文:
{ message, parentChain, session, subagent, workflow }
search() 找到有价值的消息后使用此函数。这是从单个证据点纵向扩展上下文的常用方式,无需倾倒整个会话。
sql(query, ...params)
只读 SQL SELECT/WITH,使用 ? 占位符。返回数组行。在辅助函数优先的接口无法满足需求时,可将 SQL 作为精确结构化联接和聚合的备用方案;它并非默认的检索入口。
编写非简单 SQL 前,请阅读 references/schema.md。这是原始 SQL 字段/联接速查指南。可执行的 DDL 位于 scripts/schema.sql;仅在检查与源代码的一致性时使用该 SQL 文件。常见的安全联接:
tool_calls没有时间戳。请联接messages m ON m.uuid = tc.message_uuid。tool_results没有时间戳。请联接messages m ON m.uuid = tr.message_uuid。- 筛选项目/会话时,请联接
sessions s ON s.id = <table>.session_id。 - 最终答案中,优先使用 SQL 端的
GROUP BY、COUNT、MAX、ORDER BY和LIMIT,而不是手动计数。
表:sessions、messages、tool_calls、tool_results、summaries、memories、subagents、workflows、workflow_agents、messages_fts。
结构化辅助函数
这些辅助函数是对相同 SQLite 结构的便捷访问方式。它们不能替代 sql(),但适合作为默认的初步查询入口。需要精确聚合,或需要辅助函数未提供的联接时,请使用 sql()。
所有列表辅助函数都接受有上限的 limit。许多函数还接受:
{ project, after, before, sessionId, sessions, branch, source }。依赖不常用的筛选条件或返回字段前,请查看 references/api-reference.md 或运行一个简单示例。
overview(opts?)-- 紧凑的概览图。如果能确定,则返回当前 cwd/项目、全局项目/来源计数,以及当前项目的近期会话和记忆记录。它是一张概览图,并非证据。sessions(opts?)-- 会话行,按时间从新到旧排列。project是 SQLLIKE模式。recent(n?)-- 近期会话的简写。summaries(opts?)-- 摘要行,按时间从新到旧排列:{ id, session_id, timestamp, source, content, session_title, project };此处的source是摘要类型,而非转录记录的提供方。subagents(opts?)-- 子 Agent 元数据及messageCount。workflows(opts?)-- 工作流运行记录,按时间从新到旧排列。workflowTree(runId)-- 工作流行及解析后的result和agents;可能包含体积较大的script和result_json,因此应挑选精简字段。fileHistory(filePath, opts?)-- 针对某个文件的 Read/Edit/Write 工具调用,按时间从旧到新排列;包含许多Read行。failures(opts?)-- 失败的工具结果及工具/会话上下文,按时间从新到旧排列。trace(uuid)-- 从根消息到指定消息的父级链。thread(sessionId, opts?)-- 按时间戳排列的会话消息,默认省略元消息。调查注入的上下文或命令封套时,传入{ includeMeta: true }。raw(uuid, opts?)-- 对原始 JSONL 行进行窗口化访问。memories(opts?)-- 检索记忆层。opts:{ query, project, sessionId, sessions, after, before, branch, limit }。不带query时,按时间从新到旧返回有效记忆记录。带query时,会通过安全的 FTS5 分词搜索summary/path,并返回rank;rank越小,排序越靠前。记录可能包含可为空的 JSONanchors,用于指向文件等明确的回忆入口。请读取path指向的文件以查看完整内容。
检索约定
查询范围要明确、有限且基于结构。
- 先确定范围:将定位器归类为范围、产物或语义。使用全文搜索(FTS)前,先采用最窄的结构化定位器;除非用户要求扩大范围,否则范围内无结果也是有效结果。
- 先了解概况:处理新任务时,通常先调用
overview({ limit: 6 }),再进行深入检索;如果用户给出了精确的会话/消息/文件定位器,则无需如此。它是一份导航地图;请使用memories()、search()、辅助函数,或仅在必要时使用sql()来确认事实。 - 优先使用辅助函数:首轮检索优先使用
overview()、memories()、search()、sessions()、summaries()、fileHistory()等辅助函数。只有在辅助函数无法表达所需的联接、分组或精确的架构级检查时,才升级为直接使用sql()。 - 探查前先规划:对于结论、广泛的历史记录、故障调查或文件演变问题,应编写有界检索脚本,而不是花多个回合处理中间结果。
- 先处理结构,再处理文本:在 SQL 或 JS 中计算计数、联接、分组、去重和投影;运行时 JSON 要保持精简,对于综合类任务,最好控制在 10k-12k 个字符以内。
- 先获取证据,再得出结论:返回带稳定 ID(
session_id、uuid、tool_call_id、run_id、agent_id)和简短摘录的精简证据,再在最终回答中进行综合。 - 默认排除元数据:
is_meta=1的行是注入/控制平面转录材料。辅助函数默认会隐藏这些行;使用原始 SQL 查询普通对话证据时,除非调查目标就是元数据行,否则应包含COALESCE(m.is_meta,0)=0。 - 持久化可复用结论:回答后,如果检索得出了未来会话可能复用的持久结论,且
memories()尚未涵盖该结论,请明确提出将其写入记忆。简短说明即可。在用户批准前,不要写入 markdown 文件,也不要运行--attune。
如果字段、上下文、排序、FTS 或辅助函数语义会影响查询,请先阅读
references/retrieval-semantics.md 再编写代码。如果查询出错,请先阅读
references/pitfalls.md 再重试。
记忆层
Obelisk 除了原始会话数据外,还有持久记忆层。每次检索都会查询这两层:使用 memories() 获取既有结论,使用 search() 和辅助函数获取原始会话证据。将记忆作为先前的笔记,而非最终依据。如果记忆记录影响了回答,请自然地说明这是先前记录的内容;如果正确性取决于记忆记录,则应将其与原始会话证据进行对照。原始会话数据是证据层,但单条命中并不代表完整事实;请查询并简洁引用证据。
记忆层以英语建立索引。即使用户用其他语言提问,也要在 memories({ query }) 中使用英语词语。无论当前对话使用哪种语言,remember().summary 都必须用英语书写。运行时会拒绝记忆查询和摘要中明显的 CJK 文本,以此作为防护措施。
回忆: 使用 memories({ query: 'English topic terms', project: '...' })
查找与当前任务相关的既有结论。调用 memories() 前,将非英语用户请求转换成简洁的英语查询词。记忆回忆会对 summary 和 path 使用安全的 FTS5 分词,因此连字符/标点会被分词处理,不会导致原始 MATCH 语法错误。与其他列表辅助函数一样,传入字符串会被视为 sessionId,传入数字会被视为 limit。读取 path 所指文件以获取完整内容。memories() 仅返回活跃记忆。已归档记忆属于管理/审计数据,不用于回忆。
适合作为记忆的内容包括设计决策、项目惯例、已放弃的替代方案、反复出现的失败原因、工作流模式,以及从多个原始证据点综合得出的结论。不要为一次性查询、不确定的发现或现有记忆已涵盖的结论提出写入记忆。 **记忆变更审批:**判断是否在当前回答中使用某条记忆,是 Agent 的决定,无需审批。持久化记忆变更则需要审批。 如果用户明确表示某条记忆有误、已过时、应当忘记,或现在应改为其他内容,该请求即是归档或更新完全匹配的记忆的审批。除非可能匹配多条记忆,否则不要再次请求确认。如果你自己发现可能存在冲突,请简要说明,并在更改记忆状态前征求同意。
**写入记忆:**检索得出值得持久保存的结论后,提议写入记忆文件。用户必须审批。流程:
- 使用
Write工具编写 Markdown 文件(需用户审批)。 - 在范围受限的记忆注册脚本中通过
remember()注册:
return remember({
path: '.obelisk/memories/design-decision-x.md',
session_id: 'current-session-id',
message_start: 'uuid-of-first-relevant-msg',
message_end: 'uuid-of-last-relevant-msg',
anchors: [{ kind: 'file', path: 'src/path/to/file.ts' }],
summary: 'Detailed summary: what was decided, why, what alternatives were considered, and what constraints drove the choice.'
})
使用以下命令运行注册脚本:
node $SKILL_DIR/scripts/runtime.js --attune /tmp/register-memory.mjs
--attune 仅提供记忆变更辅助函数:remember() 和 forget()。
它不提供 search()、sql()、memories() 或其他检索辅助函数。如果需要来源 ID 或记忆 ID,先使用普通的 --query 脚本查找。
remember() 会验证 path 已存在且指向一个文件。如果提供了 session_id,相对路径会根据来源会话的 project_path 解析,然后存储为规范化的绝对路径。优先使用 .obelisk/memories/... 等项目相对路径,并提供 session_id。
可选的 anchors 必须是对象数组,并以可空 JSON 文本形式存储。仅将其用于明确的回忆入口,例如与该记忆关联的文件。
summary 必须使用英文,并提供足够详细的信息,使 memories() 的结果本身就能判断相关性,而无需读取文件。应包含决策、推理和关键限制条件,而不只是标题。
message_start/message_end 范围标记了对话中得出该结论的位置。之后可用它追溯到原始证据。
**忘记记忆:**如果用户表示某条记忆已过时、有误或应当忘记,先进行常规检索,以确定准确的记忆 ID。如果只有一个明确的候选项,用户的请求即是归档该记忆的审批。如果可能匹配多条记忆,请询问要忘记哪一条。然后运行 --attune 脚本:
return forget({
id: 'mem-id-to-delete',
reason: 'Outdated by newer project guidance.',
});
forget() 会通过设置 deleted_at 和 deleted_reason 来归档记忆记录。它会将该记录从活动检索中移除,但不会删除 Markdown 文件。记忆记录在索引重建后仍会保留,且不会被自动更改。
**更新记忆:**更新记忆是一项经用户审批的操作:先使用 forget() 归档旧记忆,然后编写并使用 remember() 注册替代记忆 Markdown 文件。如果用户明确更正了记忆,该更正即是对「归档旧记忆并写入新记忆」这一完整流程的审批。如果是你自己发现不匹配,则应先征求同意。
最简模式
先搜索,再展开一个有潜力的命中结果:
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),
}));
先检查辅助字段,再假定字段名称:
const rows = summaries({ project: '%quiet-zero%', limit: 1 });
return rows.length ? Object.keys(rows[0]) : [];
无需获取完整线程即可获取相邻消息:
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
);
更长的示例请参阅 references/query-patterns.md。
说明
- 首次运行会构建索引。之后的运行会增量更新。
- 数据库位置:
~/.obelisk/obelisk.sqlite;如有需要,会将旧的~/.claude/obelisk.sqlite复制过来。 - 查询脚本在沙盒 VM 中运行,脚本内部无法访问文件系统或网络。
- 索引文本及存储的工具输入/结果会截断至 10k 个字符。对于特定的 JSONL 窗口,请使用
raw(uuid, { offset, limit })。