openai / openai/openai-agents-python

Count replayed program items as parents with client-managed input

Open
#4,839 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature:core
Dominant language
Python
Stars
29.6k
Forks
4.8k
Avg merge
1d 20h
Merged PRs (30d)
123

Description

Describe the bug

With client-managed conversation history, a program_output is only accepted when its parent program was produced in the current Runner.run. A program item that arrives in the run's input is never counted as a parent, so a program that pauses across runs cannot finish.

The case we hit is a human-in-the-loop pause. A program calls a function tool that needs approval, the run ends with the interruption, our app stores the run's items in its own transcript, the user approves, and we resume with a fresh Runner.run(agent, input=<transcript items>). The model correctly continues the program and returns its program_output, and the SDK raises:

agents.exceptions.ModelBehaviorError: Model produced program_output item that does not match a parent program item.

The cause is in run_internal/turn_resolution.py. process_model_response collects parents from existing_items (this run's generated items) and from server_managed_input_items, but the call sites only pass the input when server_manages_conversation is true:

server_managed_input_items=(
    ItemHelpers.input_to_new_input_list(original_input)
    if server_manages_conversation
    else None
),

So with store=false style client-managed history the replayed program is invisible to the scan. The RunState resume path does scan durable input items for program parents (_run_state_program_call_ids), so this is a gap specific to resuming from plain input.

Debug information
  • Agents SDK version: 0.22.0 (also on main at 89c02c8)
  • Related library versions: openai 3.3.1
  • Python version: 3.13
  • Operating system: macOS
  • Model and model provider: OpenAI Responses API
  • Does the issue reproduce with the latest Agents SDK release? Yes
  • Does the issue occur consistently or intermittently? Consistently
Repro steps
import asyncio
from agents import Agent, ModelResponse, ProgrammaticToolCallingTool, Runner, Usage, function_tool
from agents.testing import ScriptedModel
from openai.types.responses.response_output_item import Program, ProgramOutput


@function_tool(allowed_callers=["programmatic"])
def lookup_inventory(sku: str) -> str:
    return '{"sku": "%s", "available_units": 42}' % sku


program = Program(id="p1", call_id="call_program", code='lookup_inventory(sku="A-1")', fingerprint="fp", type="program")
program_output = ProgramOutput(id="po1", call_id="call_program", result='{"sku":"A-1","available_units":42}', status="completed", type="program_output")

model = ScriptedModel([[program_output, {"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "42 units", "annotations": []}], "id": "m1", "status": "completed"}]])
agent = Agent(name="inventory", model=model, tools=[ProgrammaticToolCallingTool(), lookup_inventory])

# Items from an earlier run that ended while the program was waiting on a tool result.
prior_items = [
    {"role": "user", "content": "Check inventory"},
    program.model_dump(exclude_none=True),
    {"type": "function_call", "id": "f1", "call_id": "call_lookup", "name": "lookup_inventory",
     "arguments": '{"sku":"A-1"}', "caller": {"type": "program", "caller_id": "call_program"}},
    {"type": "function_call_output", "call_id": "call_lookup", "output": '{"sku":"A-1","available_units":42}',
     "caller": {"type": "program", "caller_id": "call_program"}},
]

asyncio.run(Runner.run(agent, prior_items))
# ModelBehaviorError: Model produced program_output item that does not match a parent program item.
Expected behavior

A program replayed in the input is a parent the model may still complete, the same way RunState resume treats it. Passing the input items to the parent scan regardless of server_manages_conversation fixes it; the server-managed only branches (treating program_output and caller ids as parents when the program itself is not in the input) can stay server-only. PR to follow.

Drafted with Claude Code

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in run_internal/turn_resolution.py, focusing on process_model_response and the call sites that provide input items. Run the supplied ScriptedModel reproduction first, then verify that a replayed program from client-managed input can match its program_output while server-managed behavior remains unchanged. Done means the reproduction completes without ModelBehaviorError, with regression coverage for this path.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
ai, backend-api-design
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.