Engineering research / agents

OpenAI Agents SDK for Python 0.24.0: Deferred Approvals That Survive a Resume, Compaction That Waits for Them, MCP Annotations on Tools and Sibling Failures That Keep Finished Outputs

openai-agents-python 0.24.0 (15 October 2026), read from its commits: a session corruption after approval resumes, compaction that now waits for pending approvals, tool outputs preserved when a sibling fails, MCP annotations on converted tools, and a run-wide formatter for tool exceptions.

By Sandeep Belgavi · Published 2026-10-25 · Prepared 2026-10-19

Recent development · Practical technical guide · 19 October 2026

openai-agents-python 0.24.0 (15 October 2026), read from its commits: a session corruption after approval resumes, compaction that now waits for pending approvals, tool outputs preserved when a sibling fails, MCP annotations on converted tools, and a run-wide formatter for tool exceptions.

A small release with a long fix list

Version 0.24.0 of the OpenAI Agents SDK for Python was released on 15 October 2026. The release notes list two features and a long tail of fixes, and the interesting content is in the fixes, because several of them describe the same part of the system: what happens to a run that pauses for a human approval, gets serialised, and is resumed later, possibly after its conversation history has been compacted. That path is where production agents spend most of their wall-clock time, and in 0.23 it had a bug that corrupted sessions.

How approvals, RunState and sessions fit together

The SDK's human-in-the-loop flow is built from three parts. A tool declares needs_approval, as a boolean or a callable that decides per call and fails closed when the arguments cannot be inspected. When the model calls such a tool and no decision is stored, the run pauses and the result carries interruptions. The application converts the result to a RunState, approves or rejects each interruption, and resumes with Runner.run passing the state and the original top-level agent. RunState serialises to JSON, so the pause can span a process restart or a day. A Session, backed by SQLite, Redis, a SQL database or the OpenAI conversations store, holds the conversation items across turns, and the documentation asks that a resumed run use the same session so the resumed turn continues the stored history. Sticky decisions through always_approve persist in the context wrapper and in the RunState; for function tools they are scoped to the agent that asked, while shell and apply-patch decisions match on tool type and name across agents, which the documentation flags as a sharp edge.

The bug: a tool output whose call was never written

Commit c757a43 describes the failure precisely. With output guardrails and a non-default tool_use_behavior configured, the SDK defers writing the interrupted turn's items to the session until the turn resolves. When the approval resume continued the run, the resume path wrote only the resolved turn's new items, the tool output, and nothing ever wrote the deferred function call that produced it. The session then contained a function_call_output whose call was never persisted, and the Responses API rejected every later run over that session with the error that no tool call was found for the function call output. One approval could poison a conversation permanently.

The fix persists the deferred prefix, the current response's session items located from the resumed response boundary, ahead of the resolved turn's items once the resume commits to continuing, in both the streamed and non-streamed paths. The final-output path was already correct because it reconstructs the full response. The commit also adds RunState fixture versions and a corpus of deferred-write tests covering compaction, filters, max turns, pending writes, recovery and resume. If you run approvals with output guardrails, this fix alone justifies the upgrade.

Compaction now waits for pending approvals

OpenAIResponsesCompactionSession wraps a session and, after each turn, compacts the stored history through the Responses API when a threshold is met; the run waits for compaction before returning, and in streaming mode the event iterator can stay open while it runs. Compaction clears and rewrites the history, which is dangerous while a tool call is waiting for a decision: compacting the call away, or sending an incomplete call-and-output group to the compaction endpoint, would break the resume. Commit 5eb47a5 makes the compaction step check that every tool group in the complete history is intact, including calls whose approval is pending, and return without compacting if not. Hosted MCP approvals are keyed by request id rather than call id, so the same commit also matches mcp_approval_request and mcp_approval_response pairs by id and defers when they do not match. The practical effect is that a session with an outstanding approval is left alone until the decision lands, and compaction runs on the next turn.

Sibling failures keep finished outputs

When the model issues several tool calls in one turn and one of them raises, earlier SDK versions could lose the outputs of the siblings that had already completed. Commit 1c1a28d, a thirteen-step series, changes the tool batch failure path so that completed outputs are preserved through the failure, through RunState snapshots and through session persistence, cancels the irrelevant guardrails, distinguishes child cancellation from failure, and keeps the failure report. The design consequence for an application is that a partial batch is now a normal state: inspect the preserved outputs before deciding whether to retry the whole turn. The parallel tool call guide argues for exactly that semantics.

MCP annotations and a run-wide exception formatter

The two features are small and useful. When the SDK converts an MCP tool into a function tool, it now deep-copies the server's tool annotations and exposes them through a method on the function tool that returns a copy, or None for non-MCP tools and tools without annotations; the value uses the installed MCP SDK's ToolAnnotations type. That lets an application read the read-only, destructive, idempotent and open-world hints and feed them into its own policy, for instance by requiring approval for tools that are not read-only. The MCP annotations guide explains why hints are inputs to policy and never its enforcement.

The second feature extends RunConfig.tool_error_formatter with a third kind, tool_exception, carrying the original exception, so that one function decides what the model sees when a tool raises anywhere in the run. The docstring excludes some classes, including timeouts and cancellation, which keep their existing handling. A formatter that returns a non-string, or raises, is logged and ignored.

Illustrative use of the 0.24.0 API from the documentation and commits; ask_human is yours. Checked for syntax only.

from agents import Agent, Runner, RunConfig
from agents.run_config import ToolErrorFormatterArgs

def format_tool_error(args: ToolErrorFormatterArgs) -> str:
    if args.kind == "tool_exception":
        # one sentence the model can act on; keep stack traces out of the context
        return f"{args.tool_name} failed: {type(args.error).__name__}. Try different arguments or ask the user."
    if args.kind == "approval_rejected":
        return f"{args.tool_name} was not approved."
    return f"{args.tool_name} is not available."

def needs_approval_unless_read_only(tool) -> bool:
    ann = tool.mcp_annotations()
    return not (ann is not None and ann.readOnlyHint)

async def run(agent: Agent, prompt: str, session):
    result = await Runner.run(agent, prompt, session=session,
                              run_config=RunConfig(tool_error_formatter=format_tool_error))
    while result.interruptions:
        state = result.to_state()
        for item in result.interruptions:
            state.approve(item) if await ask_human(item) else state.reject(item)
        result = await Runner.run(agent, state, session=session)
    return result.final_output

Smaller fixes that change behaviour

FixEffect on an application
Batch OpenAI conversation session writes within the API limitLong turns no longer fail on a single oversized write; writes are chunked, serialised and recovered atomically
Preserve function-call aliases across RunState restorationA restored state resolves the same tool identities it was saved with
Reject pending input when completed stop tools end resumeInput staged for a resumed turn is refused when the run has already ended
Replace Redis compaction history atomicallyNo window where a reader sees half-compacted history
Reject unsupported Redis Cluster clients early, for transactional writesA misconfiguration fails at construction rather than mid-run
Honour the Runner tracing opt-out inside caller tracesOpting out now also suppresses spans nested under your own trace
Omit caller data from lifecycle debug logsLess application data in debug output
Warn about unhandled computer safety checksComputer-use calls with pending checks are visible rather than silent

What to do after upgrading

  1. Pin agents at 0.24.0 or later wherever approvals are used with output guardrails or a custom tool_use_behavior; the session corruption in earlier versions is not repairable from the application side.
  2. Resume paused runs with the same session instance or the same session id and backend, as the documentation requires.
  3. If you use a compaction session, expect compaction to be skipped while an approval is pending and to run on the following turn; do not force it manually in that window.
  4. Read MCP annotations into your approval policy, treating absent hints as not read-only.
  5. Set one tool_error_formatter for the run and keep stack traces out of what the model sees.
  6. Re-run your interruption tests: a parallel batch with one failing tool should now leave the completed outputs in the result.

Sources and scope

Checked against these sources on 19 October 2026. Event: openai/openai-agents-python v0.24.0 release, 15 October 2026; recheck it before acting. Examples and figures are illustrative, not measurements.

Upgrade before using approvals with output guardrails, resume with the same session, expect compaction to wait for pending approvals, and feed MCP annotations into your approval policy.