Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
| **🎯 Goal 持续驱动** | `/goal <objective>` 设定目标后,自动跨轮驱动 agent 直至完成;带 token budget、completion/blocked audit、`pause`/`resume`/`continue`/`clear` 子命令,网络中断自动暂停 | 源码 [`commands/goal/`](./src/commands/goal/) · [`services/goal/`](./src/services/goal/) |
| **📦 Artifacts(HTML 上传)** | 复刻 Anthropic 官方 Artifacts:模型把 HTML/数据看板/报告上传到公开 URL(7d/30d 自动过期),`/artifacts` 命令集中管理,Cloudflare Worker + R2 完全开源、可自托管 | [8 小时复刻报告](./docs/blog/2026-06-20-cloud-artifacts-8h-recap.md) · [在线 demo](https://cloud-artifacts.claude-code-best.win/30d/c2jfwi3E-y3fTZ1ors-KE.html) |
| **🧠 Ultracode 多 Agent 编排** | `/ultracode` 注入 workflow 编排手册 + `Workflow` 工具跑确定性 JS 脚本(`agent`/`pipeline`/`parallel`/`phase`)+ `/workflows` 双栏监控面板;支持 journal 重放、token budget、并发 cap | [文档](https://ccb.agent-aura.top/docs/features/workflow-scripts) |
| **🌿 Push/Pop 上下文栈** | `/push` 开一个继承完整上下文的讨论旁支,`/pop` 把讨论蒸馏成结构化 digest 回卷主线——讨论噪音不污染主线、只留结论;支持嵌套、`--to #N` 跨层弹出、`--list` 零成本列栈、栈感知 auto-compact 三选(`FEATURE_PUSH_POP=1`) | [设计文档](./docs/features/push-pop-context-stack.md) · 源码 [`commands/push/`](./src/commands/push/) · [`commands/pop/`](./src/commands/pop/) |
| **🍃 /digest 回溯蒸馏** | 与 `/compact` 互补的上下文优化:给只能顺序处理的 agent 补上 DFS 式回溯——`/digest` 像 `/rewind` 一样弹选择器,圈定一段失败调试/报错/发散岔路,蒸馏成四栏结论、主线**前缀无损缓存友好**(compact 是全局打包重建缓存);与 `/pop` 共享同一底层蒸馏原语(平级入口、非命令互调),`/digest` 支持事后选点;蒸馏本身要花一次压缩调用,但因前缀缓存热、成本远低于 compact 的全量重建(`FEATURE_PUSH_POP=1`) | [设计文档](./docs/features/digest.md) · 源码 [`commands/digest/`](./src/commands/digest/) |
| **Claude 群控技术** | Pipe IPC 多实例协作:同机 main/sub 自动编排 + LAN 跨机器零配置发现与通讯,`/pipes` 选择面板 + `Shift+↓` 交互 + 消息广播路由 | [Pipe IPC](https://ccb.agent-aura.top/docs/features/uds-inbox) / [LAN](https://ccb.agent-aura.top/docs/features/lan-pipes) |
| **ACP 协议一等一支持** | 支持接入 Zed、Cursor 等 IDE,支持会话恢复、Skills、权限桥接 | [文档](https://ccb.agent-aura.top/docs/features/acp-zed) |
| **Remote Control 私有部署** | Docker 自托管远程界面, 可以手机上看 CC | [文档](https://ccb.agent-aura.top/docs/features/remote-control-self-hosting) |
Expand Down
128 changes: 128 additions & 0 deletions docs/features/digest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# /digest — 上下文回溯与蒸馏(DFS 式上下文优化)

## 概述

`/digest` 是一种**上下文优化机制**,与 `/compact` 互补。它不是「push/pop 的附属」,也不是 push/pop 内部调用的命令——`/digest` 与 `/pop` 是**平级入口**,各自独立调用同一个底层蒸馏原语(局部上下文压缩 `partialCompactConversation` + 四栏 `DIGEST_PROMPT` 模板)。区别只在**由谁圈定蒸馏范围**:`/pop` 用栈标记(marker+1),`/digest` 用消息选择器手选。

Agent 天然只能**顺序处理**:一步步往下跑,一旦某段探索失败、报错、走进死胡同,这些日志与试错来回会**一直留在上下文里**稀释后续推理,且无法回收——线性历史只能追加、不能回溯。`/digest` 给这种线性历史补上**类似 DFS 的回溯能力**:把一段可隔离的探索岔路(通常是失败调试 / 报错排查 / 发散讨论)精确圈定,蒸馏成一份结构化结论,上下文回到岔路**之前**的干净状态,只多一份 digest。它是「手术刀」——精确、可控、缓存友好。

`/digest` 与 `/push`+`/pop` 是这套底层蒸馏原语的两种入口,区别只在**由谁、何时圈定蒸馏范围**:

- **`/push` + `/pop`**:预先划界。讨论开始**前** `/push` 打标记,结束后 `/pop` 弹栈蒸馏。要求用户能提前预判「接下来要岔出去了」。
- **`/digest`**:事后选点。不需要任何提前动作,事后像 `/rewind` 一样弹消息选择器,回溯选中一条较早的消息,把它到对话底部整段蒸馏。解决「污染往往事后才发现、来不及 push」的现实问题。

## 动机:给顺序 agent 补上回溯

把 agent 与用户的协作看作一棵搜索树:每个节点是一次上下文状态,展开是深入探索,回溯是收敛结论。普通对话的上下文只能**追加**、无法**回溯**——一次失败的调试、一段跑偏的排查,会永久留在上下文里,持续消耗 token 并稀释后续推理,agent 只能带着这些噪音继续往下走。

`/digest` 为这棵树补上「回溯」:圈定那段失败探索,只让**蒸馏结论**穿过节点边界,展开过程中的中间态(大日志、死胡同、被推翻的假设)被丢弃。这正是 DFS 里「子树探索完只向父节点回传结果」的语义。

## digest vs compact

两者都做上下文瘦身,但解决的膨胀类型、控制粒度、缓存影响完全不同。一句话:**compact 是「全局打包整段历史来续命」,digest 是「把一段你圈定的探索岔路沉淀成结论、主线无损」。**

| 维度 | `/compact` | `/digest` |
|------|-----------|-----------|
| **作用范围** | 整个会话——把到目前为止的全部历史总结成一份摘要,替换原始来回 | 只针对你显式圈定的那段岔路,主线其余部分完全不动 |
| **触发/控制** | 常被动触发(触顶自动 compact)或一键;边界是「到现在为止全部」,不精确控制压什么 | 完全主动、结构化——你精确决定哪一段被收敛 |
| **保留什么** | 主线原始细节大多丢失,只剩一份总摘要 | 圈定点之前的主线**逐字保留**,只有那段岔路被换成结论 |
| **意图** | 省空间、避免触顶——「不得已的续命」 | 主动沉淀结论、丢掉过程噪音——「整理」而非「救急」 |
| **前缀缓存** | 几乎重写整个上下文 → 前缀缓存**全部失效**,compact 后第一轮基本全量重建,较贵 | 主线前缀**无损**,只有尾部那段换成小 digest → 缓存损失极小 |

## digest 能否替代 compact

对懂上下文管理的开发者,digest **能大幅降低对 compact 的依赖**——把它从「常规操作」降级为「兜底手段」,但不能完全替代。

**digest 替得漂亮的场景**:凡是可隔离的探索/调试岔路(一堆试错、读大日志、跑命令,最后只想留一个结论)——这类天然有边界,圈定即可蒸馏,主线前缀无损、缓存友好、保真度高。养成「探索前先划界、或事后 `/digest` 回收」的习惯,大量本会把主线撑到触发 compact 的膨胀,会被提前在岔路里消化掉。

**compact 仍不可少的场景**:

1. **主线自身的线性膨胀**:上下文变大不是因为岔路,而是主线本身活儿多——顺着一条线读了几十个文件、跑了很多步,全是要保留的主线。这没有可划的边界,只有 compact 能压主线本身。
2. **触顶救急**:逼近窗口硬上限时往往正处在一长串主线操作中间,来不及优雅划界。compact 是兜底救命,digest 不解决「主线超长」。

**成本形态不同**:digest 的代价是**纪律/认知负担**(要持续预判哪些是岔路、记得划界,`/digest` 的事后选点缓解了这点但仍需判断选哪条消息);compact 的代价是**缓存重建 + 丢细节**,但零纪律(自动或一键)。

**建议的组合策略**(不是二选一):主力用 digest 主动隔离每段探索,让主线长期精简、缓存友好,把 compact 触发频率压下去;兜底在主线确实线性长到逼近上限时,再用 compact 做安全网。

> ⚠️ 两者都不是无损。digest 的价值高度依赖蒸馏摘要写得准;若 digest 丢了后续主线要用的关键细节,仍得回 transcript 翻原文(`ctrl+o` 查看历史里被折叠的原始尾部)。

## 用户体验

```
/digest 弹出消息选择器 → 选一条较早的 user 消息 M
→ "Distill from here into a digest"(可附加 context)
→ 从 M 到对话底部被四栏 digest 替换
```

- 选择器复用 `/rewind` 的消息列表 UI,但 digest 模式下选项收敛为两个:**Distill from here into a digest**(可选填一段附加 context 影响蒸馏侧重)与 **Never mind**;标题、拾取提示、确认文案切换为 digest 语义。
- 蒸馏进行时输入框旁显示 spinner「↓ Distilling from selected message」。
- 完成后弹出通知:`↓ Distilled from selected message (ctrl+o for history) · Context: ~X → ~Y tokens`,X/Y 为蒸馏前后的上下文规模。

### 与 /rewind 的差异

`/digest` 借用 `/rewind` 的消息选择器 UI,但语义相反:`/rewind` 是**丢弃**选中点之后的内容(回到过去、可选恢复文件),`/digest` 是**蒸馏保留**选中点之后的结论(折叠而非丢弃、不动文件、不 resubmit)。digest 模式通过选择器的 `mode` 参数与 rewind 隔离,rewind 的完整 restore 选项不受影响。

## 核心机制

`/digest` 的落地主体复用局部上下文压缩 `applyPartialCompactByUuid`(方向 `from`)——把选中消息之后的段落原地蒸馏替换。这与 `/pop` 走的是同一条压缩路径、同一份 digest 模板,差异只在「选点来源」(命令弹选择器手选 vs 弹栈顶):

```
applyPartialCompactByUuid({
pivotUuid: M.uuid,
feedback: 用户输入 ?? DIGEST_TEMPLATE,
direction: 'from',
options: { promptOverride: DIGEST_PROMPT, summaryFraming: 'digest' },
// 注意:无 resubmit —— 蒸馏后不重新触发 agent,只折叠上下文
})
```

digest 用固定的四栏结构,让蒸馏物对后续对话可直接消费:

| 栏目 | 内容 |
|------|------|
| Decisions | 探索确定了什么 + 关键理由 |
| Rejected | 过程中否决了什么 + 否决原因(防止重蹈覆辙) |
| Open questions | 未收敛的点 |
| Action items | 对主线任务的具体影响 / 待办 |

## token / 缓存成本

`/digest` **会花 token**——蒸馏是一次真实的 LLM 调用,不是免费的。但它的成本结构相对 `/compact` 明显更省,这正是 digest 的核心优势:

- **蒸馏调用的固有成本**:走一次局部压缩 fork(`partialCompactConversation`),读入被蒸馏的那段上下文 + 输出四栏 digest。这是「把一段上下文蒸馏成结论」躲不掉的成本——探索内容接着聊本来也要为它付 token。与 `/pop` 走的是**同一次**压缩调用,digest 相对 pop 不额外多花。
- **相对 compact 更省**:因主线前缀**无损**,蒸馏刚结束时前缀缓存必热,单次成本约等于一次 cache-read 加几百 token 输出;而 compact 几乎重写整个上下文、前缀缓存全失效、下一轮全量重建。所以「省」是相对 compact 而言,不是绝对零成本。
- **唯一真正零成本的部分**:完成通知里的 `Context: ~X → ~Y tokens` 复用压缩过程**已经算好**的 `preCompactTokenCount`(压缩时计算)与 `truePostCompactTokenCount`(本地消息负载估算),**不发起任何新的 token_count 请求**,纯显示层。

## 架构

| 模块 | 路径 | 职责 |
|------|------|------|
| digest 命令 | `src/commands/digest/` | 命令定义 + 调 `openMessageSelector('digest')` |
| 消息选择器 | `src/components/MessageSelector.tsx` | `mode` 参数区分 rewind / digest;digest 模式收敛选项与文案 |
| 应用层 | `src/screens/REPL.tsx` | `messageSelectorMode` state/ref + `onSummarize` 的 digest 分支(无 resubmit + spinner + token 通知) |
| digest 模板 | `src/services/pushStack/digestPrompt.ts` | 四栏 `DIGEST_PROMPT` + `DIGEST_TEMPLATE`(push/pop 与 digest 共用) |
| 局部压缩 | `src/services/compact/compact.ts` | 复用主体;`promptOverride` + `summaryFraming: 'digest'` 参数 |

`ToolUseContext.openMessageSelector` 的签名从 `() => void` 扩展为 `(mode?: 'rewind' \| 'digest') => void`(向后兼容,rewind 不传参)。特性由 feature flag `PUSH_POP` 控制(`FEATURE_PUSH_POP=1`)。

**代码血缘**:四栏 `DIGEST_PROMPT` 与局部压缩的这套复用**最初是为 `/pop` 引入的**(见 `digestPrompt.ts` 的注释「Digest prompt for `/pop`」),`/digest` 命令后加、复用同一底座。`/pop` 的落地在 `REPL.tsx` 的 `applyPop`(pivot = 栈标记 marker+1),`/digest` 的落地在 `MessageSelector` → `onSummarize` 的 digest 分支(pivot = 手选消息)——两者各自调用同一个 `applyPartialCompactByUuid`,**谁都不调用谁的命令**。所以「共享」是平级复用同一底层原语,不是命令互调,也不存在从属关系。

## 使用场景

### 场景 1:事后隔离一段失败调试

主线跑测试冒出 SIGSEGV,没来得及 `/push` 就一头扎进排查——读了大量日志、试了几个假设、改了些实验代码,二十轮后确认是环境问题。`/digest` 选中「开始排查的那条消息」,把整段排查蒸馏成「Decisions: 确认是环境问题 / Rejected: 排除了代码 bug」,主线上下文回到排查之前的干净状态、缓存无损。

### 场景 2:回收发散讨论

与 agent 就某个设计反复权衡了很久,方向已清晰但上下文塞满了被否决的中间方案。`/digest` 选中讨论起点,把发散过程折叠成「已裁决 + 被排除及原因」,后续实现不再被反复权衡的噪音稀释。

### 场景 3:主动控形替代频繁 compact

长会话里养成习惯:每完成一段可隔离的探索就 `/digest` 收一次,让主线长期精简、前缀缓存长期热。相比被动等触顶 compact(全量缓存重建 + 丢主线细节),这是「主动控形」而非「被动救命」——只在主线本身线性膨胀到逼近上限时才回落到 `/compact` 兜底。

## 演进方向

- 与 `/push --list` 打通:允许 `/digest` 直接选一个已有 push 标记作为起点。
- digest 模板自适应:按探索类型(排查型 / 方案裁决型 / 开放发散型)切换蒸馏侧重。
- 事后多段回收:一次 `/digest` 圈定多段不连续岔路分别蒸馏。
Loading