obelisk

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

obelisk

obelisk

Search and query local Claude Code, Codex, Kimi Code, and Pi session history. Obelisk indexes sessions, messages, tool calls, tool results, summaries, subagents, workflows, workflow agents, parent chains, and raw JSONL lines into SQLite + FTS5.
Obelisk has four transcript sources. Treat all of them as ordinary sessions by default: Claude rows use
source='claude'
, Codex rows use
source='codex'
, Kimi Code rows use
source='kimi'
, and Pi rows use
source='pi'
. Use
source
only when provenance matters or the user asks to scope to one provider. Provider-specific records are projected into the same canonical tables; some providers may not emit every kind of subagent or workflow metadata.
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.
搜索并查询本地Claude Code、Codex、Kimi Code和Pi的会话历史。 Obelisk将会话、消息、工具调用、工具结果、摘要、子代理、工作流、工作流代理、父链以及原始JSONL行索引到SQLite + FTS5中。
Obelisk有四种转录来源。默认将所有来源视为普通会话:Claude行使用
source='claude'
,Codex行使用
source='codex'
,Kimi Code行使用
source='kimi'
,Pi行使用
source='pi'
。仅当来源归属很重要或用户要求限定到某一提供商时才使用
source
参数。特定提供商的记录会被映射到相同的标准表中;部分提供商可能不会生成所有类型的子代理或工作流元数据。
Obelisk是CodeAct的内存层:编写一个小型JS查询,本地运行,读取JSON,然后给出答案。默认情况下,不要将历史记录转换为扁平文档或浏览整个会话。

Quick Start

快速开始

Fast keyword search (pass a unique nonce so Obelisk can recognize your own session in results):
bash
obelisk --search "keyword" --nonce "$(uuidgen 2>/dev/null || echo "$$.$RANDOM.$RANDOM")"
Custom query:
  1. Write a bounded JS query to a unique temp file — the as-typed file path is your invocation nonce:
    bash
    qdir=$(mktemp -d /tmp/obq.XXXXXX 2>/dev/null || { d="/tmp/obq.$$.$RANDOM"; mkdir "$d"; echo "$d"; })
    qfile="$qdir/query.mjs"
    The
    .mjs
    name lives inside the unique directory, so the
    mktemp
    template always ends on the
    X
    run (BSD
    mktemp
    requires that).
  2. Run:
    bash
    obelisk --query "$qfile"
  3. Parse JSON stdout and answer with concise evidence.
The query file runs inside
(async () => { ... })()
. Use
return
to emit JSON. Query scripts are read-only:
remember()
and
forget()
are not available, and
sql()
only accepts read-only SELECT/WITH queries.
快速关键词搜索(传递唯一随机数,以便Obelisk能在结果中识别你自己的会话):
bash
obelisk --search "keyword" --nonce "$(uuidgen 2>/dev/null || echo "$$.$RANDOM.$RANDOM")"
自定义查询:
  1. 将有界JS查询写入唯一的临时文件——带类型的文件路径就是你的调用随机数:
    bash
    qdir=$(mktemp -d /tmp/obq.XXXXXX 2>/dev/null || { d="/tmp/obq.$$.$RANDOM"; mkdir "$d"; echo "$d"; })
    qfile="$qdir/query.mjs"
    .mjs
    文件名位于唯一目录内,因此
    mktemp
    模板始终以
    X
    序列结尾(BSD
    mktemp
    要求如此)。
  2. 运行:
    bash
    obelisk --query "$qfile"
  3. 解析JSON标准输出并以简洁的证据作答。
查询文件在
(async () => { ... })()
中运行。使用
return
输出JSON。查询脚本是只读的:
remember()
forget()
不可用,且
sql()
仅接受只读的SELECT/WITH查询。

Your Own Session In Results

结果中的当前会话

Obelisk refreshes the index before each query, so your own live session shows up in results. The invocation nonce (
--search --nonce
, or the unique
--query
file path) lets Obelisk mark it: session projections in
search()
hits and
sessions()
rows carry
is_invoking: true
, and
overview().current.session_id
holds the invoking session id when known. Treat a session flagged
is_invoking
as your own current context, NOT as independent historical evidence. Resolution is newest-wins over recent matches; only a near-simultaneous same-nonce collision (or no match at all) leaves nothing marked and
current.session_id
null — identity is honestly unknown.
Obelisk会在每次查询前刷新索引,因此你的实时会话会出现在结果中。调用随机数(
--search --nonce
,或唯一的
--query
文件路径)可让Obelisk标记它:
search()
命中结果和
sessions()
行中的会话投影会带有
is_invoking: true
,当调用会话ID已知时,
overview().current.session_id
会保存该ID。将标记为
is_invoking
的会话视为你自己的当前上下文,而非独立的历史证据。解析规则为最新匹配优先;只有近乎同时发生的相同随机数冲突(或完全无匹配)才会导致无标记且
current.session_id
为空——此时身份无法确定。

Default First Pass

默认首次查询

Start with helpers, not raw SQL. For the first Obelisk query in a task, normally call
overview({ limit: 6 })
unless the user already gave an exact
session_id
, message
uuid
, or absolute file path.
For semantic or synthesis tasks, combine orientation, memory recall, and raw session evidence before deciding whether a detail pass is needed:
js
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 }),
};
Use
sql()
only as an escalation path for exact joins, aggregations, or schema questions that helpers cannot express cleanly. Do not use raw SQL as a generic fallback for broad retrieval.
优先使用辅助函数,而非原始SQL。在任务中首次使用Obelisk查询时,除非用户已提供确切的
session_id
、消息
uuid
或绝对文件路径,否则通常调用
overview({ limit: 6 })
对于语义或合成任务,在决定是否需要详细查询前,先结合定位信息、记忆召回和原始会话证据:
js
const map = overview({ limit: 6 });
const project = map.current.project?.project;
const topic = '从用户请求翻译而来的英文主题术语';

return {
  orientation: map.current_project,
  prior_memories: memories({ project, query: topic, limit: 5 }),
  session_evidence: search(topic.replace(/[-_]/g, ' '), { project, limit: 8 }),
};
仅当辅助函数无法清晰表达确切的连接、聚合或架构问题时,才将
sql()
作为升级方案。不要将原始SQL用作通用的广泛检索 fallback。

Intent Routing

意图路由

Obelisk supports a small intent prefix layer after
/obelisk
. This is for output intent, not retrieval architecture.
IntentDescriptionReference
recap [target]
Generate weekly/monthly recap card content for app handoff or share-style output.
references/recap/overview.md
Routing rules:
  1. If the first word is
    recap
    , read
    references/recap/overview.md
    before the first query. Everything after
    recap
    is the recap target. Common app-generated prompts include
    /obelisk recap this week
    ,
    /obelisk recap last week
    ,
    /obelisk recap this month
    , and
    /obelisk recap last month
    ; interpret these as natural period targets relative to the current date and timezone.
  2. recap
    does not create a separate retrieval layer. It still uses
    overview()
    ,
    memories()
    , helpers, and
    sql()
    only when needed.
  3. Follow the overview's card-by-card sequence. Each card has its own retrieval pattern and writing file; retrieve that card's evidence, read that card's writing file, update the JSON, then move to the next card. Do not preload all recap references before the current card is written.
  4. If the first word is not
    recap
    , do not load
    references/recap/overview.md
    . Continue with Query Routing below. Do not infer recap from broad requests for weekly/monthly summaries, charts, rankings, shareable cards, or playlist-style metaphors.
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
    。继续执行下方的查询路由。不要从对每周/每月总结、图表、排名、可分享卡片或播放列表式隐喻的广泛请求中推断回顾意图。

Reference Map

参考地图

Use references by job, not by habit:
ReferenceUse when
references/query-patterns.md
Broad synthesis, progress summaries, design history, weekly/monthly reviews, approved memory write/archive/update scripts, or questions about what the user did/learned/decided/tried/abandoned.
references/retrieval-semantics.md
Multi-step retrieval, scoped project/file/session searches, or when scope/artifact/semantic boundaries affect query design.
references/schema.md
Raw SQL field and join quick reference before writing non-trivial
sql()
.
references/api-reference.md
Helper signatures, option names, return fields, or exact
remember()
/
forget()
parameter details are unclear.
references/pitfalls.md
Error recovery, FTS syntax, aliases, ordering, row-shape surprises, or compact/raw tradeoffs.
references/recap/overview.md
Explicit
/obelisk recap ...
requests only.
按需使用参考资料,而非习惯性使用:
参考资料使用场景
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 ...
请求。

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/query-patterns.md
    before the first query for broad synthesis, progress summaries, design history, ordinary weekly/monthly reviews, or questions that ask what the user did, learned, decided, tried, or abandoned. Start from the first-pass or one-shot synthesis pattern, then run a faceted detail pass if needed.
  • 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/schema.md
    before raw
    sql()
    unless the needed table/column relationship is already explicit here. It is intentionally short and SQL-focused. Do this before running the SQL, not after a missing-column error. Do not start with raw SQL for broad synthesis unless helpers cannot express the needed aggregation or join.
  • Read
    references/api-reference.md
    when helper option names, return fields, scalar shorthand behavior, or
    remember()
    /
    forget()
    details are unclear.
  • Read
    references/pitfalls.md
    after an error or when FTS syntax, aliases, ordering, row shapes, or compact/raw tradeoffs 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.
For approved memory mutations, follow the Memory Layer section below first. Use
references/query-patterns.md
for copyable
--attune
scripts (
Attune Approved Memory
,
Forget Approved Memory
,
Update Approved Memory
), and
references/api-reference.md
only for exact parameter semantics.
在编写查询前,先对任务进行分类。逐步披露很有用,但跳过相关参考资料通常会增加额外的查询轮次。
  • 在首次查询广泛合成、进度总结、设计历史、普通每周/每月回顾,或询问用户做了什么、学到了什么、决定了什么、尝试了什么、放弃了什么的问题前,阅读
    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)
或紧凑样本。不要自行发明字段名。
对于已批准的内存变更,首先遵循下方的内存层部分。使用
references/query-patterns.md
获取可复制的
--attune
脚本(
Attune Approved Memory
Forget Approved Memory
Update Approved Memory
),仅在需要确切参数语义时使用
references/api-reference.md

Core API

核心API

search(text, opts?)

search(text, opts?)

Full-text search across main messages, subagent messages, and workflow-agent messages.
Returns:
js
[{ message: { uuid, text, content_type, is_meta, role, timestamp, model, cwd, visibility, source },
   session: { id, title, project, started_at, source, is_invoking? },
   rank,
   context }]
session.is_invoking
is
true
only when the hit belongs to the session that ran this query (see "Your Own Session In Results"); it is omitted otherwise.
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.
Use
message.content_type
to keep evidence boundaries intact:
text
is user/assistant visible language,
thinking
is trace/debug material,
tool_use
marks a tool-call message whose details live in
tool_calls
, and
tool_result
marks a tool-result message whose details live in
tool_results
.
unknown
is a conservative fallback. Do not treat
thinking
as a user-visible assistant conclusion. Real user input is
type='user'
plus
content_type='text'
; do not invent a separate
user_message
content type.
Use
message.is_meta
to separate transcript control-plane material from conversation evidence.
is_meta=1
marks injected caveats, command envelopes, or other messages that entered the transcript as user-role content but should not be treated as the user's request by default.
search()
and
thread()
omit meta messages unless
includeMeta: true
is passed;
context()
and
trace()
preserve the current causal chain and expose
is_meta
on returned rows.
Pi can preserve a branch that was tried and later superseded as
visibility='inactive'
. Only Pi populates it: other sources either do not record supersession in their transcripts or discard it while indexing, so an empty inactive result never means nothing was abandoned -- only that this source cannot say. Default helpers return only
visible
evidence. Pass
includeInactive: true
to
search()
,
context()
,
trace()
,
thread()
,
summaries()
,
raw()
,
fileHistory()
, or
failures()
only when the abandoned path matters. Every returned message or evidence row is labeled with
visibility
; describe inactive evidence as something tried and then superseded, never as the final decision.
hidden
is reserved for display-suppressed or transport-only records and is never returned by these helpers, even with the option enabled.
Opts:
{ limit, sessionId, project, after, before, cwd, source, includeMeta, includeInactive }
.
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.
source
can be
'claude'
,
'codex'
,
'kimi'
,
'pi'
, or omitted. Omitted means search all indexed sources.
对主消息、子代理消息和工作流代理消息进行全文搜索。
返回:
js
[{ message: { uuid, text, content_type, is_meta, role, timestamp, model, cwd, visibility, source },
   session: { id, title, project, started_at, source, is_invoking? },
   rank,
   context }]
仅当命中结果属于运行此查询的会话时,
session.is_invoking
才为
true
(参见“结果中的当前会话”);否则会被省略。
此处的
context
指时间相邻项:同一会话中按时间戳排列的附近消息。它不是父链。使用
context(uuid)
trace(uuid)
获取因果/父链上下文。
使用
message.content_type
保持证据边界完整:
text
是用户/助手可见的语言,
thinking
是跟踪/调试材料,
tool_use
标记工具调用消息,其详情位于
tool_calls
中,
tool_result
标记工具结果消息,其详情位于
tool_results
中。
unknown
是保守的 fallback。不要将
thinking
视为用户可见的助手结论。真实用户输入是
type='user'
加上
content_type='text'
;不要自行发明单独的
user_message
内容类型。
使用
message.is_meta
将转录控制平面材料与对话证据分开。
is_meta=1
标记注入的警告、命令信封或其他作为用户角色内容进入转录但默认不应被视为用户请求的消息。
search()
thread()
会省略元消息,除非传递
includeMeta: true
context()
trace()
会保留当前因果链并在返回行中暴露
is_meta
Pi可以保留一个已尝试但后来被取代的分支,标记为
visibility='inactive'
。只有Pi会填充此字段:其他来源要么不在其转录中记录取代信息,要么在索引时丢弃该信息,因此空的非活跃结果并不意味着没有内容被放弃——仅表示该来源无法提供相关信息。默认辅助函数仅返回
visible
证据。仅当废弃路径很重要时,才向
search()
context()
trace()
thread()
summaries()
raw()
fileHistory()
failures()
传递
includeInactive: true
。每个返回的消息或证据行都带有
visibility
标签;将非活跃证据描述为已尝试但后来被取代的内容,而非最终决策。
hidden
保留给显示抑制或仅传输的记录,即使启用选项,这些助手也永远不会返回此类记录。
选项:
{ limit, sessionId, project, after, before, cwd, source, includeMeta, includeInactive }
project
是对
sessions.project
的SQL
LIKE
过滤器,而非精确的项目标识。结果已按FTS5排名排序;排名越低越靠前。除非故意使用FTS5语义,否则优先使用返回顺序而非手动解释数值排名。
source
可以是
'claude'
'codex'
'kimi'
'pi'
,或省略。省略表示搜索所有索引来源。

context(uuid, opts?)

context(uuid, opts?)

Returns the full story around one indexed message:
js
{ 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. The target and returned ancestors must be visible by default. Pass
{ includeInactive: true }
to follow an explicitly superseded Pi path.
返回一条索引消息的完整上下文:
js
{ message, parentChain, session, subagent, workflow }
search()
找到有希望的消息后使用此函数。这是从一个证据点垂直扩展而无需转储整个会话的常用方式。默认情况下,目标和返回的祖先必须是可见的。传递
{ includeInactive: true }
以跟踪明确被取代的Pi路径。

sql(query, ...params)

sql(query, ...params)

Read-only SQL SELECT/WITH with
?
placeholders. Returns array rows. SQL is an escape hatch for exact structured joins and aggregations after the helper-first surface is insufficient; it is not the default retrieval entry point.
Before writing non-trivial SQL, read
references/schema.md
. It is the raw SQL field/join quick reference. The executable DDL is CLI-owned and is deliberately not duplicated in this docs-only skill. 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
,
memories
,
subagents
,
workflows
,
workflow_agents
,
messages_fts
.
?
占位符的只读SQL SELECT/WITH。返回数组行。SQL是在辅助函数不足以满足需求时,用于精确结构化连接和聚合的逃生舱;它不是默认的检索入口点。
在编写非平凡SQL之前,阅读
references/schema.md
。它是原始SQL字段/连接的快速参考。可执行DDL归CLI所有,不会在此仅文档化的技能中重复。常见的安全连接:
  • 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

Structured Helpers

结构化辅助函数

These helpers are convenience accessors over the same SQLite structure. They do not replace
sql()
, but they are the default first-pass surface. 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, source }
. Check
references/api-reference.md
or a tiny sample before relying on less common filters or return fields.
  • overview(opts?)
    -- compact orientation map. Returns current cwd/project if knowable, the invoking session id (
    current.session_id
    ) when the invocation nonce resolved, global project/source counts, and current-project recent sessions plus memory records. It is a map, not evidence.
  • sessions(opts?)
    -- session rows, newest first.
    project
    is a SQL
    LIKE
    pattern.
    message_count
    counts the visible canonical transcript; inactive and hidden records are excluded. The invoking session row carries
    is_invoking: true
    .
  • recent(n?)
    -- shorthand for recent sessions.
  • summaries(opts?)
    -- summary rows, newest first:
    { id, session_id, timestamp, source, content, visibility, session_title, project }
    ; inactive rows require
    includeInactive: true
    , hidden rows are never returned, and
    source
    is the summary kind rather than the transcript provider.
  • 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 and labels each result with
    visibility
    .
  • failures(opts?)
    -- failed tool results with tool/session context and
    visibility
    , newest first.
  • trace(uuid, opts?)
    -- parent chain from root to message.
  • thread(sessionId, opts?)
    -- session messages ordered by timestamp, omitting meta messages by default. Pass
    { includeMeta: true }
    for injected context or
    { includeInactive: true }
    for superseded Pi history.
  • raw(uuid, opts?)
    -- windowed source access for one visible message. Pi returns the selected source-message container whether it was stored directly or inside a retained tail. Inactive targets require
    includeInactive: true
    ; hidden targets return
    null
    .
  • memories(opts?)
    -- recall memory layer. opts:
    { query, project, sessionId, sessions, after, before, branch, limit }
    . Without
    query
    , returns active memory records newest first. With
    query
    , searches
    summary
    /
    path
    through safe FTS5 tokenization and returns
    rank
    ; lower rank sorts earlier. Records may include nullable JSON
    anchors
    for explicit recall surfaces such as files. Read the file at
    path
    for full content.
这些辅助函数是基于相同SQLite结构的便捷访问器。它们不会取代
sql()
,但却是默认的首次查询入口。当需要辅助函数未暴露的精确聚合或连接时,使用
sql()
所有列表辅助函数都接受有界的
limit
。许多还接受:
{ project, after, before, sessionId, sessions, branch, source }
。在依赖不太常见的过滤器或返回字段之前,先查看
references/api-reference.md
或小型样本。
  • overview(opts?)
    -- 紧凑的定位图。返回当前可识别的cwd/项目,当调用随机数解析成功时返回调用会话ID(
    current.session_id
    ),全局项目/来源计数,以及当前项目的近期会话和记忆记录。它是一个导航图,而非证据。
  • sessions(opts?)
    -- 会话行,按最新排序。
    project
    是SQL
    LIKE
    模式。
    message_count
    计算可见的标准转录;排除非活跃和隐藏记录。调用会话行带有
    is_invoking: true
  • recent(n?)
    -- 近期会话的简写。
  • summaries(opts?)
    -- 摘要行,按最新排序:
    { id, session_id, timestamp, source, content, visibility, session_title, project }
    ;非活跃行需要
    includeInactive: true
    ,隐藏行永远不会返回,
    source
    是摘要类型而非转录提供商。
  • subagents(opts?)
    -- 子代理元数据加上
    messageCount
  • workflows(opts?)
    -- 工作流运行记录,按最新排序。
  • workflowTree(runId)
    -- 工作流行加上解析后的
    result
    agents
    ;可能包含庞大的
    script
    result_json
    ,因此需压缩字段。
  • fileHistory(filePath, opts?)
    -- 文件的读取/编辑/写入工具调用记录,按最早排序;包含许多
    Read
    行,并为每个结果标记
    visibility
  • failures(opts?)
    -- 带有工具/会话上下文和
    visibility
    的失败工具结果,按最新排序。
  • trace(uuid, opts?)
    -- 从根到消息的父链。
  • thread(sessionId, opts?)
    -- 按时间戳排序的会话消息,默认省略元消息。传递
    { includeMeta: true }
    以获取注入的上下文,或传递
    { includeInactive: true }
    以获取被取代的Pi历史记录。
  • raw(uuid, opts?)
    -- 单条可见消息的窗口化源访问。Pi返回所选的源消息容器,无论它是直接存储还是保留在尾部。非活跃目标需要
    includeInactive: true
    ;隐藏目标返回
    null
  • memories(opts?)
    -- 召回内存层。选项:
    { query, project, sessionId, sessions, after, before, branch, limit }
    。不带
    query
    时,按最新排序返回活跃记忆记录。带
    query
    时,通过安全的FTS5分词搜索
    summary
    /
    path
    并返回
    rank
    ;排名越低越靠前。记录可能包含用于显式召回表面(如文件)的可空JSON
    anchors
    。读取
    path
    处的文件以获取完整内容。

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.
  • Orient First: for a new task, normally call
    overview({ limit: 6 })
    before deeper retrieval unless the user gave an exact session/message/file locator. It is a navigation map; confirm facts with
    memories()
    ,
    search()
    , helpers, or, only when needed,
    sql()
    .
  • Helper First: prefer
    overview()
    ,
    memories()
    ,
    search()
    ,
    sessions()
    ,
    summaries()
    ,
    fileHistory()
    , and other helpers for first-pass retrieval. Escalate to raw
    sql()
    only when helpers cannot express the needed join, grouping, or exact schema-level check.
  • 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.
  • Exclude Meta By Default:
    is_meta=1
    rows are injected/control-plane transcript material. Helpers hide them by default; raw SQL for ordinary conversation evidence should include
    COALESCE(m.is_meta,0)=0
    unless meta rows are the investigation target.
  • Exclude Superseded Paths By Default: ordinary evidence must use exact visible-only filtering. Opt into inactive Pi history only to explain an abandoned path, and label it as tried then superseded.
  • Persist Durable Conclusions: after answering, if retrieval produced a durable conclusion that future sessions are likely to reuse and
    memories()
    does not already cover it, explicitly offer to write a memory. Keep the offer brief. Do not write the markdown file or run
    --attune
    until the user approves.
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.
保持查询范围明确、有界且结构化。
  • 先限定范围:将定位符分类为范围、工件或语义。在使用FTS之前使用最窄的结构化定位符;空的范围结果是有效的,除非用户要求扩大范围。
  • 先定位:对于新任务,除非用户提供了确切的会话/消息/文件定位符,否则通常在深度检索前调用
    overview({ limit: 6 })
    。它是一个导航图;使用
    memories()
    search()
    、辅助函数,或仅在必要时使用
    sql()
    来确认事实。
  • 优先使用辅助函数:首次检索优先使用
    overview()
    memories()
    search()
    sessions()
    summaries()
    fileHistory()
    和其他辅助函数。仅当辅助函数无法表达所需的连接、分组或精确架构级检查时,才升级到原始
    sql()
  • 先规划再探测:对于结论、广泛历史、失败调查或文件演变,编写有界的检索脚本,而非在中间结果上花费轮次。
  • 先结构化再文本化:在SQL或JS中计算计数、连接、分组、去重和投影;保持运行时JSON紧凑,理想情况下合成任务的JSON应在10k-12k字符以内。
  • 先证据再结论:返回带有稳定ID(
    session_id
    uuid
    tool_call_id
    run_id
    agent_id
    )和简短片段的紧凑证据,然后在最终答案中进行合成。
  • 默认排除元数据:
    is_meta=1
    行是注入的/控制平面转录材料。辅助函数默认隐藏它们;用于普通对话证据的原始SQL应包含
    COALESCE(m.is_meta,0)=0
    ,除非元行是调查目标。
  • 默认排除被取代的路径:普通证据必须使用精确的仅可见过滤。仅在解释废弃路径时才选择非活跃Pi历史,并将其标记为已尝试但后来被取代。
  • 留存持久结论:回答后,如果检索产生了未来会话可能重用的持久结论,且
    memories()
    未涵盖该结论,则明确提出写入记忆的建议。建议要简短。在用户批准前,不要编写markdown文件或运行
    --attune
如果字段、上下文、排序、FTS或辅助函数语义影响查询,在编码前阅读
references/retrieval-semantics.md
。如果查询出错,在重试前阅读
references/pitfalls.md

Memory Layer

内存层

Obelisk has a persistent memory layer alongside raw session data. Every retrieval queries both layers:
memories()
for prior conclusions,
search()
and helpers for raw session evidence. Use memory as prior notes, not final authority. If a memory record influences your answer, say naturally that it was previously recorded, and compare it with raw session evidence when correctness depends on it. Raw session data is the evidence layer, but one hit is not a complete truth; query and cite it compactly.
The memory layer is English-indexed. Use English terms in
memories({ query })
even when the user asks in another language. Write every
remember().summary
in English, regardless of the current conversation language. The runtime rejects obvious CJK text in memory queries and summaries as a guardrail.
Recall: query
memories({ query: 'English topic terms', project: '...' })
to find prior conclusions relevant to the current task. Translate non-English user requests into concise English query terms before calling
memories()
. Memory recall uses safe FTS5 tokenization over
summary
and
path
, so hyphens/punctuation are tokenized instead of causing raw
MATCH
syntax errors. Like other list helpers, passing a string is treated as
sessionId
, and passing a number is treated as
limit
. Read the file at
path
for full content.
memories()
returns active memories only. An archived memory is management/audit data, not recall data.
Good memory candidates include design decisions, project conventions, abandoned alternatives, repeated failure causes, workflow patterns, and conclusions synthesized across multiple raw evidence points. Do not propose memory for one-off lookups, uncertain findings, or conclusions already covered by existing memories.
Mutation approvals: judging whether to use a memory in the current answer is an agent decision and does not require approval. Persistent memory changes do. If the user explicitly says a memory is wrong, outdated, should be forgotten, or should now say something else, that request is the approval to archive or update the exact matching memory. Do not ask for a second confirmation unless multiple memories could match. If you notice a possible conflict yourself, explain it briefly and ask before changing memory state.
Writing memories: after a retrieval produces a conclusion worth persisting, propose writing a memory file. The user must approve. Flow:
  1. Write a markdown file using the
    Write
    tool (user approves).
  2. Register it via
    remember()
    in a narrow memory-registration script:
js
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.'
})
Run the registration script with:
bash
obelisk --attune /tmp/register-memory.mjs
--attune
exposes only memory mutation helpers:
remember()
and
forget()
. It does not expose
search()
,
sql()
,
memories()
, or other retrieval helpers. If you need source IDs or memory IDs, find them first with a normal
--query
script.
remember()
validates that
path
already exists and points to a file. Relative paths are resolved against the source session's
project_path
when
session_id
is provided, then stored as normalized absolute paths. Prefer project-relative paths such as
.obelisk/memories/...
plus
session_id
. Optional
anchors
must be an array of objects and is stored as nullable JSON text. Use it only for explicit recall surfaces, such as files associated with the memory.
summary
must be English and detailed enough that
memories()
results alone can judge relevance without reading the file. Include the decision, the reasoning, and the key constraints — not just a title.
The
message_start
/
message_end
range marks where in the conversation this conclusion was drawn. Use it later to trace back to the original evidence.
Forgetting memories: if the user says a memory is outdated, wrong, or should be forgotten, use normal recall first to identify the exact memory ID. If there is exactly one clear candidate, the user's request is approval to archive it. If multiple memories could match, ask which one to forget. Then run an
--attune
script:
js
return forget({
  id: 'mem-id-to-delete',
  reason: 'Outdated by newer project guidance.',
});
forget()
archives the memory record by setting
deleted_at
and
deleted_reason
. It removes the record from active recall but does not delete the markdown file. Memory records survive index rebuilds and are never changed automatically.
Updating memories: updating memory is one user-approved operation: archive the old memory with
forget()
, then write and register a replacement markdown memory with
remember()
. If the user explicitly corrected the memory, that correction is approval for the combined archive-plus-write flow. If you discovered the mismatch yourself, ask first.
Obelisk在原始会话数据之外还有一个持久内存层。每次检索都会查询这两个层:
memories()
用于先前的结论,
search()
和辅助函数用于原始会话证据。将内存视为先前的笔记,而非最终权威。如果记忆记录影响你的答案,自然地说明它是先前记录的,并在正确性依赖于它时与原始会话证据进行比较。原始会话数据是证据层,但单次命中并非完整事实;要紧凑地查询并引用它。
内存层是英文索引的。即使用户用其他语言提问,在
memories({ query })
中也要使用英文术语。无论当前对话语言如何,每个
remember().summary
都要用英文编写。运行时会拒绝记忆查询和摘要中的明显CJK文本作为防护措施。
召回: 查询
memories({ query: 'English topic terms', project: '...' })
以找到与当前任务相关的先前结论。在调用
memories()
之前,将非英语用户请求翻译成简洁的英文查询术语。记忆召回通过安全的FTS5分词对
summary
path
进行搜索,因此连字符/标点会被分词,而非导致原始
MATCH
语法错误。与其他列表辅助函数一样,传递字符串会被视为
sessionId
,传递数字会被视为
limit
。读取
path
处的文件以获取完整内容。
memories()
仅返回活跃记忆。归档记忆是管理/审计数据,而非召回数据。
良好的记忆候选包括设计决策、项目约定、废弃的替代方案、重复失败的原因、工作流模式,以及跨多个原始证据点合成的结论。不要为一次性查找、不确定的发现或已被现有记忆涵盖的结论提议写入记忆。
变更批准: 判断是否在当前答案中使用记忆是代理的决策,无需批准。持久内存变更需要批准。如果用户明确表示记忆错误、过时、应被遗忘或应更新内容,该请求即为归档或更新确切匹配记忆的批准。除非多个记忆可能匹配,否则无需再次确认。如果你自己发现可能的冲突,简要解释并在更改记忆状态前询问。
写入记忆: 检索产生值得留存的结论后,提议写入记忆文件。必须获得用户批准。流程:
  1. 使用
    Write
    工具编写markdown文件(用户批准)。
  2. 在窄范围的记忆注册脚本中通过
    remember()
    注册:
js
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: '详细摘要:做出了什么决策,原因是什么,考虑了哪些替代方案,以及哪些约束驱动了选择。'
})
使用以下命令运行注册脚本:
bash
obelisk --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
脚本:
js
return forget({
  id: 'mem-id-to-delete',
  reason: '被更新的项目指南取代。',
});
forget()
通过设置
deleted_at
deleted_reason
来归档记忆记录。它会将记录从活跃召回中移除,但不会删除markdown文件。记忆记录会在索引重建后保留,且永远不会自动更改。
更新记忆: 更新记忆是一个需要用户批准的操作:使用
forget()
归档旧记忆,然后编写并注册新的markdown记忆文件。如果用户明确更正了记忆,该更正即为归档加写入组合流程的批准。如果你自己发现了不匹配,先询问。

Minimal Patterns

最小模式

Search, then expand one promising hit:
js
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:
js
const rows = summaries({ project: '%quiet-zero%', limit: 1 });
return rows.length ? Object.keys(rows[0]) : [];
Fetch message neighbors without a full thread:
js
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>=?
     AND COALESCE(visibility, 'visible') = 'visible'
   ORDER BY timestamp LIMIT 6`,
  hit.session.id,
  hit.message.timestamp
);
See
references/query-patterns.md
for longer recipes.
搜索,然后扩展一个有希望的命中结果:
js
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),
}));
在假设字段名之前先检查辅助函数字段:
js
const rows = summaries({ project: '%quiet-zero%', limit: 1 });
return rows.length ? Object.keys(rows[0]) : [];
无需完整线程即可获取消息邻居:
js
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>=?
     AND COALESCE(visibility, 'visible') = 'visible'
   ORDER BY timestamp LIMIT 6`,
  hit.session.id,
  hit.message.timestamp
);
更长的示例请参见
references/query-patterns.md

Notes

注意事项

  • First run builds the index. Later runs update incrementally.
  • DB location:
    ~/.obelisk/obelisk.sqlite
    ; old
    ~/.claude/obelisk.sqlite
    is copied forward if needed.
  • 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.
  • 首次运行会构建索引。后续运行会增量更新。
  • 数据库位置:
    ~/.obelisk/obelisk.sqlite
    ;如果需要,旧的
    ~/.claude/obelisk.sqlite
    会被复制过来。
  • 查询脚本在沙箱VM中运行,脚本内部无法访问文件系统或网络。
  • 索引文本和存储的工具输入/结果会被截断为10k字符。使用
    raw(uuid, { offset, limit })
    获取特定的JSONL窗口。