mcpField
O
obelisk

obelisk

github.com/tommy0103Agent 工作流69869 次安装1 周前更新
代码仓库

关于 obelisk

obelisk 是面向 Claude Code 和 Codex 用户的历史检索 Skill,可从过往编码会话中查找证据。它将对话及执行记录索引到 SQLite 和 FTS5,并支持有界 JavaScript 查询。

主要功能

  1. 01索引 Claude Code 和 Codex 会话
  2. 02搜索消息与工具活动
  3. 03使用 SQLite 和 FTS5 存储
  4. 04通过 JavaScript 查询历史
  5. 05提供会话与因果上下文

适用场景

  1. 01按关键词查找过往讨论
  2. 02检查工具调用及结果
  3. 03追踪消息周边上下文

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tommy0103/obelisk --skill obelisk

安装后在 Claude Code 中输入/obelisk确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CONTEXT.md
PRODUCT.md
README.md
.gitignore
LICENSE
.github/workflows/
publish-skill.yml
app/
electron.vite.config.ts
package.json
package-lock.json
app/src/main/
index.ts
recap-capture-query.ts
indexer-worker-client.ts
indexer-worker.ts
indexer-service.ts
indexer.ts
app/src/preload/
index.ts
app/src/renderer/
index.html
app/src/renderer/src/
App.vue
activity-ledger.mjs
flap-number.mjs
main.js
app/src/renderer/src/assets/
recap-cards.html
app/src/renderer/src/components/
ActivityLedger.vue
ActivityLedgerRow.vue
FlapNumber.vue
app/src/renderer/src/components/recap/
CoverCard.vue
archetypes.js
WorkflowCard.vue
card-base.css
seals.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"

自定义查询:

  1. 将有范围限制的 JS 查询写入临时文件,例如 /tmp/q.mjs。

  2. 运行:

    node $SKILL_DIR/scripts/runtime.js --query /tmp/q.mjs
    
  3. 解析 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

路由规则:

  1. 如果第一个词是 recap,请在首次查询前阅读 references/recap/overview.md。recap 之后的所有内容都是回顾目标。 常见的应用生成提示包括 /obelisk recap this week、 /obelisk recap last week、/obelisk recap this month 和 /obelisk recap last month;应根据当前日期和时区,将这些内容理解为自然语言表示的时间段目标。
  2. recap 不会创建单独的检索层。仍然只在需要时使用 overview()、memories()、辅助函数和 sql()。
  3. 按照概览中逐张卡片的顺序操作。每张卡片都有自己的检索模式和撰写文件;先检索该卡片的证据,阅读该卡片的撰写文件,更新 JSON,然后再处理下一张卡片。不要在撰写当前卡片之前预先加载所有回顾参考文档。
  4. 如果第一个词不是 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 是 SQL LIKE 模式。
  • 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 越小,排序越靠前。记录可能包含可为空的 JSON anchors,用于指向文件等明确的回忆入口。请读取 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 的决定,无需审批。持久化记忆变更则需要审批。 如果用户明确表示某条记忆有误、已过时、应当忘记,或现在应改为其他内容,该请求即是归档或更新完全匹配的记忆的审批。除非可能匹配多条记忆,否则不要再次请求确认。如果你自己发现可能存在冲突,请简要说明,并在更改记忆状态前征求同意。

**写入记忆:**检索得出值得持久保存的结论后,提议写入记忆文件。用户必须审批。流程:

  1. 使用 Write 工具编写 Markdown 文件(需用户审批)。
  2. 在范围受限的记忆注册脚本中通过 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 })。

评论

支持 Markdown