Skip to content

fix(hide-consumed): parse tag-prefixed compress call text, stub live anchor summaries - #232

Merged
ranxianglei merged 2 commits into
masterfrom
2026-09-09_hide-consumed-tag-prefix
Sep 9, 2026
Merged

fix(hide-consumed): parse tag-prefixed compress call text, stub live anchor summaries#232
ranxianglei merged 2 commits into
masterfrom
2026-09-09_hide-consumed-tag-prefix

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Fixes #230.

Bug

rewriteCompressText in src/hide-consumed.ts JSON.parsed the compress tool-call text starting at index 0. Adapters (pi) persist the rendered ref tag in front of the tool-call text, so the parse always threw on tagged calls and the call was treated as unparseable:

  1. consumed sibling entries were never dropped from kept calls' args;
  2. live compress anchors kept their full summary text in the args forever — duplicating the rendered acp_summary message in every request (~22K tokens of duplicated args measured in session 01a07b3c, billion-context-pi #336).

Reproduced on master before the fix: a tag-prefixed mixed call (live + consumed entries) kept both entries, and a fully-live call with a 5000-char summary kept all 5000 chars in its args.

Fix

Independent of any reasoning feature — shipped on its own per #230.

Relationship to PR #226

PR #226 (open) bundles the same fix as its item 3, using a regex that strips the exact ref-tag prefix and without stubbing orphaned calls. This PR implements the {-scan approach prescribed in #230 (handles any leading metadata; also stubs orphans). If this PR merges first, #226 should drop its hide-consumed hunk; if the regex variant is preferred, this PR can be reworked.

Verification

  • npm run typecheck — clean
  • npm test — 628/628 pass (4 new tests in tests/hide-consumed-stub.test.ts)
  • npm run build — success

…ve anchor summaries

rewriteCompressText JSON.parsed the compress tool-call text at index 0.
Adapters (pi) persist the rendered ref tag in front of the JSON, so the
parse always failed on tagged calls: consumed sibling entries were never
dropped and live anchor args kept their full summary text forever —
duplicating the rendered acp_summary in every request (~22K tokens
measured, billion-context-pi #336).

- locate the JSON by scanning for the first '{' before parsing, preserving
  the tag prefix on output
- stub entry summaries above 200 chars in kept calls (active and newest
  orphans); the rendered block summary remains the authoritative copy
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

📦 Built Package Artifact

Branch: 2026-09-09_hide-consumed-tag-prefix (cbc78fb)

Option A — Install from npm PR tag (recommended)

In your adapter project:

npm install acp-kernel@pr-232

Each push to this PR publishes a new version under the pr-232 npm tag.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf acp-kernel-pr232.tgz
npm install ./package

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

Linking: fixes #230 (tag-prefixed call text parse failure). Verified locally on 8a9ffbd: 628/628 tests, typecheck + build clean. Note: the separate latent prune sub-id coverage bug is tracked in #231 (no PR yet — no shipped adapter projects sub-ids).

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

Independent review on 8a9ffbd — verified and looks good:

Verification (fresh checkout, not your local run)

  • npm run typecheck — clean
  • npm test — 628/628 pass (7 suites)
  • npm run build — success

Code review

  • parseCallText first-{ scan (src/hide-consumed.ts:25-39): safe — the ref-tag prefix (<acp tokens="…" type="…">ref</acp>\n, src/render-refs.ts:39-46) contains no braces, and scanning for { is strictly more general than fix: break the reasoning context floor and live-anchor summary duplication #226's exact-tag regex (survives any future leading metadata).
  • rewriteCompressText behavior change: fully-live calls are now re-serialized too (old code bailed when kept.length === content.length). Output is idempotent and byte-stable across turns for compact JSON, so no churn.
  • Stubbing: slice(0, 199) + "…" → exactly 200 chars, only applied when summary.length > 200; safe because the rendered acp_summary message remains the authoritative copy. Orphan stubbing via the compactCompressText fallback is correctly scoped to the newest orphans (KEEP_LAST_ORPHANED).
  • Pipeline ordering is fine: hide-compress-calls runs before render-refs (src/compress.ts:572); the preserved prefix is stripped and re-rendered with fresh token accounting by render-refs, so no stale tags leak.
  • Tests: all 4 new cases are meaningful — including building the tagged input from real renderVisibleRefs output rather than a hand-written prefix, and the mixed-call case asserting the consumed sibling is dropped with the live entry byte-identical. No as any / @ts-ignore.

Process notes

Ready to merge on your side — I can't merge PRs (human-only).

Non-strict-tool providers (qwen etc.) pass content as a JSON-encoded
STRING inside the args — the compress tool accepts it, so the rewrite
must too. Measured: all 52 calls in the billion-context-pi #336 storm
session used this form, so neither the dead-range filter nor the
summary stubbing ever applied there. parseCallText now parses the
string form; serializeCompacted re-stringifies so the shape round-trips
unchanged.
@ranxianglei

Copy link
Copy Markdown
Owner Author

Extended: forensic replay of the #336 storm session showed ALL 52 compress calls use content as a stringified array (non-strict-tool provider form), 0 tag-prefixed — so the index-0 scan alone would still not have compacted that session. Added: parseCallText accepts the string form; serializeCompacted re-stringifies to preserve the original shape. Verified on the real session's last call: 924 → 278 chars, string shape kept, live range kept. 629/629 tests.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

Reviewed the extension on cbc78fb — verified and good to go:

Verification (fresh checkout)

  • npm run typecheck — clean
  • npm test — 629/629 pass
  • npm run build — success

Diff review

  • parseCallText string path (src/hide-consumed.ts:37-52): safe on all edge shapes — inner JSON.parse throwing or yielding a non-array (numbers, null, objects, garbage) leaves content = null → no rewrite; empty inner array bails like the array path.
  • serializeCompacted shape preservation: JSON.stringify(compacted) embedded as a JSON string keeps {"content":"[...]" byte-shaped; key order is preserved since content is already a key on the spread obj. Round-trip is idempotent — re-parsing a rewritten call yields stubbed (≤200-char) summaries, so compactCompressText reports no change and the text stops churning.
  • New test asserts the full round-trip: string shape preserved, consumed sibling dropped, live entry stubbed to exactly 200 chars ending in .

On the forensic finding — this is the important one, and it changes which fix actually addresses #336: on master, a stringified-content call parses fine at index 0 but then bails at the Array.isArray(obj.content) check, so the tag-prefix fix alone (8a9ffbd) would indeed not have compacted that session. The #230 repro (tag prefix) and the #336 storm (stringified content) were two distinct parse-failure modes of the same fragile parser; both are now handled. The 924 → 278 char number comes from your replay — I verified the mechanism via the test path rather than the session data, but the stub math is consistent.

Ready to merge on your side — I can't merge PRs (human-only).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hide-consumed: parseCallText fails on tag-prefixed compress call text, so live anchor args are never stubbed

1 participant