openduo/duoduo

smart-compaction

Manage duoduo's idle auto-compact for channel sessions — enable/disable per conversation, read the measured compaction stats, and retune thresholds with the break-even formula when the owner changes model/backend or asks about idle-session cost.

Voir la source
Document Skill original

Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.

Smart Compaction — idle auto-compact management

What it is (one paragraph)

Channel sessions accumulate context; after an idle gap longer than the prompt-cache TTL, the next message re-writes the whole context into cache at a premium. The daemon ships a deterministic sweeper: when a LIVE session has been idle auto_compact_idle_minutes and its context exceeds auto_compact_min_context_tokens, it silently runs /compact. It is off by default everywhere; you (the agent) are the policy layer that enables and tunes it per conversation. Claude runs that /compact in-band on the live streaming subprocess. Grok uses the same knobs but executes _x.ai/compact_conversation (silent, no channel ack). Pi uses the same two knobs too, and pi compaction is two-layer: pi's own native threshold compaction stays on (inherited from the user's pi settings) alongside duoduo's idle auto-compact — both layers coexist by design. Codex remains the only excluded runtime. Grok compact returns no token counts, so the fuse and the compact notice never self-calibrate on a grok-only session — do not treat the first grok idle-compact as a calibration measurement. Pi is in the same boat: its compact result reports the pre-compaction size only (right after a compaction pi cannot measure the new resident context yet), so the fuse never self-calibrates on a pi-only session either. Pi is cheaper in the worst case, though — a session with nothing worth compacting answers with a free no-op instead of a paid no-gain compact.

The knobs (per instance, per kind)

duoduo session config <session|alias> get            # merged view + per-key source + measured stats
duoduo session config <session|alias> set auto_compact_idle_minutes=50 auto_compact_min_context_tokens=100000
duoduo session config <session|alias> unset auto_compact_idle_minutes   # back to kind default
duoduo session config --kind <kind> set ...          # kind-wide default (ONLY when the user explicitly says "kind"/"所有会话")
  • Recommended starting values (signed off for subscription/1h-TTL backends):

idle_minutes=50 (fires inside the 1h cache TTL → the compaction read is 10× cheaper), min_context_tokens=100000 (≈2× the measured post-compact floor).

  • OFF = set auto_compact_idle_minutes=0. This RETAINS the calibrated

threshold — the on/off switch and the calibration are separate fields (thermostat rule: switching off doesn't forget the set-point). A fresh start is an explicit unset.

  • Scope discipline: default to the CURRENT instance. Touch --kind only when

the user explicitly widens scope.

  • set/unset take effect on the session's NEXT drain or the next 60s sweep —

no restart. get never mutates anything.

Reading the stats — never mental-arithmetic what the kernel precomputed

session config get prints the session's compact_stats after each compaction (any origin — manual, auto, or the model's own reactive compact):

fieldmeaning
post_totalmeasured S_total: the floor compaction cannot go below (prompt chain + summary)
history_postthe compacted history size — this is the summary's OUTPUT cost (formula's O)
p_estimatefixed prompt-chain overhead (compaction can never reclaim this)
suggested_min_context_tokenskernel-computed 2 × post_total — your default retune target
g_gt1h_since_prev_compactcold (>1h-gap) returns between the last two compactions — the measured G
g_5m1h_since_prev_compactmid-gap returns — becomes cold too on 5-minute-TTL backends
idle_mshow long the session had been idle when the auto fire happened (absent for manual)
threshold_at_firethe min_context_tokens in effect when the auto fire triggered (absent for manual/reactive)

The compact notice (kernel → you)

On a smart-compaction-enabled session (and only there — sessions with the feature off get no injection), the first real turn after ANY compaction carries a <smart-compact-notice> block:

  • data linecompacted <ts>: ctx <pre>→<post>, history <pre>→<post>.

post is the measured floor; no need to query stats for the basics.

  • mismatch line (only when mis-calibrated) — `threshold <T> < suggested

<S> — re-calibrate via smart-compaction skill`. This is your retune signal: apply guardrail 4 immediately. It also means the kernel FUSE is now armed: auto-compact refuses to fire again while the threshold sits at or below the measured floor (a fire there can never get under the threshold — a pure no-gain loop), so retuning is what re-arms auto-compaction.

  • transcript line — the full pre-compact transcript path on disk.

Compaction summarizes your context but deletes nothing: for lost RECENT detail, spawn a cheap subagent (haiku/sonnet) to search that file — do not read it whole into your freshly-slimmed context. memory/dossiers lag hours behind live chat; they are the long-term layer, not the recent-recall layer.

The break-even algorithm (only for the judgment the arithmetic can't make)

C* = (5·O + 1.25·G·S) / (1.25·G − w)
  • Scompact_stats.post_total (measured, this session — never assume)
  • O ← the summarization OUTPUT size, i.e. the tokens the compact summary

itself costs: compact_stats.history_post (the compacted history — ~4K typical). Do NOT use the history SHRINK (history_pre − history_post, which can be ~50K); that is what was removed, not what the summary costs, and using it inflates C* by an order of magnitude.

  • Gg_gt1h_since_prev_compact (add g_5m1h too when the backend TTL is

5-minute-class). These are kernel-measured from the ledger — read them.

  • w ← trigger warmth: 0.1 if the backend cache TTL exceeds idle_minutes

(subscription = 1h TTL), else 1.0 (metered API default, Bedrock, Vertex)

Domain: the formula is only valid when 1.25·G > w. When 1.25·G ≤ w the denominator is zero or negative — there are too few cold returns per cycle for compaction to pay for itself at any context size; that region is governed by guardrail 2 (raise the threshold or disable), not by C*.

Sessions with context above C* profit from compaction; below it, compaction loses money. TTL is a fact about the backend you must know or ask — it is deliberately NOT in the kernel.

G is a feedback variable, not a fixed input. The threshold you set determines how often the session compacts, which sets the length of each compaction cycle, which changes how many cold returns fall inside it — i.e. the very G you will measure next time. Treat each retune as one step of a loop: set → observe the next compact_stats (new G) → re-evaluate. Don't over-fit to a single cycle's G; watch the trend across a few compactions.

Guardrails (hard rules)

  1. Never set the threshold below 1.2 × measured `post_total` — below the

physical floor, compaction is meaningless churn plus fidelity loss.

  1. Metered / short-TTL backends: keep 100000 only if measured G ≥ 2.

For G≈1 sessions (one heavy task, one overnight return, re-fattened next day) the cold break-even is ≈305K — raise the threshold there or disable.

  1. Retune = act-then-inform: apply the change, then tell the owner in ONE

line what changed and why (e.g. "已把这个会话的压缩阈值从 100K 调到 76K — 实测压缩地板是 38K"). Never silently, never with a wall of text.

  1. First auto-compaction IS the calibration (Claude, which writes

compact_stats): when the first compact notice arrives, read suggested_min_context_tokens and retune the instance to it. No pre-calibration ritual is needed for new kinds. Grok does not emit that notice; skip this step on grok. Pi does not emit it either (its compact result has no post-compaction total) — skip on pi too, and tune pi thresholds by the break-even formula alone.

  1. When the owner says a conversation is precious ("这个会话的上下文别动"),

set auto_compact_idle_minutes=0 on that instance and confirm.

  1. After any compaction, if you are unsure of an earlier detail: for recent

specifics, have a cheap subagent search the transcript path the notice gives you; for long-term knowledge, consult memory/dossiers. Never reconstruct from the summary alone.

  1. Configure a kind AFTER installing its channel. --kind <kind> on a kind

whose channel is not yet installed CREATES the kind config file; a later channel install then SKIPS its seed (the file already exists), so the channel comes up without its shipped defaults. Prefer instance-scope (config <session>) for anything but a deliberate kind-wide rollout.

Cache-less compatible backends

If the session runs on an endpoint with no prompt caching at all, every turn pays full input price on the whole context — compaction helps MORE there, not less. Same knobs, same formula with w=1 and cache premiums read as plain input cost.

du même dépôt

Autres Skills

Tous les Skills
openduo
Communauté

duoduo-admin

Explain and manage a host-mode duoduo installation after onboarding. Use when the user asks how duoduo works, how stdio/daemon/channel/session fit together, where duoduo stores config and state, how to inspect current setup, how to upgrade duoduo itself, where something lives on disk (kerneldir, runtimedir, .env, descriptor.md), how to archive or recover a specific session (duoduo session archive, sessions-archive directory, restoring an archived session), or for broad 'configure duoduo' / 'fix my duoduo' requests that haven't narrowed to a specific channel or runtime setting yet. Also trigger for Chinese: 帮我理解 duoduo, duoduo 是怎么工作的, 看看我现在的 duoduo 配置, 帮我管理 duoduo, 升级 duoduo, 升级要注意什么, duoduo 哪个路径存什么, 归档 session, 删掉 session, 恢复归档 session.

installations
1
GitHub Stars
9
Mis à jour
9 sept.
openduo
Communauté

duoduo-channel-admin

Install, start, stop, inspect, reset, and configure duoduo host-mode channels. Use when the request involves: channel lifecycle (install/list/start/stop/status/logs), Feishu setup card or /setup command, Feishu owner DM / main session / FEISHUBOTOWNER configuration, the 'main session is locked' refusal, stale card error, resetting a bound Feishu chat, WeChat QR login or packaging, ACP editor integration, channel descriptor editing (kind vs instance). Also trigger for Chinese: 拉起 feishu 通道, 拉起微信 channel, 配置 channel 提示词, 改 stdio 的 workspace, 查看 channel 状态, 飞书机器人怎么配, 设置 owner, 清除 session, 重置 channel.

installations
1
GitHub Stars
9
Mis à jour
9 sept.
openduo
Communauté

duoduo-loop

Set up, manage, and troubleshoot recurring loops on a duoduo install — the /loop command and the background jobs it creates. Use when the user wants duoduo to do something repeatedly or on a schedule, watch something until it finishes, run a long-term tracker, or inspect/stop/pause/re-pace an existing loop. Also trigger for Chinese: 定时任务, 循环任务, 周期任务, 每天帮我, 每小时, 盯着…直到, 持续跟进, 长期跟踪, 看看我的循环, 停掉那个 loop, 暂停循环, 改一下节奏, 让多多定期做某事.

installations
1
GitHub Stars
9
Mis à jour
9 sept.
openduo
Communauté

duoduo-pipeline

Build event-driven pipelines that connect a zero-LLM mechanical layer to a duoduo brain job via session notify. Use when the user wants to monitor an external data source (feeds, prices, files, webhooks) and only wake an agent when something worth acting on actually arrives — keeping LLM costs at zero during idle periods. Also trigger for: 机械采集, 事件驱动, 按需唤醒, 数据监控, keepalive job, 外部触发, 只在有料时干活, 省 token.

installations
1
GitHub Stars
9
Mis à jour
9 sept.