refactor(compact): store tool-pairing balance once per surface cut

toolPairingBalancedAfter previously answered by resolving a cached
positional successor and reading its before-balance, with a null-successor
depth fallback. Both queries are the same prefix property sampled at
adjacent cuts, so the cache now holds one per-cut balance sequence
(N nodes -> N+1 cuts) plus a seq->position index; before/after differ
only by a cut offset. The successor map, the duplicate rebuild/extend
fold loops, and the non-null assertion are gone, and the running counter
is named inProgressToolCalls. Docs describing the successor mechanism
are updated in place.
This commit is contained in:
Hypatia May
2026-07-16 11:40:00 +08:00
parent 3cccecee0a
commit 2793325df0
6 changed files with 51 additions and 79 deletions
+2 -2
View File
@@ -25,9 +25,9 @@ Both methods are **abstract** — the backend owns the entire strategy (token es
## Tool-pairing boundaries
The interface exports `toolPairingBalancedBefore(session, node)` and `toolPairingBalancedAfter(session, node)` for snapping and validating compaction edges. A safe edge has no unanswered assistant tool call crossing it. Each helper validates the node's seq against current surface membership and resolves the trailing edge from its cached positional successor, so a stale caller-held `node.next` cannot choose the cut.
The interface exports `toolPairingBalancedBefore(session, node)` and `toolPairingBalancedAfter(session, node)` for snapping and validating compaction edges. A safe edge has no unanswered assistant tool call crossing it. Each helper identifies the node by seq alone and answers from balances cached per cut in current surface order, so a stale caller-held `node.next` cannot choose the cut.
The private per-session cache is keyed by `session.surface.replaceGeneration` and the processed surface-node count. An unchanged generation extends the fold with unseen tail nodes only; a log-only append with no new surface node does no event reads, while a replacement generation rebuilds current membership, successors, and balances. Missing event seqs and a `tool/result` without a preceding open call reject as corrupt surface state.
The private per-session cache is keyed by `session.surface.replaceGeneration` and the processed surface-node count. An unchanged generation extends the fold with unseen tail nodes only; a log-only append with no new surface node does no event reads, while a replacement generation rebuilds current membership and balances. Missing event seqs and a `tool/result` without a preceding open call reject as corrupt surface state.
## Surface contract