上下文压缩
会话逼近模型上下文窗口时,compaction 插件先做确定性的 tool-result 剪裁,不够再用一次模型摘要把较早的 surface 换成一条 checkpoint。
对应实现:src/Tether.Compaction/;对照 dsh compaction-basic 与 compaction-tool-result-pruner(锚点 ddefc45f)。
三个入口
| 入口 | 触发 | 阈值 | 失败时 |
|---|---|---|---|
| 步间压力 | agent/pre-step,auto 开启时(缺省开) | 超过压力阈值才动 | 告警后照常进入本 step;同一路由目标的配置失败只告警一次 |
| 溢出恢复 | agent/request-error 收到 CONTEXT_WINDOW_EXCEEDED,auto 开启时 | 绕过阈值,强制一次有用的缩减 | 告警并保留原请求错误;取消优先 |
/compact | 人工命令,只在 Agent idle 时可用 | 绕过阈值 | 命令报错 |
auto: false 同时卸下前两个监听器,只留 /compact。告警走 contained internal/error(dsh ctx.logger.warn 的对位通道),宿主的 lifecycle supervisor 把它记成一条 degraded incident。
步间压力
每个 step 进入前,插件按会话最近一次 durable request/header 的 provider/model 评估压力:
- 没有路由面(会话从未发过请求)就跳过。
- 窗口
W取自ILlmModelCatalog。目录不知道该模型的窗口时抛TargetPressureConfigException:告警一次,不静默退回某个绝对阈值。 - 估算低于阈值就结束。
- 超过阈值时先落确定性剪裁并重新计量;剪裁已经足够就不调用摘要模型。
- 仍超阈值才摘要,保留尾部按逐节点定价从末尾回积到保留预算;每次摘要后重新计量,最多
1 + compactionRetries次,之后仍超阈值即失败(告警后继续 turn)。
阈值与保留预算的公式见下文登记的偏离:Tether 已按 v0.22 目标扣除输出预留与 headroom。
溢出恢复
provider 确认上下文溢出时,监听器先剪裁、再强制一次摘要(保留 retainedTailNodes 个尾部节点),只要有一个 durable surface 替换落地就让同一请求重试——单独的剪裁也算。其余规则:
- 恢复本身抛错时,已落地的剪裁仍作为重试依据;没有任何进展就告警,并把原请求错误交给下游 listener。恢复异常不会逃出
agent/request-errorwaterfall。 - 请求已取消时不重试,即使剪裁已经落地。
- 会话没有 durable 路由面、或失败码不是
CONTEXT_WINDOW_EXCEEDED时直接交给下游。 - 预算
overflowRetriesPerRequest(缺省 1,0关闭溢出恢复)按请求序列计:一次成功响应(Append形态的assistant/message)或 Agent 离开 running 都开启新序列。compaction 自己以Replace落地的 summary 不算响应。
什么算溢出由适配器决定:DeepSeek 适配器与通用 OpenAI / Anthropic wire 都用 LlmErrorCodes.IsContextWindowExceeded 判定错误信封(code、type、message)。只是偶然提到 “context” 的 400(字段校验、context_management 之类)仍是 INVALID_REQUEST,不会触发无意义的压缩。
确定性剪裁
pruner 只处理当前 surface 上结果为字符串且超过 toolResultPruneThresholdChars 的 tool/result:保留 head/tail 各至多 toolResultPruneKeepChars 个字符,中段换成带计数的省略标记,然后以 Replace(N,N) 落地。
- 已被替换出 surface 的节点(包括先前剪裁掉的原始结果)不再入选,所以重复剪裁只处理新出现的超限结果。
- head/tail 边界落在代理对中间时向内收缩,不产生孤立代理项。
- 选项校验要求
2 × keep + 标记 ≤ threshold,剪裁产物因此不会再次超限。 - 替换只改 result 值:失败标记与 durable
error原样保留,失败结果剪裁后仍显示为失败。
事务与锁
一次摘要是一个带 log-only 锁的事务:
compaction/start经AppendIfAsync与锁判定原子提交;锁被占用时本次调用 no-op。- 选出保持 tool call/result 配平的 surface 段,调用摘要模型,以一条
assistant/message的Replace落地 checkpoint。 compaction/end闭锁。start 之后的任何失败(包括取消)都恰好追加一次 end,取消记为failed;只有 end 本身追加失败才会留下未闭合的 start。
锁有效的判定是:compaction/start、compaction/end、session/end-seed 三者中最新的一个是 start。resume 与 fork 构造时追加的 session/end-seed 标出新的会话生命周期,崩溃遗留的 start 因此不会永久阻塞后续压缩。
进程内的剪裁与摘要按 exact Session 串行,不同 Session 互不阻塞;跨实例、跨进程的互斥由 durable 锁裁决。
summary 请求发往会话自己的路由——最近一次 request/header 的 provider(tether:provider)与 model——而不是默认 route;compaction/start 记录这对路由。从未路由过的会话才落到 client 默认。
配置
base.yaml 的 compaction 行接受 CompactionOptions:
| 键 | 缺省 | 含义 |
|---|---|---|
auto | true | 安装步间压力与溢出恢复两个自动监听器 |
pressureRatio | 0.8 | 压力阈值占窗口的比例,(0, 1] |
headroomTokens | 65536 | 输出预留之外的额外压力余量 |
retainRatio | 0.16 | 逐字保留的近期对话占消息预算的比例,必须小于 pressureRatio |
summaryMaxTokens | 跟随 headroomTokens | summary 请求的输出上限 |
compactionRetries | 1 | 压力路径首次摘要后的额外摘要次数 |
overflowRetriesPerRequest | 1 | 每个请求序列的溢出恢复重试次数,0 关闭 |
retainedTailNodes | 4 | 溢出恢复与 /compact 保留的尾部节点数 |
toolResultPruneThresholdChars | 20000 | 单个 tool 结果超过该字符数才剪裁 |
toolResultPruneKeepChars | 4000 | 剪裁保留的 head/tail 字符数 |
与 dsh 锚点的偏离
按对齐制度登记,每条写明 dsh 语义、Tether 映射、差异后果与验收证据。
压力预算与 summary 输出上限
- dsh 语义(锚点
ddefc45f):threshold = floor(W × thresholdRatio),retain = floor(W × retainRatio),summarymaxTokens缺省8192,没有headroomTokens。 - Tether 映射:#365 提前实现 v0.22 目标
c36a83ff(dsh-v0.1.7-alpha.1)的公式。输出预留O取request/header的maxOutputTokens,缺席时取 provider 目录的模型输出上限,再缺席为0;threshold = floor(min(W × pressureRatio, W − O − headroomTokens)),retain = floor((W − O) × retainRatio);summary 上限缺省等于headroomTokens(65536);W − O ≤ 0或W − O − headroomTokens ≤ 0时 fail-fast。 - 差异后果:输出预留大的路由更早触发压力;窗口小的路由在锚点公式下可用的配置,可能因 headroom 被
TargetPressureConfigException拒绝;summary 允许更长的输出,而 summary 请求发往会话模型,上限高于该模型输出上限时由 provider 拒绝(v0.22 目标同样如此)。 - 验收证据:
tests/Tether.Compaction.Tests/CompactionServiceTests.cs的Output_reservation_pulls_the_pressure_threshold_earlier、Catalog_output_cap_is_the_fallback_output_reservation、Insufficient_message_or_pressure_budget_fails_fast_with_target_key、Ratio_parameters_reproduce_the_absolute_96000_trigger、Summary_cap_defaults_to_headroom_and_rejects_zero。 - 重评条件:锚点迁移到
c36a83ff时本条转为 implemented 并从本节删除。