You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An orchestrator driving sub-agents has no way to answer two questions about any session it manages — its own children included:
How full is that session's context right now?
When exactly did its last turn finish?
Both are prerequisites for deliberate context management. Without them, the only available strategy is to keep sending turns until something breaks (see #2967: a full context window bricks a session with "Prompt is too long" and the turn path never auto-compacts), or to compact/respawn on a blind guess.
sys_session_get_info — the one agent-facing surface that returns session metadata — currently returns neither:
No token counts, no context window, no timestamps of any kind. sys_session_get_history does not help either — its items are bare {type, role, text} with no timestamps and no usage. So status: "idle" is the entire temporal signal available: it distinguishes "not running" from "running", and nothing else. A session that finished 30 seconds ago and one that finished three hours ago are indistinguishable.
Why this matters
Deciding compact vs. new session. These are different operations with different costs, and picking correctly requires knowing the fill level. Compaction is lossy and, per #3254, repeated compaction can leave a sub-agent silently stalled; starting fresh costs a re-prime of the task context. An orchestrator should be able to see "this child is at 78% of its window" and act before the wall, rather than after.
Prompt-cache economics on Claude subscriptions. The prompt cache has a ~1 hour TTL. That makes elapsed-time-since-last-turn directly load-bearing for a cost decision:
Last turn finished 4 minutes ago → the prefix is almost certainly still cached; continuing the existing conversation is dramatically cheaper than re-priming, even at a high context fill.
Last turn finished 90 minutes ago → the cache is gone. The next turn re-reads the entire (possibly large) context at full input price. At that point starting a fresh session with a compact hand-off can be cheaper than continuing, and the crossover depends on exactly the two numbers this issue asks for.
Right now that arithmetic is impossible to perform, so the choice is made by vibes.
The data already exists — it just isn't projected
This is a plumbing gap, not a measurement gap.
Context size is already computed by every harness family and normalized to the same context_tokens key (input + cache_creation + cache_read):
inner/openai_agents_sdk_executor.py:1832, and max_context_tokens() on inner/executor.py:557 + per-executor overrides
It is already persisted on the session row as reserved instance-scoped labels (stores/conversation_store/__init__.py:111-112, where the comment calls them "the context-size metrics"):
omnigent.last_context_tokens
omnigent.last_context_window
written server-side at server/routes/sessions.py:3720-3722 and read back at :22387 / :22475.
And it is already surfaced — to the web UI, not to agents. The session snapshot exposes it as last_total_tokens (server/routes/sessions.py:22385-22389), which drives the context-occupancy ring. #1533 discusses that meter's behavior, so the value is understood to be user-facing already.
Timestamps likewise exist.created_at / updated_at are on conversation rows and on ChildSessionSummary (server/schemas.py:254-255, :777-778), and Usage already carries cache_read_input_tokens / cache_creation_input_tokens (server/schemas.py:821-827, "Consumed by the cache-aware server-side cost path").
The gap is a single projection: _session_get_info_via_rest (runner/tool_dispatch.py:3439) hand-picks ~15 fields from the server snapshot and includes no timestamp and no usage field. The in-process SysSessionGetInfoTool path has the same omission.
Proposed shape
Extend sys_session_get_info (and, in reduced form, the sessions rows of sys_session_list) with:
{
// … existing fields …"context": {
"tokens": 48213, // omnigent.last_context_tokens"window": 200000, // omnigent.last_context_window"used_fraction": 0.24,
"as_of": 1785000000, // when this measurement was taken"stale": false// true when the last turn failed/stalled — see #1533
},
"last_turn_completed_at": 1785000000,
"seconds_since_last_turn": 2317,
"created_at": 1784990000,
"updated_at": 1785000000,
"last_turn_cache": { // optional but directly answers the TTL question"cache_read_input_tokens": 41002,
"cache_creation_input_tokens": 1180
}
}
seconds_since_last_turn is derived, but worth returning explicitly: the client has no reliable clock relationship to the server, and the whole point is comparing against a ~1h TTL.
Parity across SDK and native is the requirement, not a nice-to-have. An orchestrator like polly mixes both in one tree and must apply one policy across them. Since all harness families already normalize to context_tokens, parity is mostly already paid for.
last_turn_cache is the lowest-confidence part of the ask — if the per-turn cache split is awkward to persist, last_turn_completed_at alone is enough to reason about the TTL.
Problem
An orchestrator driving sub-agents has no way to answer two questions about any session it manages — its own children included:
Both are prerequisites for deliberate context management. Without them, the only available strategy is to keep sending turns until something breaks (see #2967: a full context window bricks a session with "Prompt is too long" and the turn path never auto-compacts), or to compact/respawn on a blind guess.
sys_session_get_info— the one agent-facing surface that returns session metadata — currently returns neither:No token counts, no context window, no timestamps of any kind.
sys_session_get_historydoes not help either — its items are bare{type, role, text}with no timestamps and no usage. Sostatus: "idle"is the entire temporal signal available: it distinguishes "not running" from "running", and nothing else. A session that finished 30 seconds ago and one that finished three hours ago are indistinguishable.Why this matters
Deciding compact vs. new session. These are different operations with different costs, and picking correctly requires knowing the fill level. Compaction is lossy and, per #3254, repeated compaction can leave a sub-agent silently stalled; starting fresh costs a re-prime of the task context. An orchestrator should be able to see "this child is at 78% of its window" and act before the wall, rather than after.
Prompt-cache economics on Claude subscriptions. The prompt cache has a ~1 hour TTL. That makes elapsed-time-since-last-turn directly load-bearing for a cost decision:
Right now that arithmetic is impossible to perform, so the choice is made by vibes.
The data already exists — it just isn't projected
This is a plumbing gap, not a measurement gap.
Context size is already computed by every harness family and normalized to the same
context_tokenskey (input + cache_creation + cache_read):claude_native_bridge.py:4194,claude_native_forwarder.py:4224codex_native_forwarder.py:6066opencode_native_forwarder.py:788inner/openai_agents_sdk_executor.py:1832, andmax_context_tokens()oninner/executor.py:557+ per-executor overridesIt is already persisted on the session row as reserved instance-scoped labels (
stores/conversation_store/__init__.py:111-112, where the comment calls them "the context-size metrics"):omnigent.last_context_tokensomnigent.last_context_windowwritten server-side at
server/routes/sessions.py:3720-3722and read back at:22387/:22475.And it is already surfaced — to the web UI, not to agents. The session snapshot exposes it as
last_total_tokens(server/routes/sessions.py:22385-22389), which drives the context-occupancy ring. #1533 discusses that meter's behavior, so the value is understood to be user-facing already.Timestamps likewise exist.
created_at/updated_atare on conversation rows and onChildSessionSummary(server/schemas.py:254-255,:777-778), andUsagealready carriescache_read_input_tokens/cache_creation_input_tokens(server/schemas.py:821-827, "Consumed by the cache-aware server-side cost path").The gap is a single projection:
_session_get_info_via_rest(runner/tool_dispatch.py:3439) hand-picks ~15 fields from the server snapshot and includes no timestamp and no usage field. The in-processSysSessionGetInfoToolpath has the same omission.Proposed shape
Extend
sys_session_get_info(and, in reduced form, thesessionsrows ofsys_session_list) with:{ // … existing fields … "context": { "tokens": 48213, // omnigent.last_context_tokens "window": 200000, // omnigent.last_context_window "used_fraction": 0.24, "as_of": 1785000000, // when this measurement was taken "stale": false // true when the last turn failed/stalled — see #1533 }, "last_turn_completed_at": 1785000000, "seconds_since_last_turn": 2317, "created_at": 1784990000, "updated_at": 1785000000, "last_turn_cache": { // optional but directly answers the TTL question "cache_read_input_tokens": 41002, "cache_creation_input_tokens": 1180 } }Notes on the shape:
as_of/staleare not optional decoration. Context-occupancy meter (context_tokens) freezes on failed turns — only emitted at successful completion, so a stalled/failed turn shows a stale low % (observed 3% on a session actually ~14%) #1533 documents thatcontext_tokensis only emitted on successful turn completion, so a stalled or failed turn leaves a stale low value. A bare number would therefore be actively misleading for a compaction decision. Shipping the measurement with its own timestamp makes staleness detectable by the caller instead of silently wrong.seconds_since_last_turnis derived, but worth returning explicitly: the client has no reliable clock relationship to the server, and the whole point is comparing against a ~1h TTL.context_tokens, parity is mostly already paid for.last_turn_cacheis the lowest-confidence part of the ask — if the per-turn cache split is awkward to persist,last_turn_completed_atalone is enough to reason about the TTL.Related
running/runner_online. Same root need: the orchestrator cannot see context state and so cannot tell healthy from wedged.context_tokensmeter freezes on failed turns. Directly motivates theas_of/stalefields above.