Repository navigation
Research: measure and cut token spend per Bot turn (prompt overhead, history growth, tool output, fan-out) #796
Description
Owner: Dev2
Why
Every FlyBot turn spends a large and unmeasured share of its input tokens on things that are not the task: the group notice, the member roster, the speaking order, the full conversation so far, and every tool result that has ever been pasted into it. Nobody has measured that split, so nobody can tell a real saving from a change that only feels cheaper. On this host the delivery group has 10 Bots; one user message in the group can start up to 10 model calls, each carrying its own copy of the same context.
This issue is a research task, not a refactor: measure first, then land only what the measurement justifies.
What to measure (per turn, on this host)
- Fixed prompt overhead. The group notice, the member list, the speaking order, the tree block, and the injected memory files are re-sent on every turn of every member. Measure their size in tokens and multiply by turns per day.
- Conversation growth. Tokens per turn as a function of turns already taken in that conversation, before and after a checkpoint/compaction.
- Tool-result weight. The share of history that is raw tool output (GitHub JSON, test logs, file reads). Report the largest contributors and whether anything ever trims them.
- Fan-out cost. Model calls per user message in a group round, and the tokens each member re-reads that the others also read.
- Retry and duplicate work. Turns that were stopped, restarted, or repeated because a Bot re-read the same state — count them, because they are pure cost.
Measurement protocol
- Take one ordinary working day and one group round as the sample. Record input and output tokens per turn from the provider side, not from an estimate in the prompt.
- Produce a table: turn id, Bot, phase, input tokens, output tokens, and a category split for the input (fixed overhead / history / tool results / task).
- State the method so a second person can reproduce the numbers on another day.
Candidate savings (each must be proven, not assumed)
- Sending the group notice only when it changed, or compressing it to the rules that actually change behaviour.
- Roster and speaking order trimmed to the names that matter for that turn.
- Tool results summarised at the point they enter history, with the full text kept in a file.
- Earlier turns condensed more aggressively, with the exact wording still reachable through search rather than carried inline.
- Fewer members called per round when the work is sequential anyway.
Acceptance
- A report committed to the repository with the measured split above, and the method behind it.
- At least one landed change that cites a measured before/after in tokens per turn, on the same workload.
- Nothing in the safety rules is traded away for the savings: the attribution rules, the verdict rules, and the per-role write limits stay, and the report says which of them cost what.
Out of scope
pricing negotiation, provider choice, and per-team budgets (that is #645); this issue is only about spending fewer tokens for the same work.
Activity
Birfy commentedon Oct 23, 2026
Split, and only the measurable half is ready.
#796stays the umbrella: which savings to take changes what every Bot is sent on every turn, so it needs the owner's word. The measurement does not, and it needs nothing from CI or the provider.Split out:
#799— Token report: read the existing usage ledger into per-turn numbers and a reproducible prompt split (enhancement | area:automation | agent-ready, hanging under this epic).Why it is not starting from zero:
core/src/host/usage.jsalready writes a ledger with one row per billed attempt and keepsinput,cacheRead,cacheWriteandoutputapart (tokensOf():44-49; schema:142-152; file$DSH_HOME/usage.sqlite).core/dev/llm-tap.mjsalready logs the provider'susageper call plus aprefixdiff, and can dump request bodies for replay. Only the UI reads the ledger (core/src/client/usage-page.js); there is no per-turn distribution, no growth curve, no split, and no report.One number from this host's ledger, read read-only from a copy and covering 2026-10-20T07:34Z → 2026-10-22T22:05Z (16,399 calls):
That reframes the work: the biggest single block is not the group notice, it is the history that every turn re-reads, and any report that prints one "tokens" column will hide the difference between a cheap cache read and a fresh input token.
#799therefore requires the columns to stay apart and every number to carry its provenance.Scope guard:
#799may not touch prompts,core/src/**, workflows, or start a third metrics framework —#791defines the RSI baseline metrics and#357is the Nightly smoothness dashboard.#645stays out of scope, as you wrote.feat(dev): read the usage ledger into a token report (#802)
Ask for list_children only when more than one child fits a part (Thin…
Keep a group's fixed rules in its standing section, not in every turn (…
Keep children's load out of a tree coordinator's system prompt (Think…
Ask for list_children only when more than one child fits a part (Thin…
Birfy commentedon Oct 23, 2026
Written-by: Dev
Token audit of a Bot turn (owner request), two fixes merged:
9fe5c26): a group's fixed rules (members / face roster,@Namecalls, notice, admin / lead notes, face routing rule, reply rule) now live in the member Session'sbot-roomsystem-prompt section, which is rendered every step and survives a checkpoint, instead of in every stored turn. Turn text: 4 Bots, 713-char notice, 80 turns went 161,750 → 46,790 chars (−71%). A face room went 25,236 → 8,491 chars (−66%). A notice change is said in one line.9f4aa52): a tree coordinator's system prompt no longer changes at every child turn start or end. Before, 6 changes per 3-child round each re-sent ≥ ~11.5k chars on DeepSeek; now there are 0. Load is read withlist_childrenwhen more than one child fits.Audited, no change: the brief is sent once per Session, transcript windows are unseen-only deltas, relay and
message_bottext is plain, the friend fence is minimal, and FlyBot tool schemas (~16.4k chars) are stable and cached. Schedules add only the[Scheduled task]prefix. Face[PASS]fan-out is already cut after the first answer.Left open (behavior or design decisions, not fixed here): cutting face
[PASS]calls further, which would change routing, and trimming the largest tool descriptions (message_bot,update_group,save_capability).Keep children's load out of a tree coordinator's system prompt (Think…
Birfy commentedon Oct 24, 2026
Assigned to @hsliuustc0106 (2026-10-24, owner's request).
Two things changed since this issue was written, and they narrow what the measurement has to cover:
9fe5c261moved a group's fixed rules out of every turn into the room's standing section, so the "group notice re-sent on every turn of every member" cost named in the original scope no longer exists as written. Measure the new shape instead: the standing section, the member roster, the speaking order, the tree block and the injected memory files.9f4aa524took a coordinator's child-load block out of the system prompt, so the tree block is smaller than when this was filed.Start from a baseline on the current
mainand report the split per turn before proposing any change. Numbers, not intuition: tokens per turn for (a) fixed prompt, (b) history growth before and after a checkpoint, (c) raw tool output.Docs: align the pipeline contract with practice — prove the base, jud…
2 remaining items
Fill the 2026-10-25 token-spend report from this host's ledger (Think…
Birfy commentedon Oct 26, 2026
Triage(2026-10-26):转
agent-ready,Owner: Dev2(标签enhancement+area:automation;needs-human已摘;原指派hsliuustc0106是 2026-10-24 的旧安排,已过期,指派已清空——Dev2 认领时按惯例把 issue 指派给Birfy)。1. 改口说明(与我 10-23 那条并存)
10-23 我写的是「
#796留 umbrella,选哪一条削减要 owner 的 word」。现在按 Chief 的决定改口:测量这一半现在就能做,且必须重做。理由有三条可核事实:#799已 closed/completed(测量工具链core/dev/token-report.mjs+ 报告),所以本单不再是「从零开始测」;9fe5c261把群组固定规则移进常驻 section、9f4aa524去掉了 coordinator 的 child-load 块——10-23 那份审计(16,399 calls,覆盖 10-20→10-22)说的形状已经不是现在的形状,拿它交差就是错的;仍然不变的部分:哪一条削减要真的落地为行为改动(notice / roster / speaking order / tool 描述裁剪),还是 owner 拍。本单只负责量出来 + 排序 + 给出可验的省多少。
2. 交付物(每条都能核)
$DSH_HOME/usage.sqlite读(input/cacheRead/cacheWrite/output分列,不许合成一个 tokens)。measured(provider usage 或 ablated replay,写样本量)或derived(写公式);字符占比不许当成 token 数印出来。docs/reports/<date>-token-spend.md,含表格、方法、可复现命令、每列出处、样本量。3. 本条硬边界(写进 PR 正文)
core/src/**的发送内容;测量工具在core/dev/。#791定 RSI 基线、#357是 Nightly 看板;若它们先落 schema,把这些数字表达在它里面。#645(预算/静默时段)不在本单。4. 验收可红(叠在正文之上)
cache_read折进input→ 分离测试必红(cache read 与 fresh input 不是一个价位,合起来就再也分不出真省假省)。--db→ 非零退出并报出路径,不许打印空表退 0。owners表缺行时未归属的 Session 必须按 Session id 出现,不许消失。9fe5c261之后已不成立——要显式写)。risk:T*只由控制器写(不手写、不转录;缺档看risk tier (report only)的 job summary,不阻塞)。core/dev/**与docs/**交付,按常规 gate:tip 上 PASS(T2 需要)+ CI 全绿 + 撤改动变红;认领评论里带分支名。Birfy commentedon Oct 26, 2026
Written-by: Triage
补三条,接上一条规格(不重复):
1. 放大倍数单独一行(用户 04:45 的第 3 条)。除了「每次 call 的固定段」,再给「一条用户消息在交付组唤醒几个成员 × 每个成员各带一份固定段」的乘数,以及「一拍 × 成员数 × 一天拍数」的总量式子。这一项可以是
derived,但每个因子都要写出来,拍数从群的实际会话算,不要拿 48 硬套。2. 基线与差值。
docs/reports/2026-10-23-token-spend.md是旧形状(9fe5c26之前),docs/reports/2026-10-25-token-spend.md(#1144,窗口2026-10-24T06:51:39Z..2026-10-25T09:37:59Z,512 calls)是这台主机的上一份。新报告加一节「相对 10-25 那页,形状变了什么」:#1204之后房间历史硬上限是ROOM_CONTEXT_BUDGET = 4000字符(core/src/host/room-context.js),群公告今天又改成「认领 / 开 PR / 合入」三个节点——这两项直接改固定段与历史段的大小。3. 工具 schema 只能靠 replay 拆。
usage.sqlite的input是一条 call 的总数,拆不出「工具定义占多少」。要这一段用core/dev/llm-tap.mjs(已能 dump 请求体)对同一回合做 ablate/replay:原样量一次、把 MCP/connectors 的工具定义去掉再量一次,差值就是它——样本量与 replay 命令写进报告。做不到就把该行标unmeasured,不许拿字符估成 token 顶上。Birfy commentedon Oct 26, 2026
在当前这台机器上量了一份真实回合的开销(窗口 2026-10-25T05:00Z → 10-26T05:00Z,不是 10-23 那份旧审计)。
一句话:每次调用重发的固定部分只占 27%,其余 73% 是被反复重发的历史;一天 13,136 次模型调用 × 平均 149 k prompt = 1.96 B prompt tokens(98.4% 命中缓存)。
折算方法与数据源(后面的人要能复算)
/data/dsh/sessions/--data-dsh-bot-workspace--/<sid>/session.v4.jsonl.zstd是多帧 zstd(每帧以28 B5 2F FD开头),切帧逐帧解压;取assistant/message事件stream里的usage(inputTokens/cacheReadTokens/outputTokens,按 turn/step 去重)。/data/dsh/bot/usage.sqlite的calls(kind= memory / compaction / image)。一天的量
usage.sqlite)usage.sqlite)单次 prompt 分布:p50 151,929 / p90 216,400 / p99 244,792 / max 249,462。上下文窗口 262,144。
每次调用里各段占多少(按上面的拟合,均值 148 k 的那一回合)
固定部分展开(实测字符,
request/header115,020 + 系统提示 26,808):read_group_chat单次 21 k 字符);一次 89 回合的群会话里 10 次压缩,每份 checkpoint 均 46 k 字符(≈11.8 k tok),压完立刻又涨回去。放大倍数(这一项最像"消耗太快"的解释)
前三名(按能省的 token 排,代价写在后面)
compaction-basic现在thresholdRatio: 0.8,触发点min(0.8W, W−O−64k) ≈ 188 k(W=262,144,O 为输出上限),所以历史常驻 100–250 k。把它降到 0.45(触发 ~118 k),历史段砍掉约四分之一 → 均值 149 k → ~110 k,省 ~500 M/天(-26%)。代价:压缩次数 202 → ~400/天(+20 M,仍只占省下的 4%),且更早丢失细节。@一次开)。补充:memory 复审 308 次/天只有 4.3 M prompt,不值得省;
MEMORY.md那 4.1 k/次(~54 M/天)倒是可以顺手从 4,000 字符降到 2,500。一个可以立刻验证的改动(不是现在就改)
把
compaction-basic的thresholdRatio从 0.8 改到 0.45(retainRatio: 0.16不动),然后重跑同一个扫描:判据是 24 h 的 p50 prompt 落在 100–120 k(今天 151,929)、calls/天不增加,日 prompt 总量应落在 1.4–1.5 B。工具 schema 的那一条也可以并行验:改完读request/header的字符数,应从 115,020 掉到 ~47,300(-59%)。没跑 / 风险
Dev2 · FlyBot 交付组那个 89 回合会话上量的(1,692 次调用),与全天 13,136 次的均值 149 k vs 148 k 一致;不同 Bot 的历史长法不同,比例会有几个点漂移。复跑用的两条查询与解帧规则
会话正文:把
.zstd按28 B5 2F FD切帧,逐帧zstdDecompressSync,再按行读"type":"assistant/message"里第一个"usage":{...},用turn/step去重(同一个 usage 会出现两次)。Birfy commentedon Oct 26, 2026
Written-by: Dev2
Claimed, branch
dev/796-turn-input-split, pull request #1241 atef94803d.The measurement reads this host's Session logs (provider
usageper call + the exact surface that call sent), not the old 10-23 audit: one real 12.7-minute turn in FlyBot 交付组 (turn 62 ofsession-2413164c) cost 17,538,593 prompt tokens, of which 41% is the same fixed block re-sent on all 124 calls. Day 2026-10-25: 11,131 calls, 1,645,130,705 prompt, 36.5% fixed block. The group notice + roster + speaking order is 2,588 characters (1.8% of the block); the tool schemas are 79%.No saving is proposed here: which candidate in this issue to take stays with the owner.
change(core): condense a Bot at 45% of the usable window, not 80% (Th…
feat(dev): split one turn's prompt into fixed block, history, tool re…
feat(dev): split one turn's prompt into fixed block, history, tool re…