feat(router): route embedded & nested JSON through the compressor dispatch (#2623)

## Description

`ContentRouter` only compressed JSON when the **whole** `tool_result`
block was a single JSON value. JSON embedded inside larger output (`gh
api` dumps, MCP tool results, `curl | jq` tails, log lines ending in a
JSON blob) was invisible to the JSON compressors — and in practice that
embedded shape is the large majority of JSON an agent actually sees.

This adds a structural routing step: find balanced JSON spans at **any
offset** in a block and route each one through the router's **existing,
unchanged** `_apply_strategy_to_content`, splicing the result back with
the surrounding bytes kept exact.

Because each span takes the same dispatch path a whole-block JSON
already takes, SmartCrusher/CodeCompressor register their `<<ccr:…>>`
retrieval markers exactly as before — CCR is hash-keyed, so it is
location-agnostic and unaffected by nesting.

Closes #

## Type of Change

- [x] New feature (non-breaking change that adds functionality)

## Changes Made

- New `headroom/transforms/recursive_json.py` — `route_embedded_json()`:
deterministic balanced-span scan + splice; skips spans already carrying
a `<<ccr:` marker (never re-compresses); token-gated.
- One guarded call at the top of `_apply_strategy_to_content` plus an
`_allow_embedded` **one-shot re-entrancy guard** (not a depth cap).
- **No size/min or depth thresholds** — the only gates are correctness
(round-trip) and benefit (token reduction). Strict no-op when a block
has no embedded JSON, so the 97%+ of non-JSON blocks are byte-identical
to today.
- Deterministic + per-block → prefix-cache- and CCR-store-stable.
- `tests/test_recursive_json.py`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check`)
- [x] Type checking passes (`mypy`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
$ python -m pytest tests/test_recursive_json.py tests/test_content_router_compact_json.py tests/test_content_router_tool_role_reversibility.py -q
19 passed in 5.90s

$ ruff check headroom/transforms/recursive_json.py headroom/transforms/content_router.py tests/test_recursive_json.py
All checks passed!

$ mypy headroom/transforms/recursive_json.py headroom/transforms/content_router.py
Success: no issues found in 2 source files
```

## Real Behavior Proof

- **Environment:** local,
`ContentRouter(ContentRouterConfig(lossless=False))`, pure-Python
content detector.
- **Exact steps:** `router.compress(block)` where `block` = prose with a
120-row JSON array embedded mid-text (`"I queried the ECS API
...\n[{...}]\nAll services healthy."`).
- **Observed result:** `strategy_used=MIXED`; block **11,986 → 3,798
chars**; leading/trailing prose preserved byte-exact; the embedded JSON
folded to a columnar table. Previously this block's embedded array was
not routed to the JSON compressor at all.
- **CCR:** a span already containing `<<ccr:` is passed through
untouched (unit-tested); folded spans go through the unchanged dispatch,
so markers register and resolve identically to whole-block JSON.
- **Not tested:** live proxy end-to-end with markers force-enabled
(covered by the unchanged dispatch path + unit tests); non-CC transcript
shapes beyond the local corpus.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] My changes generate no new warnings
- [x] I have added tests that prove my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I did **not** edit `CHANGELOG.md`

## Additional Notes

Scoped to the OSS structural-routing step only. The lossless *fold
kinds* it routes into (JSON columnar / log template) are maintained in
the `headroom-lossless-guard` extension. N/A: no docs/screenshots;
`Closes #` left blank (no tracking issue).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Tejas Chopra 2026-07-27 20:52:18 -07:00 committed by GitHub
parent e530de5ad2
commit 57bf720d5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 261 additions and 0 deletions

View file

@ -2807,6 +2807,7 @@ class ContentRouter(Transform):
language: str | None = None,
question: str | None = None,
bias: float = 1.0,
_allow_embedded: bool = True,
) -> tuple[str, int, list[str]]:
"""Apply a compression strategy to content.
@ -2827,6 +2828,37 @@ class ContentRouter(Transform):
log]``). Log readers use this to see *how* we got to the
final compressor without parsing decision_reason strings.
"""
# ── STRUCTURAL (embedded) JSON routing ───────────────────────────────
# Before anything else: if this block is not a single JSON value but
# CONTAINS balanced JSON span(s), route each span through this very
# dispatch and splice the result back (surrounding bytes kept exact).
# This is how nested/embedded JSON reaches the JSON compressors at all —
# today's linear splitter never sees it. Each span goes through the
# UNCHANGED path, so SmartCrusher/CodeCompressor register their
# `<<ccr:…>>` markers exactly as for a whole-block JSON (CCR is hash-
# keyed → location-agnostic). `_allow_embedded=False` on the recursive
# call is a one-shot re-entrancy guard (NOT a depth cap). Deterministic +
# benefit-gated (no size/min thresholds) → prefix-cache- and CCR-store-
# stable, and a strict no-op when the block has no embedded JSON.
if _allow_embedded:
from headroom.transforms.recursive_json import route_embedded_json
def _dispatch_span(span: str) -> str | None:
strat = self._strategy_from_detection_type(_detect_content(span).content_type)
text, _t, _c = self._apply_strategy_to_content(
span,
strat,
context,
question=question,
bias=bias,
_allow_embedded=False,
)
return text if text != span else None
routed = route_embedded_json(content, _dispatch_span, tok=_estimate_tokens)
if routed is not None:
return routed, _estimate_tokens(routed), ["embedded_json"]
# Track original tokens for TOIN recording
original_tokens = _estimate_tokens(content)
compressed: str | None = None

View file

@ -0,0 +1,162 @@
"""Structural (recursive) JSON routing for the ContentRouter.
Today the router is *linear*: it splits a block into textual sections and picks
one strategy per section. It never looks *inside* a structure, so JSON embedded
in a larger payload (a ``gh api`` dump, an MCP result, a ``curl | jq`` tail) is
invisible to the JSON compressors even though, in practice, that embedded shape
is the overwhelming majority of JSON the agent ever sees.
This module adds the missing structural step: find balanced JSON spans at any
offset in a block and route each one through the router's *existing* dispatch,
splicing the result back in place with the surrounding bytes kept exact.
Why this is CCR-safe by construction
-------------------------------------
Each span is handed to the router's own ``_apply_strategy_to_content`` — the same
code path a whole-block JSON already takes so SmartCrusher / CodeCompressor
register their ``<<ccr:HASH>>`` retrieval markers exactly as they do today. CCR
is hash-keyed and therefore location-agnostic: a marker resolves whether it sits
at the top of a block or nested inside one. This module never touches the CCR
store; it only relocates where the dispatch is invoked.
Safety invariants (no thresholds outcome-gated only):
* A span that already contains a ``<<ccr:`` marker is passed through untouched
(never re-compressed / re-hashed).
* Traversal is deterministic (left-to-right, no clocks/rng) so prompt bytes and
CCR hashes are stable across turns prefix cache and store both stay stable.
* A rewrite is kept only if it is strictly smaller in tokens; otherwise the
original bytes are returned unchanged. No min-size, no max-depth.
"""
from __future__ import annotations
import json
from collections.abc import Callable
_OPEN = "[{"
_CLOSE = "]}"
_PAIR = {"}": "{", "]": "["}
#: A dispatch callback: given a JSON span's text, return the compressed text
#: (which may carry CCR markers) or ``None`` to leave it unchanged.
Dispatch = Callable[[str], "str | None"]
def _match_span(text: str, start: int) -> int | None:
"""Index just past the balanced JSON container opening at ``start`` (honoring
string/escape rules), or ``None`` if it never balances."""
stack: list[str] = []
in_str = esc = False
for j in range(start, len(text)):
ch = text[j]
if in_str:
if esc:
esc = False
elif ch == "\\":
esc = True
elif ch == '"':
in_str = False
continue
if ch == '"':
in_str = True
elif ch in _OPEN:
stack.append(ch)
elif ch in _CLOSE:
if not stack or stack[-1] != _PAIR[ch]:
return None
stack.pop()
if not stack:
return j + 1
return None
def _spans(text: str) -> list[tuple[int, int]]:
"""Deterministic list of ``(start, end)`` for top-level balanced JSON spans.
Nested spans are not returned separately the dispatch handles depth."""
out: list[tuple[int, int]] = []
i, n = 0, len(text)
while i < n:
if text[i] in _OPEN:
end = _match_span(text, i)
if end is not None:
out.append((i, end))
i = end
continue
i += 1
return out
def _has_routable_json(span: str) -> bool:
"""True if ``span`` parses and contains an array of objects somewhere — the
shape the JSON compressors actually act on. Cheap structural check, no size
threshold."""
try:
v = json.loads(span)
except (ValueError, TypeError):
return False
found = False
def walk(x: object) -> None:
nonlocal found
if found:
return
if isinstance(x, list):
if len(x) >= 2 and sum(isinstance(e, dict) for e in x) >= 0.8 * len(x):
found = True
return
for e in x:
walk(e)
elif isinstance(x, dict):
for e in x.values():
walk(e)
walk(v)
return found
def route_embedded_json(
content: str,
dispatch: Dispatch,
*,
tok: Callable[[str], int] | None = None,
) -> str | None:
"""Route every embedded JSON span in ``content`` through ``dispatch`` and
splice the results back in place. Returns the rewritten block, or ``None``
when nothing safe/smaller applied.
``content`` that is itself a single JSON value is intentionally skipped the
caller already routes pure-JSON blocks; this exists for the *embedded* case.
"""
tok = tok or (lambda s: max(1, len(s) // 4))
spans = _spans(content)
if not spans:
return None
# Whole-block JSON is the caller's job, not ours.
if len(spans) == 1 and spans[0] == (0, len(content.strip())):
return None
repls: list[tuple[int, int, str]] = []
for a, b in spans:
chunk = content[a:b]
if "<<ccr:" in chunk: # R1: already compressed — never re-route
continue
if not _has_routable_json(chunk):
continue
out = dispatch(chunk)
if out is None or out == chunk:
continue
if tok(out) < tok(chunk): # benefit gate (outcome, not a threshold)
repls.append((a, b, out))
if not repls:
return None
parts: list[str] = []
last = 0
for a, b, out in repls:
parts.append(content[last:a])
parts.append(out)
last = b
parts.append(content[last:])
new = "".join(parts)
return new if tok(new) < tok(content) else None

View file

@ -0,0 +1,67 @@
"""Unit tests for headroom.transforms.recursive_json — the structural (embedded)
JSON routing step. Uses a fake dispatch so the mechanism is tested in isolation
from the real compressors."""
from __future__ import annotations
import json
from headroom.transforms.recursive_json import route_embedded_json
def _upper_dispatch(span: str) -> str | None:
"""Fake compressor: returns a shorter deterministic stand-in for any span."""
try:
v = json.loads(span)
except ValueError:
return None
return f"<TABLE n={len(v)}>" if isinstance(v, list) else "<OBJ>"
def test_embedded_json_routed_and_surroundings_exact() -> None:
payload = json.dumps([{"id": i, "ok": True} for i in range(6)], separators=(",", ":"))
content = f"Fetched rows from API:\n{payload}\nDone (200 OK)."
out = route_embedded_json(content, _upper_dispatch)
assert out is not None
assert out.startswith("Fetched rows from API:\n")
assert out.endswith("\nDone (200 OK).")
assert "<TABLE n=6>" in out
def test_ccr_marker_span_passed_through() -> None:
# A span already carrying a CCR marker must never be re-routed (R1).
content = 'prefix [{"a":1,"b":2},{"a":3,"b":"<<ccr:deadbeef,json,900>>"}] suffix'
out = route_embedded_json(content, _upper_dispatch)
assert out is None # only span contains a marker → skipped → nothing to do
def test_no_json_is_noop() -> None:
assert route_embedded_json("just prose, nothing structured here", _upper_dispatch) is None
def test_whole_block_json_is_callers_job() -> None:
# A block that IS a single JSON value is skipped (routed by the caller).
content = json.dumps([{"a": i} for i in range(5)], separators=(",", ":"))
assert route_embedded_json(content, _upper_dispatch) is None
def test_benefit_gate_declines_when_not_smaller() -> None:
payload = json.dumps([{"a": i} for i in range(5)], separators=(",", ":"))
content = f"x {payload} y"
# Dispatch that returns something LARGER → must be declined (outcome gate).
assert route_embedded_json(content, lambda s: s + " " * 999) is None
def test_deterministic() -> None:
payload = json.dumps([{"k": i} for i in range(8)], separators=(",", ":"))
content = f"a {payload} b {payload} c"
r1 = route_embedded_json(content, _upper_dispatch)
r2 = route_embedded_json(content, _upper_dispatch)
assert r1 == r2 and r1 is not None
assert r1.count("<TABLE n=8>") == 2 # both embedded spans routed
def test_scalar_array_not_routed() -> None:
# array of scalars is not a "routable" JSON shape (no dict rows)
content = "nums: [1,2,3,4,5,6,7,8] done"
assert route_embedded_json(content, _upper_dispatch) is None