Skip to content

Commit 4300a84

Browse files
authored
feat(dev): split one turn's prompt into fixed block, history, tool results and task (#796) (#1241)
Owner: Dev2 Reviewer: Reviewer Written-by: Dev2 Pushed-by: Dev2 (INC 补件 + rebase) Fixes #796 Tip SHA: a552d6a #796 asked where a turn's input tokens go. The [2026-10-25 page](https://github.com/ThinkFlowLab/FlyBot/blob/main/docs/reports/2026-10-25-token-spend.md) answered the ledger questions and said the composition was **not measured**, because the ledger's JSON holds no request. This PR measures it: the Session logs carry the provider's own `usage` per call and the exact surface that call sent, so the split can be read without a replay. ## Reproduce - Sample: `session-2413164c`, turn 62 — Dev2 in group **FlyBot 交付组**, `2026-10-25T10:01:33.511Z .. 10:14:17.073Z` (12.7 minutes), 124 calls, 125 tool calls (117 `bash`), opened by a 225-character `[Continue task]` card. - `node core/dev/turn-split.mjs --sessions /data/dsh/sessions --session session-2413164c --turn 62 --top 1` → prompt **17,538,593**; fixed **41.0%**, history **29.0%**, tool results **24.1%**, task **6.0%**; first call 215,263, largest 248,706, last 156,754. - `node core/dev/turn-split.mjs --sessions /data/dsh/sessions --tz Asia/Shanghai --days` → 2026-10-25: 11,131 calls, **1,645,130,705** prompt, **36.5%** of it the fixed block. - The finding: the fixed block is 48,583 tokens per call at p50 and is re-sent on every call; one 12.7-minute turn paid it 124 times. The group notice + roster + speaking order is 2,588 characters — 1.8% of the block — while the tool schemas are 79%. ## What changed - `core/dev/turn-split.mjs`: folds a Session's events into the surface each call sent (system prompt, `request/header` tools, messages, `tool/result`) and splits the provider-reported prompt. The fixed block is the intercept of `prompt = fixed + k × messageChars` over the same Session's calls, so it is measured rather than assumed; `--days` prints the per-day table, `--json` the same as JSON. - `core/test/turn-split.test.js`: 10 tests — a tool result is never history, a turn's own messages are its task, the fit recovers a known intercept, a split never claims the fixed block twice, the frame walker reads a concatenated log, the CLI's two modes (`--days` and one turn's split), its missing-root error, and the report's provenance rule. - `docs/reports/2026-10-26-turn-input-split.md`: the measurement, the method and every number's provenance. - `docs/subsystems/ds-bot-core.md`: names the script (`core/dev/` is owned by that page). - `patches/ds-bot/files/dev/turn-split.mjs`, `patches/ds-bot/files/test/turn-split.test.js`: whole-file mirrors, which `check` requires. ## What I ran - `node --test core/test/turn-split.test.js` → `# tests 10 / # pass 10 / # fail 0`. - Rebased onto `2d3eb88c` (main) with no conflict — main’s nine merges since `d04712bb` touch none of this PR’s six files. - `node scripts/ds-bot-patches.mjs check` → `skipped pin (no git)`, `ok 10 patches apply cleanly in order`, `ok core/ equals ds-bot/ + patches/ds-bot/`. - `node .github/scripts/check-mojibake.mjs --check` → 0 occurrences of U+FFFD in 1,071 text files. - oxlint (97 rules) on the two new files → 0 warnings, 0 errors. - Both CLI modes against this host's real logs, at the tip: the numbers in §Reproduce. ## Red without the source change - Knife 1: `splitStep` returns `fixed = 0` → tests 5 and 8 red (`a split accounts for the whole prompt and never claims the fixed block twice`, `the --days table divides each day by the same measured fixed block`), 8 pass / 2 fail. - Knife 2: a `tool/result` counted as history → tests 1 and 2 red, 8 pass / 2 fail. - Knife 3, the one the INC added: `row.fixed += 0` in the `--days` loop → `not ok 8`, `0.0% !== 96.1%`, 9 pass / 1 fail. - Restored → 10/10. ## Not run - `npm run verify` and the full core suite: this host has no `git` and cannot install the harness (`npm ci` fails on esbuild here), so the core suite, `build-client --check` and the pack check were not run. CI runs them. - An ablated replay through `core/dev/llm-tap.mjs` per category: each variant is a real provider call. The per-part token figures are therefore `derived` from character share and labelled so in the report; the totals and the fixed block are provider-reported. ## Risk - Read-only tooling plus a report: no runtime path, no prompt, no workflow, no user-visible change. - The split inside one call is derived, not ablated; the report says which rows are which, and the provenance test fails if a row loses its source. - The clamp in `splitStep` keeps a prompt smaller than the fitted fixed block from producing a negative remainder; a Session whose system prompt shrank mid-run gets a slightly conservative fixed block. ## Deferred from the INC, non-blocking — tracked in #1259 - §2's fixed characters per call use each Session's **first** call surface while §4 counts the sample turn's **last** call; one clause naming the two moments. - §5, §7 and §8 have no rerun command in the repo; a `--json` note or a second mode would give them one. - `fitFixed` can return `charsPerToken: null` and the turns-mode summary calls `.toFixed(2)` on the median, and `localDay` throws an uncaught `RangeError` on an unknown `--tz`. ## Checkpoint <!-- flybot:checkpoint --> ```json { "pr": 1241, "claim": "Dev2", "issue": 796, "summary": "#796's composition is now measured from the Session logs rather than declared unmeasurable. core/dev/turn-split.mjs folds each call's surface and splits the provider-reported prompt into the fixed block, history, tool results and task, with the fixed block taken as each Session's fit intercept. The report records one real 12.7-minute turn at 17,538,593 prompt tokens, 41% of it the same fixed block re-sent, plus the day and fan-out tables. A 10-test suite pins the split rules and the report's provenance.", "files": [ "core/dev/turn-split.mjs", "core/test/turn-split.test.js", "docs/reports/2026-10-26-turn-input-split.md", "docs/subsystems/ds-bot-core.md" ], "checks": { "ran": "node --test core/test/turn-split.test.js; node scripts/ds-bot-patches.mjs check; node .github/scripts/check-mojibake.mjs --check", "result": "green", "at": "a552d6a6dcc72e223bf445c96f7c711231023496" }, "next": "INC at ef94803 answered: the --days path now has a test that fails 0.0% !== 96.1% without it; ready for a fresh verdict at this tip" } ``` <!-- /flybot:checkpoint -->
1 parent 2d3eb88 commit 4300a84

6 files changed

Lines changed: 1604 additions & 0 deletions

File tree

0 commit comments

Comments
 (0)