diff --git a/src/agents/run_state.py b/src/agents/run_state.py index 1f5fb2f9a..dc0c3144c 100644 --- a/src/agents/run_state.py +++ b/src/agents/run_state.py @@ -94,8 +94,13 @@ ContextSerializer = Callable[[Any], Mapping[str, Any]] ContextDeserializer = Callable[[Mapping[str, Any]], Any] -# Schema version for serialization compatibility -CURRENT_SCHEMA_VERSION = "1.0" +# RunState schema policy. +# 1. Bump CURRENT_SCHEMA_VERSION when serialized shape/semantics change. +# 2. Keep older readable versions in SUPPORTED_SCHEMA_VERSIONS for backward reads. +# 3. to_json() always emits CURRENT_SCHEMA_VERSION. +# 4. Forward compatibility is intentionally fail-fast (older SDKs reject newer versions). +CURRENT_SCHEMA_VERSION = "1.1" +SUPPORTED_SCHEMA_VERSIONS = frozenset({"1.0", CURRENT_SCHEMA_VERSION}) _FUNCTION_OUTPUT_ADAPTER: TypeAdapter[FunctionCallOutput] = TypeAdapter(FunctionCallOutput) _COMPUTER_OUTPUT_ADAPTER: TypeAdapter[ComputerCallOutput] = TypeAdapter(ComputerCallOutput) @@ -1894,10 +1899,12 @@ async def _build_run_state_from_json( schema_version = state_json.get("$schemaVersion") if not schema_version: raise UserError("Run state is missing schema version") - if schema_version != CURRENT_SCHEMA_VERSION: + if schema_version not in SUPPORTED_SCHEMA_VERSIONS: + supported_versions = ", ".join(sorted(SUPPORTED_SCHEMA_VERSIONS)) raise UserError( f"Run state schema version {schema_version} is not supported. " - f"Please use version {CURRENT_SCHEMA_VERSION}" + f"Supported versions are: {supported_versions}. " + f"New snapshots are written as version {CURRENT_SCHEMA_VERSION}." ) agent_map = _build_agent_map(initial_agent) diff --git a/tests/test_run_state.py b/tests/test_run_state.py index ef5e9091f..c819d62bd 100644 --- a/tests/test_run_state.py +++ b/tests/test_run_state.py @@ -61,6 +61,7 @@ ) from agents.run_state import ( CURRENT_SCHEMA_VERSION, + SUPPORTED_SCHEMA_VERSIONS, RunState, _build_agent_map, _deserialize_items, @@ -286,11 +287,13 @@ async def test_throws_error_if_schema_version_is_missing_or_invalid(self): await RunState.from_string(agent, str_data) json_data["$schemaVersion"] = "0.1" + supported_versions = ", ".join(sorted(SUPPORTED_SCHEMA_VERSIONS)) with pytest.raises( Exception, match=( f"Run state schema version 0.1 is not supported. " - f"Please use version {CURRENT_SCHEMA_VERSION}" + f"Supported versions are: {supported_versions}. " + f"New snapshots are written as version {CURRENT_SCHEMA_VERSION}." ), ): await RunState.from_string(agent, json.dumps(json_data)) @@ -3330,6 +3333,31 @@ async def test_from_json_unsupported_schema_version(self): with pytest.raises(UserError, match="Run state schema version 2.0 is not supported"): await RunState.from_json(agent, state_json) + @pytest.mark.asyncio + async def test_from_json_accepts_previous_schema_version(self): + """Test that from_json accepts a previous, explicitly supported schema version.""" + agent = Agent(name="TestAgent") + state_json = { + "$schemaVersion": "1.0", + "original_input": "test", + "current_agent": {"name": "TestAgent"}, + "context": { + "context": {"foo": "bar"}, + "usage": {"requests": 0, "input_tokens": 0, "output_tokens": 0, "total_tokens": 0}, + "approvals": {}, + }, + "max_turns": 3, + "current_turn": 0, + "model_responses": [], + "generated_items": [], + } + + restored = await RunState.from_json(agent, state_json) + assert restored._current_agent is not None + assert restored._current_agent.name == "TestAgent" + assert restored._context is not None + assert restored._context.context == {"foo": "bar"} + @pytest.mark.asyncio async def test_from_json_agent_not_found(self): """Test that from_json raises error when agent is not found in agent map."""