-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix(run-state): preserve agent-tool owner provenance across restore #5046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ | |
| from typing_extensions import TypedDict, TypeVar | ||
|
|
||
| from ._run_state_agent_identity import ( | ||
| _agent_identity_signature, | ||
| _build_agent_identity_keys_by_id, | ||
| _build_agent_identity_map, | ||
| _build_agent_map, | ||
|
|
@@ -226,8 +227,9 @@ def _default_run_state_validation_error( | |
| "override a sticky decision for the same tool." | ||
| ), | ||
| "1.17": ( | ||
| "Persists Docker container labels and current-response generated-item ownership across " | ||
| "resume flows, including pending resumed Session writes and terminal-unrecoverable runs." | ||
| "Persists Docker container labels, current-response generated-item ownership, and " | ||
| "Agent.as_tool() owner provenance across resume flows, including pending resumed Session " | ||
| "writes and terminal-unrecoverable runs." | ||
| ), | ||
| } | ||
| SUPPORTED_SCHEMA_VERSIONS = frozenset(SCHEMA_VERSION_SUMMARIES) | ||
|
|
@@ -2817,27 +2819,41 @@ def _serialize_pending_nested_agent_tool_runs( | |
| continue | ||
|
|
||
| try: | ||
| entry["agent_run_state"] = nested_state.to_json( | ||
| nested_state_data = nested_state.to_json( | ||
| context_serializer=context_serializer, | ||
| strict_context=strict_context, | ||
| include_tracing_api_key=include_tracing_api_key, | ||
| ) | ||
| function_tool = getattr(function_run, "function_tool", None) | ||
| owner = getattr(function_tool, "_agent_instance", None) | ||
| owner_signature = _agent_identity_signature(owner) if isinstance(owner, Agent) else None | ||
|
Comment on lines
+2827
to
+2829
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the original inner agent uses a stateful AGENTS.md reference: AGENTS.md:L117-L117 Useful? React with 👍 / 👎.
Comment on lines
+2827
to
+2829
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a snapshot created for owner A is restored against replacement owner B and then serialized again before AGENTS.md reference: AGENTS.md:L115-L115 Useful? React with 👍 / 👎. |
||
| except Exception: | ||
| if strict_context: | ||
| raise | ||
| logger.warning( | ||
| "Failed to serialize nested agent run state for tool call %s.", | ||
| tool_call.call_id, | ||
| ) | ||
| continue | ||
|
|
||
| entry["agent_run_state"] = nested_state_data | ||
| if owner_signature is not None: | ||
| entry["agent_tool_owner_signature"] = owner_signature | ||
|
Comment on lines
+2840
to
+2841
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an interrupted AGENTS.md reference: AGENTS.md:L16-L17 Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| class _SerializedAgentToolRunResult: | ||
| """Minimal run-result wrapper used to restore nested agent-as-tool resumptions.""" | ||
|
|
||
| def __init__(self, state: RunState[Any, Agent[Any]]) -> None: | ||
| def __init__( | ||
| self, | ||
| state: RunState[Any, Agent[Any]], | ||
| *, | ||
| agent_tool_owner_signature: str | None = None, | ||
| ) -> None: | ||
| self._state = state | ||
| self.interruptions = list(state.get_interruptions()) | ||
| self.final_output = None | ||
| self.agent_tool_owner_signature = agent_tool_owner_signature | ||
|
|
||
| def to_state(self) -> RunState[Any, Agent[Any]]: | ||
| return self._state | ||
|
|
@@ -2849,6 +2865,7 @@ class _DeserializedFunctionAction: | |
|
|
||
| action: ToolRunFunction | ||
| nested_agent_run_state_data: Mapping[str, Any] | None | ||
| agent_tool_owner_signature: str | None = None | ||
|
|
||
|
|
||
| def _serialize_guardrail_results( | ||
|
|
@@ -3013,7 +3030,10 @@ async def _restore_pending_nested_agent_tool_runs( | |
| ) | ||
| continue | ||
|
|
||
| pending_result = _SerializedAgentToolRunResult(nested_state) | ||
| pending_result = _SerializedAgentToolRunResult( | ||
| nested_state, | ||
| agent_tool_owner_signature=function_action.agent_tool_owner_signature, | ||
| ) | ||
|
Comment on lines
+3033
to
+3036
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the configured agent-tool owner has been replaced, provenance is merely attached here after AGENTS.md reference: AGENTS.md:L118-L119 Useful? React with 👍 / 👎. |
||
| if not pending_result.interruptions: | ||
| continue | ||
|
|
||
|
|
@@ -3234,6 +3254,12 @@ def _deserialize_function_actions() -> list[_DeserializedFunctionAction]: | |
| ) | ||
|
|
||
| nested_state_data = entry.get("agent_run_state") | ||
| owner_signature = entry.get("agent_tool_owner_signature") | ||
| if owner_signature is not None and not isinstance(owner_signature, str): | ||
| raise validation_error_factory( | ||
| "Run state Agent.as_tool() owner provenance has an invalid type.", | ||
| UserError, | ||
| ) | ||
|
Comment on lines
3256
to
+3262
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a current-version AGENTS.md reference: AGENTS.md:L119-L119 Useful? React with 👍 / 👎. |
||
| deserialized.append( | ||
| _DeserializedFunctionAction( | ||
| action=ToolRunFunction( | ||
|
|
@@ -3243,6 +3269,9 @@ def _deserialize_function_actions() -> list[_DeserializedFunctionAction]: | |
| nested_agent_run_state_data=( | ||
| nested_state_data if isinstance(nested_state_data, Mapping) else None | ||
| ), | ||
| agent_tool_owner_signature=( | ||
| owner_signature if isinstance(owner_signature, str) else None | ||
| ), | ||
| ) | ||
| ) | ||
| return deserialized | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a restored graph replaces the inner agent with another same-name agent containing same-name tools, this comparison accepts the replacement even if those tools have different callbacks, schemas, approval policies, or guardrails:
_agent_identity_signature()reduces ordinary function tools to type/name/routing fields and omits all of those behavior-affecting values. The nested approved call can therefore execute the replacement implementation and its side effects, reproducing the provenance bypass this patch is intended to close; use provenance that distinguishes behavior-changing owner configurations.AGENTS.md reference: AGENTS.md:L115-L119
Useful? React with 👍 / 👎.