fix(agent): hoist checkpoint carrier guard above the reasoning branches

The cherry-picked guard sat inside the codex-items block, which (a) is
skipped entirely in codex_responses mode (conversation_loop passes
drop_codex_reasoning_items=False there) and (b) is unreachable for
carriers whose adapter-joined commentary populates msg['reasoning'] —
the string-reasoning branch returns True first. Hoist the checkpoint
check above every reasoning branch so no carrier shape can be dropped,
in any api_mode. Adds the two carrier-shape tests that pin exactly this
(both fail with the guard in its original position).
This commit is contained in:
Teknium
2026-08-13 02:46:11 -07:00
parent 6c2d4efd02
commit ecdc25cacc
2 changed files with 29 additions and 10 deletions

View File

@@ -4655,6 +4655,18 @@ class AIAgent:
return False
elif content is not None and content != "":
return False
# A native compaction checkpoint makes a carrier never thinking-only,
# regardless of api_mode or which reasoning field is populated. The
# checkpoint is the server-side stand-in for already-pruned history
# and exists in exactly one place; the codex_responses adapter also
# surfaces commentary text via msg["reasoning"], so the string branch
# below would otherwise drop a carrier before the sidecar is ever
# inspected. Checked here — above every reasoning branch — so no
# carrier shape can fall into a drop path (#82108 review finding).
from agent.native_compaction import has_compaction_checkpoint
if has_compaction_checkpoint(msg.get("codex_reasoning_items")):
return False
reasoning = msg.get("reasoning_content") or msg.get("reasoning")
if isinstance(reasoning, str) and reasoning.strip():
return True
@@ -4668,16 +4680,6 @@ class AIAgent:
# empty-turn handling instead of being dropped here.
codex_items = msg.get("codex_reasoning_items")
if drop_codex_reasoning_items and isinstance(codex_items, list):
# A native compaction checkpoint rides this same sidecar and is
# the server-side stand-in for already-pruned history. Dropping
# the turn takes the checkpoint with it — the request then carries
# neither the compacted history nor the checkpoint that replaces
# it. Compaction pruning filters items for this reason rather than
# popping the key; a carrier is never thinking-only.
from agent.native_compaction import has_compaction_checkpoint
if has_compaction_checkpoint(codex_items):
return False
return any(
isinstance(item, dict) and item.get("type") == "reasoning"
for item in codex_items

View File

@@ -225,3 +225,20 @@ class TestCompactionCheckpointCarrier:
if item.get("type") == "compaction"
]
assert surviving == [self.CHECKPOINT]
def test_reasoning_text_carrier_is_not_thinking_only(self):
"""codex_responses adapter surfaces commentary via msg['reasoning'];
the string branch must not drop a checkpoint carrier (#82108 review:
the original guard sat below this branch and never fired)."""
msg = self._carrier([self.CHECKPOINT])
msg["reasoning"] = "some commentary the adapter joined in"
assert not AIAgent._is_thinking_only_assistant(msg)
def test_reasoning_text_carrier_survives_even_when_codex_items_kept(self):
"""codex_responses mode passes drop_codex_reasoning_items=False; the
guard must still protect the carrier through the reasoning branch."""
msg = self._carrier([self.REASONING, self.CHECKPOINT])
msg["reasoning"] = "commentary"
assert not AIAgent._is_thinking_only_assistant(
msg, drop_codex_reasoning_items=False
)