Bug Summary
After OpenAIResponsesCompactionSession compacts a session's history, the very next turn fails with a 400 on the Responses API:
openai.BadRequestError: Error code: 400 - {'error': {'message': "Unknown parameter: 'input[1].created_by'.", 'type': 'invalid_request_error', 'param': 'input[1].created_by', 'code': 'unknown_parameter'}}
This happens on the turn immediately following a successful compaction, every time — the compacted history is effectively unusable once produced.
Root cause
responses.compact() returns a ResponseCompactionItem (the output type), which carries a created_by field (openai/types/responses/response_compaction_item.py). But the input type for replaying a compaction item, ResponseCompactionItemParam (openai/types/responses/response_compaction_item_param.py), has no created_by field at all — only encrypted_content, type, id.
OpenAIResponsesCompactionSession._run_compaction_locked stores the raw output items (via _normalize_compaction_output_items, which just does item.model_dump(exclude_unset=True, warnings=False) — created_by survives because it's explicitly set on the constructed object, not merely a default). Those stored items later get replayed as input for the next turn via prepare_input_with_session() in agents/run_internal/session_persistence.py:399:
strip_internal_input_item_metadata(ensure_input_item_format(item)) for item in history
strip_internal_input_item_metadata() (agents/run_internal/items.py:715) only strips TOOL_CALL_SESSION_DESCRIPTION_KEY and TOOL_CALL_SESSION_TITLE_KEY today — created_by isn't in that list, so it rides along into the next request's input array and gets rejected.
Prior art
This is the same class of bug as #1911 (closed as a duplicate of #1882/#1883) — an output-only field leaking unstripped into replayed input, just for a different field (status).
It's also already fixed in the sibling JS SDK: openai/openai-agents-js#1744 ("fix(core): strip created_by from RunItem replay", merged by @seratch, who's also the author of the two most recent Python-side compaction fixes — #… preserve concurrent writes during compaction (0.22.1), normalize compacted Responses user inputs before session reuse (0.14.2)). The JS fix strips created_by from top-level items, providerData, and shell output chunks at the same "replay boundary" concept strip_internal_input_item_metadata() already exists for on the Python side — it just doesn't cover this field yet.
Repro
"""
Reproduces: BadRequestError - Unknown parameter: 'input[1].created_by' (code: unknown_parameter)
on the second turn of a run using OpenAIResponsesCompactionSession.
Requires: pip install openai==3.16.2 openai-agents[redis]==0.22.3
Set OPENAI_API_KEY in environment.
"""
import asyncio
from agents import Agent, OpenAIResponsesModel, RunConfig, Runner, function_tool
from agents.memory.openai_responses_compaction_session import OpenAIResponsesCompactionSession
from agents.memory.sqlite_session import SQLiteSession
from openai import AsyncOpenAI
@function_tool
async def lookup(question: str) -> str:
return "42"
agent = Agent(name="test", instructions="Answer using the lookup tool.", tools=[lookup])
async def main():
client = AsyncOpenAI()
underlying = SQLiteSession(session_id="repro-created-by")
session = OpenAIResponsesCompactionSession(
session_id="repro-created-by",
underlying_session=underlying,
client=client,
model="gpt-5",
compaction_mode="input",
should_trigger_compaction=lambda ctx: len(ctx["compaction_candidate_items"]) >= 2,
)
run_config = RunConfig(model=OpenAIResponsesModel("gpt-5", openai_client=client), tracing_disabled=True)
# Turn 0 succeeds and auto-triggers compaction (threshold met). The stored
# compaction item retains `created_by`, copied straight from the output type.
result = await Runner.run(agent, "How many are there?", session=session, run_config=run_config)
print("turn 0:", result.final_output)
# Turn 1 fails: the stored compaction item (with `created_by` still attached)
# gets replayed as `input`, and the Responses API rejects it.
result = await Runner.run(agent, "And how many of those are urgent?", session=session, run_config=run_config)
print("turn 1:", result.final_output)
if __name__ == "__main__":
asyncio.run(main())
Debug information
- Agents SDK version:
0.22.3 (latest on PyPI at time of filing)
- openai version:
3.16.2 (latest on PyPI at time of filing)
- Model: reproduced via a corporate Azure OpenAI-backed gateway (litellm) fronting the model; the bug itself is client-side (the SDK constructs the invalid
input payload before it ever reaches any provider), so it should reproduce identically against the direct OpenAI API — I haven't verified against a bare OPENAI_API_KEY myself, flagging that as unconfirmed rather than assuming.
Expected behavior
A turn following a compaction should replay cleanly, the same way the JS SDK now handles it after #1744.
Bug Summary
After
OpenAIResponsesCompactionSessioncompacts a session's history, the very next turn fails with a 400 on the Responses API:This happens on the turn immediately following a successful compaction, every time — the compacted history is effectively unusable once produced.
Root cause
responses.compact()returns aResponseCompactionItem(the output type), which carries acreated_byfield (openai/types/responses/response_compaction_item.py). But the input type for replaying a compaction item,ResponseCompactionItemParam(openai/types/responses/response_compaction_item_param.py), has nocreated_byfield at all — onlyencrypted_content,type,id.OpenAIResponsesCompactionSession._run_compaction_lockedstores the raw output items (via_normalize_compaction_output_items, which just doesitem.model_dump(exclude_unset=True, warnings=False)—created_bysurvives because it's explicitly set on the constructed object, not merely a default). Those stored items later get replayed asinputfor the next turn viaprepare_input_with_session()inagents/run_internal/session_persistence.py:399:strip_internal_input_item_metadata()(agents/run_internal/items.py:715) only stripsTOOL_CALL_SESSION_DESCRIPTION_KEYandTOOL_CALL_SESSION_TITLE_KEYtoday —created_byisn't in that list, so it rides along into the next request'sinputarray and gets rejected.Prior art
This is the same class of bug as #1911 (closed as a duplicate of #1882/#1883) — an output-only field leaking unstripped into replayed
input, just for a different field (status).It's also already fixed in the sibling JS SDK: openai/openai-agents-js#1744 ("fix(core): strip created_by from RunItem replay", merged by @seratch, who's also the author of the two most recent Python-side compaction fixes — #… preserve concurrent writes during compaction (0.22.1), normalize compacted Responses user inputs before session reuse (0.14.2)). The JS fix strips
created_byfrom top-level items,providerData, and shell output chunks at the same "replay boundary" conceptstrip_internal_input_item_metadata()already exists for on the Python side — it just doesn't cover this field yet.Repro
Debug information
0.22.3(latest on PyPI at time of filing)3.16.2(latest on PyPI at time of filing)inputpayload before it ever reaches any provider), so it should reproduce identically against the direct OpenAI API — I haven't verified against a bareOPENAI_API_KEYmyself, flagging that as unconfirmed rather than assuming.Expected behavior
A turn following a compaction should replay cleanly, the same way the JS SDK now handles it after #1744.