fix(agent): a fold is described as the archive it is: the decline wording no longer says a summary is being paid for, /context and the TUI name an archived fold instead of calling it summarized or printing the raw mode, and the codes, modes and translations nothing produces any more are removed

This commit is contained in:
yhh committed 2026-11-06 23:08:04 -08:00
1 parent be92796684
commit 05c8743f03
20 files changed
+90 -54

No files matched your search

@@ -6,11 +6,9 @@ export const FOLD_WHY: Record<string, string> = {
summary_ceiling: "kernel.the_request_that_writes_the",
summary_truncated: "kernel.the_summary_hit_the_output",
summary_not_digest: "kernel.the_summarizer_did_not_return",
summary_input_too_large: "kernel.the_content_to_fold_still",
hook_refused: "kernel.an_extension_refused_this_fold",
persist_failed: "kernel.the_folded_result_could_not",
context_changed: "kernel.the_conversation_changed_while_the",
digest_lost_every_change: "kernel.the_summary_recorded_none_of",
candidate_not_smaller: "kernel.the_folded_context_was_no",
candidate_above_ceiling: "kernel.the_folded_context_still_exceeds_2",
candidate_above_trigger: "kernel.the_folded_context_was_still",
@@ -743,9 +743,7 @@ export const EN_SETTINGS: Record<string, string> = {
"The request that writes the summary was still running after 30 minutes and was stopped",
"摘要在输出上限处被截断,没有采用": "The summary hit the output limit and was discarded",
"摘要模型没有按要求的标题给出摘要,没有采用": "The summarizer did not return a briefing under the required headings, so it was not used",
"待折叠的内容缩减后仍超过单次摘要请求的容量": "The content to fold still exceeds one summary request after shortening",
"摘要生成期间对话发生了变化,已放弃这次折叠": "The conversation changed while the summary was written, so this fold was dropped",
"摘要没有记下这段内容里的任何一处改动,已放弃这次折叠": "The summary recorded none of this span's changes, so this fold was dropped",
"折叠后的上下文不比原来小,没有采用": "The folded context was no smaller, so it was not used",
"折叠后仍超过检查点上限(受保护的内容太多),没有采用": "The folded context still exceeds the checkpoint limit (too much protected content), so it was not used",
"折叠后仍不低于压缩阈值,没有采用": "The folded context was still at or above the compaction threshold, so it was not used",
@@ -758,7 +756,7 @@ export const EN_SETTINGS: Record<string, string> = {
"有一轮对话或会话切换正在进行,请稍后再试": "A turn or a session change is in progress; try again shortly",
"上下文自上次整理后没有变化": "The context has not changed since it was last tidied",
"自上个检查点以来没有已结束的内容": "Nothing has closed since the last checkpoint",
"新增内容太少,不值得再生成一次摘要": "Too little has been added to pay for another summary",
"距上次折叠新增的内容太少,不值得再折叠一次": "Too little has been added since the last fold to be worth another one",
"正在进行的这一轮需要先结束": "The turn in flight has to finish first",
"没有可折叠的内容": "No foldable region remains",
"没有值得折叠的内容": "Nothing left worth folding",
+1 -3
View File
@@ -429,7 +429,6 @@ export const KERNEL: Messages = {
"kernel.the_connection_of_the_chosen": { zh: "所选搜索模型所在的连接没有凭据,搜索已关闭;到模型偏好里重新选择,或恢复自动", en: "The connection of the chosen search model has no credentials, so search is off; choose one again in Model preferences, or set it back to automatic" },
"kernel.the_connection_of_the_chosen_2": { zh: "所选搜索模型所在的连接已被移出,搜索已关闭;到模型偏好里重新选择,或恢复自动", en: "The connection of the chosen search model has been removed, so search is off; choose one again in Model preferences, or set it back to automatic" },
"kernel.the_connection_test_did_not": { zh: "连接测试未通过", en: "The connection test did not pass" },
"kernel.the_content_to_fold_still": { zh: "待折叠的内容缩减后仍超过单次摘要请求的容量", en: "The content to fold still exceeds one summary request after shortening" },
"kernel.the_contents_of_this_backup": { zh: "这份备份的内容无法识别", en: "The contents of this backup could not be read" },
"kernel.the_context_has_not_changed": { zh: "上下文自上次整理后没有变化", en: "The context has not changed since it was last tidied" },
"kernel.the_context_holds_about_total": { zh: "上下文约 {total},已超过压缩阈值 {at},没有更多可折叠的内容。占用最多:{top}。{active}", en: "The context holds about {total}, over the {at} compaction threshold, and nothing more can be folded. Largest: {top}. {active}" },
@@ -621,7 +620,6 @@ export const KERNEL: Messages = {
"kernel.the_sub_agent_write_scope": { zh: "子代理写入范围设置未能保存:{detail}", en: "The sub-agent write scope setting was not saved: {detail}" },
"kernel.the_summarizer_did_not_return": { zh: "摘要模型没有按要求的标题给出摘要,没有采用", en: "The summarizer did not return a briefing under the required headings, so it was not used" },
"kernel.the_summary_hit_the_output": { zh: "摘要在输出上限处被截断,没有采用", en: "The summary hit the output limit and was discarded" },
"kernel.the_summary_recorded_none_of": { zh: "摘要没有记下这段内容里的任何一处改动,已放弃这次折叠", en: "The summary recorded none of this span's changes, so this fold was dropped" },
"kernel.the_summary_request_used_in": { zh: "摘要请求用了 {in} 输入、{out} 输出 token,缓存命中 {hit}。", en: "The summary request used {in} input and {out} output tokens, {hit} from cache." },
"kernel.the_surface_contains_an_unsupported": { zh: "界面包含不支持的组件", en: "The surface contains an unsupported component." },
"kernel.the_surface_description_does_not": { zh: "界面描述不符合约定", en: "The surface description does not match the contract." },
@@ -811,7 +809,7 @@ export const KERNEL: Messages = {
"kernel.this_workspace_is_not_a": { zh: "这个工作区不是 git 仓库,没有分支可切换", en: "This workspace is not a git repository, so there is no branch to switch to" },
"kernel.this_workspace_is_not_a_2": { zh: "这个工作区不是 git 仓库,无法提交", en: "This workspace is not a git repository, so there is nothing to commit" },
"kernel.through_proxy": { zh: "经由 {proxy}", en: "Through {proxy}" },
"kernel.too_little_has_been_added": { zh: "新增内容太少,不值得再生成一次摘要", en: "Too little has been added to pay for another summary" },
"kernel.too_little_has_been_added": { zh: "距上次折叠新增的内容太少,不值得再折叠一次", en: "Too little has been added since the last fold to be worth another one" },
"kernel.too_many_panel_placements_are": { zh: "记录的面板位置已达上限(最多 {limit} 个),请先清除一个", en: "Too many panel placements are recorded (at most {limit}); clear one first" },
"kernel.too_many_requests_wait_a": { zh: "请求太频繁,请稍等一分钟再试", en: "Too many requests - wait a minute and try again" },
"kernel.too_many_submissions_try_again": { zh: "提交太频繁了,请稍后再试", en: "Too many submissions - try again a little later" },
@@ -40,9 +40,9 @@ describe("what the card says sent a fold", () => {
describe("what the card says when a fold installed nothing", () => {
it("names the cause the host settled", () => {
const html = draw({ trigger: "auto", code: "digest_lost_every_change" });
const html = draw({ trigger: "auto", code: "fixed_prefix_above_trigger" });
expect(html).toContain("压缩未完成");
expect(html).toContain("摘要没有记下");
expect(html).toContain("无法折叠的固定部分");
expect(html).not.toContain("压缩完成");
});
+1 -2
View File
@@ -86,8 +86,7 @@ type Messages struct {
NoticeCompactionFmt string // a fold finished — %s how (one of the four below), %d tokens before, %d after
CompactionHowSummarized string
CompactionHowDegraded string
CompactionHowNative string
CompactionHowSnip string
CompactionHowArchived string
NoticeJobStartedFmt string // a background job started — %s its label, else its id
NoticeJobFinishedFmt string // a background job ended — %s its label, else its id
NoticeJobKilledFmt string // a background job was killed — %s its label, else its id
+2 -5
View File
@@ -32,11 +32,9 @@ var English = Messages{
"summary_ceiling": "The request that writes the summary was still running after 30 minutes and was stopped",
"summary_truncated": "The summary was cut off at the output limit and was not used",
"summary_not_digest": "The summarizer did not return a briefing under the required headings, so it was not used",
"summary_input_too_large": "The content to fold still exceeds one summary request after shortening",
"hook_refused": "An extension refused this fold",
"persist_failed": "The result of the fold could not be saved",
"context_changed": "The conversation changed while the summary was written, so the fold was dropped",
"digest_lost_every_change": "The summary recorded none of the changes in this stretch, so the fold was dropped",
"candidate_not_smaller": "The folded context was no smaller than the original and was not used",
"candidate_above_ceiling": "The folded context still exceeds the checkpoint ceiling (too much protected content) and was not used",
"candidate_above_trigger": "The folded context is still at or above the compaction threshold and was not used",
@@ -50,7 +48,7 @@ var English = Messages{
"busy": "A turn or a session change is in progress; try again shortly",
"input_unchanged": "The context has not changed since it was last tidied",
"no_new_closed_prefix": "Nothing has closed since the last checkpoint",
"fold_below_economics": "Too little has been added to pay for another summary",
"fold_below_economics": "Too little has been added since the last fold to be worth another one",
"active_turn_boundary": "The turn in flight has to finish first",
"no_foldable_region": "No foldable region remains",
"": "Nothing left worth folding",
@@ -93,8 +91,7 @@ var English = Messages{
NoticeCompactionFmt: "Context compacted (%s): about %d → %d tokens",
CompactionHowSummarized: "summarized",
CompactionHowDegraded: "degraded, no summary available",
CompactionHowNative: "compacted by the provider",
CompactionHowSnip: "old tool output trimmed",
CompactionHowArchived: "archived to a file",
NoticeJobStartedFmt: "Background job started: %s",
NoticeJobFinishedFmt: "Background job finished: %s",
NoticeJobKilledFmt: "Background job killed: %s",
+2 -5
View File
@@ -33,11 +33,9 @@ var Chinese = Messages{
"summary_ceiling": "生成摘要的请求运行满 30 分钟仍未完成,已停止",
"summary_truncated": "摘要在输出上限处被截断,没有采用",
"summary_not_digest": "摘要模型没有按要求的标题给出摘要,没有采用",
"summary_input_too_large": "待折叠的内容缩减后仍超过单次摘要请求的容量",
"hook_refused": "扩展拒绝了这次折叠",
"persist_failed": "折叠结果没能保存",
"context_changed": "摘要生成期间对话发生了变化,已放弃这次折叠",
"digest_lost_every_change": "摘要没有记下这段内容里的任何一处改动,已放弃这次折叠",
"candidate_not_smaller": "折叠后的上下文不比原来小,没有采用",
"candidate_above_ceiling": "折叠后仍超过检查点上限(受保护的内容太多),没有采用",
"candidate_above_trigger": "折叠后仍不低于压缩阈值,没有采用",
@@ -51,7 +49,7 @@ var Chinese = Messages{
"busy": "有一轮对话或会话切换正在进行,请稍后再试",
"input_unchanged": "上下文自上次整理后没有变化",
"no_new_closed_prefix": "自上个检查点以来没有已结束的内容",
"fold_below_economics": "新增内容太少,不值得再生成一次摘要",
"fold_below_economics": "距上次折叠新增的内容太少,不值得再折叠一次",
"active_turn_boundary": "正在进行的这一轮需要先结束",
"no_foldable_region": "没有可折叠的内容",
"": "没有值得折叠的内容",
@@ -94,8 +92,7 @@ var Chinese = Messages{
NoticeCompactionFmt: "上下文已压缩(%s):约 %d → %d tokens",
CompactionHowSummarized: "摘要压缩",
CompactionHowDegraded: "降级压缩,摘要不可用",
CompactionHowNative: "服务商原生压缩",
CompactionHowSnip: "裁剪旧工具输出",
CompactionHowArchived: "归档到文件",
NoticeJobStartedFmt: "后台任务已开始:%s",
NoticeJobFinishedFmt: "后台任务已结束:%s",
NoticeJobKilledFmt: "后台任务已终止:%s",
+2 -5
View File
@@ -33,11 +33,9 @@ var ChineseTraditional = Messages{
"summary_ceiling": "生成摘要的請求執行滿 30 分鐘仍未完成,已停止",
"summary_truncated": "摘要在輸出上限處被截斷,沒有採用",
"summary_not_digest": "摘要模型沒有按要求的標題給出摘要,沒有採用",
"summary_input_too_large": "待折疊的內容縮減後仍超過單次摘要請求的容量",
"hook_refused": "擴充功能拒絕了這次折疊",
"persist_failed": "折疊結果沒能儲存",
"context_changed": "摘要生成期間對話發生了變化,已放棄這次折疊",
"digest_lost_every_change": "摘要沒有記下這段內容裡的任何一處改動,已放棄這次折疊",
"candidate_not_smaller": "折疊後的上下文不比原來小,沒有採用",
"candidate_above_ceiling": "折疊後仍超過檢查點上限(受保護的內容太多),沒有採用",
"candidate_above_trigger": "折疊後仍不低於壓縮閾值,沒有採用",
@@ -51,7 +49,7 @@ var ChineseTraditional = Messages{
"busy": "有一輪對話或會話切換正在進行,請稍後再試",
"input_unchanged": "上下文自上次整理後沒有變化",
"no_new_closed_prefix": "自上個檢查點以來沒有已結束的內容",
"fold_below_economics": "新增內容太少,不值得再生成一次摘要",
"fold_below_economics": "距上次折疊新增的內容太少,不值得再折疊一次",
"active_turn_boundary": "正在進行的這一輪需要先結束",
"no_foldable_region": "沒有可折疊的內容",
"": "沒有值得折疊的內容",
@@ -94,8 +92,7 @@ var ChineseTraditional = Messages{
NoticeCompactionFmt: "上下文已壓縮(%s):約 %d → %d tokens",
CompactionHowSummarized: "摘要壓縮",
CompactionHowDegraded: "降級壓縮,摘要不可用",
CompactionHowNative: "服務商原生壓縮",
CompactionHowSnip: "裁剪舊工具輸出",
CompactionHowArchived: "歸檔到檔案",
NoticeJobStartedFmt: "背景任務已開始:%s",
NoticeJobFinishedFmt: "背景任務已結束:%s",
NoticeJobKilledFmt: "背景任務已終止:%s",
+2 -4
View File
@@ -486,10 +486,8 @@ func compactionNoticeText(detail string) (string, bool) {
how = i18n.M.CompactionHowSummarized
case "degraded":
how = i18n.M.CompactionHowDegraded
case "native":
how = i18n.M.CompactionHowNative
case "snip":
how = i18n.M.CompactionHowSnip
case "archived":
how = i18n.M.CompactionHowArchived
}
return fmt.Sprintf(i18n.M.NoticeCompactionFmt, how, p.SourceTokens, p.ProjectionTokens), true
}
+9
View File
@@ -172,6 +172,15 @@ func TestACompactionNoticeFollowsTheUILanguage(t *testing.T) {
}
}
func TestAnArchivedFoldIsNamedForWhatItIs(t *testing.T) {
inChinese(t)
detail := event.CompactionNotice{Mode: "archived", SourceTokens: 122525, ProjectionTokens: 61192}.Encode()
got := renderNotice(&Item{Code: event.NoticeCodeCompactionTelemetry, Text: "Context compacted (archived): about 122525 -> 61192 tokens.", Detail: detail})
if !strings.Contains(got, "上下文已压缩(归档到文件):约 122525 → 61192 tokens") {
t.Fatalf("compaction = %q", got)
}
}
func TestJobNoticesFollowTheUILanguage(t *testing.T) {
inChinese(t)
detail := event.JobNotice{Kind: "bash", ID: "bash-126", Label: "make build"}.Encode()
+1 -1
View File
@@ -125,7 +125,7 @@ func (a *contextWindow) refoldMinTokens() int {
// checkpointCeiling is the auto-checkpoint acceptance upper bound: candidates
// below it are accepted without padding. The default is half the window; a
// user who would rather accept a looser fold than pay for another summary
// user who would rather accept a looser fold than fold again sooner
// raises it.
func (a *contextWindow) checkpointCeiling() int {
window := a.effectiveContextWindow()
@@ -0,0 +1,21 @@
package agent
import (
"strings"
"testing"
"reasonix/internal/base/i18n"
)
// A fold archives to a file and no summary is written, so no reason a fold was
// declined may say one is being paid for.
func TestDeclineWordingNamesNoSummary(t *testing.T) {
for _, reason := range []CompactionNoopReason{NoopFoldBelowEconomics, NoopInputUnchanged, NoopNoNewClosedPrefix} {
if text := CompactDeclineText(reason); strings.Contains(strings.ToLower(text), "summar") {
t.Errorf("%s: %q mentions a summary", reason, text)
}
}
if text := i18n.M.CompactionWhy["fold_below_economics"]; text == "" || strings.Contains(strings.ToLower(text), "summar") || strings.Contains(text, "摘要") {
t.Errorf("localized wording = %q", text)
}
}
+12 -14
View File
@@ -16,21 +16,19 @@ const (
NoopCandidateAboveTrigger CompactionNoopReason = "candidate_above_trigger"
NoopCandidateAbovePhysical CompactionNoopReason = "candidate_above_physical_ceiling"
NoopSavingsBelowMinimum CompactionNoopReason = "savings_below_minimum"
NoopDigestLostEveryChange CompactionNoopReason = "digest_lost_every_change"
FailSummaryFailed CompactionNoopReason = "summary_failed"
FailSummaryTimeout CompactionNoopReason = "summary_timeout"
FailSummaryCeiling CompactionNoopReason = "summary_ceiling"
FailSummaryTruncated CompactionNoopReason = "summary_truncated"
FailSummaryNotDigest CompactionNoopReason = "summary_not_digest"
FailSummaryInputTooLarge CompactionNoopReason = "summary_input_too_large"
FailContextChanged CompactionNoopReason = "context_changed"
FailHookRefused CompactionNoopReason = "hook_refused"
FailPersistFailed CompactionNoopReason = "persist_failed"
FailResultAboveTrigger CompactionNoopReason = "result_above_trigger"
FailCancelled CompactionNoopReason = "cancelled"
FailUnclassified CompactionNoopReason = "unclassified"
FailBusy CompactionNoopReason = "busy"
FailSummaryFailed CompactionNoopReason = "summary_failed"
FailSummaryTimeout CompactionNoopReason = "summary_timeout"
FailSummaryCeiling CompactionNoopReason = "summary_ceiling"
FailSummaryTruncated CompactionNoopReason = "summary_truncated"
FailSummaryNotDigest CompactionNoopReason = "summary_not_digest"
FailContextChanged CompactionNoopReason = "context_changed"
FailHookRefused CompactionNoopReason = "hook_refused"
FailPersistFailed CompactionNoopReason = "persist_failed"
FailResultAboveTrigger CompactionNoopReason = "result_above_trigger"
FailCancelled CompactionNoopReason = "cancelled"
FailUnclassified CompactionNoopReason = "unclassified"
FailBusy CompactionNoopReason = "busy"
)
var (
@@ -14,7 +14,7 @@ func TestPlanRefusalReceiptStillHoldsAfterGrowth(t *testing.T) {
a := newPressureAgent(t, p)
w := a.window()
w.recordContextMaintenanceOutcome("", CompactionTriggerPressure, "summary", "blocked", FailHookRefused, "compaction extension refused the fold")
for _, code := range []CompactionNoopReason{FailHookRefused, FailPersistFailed, NoopCandidateAboveTrigger, NoopDigestLostEveryChange} {
for _, code := range []CompactionNoopReason{FailHookRefused, FailPersistFailed, NoopCandidateAboveTrigger} {
r := &sessionstore.ContextMaintenanceReceipt{Status: "blocked", Action: "summary", Code: string(code), InputTokens: 1000}
if !w.blockedReceiptHolds(r, 1000+w.retryGrowthStep(1000)*4) {
t.Errorf("%s: a refusal growth cannot change was released", code)
@@ -47,7 +47,7 @@ func prepare(t *testing.T, a *Agent, trigger string) error {
func TestDeterministicFailureStaysBlockedDespiteGrowth(t *testing.T) {
p := &failingSummaryProvider{}
a := newPressureAgent(t, p)
a.window().recordContextMaintenanceOutcome("", CompactionTriggerPressure, "summary", "blocked", FailSummaryInputTooLarge, "too large")
a.window().recordContextMaintenanceOutcome("", CompactionTriggerPressure, "summary", "blocked", NoopFixedPrefixAboveTrigger, "too large")
growBy(a, 8000)
_ = prepare(t, a, CompactionTriggerPressure)
if p.calls != 0 {
@@ -38,7 +38,7 @@ func TestHoldBelowTheTriggerIsNotAnnounced(t *testing.T) {
{Role: provider.RoleUser, Content: "task"},
}}, Options{ContextWindow: 20_000, CompactRatio: 0.5, WorkspaceID: "workspace", ModelRef: "model"}, rec)
a.BindSessionPath(testenv.TempDir(t)+"/session.jsonl", true)
a.window().recordContextMaintenanceOutcome("", CompactionTriggerPressure, "summary", "blocked", FailSummaryInputTooLarge, "too large")
a.window().recordContextMaintenanceOutcome("", CompactionTriggerPressure, "summary", "blocked", NoopFixedPrefixAboveTrigger, "too large")
_ = prepare(t, a, CompactionTriggerPressure)
if got := rec.held(); len(got) != 0 {
t.Fatalf("a hold with nothing due was announced: %+v", got)
@@ -21,7 +21,7 @@ func TestRefusedCandidateHoldLiftsOnGrowthAndOverflow(t *testing.T) {
t.Errorf("%s: the hold survived the growth step", code)
}
}
for _, code := range []CompactionNoopReason{FailPersistFailed, FailHookRefused, NoopFixedPrefixAboveTrigger, NoopCandidateAboveTrigger, NoopDigestLostEveryChange} {
for _, code := range []CompactionNoopReason{FailPersistFailed, FailHookRefused, NoopFixedPrefixAboveTrigger, NoopCandidateAboveTrigger} {
r := &sessionstore.ContextMaintenanceReceipt{Status: "blocked", Action: "summary", Code: string(code), InputTokens: failedAt}
if !a.blockedReceiptHolds(r, failedAt+a.retryGrowthStep(failedAt)*4) {
t.Errorf("%s: a class growth cannot change must keep holding", code)
+4 -1
View File
@@ -70,7 +70,10 @@ func (a *contextWindow) contextReport() ContextReport {
if r := st.LastReceipt; r != nil && r.Status == "applied" {
rep.LastTrigger = r.Trigger
if r.Action == "summary" {
rep.LastMode = CompactionModeSummarized
rep.LastMode = CompactionModeArchived
if r.SummaryUsage.Calls > 0 || r.SummaryUsage.RequestAttempts > 0 {
rep.LastMode = CompactionModeSummarized
}
} else if r.Action != "" {
rep.LastMode = r.Action
}
@@ -52,3 +52,28 @@ func TestContextReportLeavesThresholdsZeroWhenDisabled(t *testing.T) {
t.Errorf("disabled maintenance reported thresholds: %+v", rep)
}
}
// A fold that wrote no summary is not reported as summarized; only a receipt
// that recorded a summarizer call, from a session saved before folds were
// archived, is.
func TestContextReportNamesHowTheLastFoldWasMade(t *testing.T) {
for _, tc := range []struct {
name string
usage sessionstore.CompactionUsage
want string
}{
{"archived", sessionstore.CompactionUsage{}, CompactionModeArchived},
{"summarized earlier", sessionstore.CompactionUsage{Calls: 1, RequestAttempts: 1}, CompactionModeSummarized},
} {
t.Run(tc.name, func(t *testing.T) {
a := New(&fakeProvider{reply: "unused"}, tool.NewRegistry(), sessionstore.NewSession("sys"), Options{
ContextWindow: 200_000, CompactRatio: 0.85}, event.Discard)
a.sess.win.compactionMu.Lock()
a.sess.win.compactionState.LastReceipt = &sessionstore.ContextMaintenanceReceipt{Status: "applied", Action: "summary", SummaryUsage: tc.usage}
a.sess.win.compactionMu.Unlock()
if got := a.ContextReport().LastMode; got != tc.want {
t.Fatalf("LastMode = %q, want %q", got, tc.want)
}
})
}
}
+1 -3
View File
@@ -29,11 +29,9 @@ const (
// Compaction mode labels.
const (
CompactionModeNative = "native"
CompactionModeSummarized = "summarized"
CompactionModeDegraded = "degraded"
CompactionModeArchived = "archived"
CompactionModeSnip = "snip"
)
// CompactRequest is a maintenance the user asked for. Asking is what makes it
@@ -93,7 +91,7 @@ func CompactDeclineText(reason CompactionNoopReason) string {
case NoopNoNewClosedPrefix:
return "nothing has closed since the last checkpoint"
case NoopFoldBelowEconomics:
return "too little has been added to pay for another summary"
return "too little has been added since the last fold to be worth another one"
case NoopActiveTurnBoundary:
return "the turn in flight has to finish first"
case NoopFoldEmptyAfterHooks: