gbrain conversation-archive 技能实战:把 AI 聊天导出与会话转录归档为可检索、可追溯的大脑页面
【免费下载链接】gbrainGarry's Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain
导读
conversation-archive是 gbrain 中负责"历史对话资产化"的核心技能:它将 ChatGPT / Claude / Perplexity 等 AI 助手的聊天导出(conversations.json)以及 Claude Code、Codex、OpenClaw、Hermes、Grok Build 等 Agent 的会话日志,导入大脑成为conversations/目录下一页一次对话的带日期 Markdown 页面,并通过内置对话解析器校验、原生事实提取、缺口检测与回填循环保证档案"无缝隙"。读完本文,你将掌握从原始导出到可检索档案的完整六步流水线、原生导入命令gbrain transcripts ingest的参数与行为边界、三条防数据丢失不变式、密钥/隐私脱敏机制,以及"我第一次讨论 X 是什么时候"这类时间线追溯问题的标准检索路径。技能定义位于 plugin/skills/conversation-archive/SKILL.md。
一、它解决什么问题:两半合一的循环
对大多数用户而言,数年 AI 助手的对话历史是个人拥有的最大语料之一。conversation-archive的定位是让这段历史成为大脑的一等公民内容,而不是 downloads 文件夹里的一坨 JSON。技能本身由两个半环组成:
- IMPORT(导入半环)——原始导出或会话日志 →
conversations/下带日期的 Markdown 页面(原生导入器直接写页并拆分长会话;手动路径先一页一页转换,再gbrain import/gbrain sync)→ 解析器校验 → 事实提取 → 缺口检查。 - RETRIEVE(检索半环)——搜索档案、拉取线程、构建时间线,回答"我第一次讨论 X 是什么时候"。
技能元数据(frontmatter)声明了它的触发词、副作用与写入范围:
triggers: - "chatgpt export" - "claude export" - "perplexity export" - "when did I first discuss" - "backfill missing conversations" mutating: true writes_pages: true writes_to: - conversations/ upstream: conversation-history+transcript-save@fc834ee完整触发列表还包括conversation history、import my conversations、search my conversations、archive my session transcripts等(见 SKILL.md 的 frontmatter)。与之配套的路由评测样例(routing-eval.jsonl)给出了结构性匹配要求:每个正向意图必须包含至少一个触发词子串,同时保留真实用户口语化表达;例如 "when did I first discuss seed-stage pricing with any AI assistant?" 与 "search my conversations with Claude and Perplexity about agent memory and build me a timeline" 都路由到本技能,而 "did my colleague answer in the group channel last night?"(实时聊天状态问题)则明确不应命中,expected_skill为 null。
二、原生导入通道:gbrain transcripts ingest
技能文档首先强调:优先使用原生导入器。当来源是以下七种格式之一时,检测、密钥脱敏、imessage-slack 渲染、长会话拆分、幂等重跑全部原生完成,应胜过手动流程:
gbrain transcripts ingest ~/Downloads/conversations.json # 导出先解压 gbrain transcripts ingest # 自动发现 harness 日志 gbrain transcripts ingest --max-bytes 4gb <store> # 超大存储(省略则用各格式自带上限) gbrain transcripts status # found vs imported 缺口表2.1 支持的格式与命令家族
从 src/commands/transcripts.ts 的FORMATS常量可以看到,原生通道覆盖七种格式:claude-code、codex、openclaw、hermes、grok、chatgpt、claude-export;同一命令还承载gbrain transcripts recent --days 7(从梦周期语料目录读取近期原始转录,仅限本地)与gbrain transcripts status。ingest解析后调用核心实现runTranscriptsIngest(src/core/transcripts/ingest.ts),其逐会话管线为:
detect → adapter.parse (AsyncGenerator, 逐会话) → since/limit 过滤 → redactSession (fail-closed) → renderSessionParts → 逐 part 导入 → putRawData(baseSlug) → 陈旧 part 对账(删除超出数量的 part)关键参数(transcripts.ts 的parseIngestArgs):
| 参数 | 作用 |
|---|---|
--dry-run | 只解析 + 脱敏 + 报告,零写入(批量--all前先预览将被擦除的内容) |
--all | 导入发现到的全部会话日志 |
--since T | 只导入最后一条消息严格晚于 ISO 时间 T 的会话;字面量last表示"上次干净扫描的水印" |
--limit N | 以会话为粒度的最大导入数(被截断 ⇒ 不算干净扫描,水印不前移) |
--format F | 显式指定格式,胜过自动检测 |
--embed | 导入时立即嵌入(默认关闭:批量导入推迟到 embed 回填通道) |
--max-bytes N | 逐格式字节上限的显式覆盖 |
--source <id> | 解析出的 source id,贯穿导入、raw-data、对账 |
--facts | 导入后走原生事实提取(--dry-run下为预计划) |
--include-self | 发现阶段也纳入 gbrain 自身 claude-cli 子进程会话(issue #4472) |
2.2 幂等、原子性与水印的源码级细节
src/core/transcripts/ingest.ts 的头部注释揭示了三个实现事实,值得在理解命令行为时牢记:
- 原子性粒度是会话而非文件:一个多会话文件按"通过则提交、失败则跳过"处理,幂等重跑会补齐剩余会话;
- 错误分层:文件级错误(不可读/未知格式/符号链接)计数后继续;会话级错误(扫描失败、超大 part、adapter 抛错)计数后文件继续;运行级错误 fail-closed——导入重复查找、回读失败、
putRawData缺失会中止整个运行; - 水印只在干净扫描后前进:结果携带
cleanScan(全程无错误且未被 limit 截断)与maxSessionTs,命令仅在此条件下推进--since last检查点,被截断或部分失败的运行绝不会被误认为已扫描完。
关于--max-bytes,SKILL.md 专门强调:上限是--since last检查点指纹的一部分(参见 transcripts.ts 中可单测的ingestCheckpointFingerprintInput)——用不同上限(或去掉上限)运行会开启全新的水印作用域,因此一次带上限运行跳过的小尾巴永远不会被误认为已扫描。这对应 gbrain issue #4149:指纹中只有显式上限才携带maxBytes键,无条件写入maxBytes: 'auto'会让升级时所有既有水印被重新哈希,静默触发一次性全量重扫。
2.3 原生通道与手动通道的三个差异
- 脱敏按"格式"而非仅按厂商前缀:vendor 密钥前缀、JWT、云端/API key 形状、
Bearer头、携带内联密码的连接串、PEM 私钥块、高熵KEY=/TOKEN=/PASSWORD=赋值,加上你的~/.gbrain/harvest-private-patterns.txt正则,以及把"agent 指令式语句"计数进 frontmatter。但广义 PII(姓名、电话、地址)仍需你人工审查——手动流程的人工清洗步骤对敏感语料依然适用。 - 每条消息正文有约 4K 字符上限(可读档案而非逐字稿,
source_uri指名的会话文件才是逐字记录);工具/思考流量只以单行占位符出现。 - 没有原生 adapter 的提供商(如 Perplexity)继续走下面的手动转换。
提示:
gbrain transcripts ingest与所有打开引擎的命令一样,无法在gbrain serve持有单写者锁时运行——锁错误会指名持有 PID(见 transcripts.ts)。
三、对话档案落在哪里:目录布局与 slug 冲突
conversations/chatgpt/YYYY-MM-DD-<slug>.md — ChatGPT 线程 conversations/claude/YYYY-MM-DD-<slug>.md — Claude 线程 conversations/perplexity/YYYY-MM-DD-<slug>.md — Perplexity 线程 conversations/sessions/YYYY-MM-DD-<slug>.md — Agent 会话转录一页一次对话。日期前缀的 slug 让来源溯源可排序,并喂给时效性排序;frontmatter 中的date:驱动页面effective_date(供--since/--until过滤使用)。
Slug 冲突是真实存在的,必须确定性消歧。无标题线程共享 "New chat" 标题,且同日可落多条对话,于是YYYY-MM-DD-new-chat会跨线程碰撞。put_page没有 compare-and-swap:对碰撞 slug 的第二次写入会静默覆盖第一次(数据无声丢失)。正确做法是给 slug 追加线程 id 或导出 URL 的短稳定哈希(YYYY-MM-DD-new-chat-a1b2c3),并先查后写(gbrain get <slug>)——若命中但不是同一线程,就追加哈希而非覆盖。
四、手动导入六步流水线
Step 1 — 解析导出
- ChatGPT:Settings → Data controls → Export data → 得到
conversations.json。每条对话在mapping中以树结构存消息,需沿父指针从current_node走回线性线程。 - Claude:Settings → Privacy → Export data → 得到每条对话含扁平
chat_messages数组的conversations.json。 - Perplexity:无整库导出,线程一次只来一条(页面保存或粘贴)。同样适用下述页面格式。
格式会随导出版本漂移——写转换器前先检查实际 JSON 结构,不要相信记忆中的 schema。
Step 1.5 — 脱敏密钥与 PII(强制,写前执行)
聊天导出与会话转录常含有粘贴进来的密钥与个人数据——有人丢进 prompt 的 API key、访问令牌、私人地址。扫描不可省略:每写一页conversations/前都要跑,因为写出的页面会被索引、被搜索,一旦大脑被分享或发布就会泄露。
写页前扫描"密钥形状字符串"与 PII,把每个命中替换为带标签的占位符([REDACTED_API_KEY]、[REDACTED_TOKEN]、[REDACTED_EMAIL])。扫描范围包括:
- 厂商前缀 key(
sk-…、ghp_…、AKIA…/ASIA…、AIza…、sk_live_…、glpat-…、npm_…、hf_…); - 无前缀、仅凭格式即可识别的凭据:JWT(
eyJ….eyJ….…)、账户 SID、携带内联密码的连接串; - bearer/authorization 令牌、PEM 私钥块、值呈高熵的
KEY=/TOKEN=/PASSWORD=赋值; - 转录本本不该发布的人个数据:电话号码、住址、政府证件号、私人邮箱。
SKILL.md 指出,模型的依据是 gbrain 自己的~/.gbraindeny-list /runPrivacyLint模式(src/core/skillpack/harvest-lint.ts):一套固定、确定性匹配的密钥形状模式,在内容提交前完成脱敏。该文件的默认模式还包含邮箱正则、Slack 频道形如#channel-name的模式(以及 fork 名Wintermute的屏蔽);正则文件语法错误会在加载时响亮失败,避免带病配置进入任何 harvest。脱敏改变了转录原文,因此要在导入回执中注明(Redacted: N secrets / M PII spans)——这是对逐字转录的唯一一次被允许的编辑,"逐字"从来不代表"放出活凭据"。
源码深处的脱敏实现(两阶段 + 会话级 echo 字典)
src/core/transcripts/render.ts 展示了原生通道脱敏的工程细节:两阶段、一个会话级 echo 字典。阶段 1 对每个将被持久化的字段(消息正文、speaker 标签、标题、raw-meta 字符串字段)做扫描与 span 认领,所有字段认领到的 bearer/高熵值累积进同一个echoValues字典;阶段 2 应用各计划时读取完整字典——于是某条工具消息Authorization: Bearer …头里认领的令牌,也会在另一条消息中 assistant 裸回显它的位置被一并擦除(无论两者出现先后)。逐字段独立脱敏曾让跨字段回显漏进页面。另外注意三点:
- 扁平性是被强制的:字符串字段被计划化,原始标量透传,任何嵌套结构(恶意导出数据放行的数组/对象)会被丢弃——否则它们会未扫描地抵达
putRawData; - 转录内容先过
sanitizeForJsonb(NUL 剥离 + 规范化),因为转录会捕获合法的 U+0000,而 Postgres text/jsonb 在写边界拒绝它(issue #4392); - 高熵赋值启发式(值须含数字且通过熵门槛)仅在本通道开启——转录是最可能粘贴整行
.env的语料,此处"召回优先于误报成本";push gate 与编译上下文扫描则保持该启发式关闭。
Step 2 — 转换:一次对话一页 Markdown
--- title: Agent memory architectures type: conversation date: 2025-03-15 source: chatgpt url: https://chatgpt.com/c/<thread-id> message_count: 24 tags: [conversation, chatgpt] --- **You:** How should long-term agent memory be structured? **ChatGPT:** There are three broad approaches...让页面"机器可读而不只是人可读"的规则:
type: conversation必填——它使页面有资格被gbrain extract-conversation-facts处理;- 消息行用
**Speaker:** text(走内置bold-name-no-time模式,日期取自 frontmatter);导出若带逐条时间戳,优先用**Speaker** (YYYY-MM-DD H:MM AM): text(imessage-slack模式,行内日期)。运行gbrain conversation-parser list-builtins可查看全部受支持的行形态; - 转录正文逐字保留。用户的精确措辞就是信号——正文里不做转述、清洗、摘要;
- 你的示例与报告中的"人/公司形状"名字保持泛化(
alice-example、acme-example);导入的转录本身是用户私密内容,保持精确。
解析器侧的佐证
src/commands/conversation-parser.ts 实现了gbrain conversation-parser三个子命令:scan <slug>(在页面上干跑解析器,报告命中模式与消息数)、list-builtins(打印内置模式注册表,含 id、正则形态、source_doc)、validate <file>(校验用户声明的 simple_pattern JSON 规范)。内置模式注册表在 src/core/conversation-parser/builtins.ts:18 个手工审定模式(不是文档所说的 12 个——以当前仓库为准),每条都携带test_positive/test_negative集合并在启动时强制校验;imessage-slack的正则形如:
^\*\*(.+?)\*\*\s*\((\d{4}-\d{2}-\d{2})\s+(\d{1,2}):(\d{2})\s*(AM|PM|am|pm)?\)\s*:\s*(.*)$优先级约定:行内日期格式(歧义更少)排在仅时间格式之前;quick_reject提供 O(1) 前缀筛查;每条模式声明multi_line与timezone_policy;内置正则经过手工 ReDoS 审查,任意用户正则会在 config-set 时被拒绝(v1 仅支持simple_pattern结构化规范)。
Step 3 — 先试后批量
先转换 3-5 条对话,跑完 Step 4-5 并读页,然后才跑全量档案。对数千线程的导出,用 bulk-ingestion 的 manifest 跟踪运行,崩溃后可从 ground truth 续跑。
Step 4 — 导入
- 页面写在 brain repo 内:
gbrain sync --no-pull - 独立转换目录:
gbrain import <dir> --source-id <id>
写路径 == 提交路径(见下文不变式 3):转换器写入的目录与 import/commit 覆盖的目录必须来自同一个常量。绝不要让包装脚本git add或 import 转换器实际并不写入的路径——这种失败是静默且永久的。
Step 5 — 用对话解析器表面校验
gbrain conversation-parser scan conversations/chatgpt/2025-03-15-agent-memory报告命中哪个模式与解析出的消息数。转录页上的no_match意味着转换器发出了解析器读不了的行形态——修转换器并重新生成,不要手工补丁个别页面。
Step 6 — 原生流程提取事实
# 预览:分段 + 计数,不写库 gbrain extract-conversation-facts --types conversation --dry-run --limit 5 # 正式运行,带成本上限;大档案用 --background gbrain extract-conversation-facts --types conversation --max-cost-usd 5这是随包发布的批处理提取器(gbrain extract-conversation-facts --help可看 workers、逐页--slug、可恢复性),在 src/cli.ts 中被注册为extract-conversation-facts(主机侧命令:要求本地引擎 + 聊天网关)。实体页、反向链接与更深层富集走既有 ingest / enrich 技能——不要在这里重新实现它们。
五、三条不变式(上游根因已定位——不要重新引入)
上游某次部署的这条管线静默丢失了数天转录。根因是三个叠加的 bug;修复是结构性的。用本技能构建任何归档器都要守住它们:
- 采集节奏必须跑赢存储淘汰(capture cadence must outrun store eviction)。会话存储会把内容轮出保留窗口。长会话早期写入、在下次归档 tick 前被淘汰的内容不可恢复。选择的归档周期必须严格短于来源的保留窗口(对日内淘汰的存储,每 6 小时优于每日)。若用户明确说过的话缺失,先查"淘汰 vs 节奏"。
- 没有缺口检测 = 静默空洞。"只归档昨天"的归档器会把任何一次漏跑(机器宕机、任务失败、重启)变成永久缺失的一天且无告警。每次运行都要在尾随窗口内对比来源日期与已归档页面,并回填差异——每个 tick 自愈。
- 写路径 == 提交路径。最致命的一个 bug:包装脚本提交了转换器从未写过的目录,使定时归档成为永久的 no-op,只在手动运行时"有效"。一个常量定义输出目录,写入器与 commit/import 步骤都读它。
六、缺口自愈回填流程
任何导入之后运行它,持续采集则定期运行:
- 枚举来源:尾随窗口(30 天是好的默认值;首次导入后用全范围)内,从导出文件或会话存储取得对话日期/ID;
- 枚举档案:在 brain repo 中列出同一窗口内的
conversations/页面(日期前缀 slug 让这变成一次文件名扫描); - Diff。任何没有对应页面的来源对话都是一个缺口;
- 治愈:转换缺失对话,重新导入(Step 4-6);
- 验证:重跑 diff。第二次扫描报告零缺口才是"完成"信号——一次不算。
对持续会话采集,通过 cron-scheduler / minion-orchestrator 调度"归档 + 缺口治愈"。调度是用户设置的路由约定——技能存在本身不会机械触发任何东西;提出它时要明说这一点。
七、Agent 会话转录(harness 日志)
同一管线也归档 Agent 自己的会话日志:conversations/sessions/下每会话(或每天)一页,相同 frontmatter、相同消息格式、相同三条不变式。写入前过滤:
- 子 Agent 会话与 cron 触发的运行
- 系统消息、心跳、bootstrap 提示
- 空会话
相关原生表面:gbrain transcripts recent --days 7从梦周期语料目录读取近期原始转录(仅本地)。那是原始语料的读取,不是持久档案——让会话历史变得永久、可搜索、已提取事实的,是这个技能。
八、检索与追溯
- 找一次对话:
gbrain search "<你记得的内容>" --limit 20——然后按提供商前缀过滤结果到conversations/slug; - 拉一条线程:
gbrain get conversations/chatgpt/2025-03-15-agent-memory - "我第一次讨论 X 是什么时候":
gbrain query "X" --limit 50,按 slug 的日期前缀排序conversations/命中;- 更早探测:
gbrain query "X" --until <earliest-date-found>,重复直到无更早命中; - 宣布起源前先用同义词与相邻措辞重试——用户对某个想法的早期词汇往往与当前术语不同;
- 读最早页面确认它是真正的首次讨论,然后用日期、逐字引用与 slug 作答。
- 想法演变时间线:收集带日期命中,逐字引用关键节点,按旧 → 新呈现,slug 作为引文;
- 某日上下文:
gbrain day 2025-03-15看那天还发生了什么;gbrain recall --query "X"走已提取事实支路。
九、输出格式
导入回执(任何导入或回填运行后):
## Conversation Archive Import — YYYY-MM-DD - Source: chatgpt export (conversations.json, N threads) - Pages written: N under conversations/chatgpt/ (YYYY-MM-DD → YYYY-MM-DD) - Redacted: N secrets / M PII spans (pre-write scan) - Parser validation: N/N scanned clean (pattern: bold-name-no-time) - Facts extracted: N facts / N pages (cost $X.XX) - Gaps healed: N (dates: ...) | Gap re-check: clean追溯答案(针对"我第一次讨论 X 是什么时候"):
First discussed: YYYY-MM-DD — conversations/chatgpt/YYYY-MM-DD-<slug> > "<首次提及的逐字引用>" Evolution: - YYYY-MM-DD — <一行进展> (conversations/...) - YYYY-MM-DD — <一行进展> (conversations/...)十、反模式清单(对照自查)
- ❌ 导入时对转录做摘要或转述——页面就是转录;只留精确措辞
- ❌ 不先做写前密钥/PII 扫描就写转录——粘贴了
sk-…key 或ghp_…令牌的 prompt 会变成一页可索引、可搜索、可泄露的页面(脱敏是唯一被允许的编辑) - ❌ 覆盖碰撞 slug(同一天多条 "New chat")——追加短线程哈希;
put_page无 CAS,盲写会静默丢失第一个线程 - ❌ 发明解析器读不了的消息行格式——批量转换前先用
gbrain conversation-parser scan校验 - ❌ 手工补丁解析器拒绝的页面——修转换器并重新生成(写路径纪律)
- ❌ "只归档昨天"——每次运行 diff 尾随窗口并回填(不变式 2)
- ❌ 归档节奏慢于来源淘汰——被淘汰内容不可恢复(不变式 1)
- ❌ 包装脚本提交/导入与转换器写入不同的目录(不变式 3)
- ❌ 一次搜索失败就宣称"你从未讨论过 X"——先试同义词、查
gbrain recall,然后才能给出否定答案 - ❌ 校验 3-5 页样本前就批量转换数千线程
- ❌ 把对话归到
sources/或作为摘要笔记——导入聊天导出的归档规则是conversations/
十一、与其他技能的边界(去重,边界清晰)
- chat-connectors——在线、已连接账号通道:连接 ChatGPT/Claude 账号自动同步新对话(cookie/OAuth、增量水印、定时调度)。本技能拥有导出文件通道(下载的
conversations.json)与全部检索/追溯。路由 "connect my chatgpt / keep my conversations synced" 去那边;"I downloaded my export" / "when did I first discuss X" 来这边。Perplexity(无在线连接器)使用本技能的手动转换。 - voice-note-ingest——音频。语音备忘与音频消息去那边(转写 + 精确措辞归档)。本技能处理文本聊天导出与会话日志。
- meeting-ingestion——人类会议。会议转录归档到
meetings/,带与会者富集与时间线合并。AI 助手线程不是会议。 - capture——单条目前门(
gbrain capture→inbox/)。单条粘贴片段去那边;对话语料来这边。 - bulk-ingestion——通用大语料生命周期(manifest、试转 → 批量、续跑)。数千线程导出时用它的 manifest 跟踪本技能的转换流程——两者是组合而非竞争。
- concept-synthesis——跨全脑(概念、笔记、文章)的"想法演变追踪"。本技能回答想法在对话语料内何时/如何出现;把发现交给 concept-synthesis 做跨语料工作。
- signal-detector——实时逐条消息的实体/信号捕获。档案是批量持久层:它保留一切,而不只是检测到的信号。
配套的 routing-eval.jsonl 还记录了一个真实歧义场景:4000 线程的 Claude 导出既是"对话归档"也是"大语料生命周期",标注为ambiguous_with: ["bulk-ingestion"]——两个技能都可能触发,这是设计使然而非缺陷。
十二、技能契约(conformance test 的验收清单)
本技能保证:
- 导入的对话以
conversations/<provider>/YYYY-MM-DD-<slug>.md落为每对话一页,带type: conversation、date:frontmatter,以及解析器可识别消息格式的逐字转录; - 每段对话在写页前都按线格式(而非仅厂商前缀——JWT、云/API key 形状、连接串凭据、高熵赋值)与 PII 扫描;命中脱敏为带标签占位符并计入导入回执(untrusted-content 约定)。仍捕获到密钥的页面立即用
gbrain delete <slug> --purge移除(仅限本地 CLI,无 72 小时墓碑期;brain-repo git 历史或已同步文件仍可能持有它,需先轮换密钥); - 碰撞 slug(无标题/同日线程)用短稳定线程哈希与先查后写消歧,绝不覆盖;
- 每次导入运行在批量转换前用
gbrain conversation-parser scan校验样本,并在导入回执中报告解析结果; - 事实提取走原生
gbrain extract-conversation-facts流程(成本封顶、可恢复)——绝不使用手写提取器; - 每次导入或定时归档运行在尾随窗口上执行缺口 diff(来源 vs 档案)并回填差异;仅在干净的第二次通过后宣称完成;
- 基于本技能构建的任何归档器守住三条不变式:节奏跑赢淘汰、缺口被检测并治愈、写路径等于提交路径;
- 追溯答案引用带日期 slug 与逐字引用;否定答案("从未讨论")只在同义词重试与事实支路检查之后给出;
- 输出只写
writes_to:声明的目录; - 隐私契约:示例与报告中无真实姓名、无 fork 特定文件系统路径字面量、无上游 fork 引用。
完整行为契约见正文各节;本节约束存在是为 conformance test 服务。
【免费下载链接】gbrainGarry's Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考