Skip to content

feat(codex): republish todo_list revisions as they are ticked off - #473

Open
declan-scale wants to merge 2 commits into
nextfrom
declan-scale/codex-todo-list-updates
Open

feat(codex): republish todo_list revisions as they are ticked off#473
declan-scale wants to merge 2 commits into
nextfrom
declan-scale/codex-todo-list-updates

Conversation

@declan-scale

@declan-scale declan-scale commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What

Codex does not reopen its plan. One todo_list item is opened at the start of a turn and revised in place through item.updated, with item.completed only arriving at the very end. The tap dropped those updates, so a consumer saw the plan exactly twice:

  • as first written, nothing done
  • at end of turn, everything done

Real trace from the golden agent (item_1), request vs response of the same call:

arguments: [{"text": "Probe safe local utility tools", "completed": false}, ... x4]
result:    [{"text": "Probe safe local utility tools", "completed": true},  ... x4]

Nothing in between, though the agent ticked them off over ~7 minutes.

This forwards each revision as a ToolResponseContent under the item's existing tool_call_id. Consumers that key responses by call id (the GenAI Ops Hub chat does) now render the checklist filling in as the turn runs, and the last response received is still the final state.

Scope

_PROGRESSIVE_TOOL_ITEMS is {"todo_list"} only. The other tool item types carry no intermediate state worth acting on, and republishing command_execution output on every chunk would be pure noise. Updates for an item that was never opened are ignored — there is no request for them to answer.

tool_call_count still counts the call once, not once per revision.

Testing

  • pytest tests/lib/adk/test_codex_sync.py — 55 pass, 4 new (TestTodoListUpdates: republish per revision, call counted once, orphan update ignored, other tool items unaffected)
  • pytest tests/lib/core/harness/test_harness_codex_sync.py — 9 pass
  • ./scripts/lint — ruff + pyright clean

Paired change

The GenAI Ops Hub side of this is scaleapi/scaleapi#153721's follow-up, which maps every codex tool name onto a real chat block. It already takes the newest response per call id, so it picks these up with no further change.

🤖 Generated with Claude Code

Greptile Summary

This PR fixes a gap in the Codex event-stream parser where todo_list checklist revisions were silently dropped between the initial open and final completion, meaning consumers only ever saw the plan at its two extremes. Each item.updated for a todo_list item is now forwarded as a ToolResponseContent under the same tool_call_id, letting consumers render the checklist filling in progressively during a turn.

  • _codex_sync.py: Adds a _PROGRESSIVE_TOOL_ITEMS frozenset ({"todo_list"}) and a new item.updated branch in _handle_item that emits an intermediate StreamTaskMessageFull(ToolResponseContent) for every revision, guarded by item_id in self._tool_open so orphan updates (no preceding item.started) are silently ignored. tool_call_count is incremented only on item.started, preserving the existing count semantics.
  • obs_ids.py: Updates the ddtrace tracer import path from ddtrace to ddtrace.trace to match the newer ddtrace API; the existing ImportError catch ensures graceful degradation on older installs.
  • Tests: Four new cases in TestTodoListUpdates cover progressive republish, single-count invariant, orphan-update guard, and the negative case confirming other tool types remain unaffected.

Confidence Score: 5/5

Safe to merge — the change is additive and well-tested, with clear guards against the orphan-update edge case.

The new item.updated path is additive: it only activates for todo_list items that already have an open item.started, and the guard is unit-tested. The item.completed handler is unchanged and still emits the definitive final response. The obs_ids.py change degrades gracefully on older ddtrace installs. All four new tests pass and cover the meaningful edge cases.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
src/agentex/lib/adk/_modules/_codex_sync.py Adds item.updated forwarding for todo_list tool items as intermediate ToolResponseContent under the same tool_call_id; guard on _tool_open prevents dangling responses for orphan updates. Logic is correct and well-isolated.
src/agentex/lib/core/tracing/obs_ids.py Updates ddtrace tracer import from ddtrace to ddtrace.trace to match newer API; ImportError catch ensures graceful fallback on older installs. Import order resorting is cosmetic.
tests/lib/adk/test_codex_sync.py Adds 4 new tests covering: progressive republish on each update, single count across updates, orphan update ignored, and non-todo tool items unaffected. Tests are comprehensive and accurate.

Reviews (3): Last reviewed commit: "fix(tracing): satisfy ruff and pyright o..." | Re-trigger Greptile

Codex does not reopen its plan: one `todo_list` item is opened at the
start of a turn and revised in place through `item.updated`, with
`item.completed` only arriving at the very end. The tap dropped those
updates, so a consumer saw the plan exactly twice — as first written
(nothing done) and then, at end of turn, fully finished.

Forward each revision as a `ToolResponseContent` under the item's
existing `tool_call_id`. Consumers that key responses by call id (the
GenAI Ops Hub chat does) now render the checklist filling in as the turn
runs, and the last response received is still the final state.

Scoped to `todo_list` via `_PROGRESSIVE_TOOL_ITEMS`: the other tool item
types carry no intermediate state worth acting on, and republishing
`command_execution` output on every chunk would be pure noise. Updates
for an item that was never opened are ignored — there is no request for
them to answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@declan-scale
declan-scale force-pushed the declan-scale/codex-todo-list-updates branch from 078bfad to da3b575 Compare July 28, 2026 14:08
@declan-scale
declan-scale changed the base branch from main to next July 28, 2026 14:08
CI lint has been red on `next` since c67e177 added this file:

- ruff I001: typing import block not sorted per the repo's isort config
- pyright reportPrivateImportUsage: `tracer` is not re-exported from
  `ddtrace`, so it must be imported from `ddtrace.trace`

The `except ImportError` guard still covers ddtrace being absent, and
`ddtrace.trace.tracer` exists in the pinned ddtrace 4.10.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant