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:
22
run_agent.py
22
run_agent.py
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user