openai / openai/openai-agents-python
Count replayed program items as parents with client-managed input
Nobody has claimed this yet.
- 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
mainat 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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