Skip to content

fix(codex): extract turn/completed token usage on app-server runtime - #37196

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/codex-app-server-extract-turn-usage-36801
Closed

fix(codex): extract turn/completed token usage on app-server runtime#37196
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/codex-app-server-extract-turn-usage-36801

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

What does this PR do?

The codex app-server runtime (provider: openai-codex) extracts NO token usage from turn/completed events, so Hermes is blind to a session growing toward the context window until the backend 400s and the session hard-resets to history=0 (issue #36801, root cause #2). Other transports already surface per-turn usage (bedrock.py, chat_completions.py, anthropic.py); the codex app-server path was the lone gap. This PR parses turn.usage off the terminal turn/completed event into TurnResult.usage and surfaces it as codex_usage on the runtime return dict, giving the conversation loop the context-growth signal it needs.

Sibling code paths that may need the same fix: proactive-threshold compaction and retire-handoff seeding in agent/conversation_loop.py (issue #36801 parts 2-4). Intentionally left out of this PR's scope to keep the diff small and the usage-extraction root cause independently reviewable — happy to widen if preferred.

Related Issue

Fixes #36801

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/transports/codex_app_server_session.py: add usage: Optional[dict] to TurnResult; parse turn.usage in the existing turn/completed handler (tolerant of the {input_tokens,output_tokens,total_tokens} dict shape and a bare-int total; derives total when absent; never raises).
  • agent/codex_runtime.py: surface codex_usage on the run_codex_app_server_turn return dict.
  • tests/agent/transports/test_codex_app_server_session.py: add TestTurnUsage (4 cases) + a no-usage regression assertion on the existing happy-path turn.

How to Test

  1. uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest tests/agent/transports/test_codex_app_server_session.py -v
  2. Regression-verified: stripping the turn.usage parse turns the 3 extraction tests red (TypeError: 'NoneType' object is not subscriptable); restoring it returns all 61 to green.
  3. The no-usage case (test_turn_completed_missing_usage_is_none) proves older app-server builds / omitted usage leave usage as None rather than fabricating zeros.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run the focused suite (tests/agent/transports/test_codex_app_server_session.py, 61 passing) — full pytest tests/ -q not run locally
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A (no config keys)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) — pure-Python dict parsing, no platform-specific paths; only macOS exercised
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Copilot AI review requested due to automatic review settings June 2, 2026 04:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds per-turn token usage extraction for the Codex app-server transport, and surfaces it to the runtime so the conversation loop can observe context growth similar to other transports.

Changes:

  • Parse turn.usage from the turn/completed notification into TurnResult.usage (supporting dict and bare-int shapes).
  • Surface the parsed usage in run_codex_app_server_turn output as codex_usage.
  • Add/extend tests to cover usage presence/absence and total derivation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
tests/agent/transports/test_codex_app_server_session.py Adds assertions and a new test class to validate per-turn usage parsing behavior.
agent/transports/codex_app_server_session.py Extends TurnResult and parses usage from turn/completed events.
agent/codex_runtime.py Exposes per-turn usage on the returned runtime turn dict (codex_usage).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +596 to +602
if tot is None and isinstance(inp, int):
tot = inp + (out or 0)
result.usage = {
"input_tokens": inp or 0,
"output_tokens": out or 0,
"total_tokens": tot or 0,
}
# per-turn usage; the codex app-server path was the lone blind spot
# (issue #36801) — without this the runtime cannot see a session growing
# toward the context window until the backend 400s and the session resets.
usage: Optional[dict] = None
Comment on lines +587 to +595
# Capture per-turn token usage when codex reports it. The
# app-server nests it under turn.usage; tolerate both the
# {input_tokens,output_tokens,total_tokens} dict shape and a
# bare int total. Advisory only — never raises.
raw_usage = turn_obj.get("usage")
if isinstance(raw_usage, dict):
inp = raw_usage.get("input_tokens")
out = raw_usage.get("output_tokens")
tot = raw_usage.get("total_tokens")
Comment thread agent/codex_runtime.py
# so the conversation loop can observe context growth on this runtime
# path the way the chat-completions path already does — the
# prerequisite signal for proactive compaction (left to a follow-up).
"codex_usage": getattr(turn, "usage", None),
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/openai OpenAI / Codex Responses API codex P2 Medium — degraded but workaround exists labels Jun 2, 2026
The codex app-server runtime (provider: openai-codex) extracted no token
usage from turn/completed events, so Hermes was blind to a session growing
toward the context window until the backend 400s and the session hard-reset
to history=0 (issue NousResearch#36801, root cause NousResearch#2). Other transports already surface
per-turn usage (bedrock.py, chat_completions.py, anthropic.py); the codex
app-server path was the lone gap.

Parse turn.usage off the terminal turn/completed event into TurnResult.usage
(tolerant of the {input_tokens,output_tokens,total_tokens} dict shape and a
bare-int total; derives total when absent; never raises) and surface it as
codex_usage on the codex_runtime return dict, giving the conversation loop
the context-growth signal it needs.
@briandevans
briandevans force-pushed the fix/codex-app-server-extract-turn-usage-36801 branch from 8c0e05f to 605f0e3 Compare June 5, 2026 22:16
@briandevans

Copy link
Copy Markdown
Contributor Author

Closing to keep the queue focused — no maintainer pickup in 26 days. Happy to reopen if the Codex app-server token-usage extraction is still useful.

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

Labels

codex comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/openai OpenAI / Codex Responses API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex app-server runtime: long sessions grow unbounded → hard context reset (no proactive compaction)

3 participants