OpenAI Agents SDK RunState: Resume Tool Approvals Across Processes
The lab used Python 3.10.2, openai-agents 0.18.3, a deterministic custom Model, and SQLite. It made no OpenAI or third-party model-provider API calls. The results verify SDK runtime semantics, cross-process state recovery, redelivery, and context serialization—not model tool-selection quality, network latency, token cost, or hosted queue performance.
OpenAI Agents SDK RunState: Resume Tool Approvals Across Processes
Here is the conclusion first: OpenAI Agents SDK RunState can persist an Agent run that is waiting for human approval, let the original process exit, and resume the approved or rejected call in another process. But it only answers “where should the Agent continue?” It does not answer whether the business operation is still authorized, whether the same state will execute twice, or whether the state blob contains credentials.
I built a deterministic, provider-free lab with openai-agents==0.18.3. The Agent emitted a deploy_release tool call and paused before the database recorded any effect. A second process loaded the JSON state and approved it; an independent worker rebuilt the Agent and resumed the run, producing one effect. The rejected path produced zero effects. The important failure appeared when the same approved snapshot was delivered to two workers: the tool executed twice. Adding an application-owned idempotency ledger reduced the effect count back to one.
This is not a tutorial for drawing an Approve button. It is about the production case in which approval may take hours, the original process is gone, the Agent definition has changed, a queue can redeliver, an operator may no longer have permission, and RunContextWrapper.context may contain a token. RunState is important infrastructure, but it is not a complete approval system.

1. Why This Is a Separate Problem from a LangGraph Approval Workflow
XBSTACK already has a LangGraph human-in-the-loop approval guide. That article is about interrupt, checkpointers, thread_id, and resuming graph nodes. The OpenAI Agents SDK search intent is different: developers work with function_tool(needs_approval=True), RunResult.interruptions, RunState.to_json(), and Runner.run(agent, state). There is no LangGraph topology or checkpointer that automatically becomes your business approval store.
Python HITL support also came from a concrete product need. The historical GitHub issue requesting native support described the same requirements developers still care about: pause before tool execution, expose a structured interruption, serialize the complete run, and resume after approve or reject. Those primitives now exist in openai-agents 0.18.3. The production gap begins after they work.
The official documentation explains that serialized state can include application context, approvals, usage, serialized tool input, nested agent-as-tool resumption data, trace metadata, and server-managed conversation settings. It also recommends recording Agent or SDK version information for approvals that may remain pending for a long time.
That produces the central conflict for this article:
RunState makes an Agent run resumable. The more durable that resume becomes, the less safely you can delegate authorization, idempotency, secrets, and version compatibility to SDK defaults.
The official in-process example is sufficient when a CLI immediately asks a person for a decision. A web admin panel, OA system, Slack workflow, email approval, or mobile client that resumes hours later needs a business record and failure model around the SDK state.
2. Test Environment, Tasks, and Evidence Boundary
The complete lab lives at:
experiments/openai-agents-runstate-approval-resume/
| Item | Value |
|---|---|
| Python | 3.10.2 |
| OpenAI Agents SDK | openai-agents==0.18.3 |
| RunState schema | $schemaVersion = 1.12 |
| Model | deterministic custom Model |
| Provider API | not used |
| Persistence | JSON files + SQLite |
| Sensitive tool | deploy_release |
| Idempotency key | tenant-a:release:2026.07.24 |
I deliberately did not ask a real model to decide whether to deploy. That would mix model variance, networking, provider cost, and tool-choice quality into the runtime experiment. The deterministic model always emits one deploy_release call on the first response and a final message after receiving the tool result.
The lab verifies six paths:
- the run pauses before any external effect;
- another process can load, approve, and resume it;
- rejection does not execute the tool;
- replaying the same approved state shows the redelivery behavior;
- an idempotency ledger prevents the duplicate effect;
- a context serializer prevents a demo secret from entering the state JSON.
It does not test a real OpenAI Responses API request, streamed interruptions, nested Agent.as_tool() approvals, hosted MCP approvals, a real Redis/Temporal/Celery queue, simultaneous worker races, or exported traces. The results therefore say nothing about model selection accuracy, production latency, or token cost.
3. How Tool Approval Pauses and Resumes
The documented lifecycle has seven steps: the model emits a tool call; the Runner evaluates needs_approval; a missing decision creates a ToolApprovalItem; RunResult.interruptions surfaces it; the application converts the result to RunState and persists it; a human approves or rejects; then Runner.run(agent, state) resumes the original top-level run.

The tool boundary is small:
from agents import function_tool
@function_tool(needs_approval=True)
def deploy_release(release: str, idempotency_key: str) -> str:
return f"deployed:{release}"
The first run:
result = await Runner.run(
agent,
"Deploy release 2026.07.24",
context=AppContext(
tenant_id="tenant-a",
api_token=runtime_token,
),
)
assert len(result.interruptions) == 1
assert effect_count() == 0
state = result.to_state()
The observed paused result was:
{
"interruption_count": 1,
"tool_name": "deploy_release",
"arguments": "{\"release\":\"2026.07.24\",\"idempotency_key\":\"tenant-a:release:2026.07.24\"}",
"effect_count_before_approval": 0
}
This is an execution-layer boundary, not a prompt convention. The model already produced the call and arguments, but the function has not run.
Approval support extends beyond a plain function tool. The current docs cover Agent.as_tool(), ShellTool, ApplyPatchTool, local MCP server require_approval, and hosted MCP approval. An approval raised inside a nested agent surfaces on the outer run, so a production approval center should model the outer run and individual tool-call IDs rather than inventing an isolated UI state for every sub-agent.
4. RunState Owns SDK Execution State, Not the Whole Business Approval
RunState packages the information the Runner needs to continue: current Agent and turn, original input, model responses, generated items, approval state, usage, serialized tool input, guardrail results, trace metadata, and optional conversation_id or previous_response_id settings.
After filtering the custom context, the paused JSON in this lab was 6,279 bytes and contained top-level fields such as:
$schemaVersion
current_turn
current_agent
original_input
model_responses
context
tool_use_tracker
generated_items
current_step
last_model_response
last_processed_response
conversation_id
previous_response_id
trace
It should not be the only source of truth for:
- tenant ownership;
- operator permission;
- expiry or revocation;
- argument integrity after approval;
- queue redelivery or worker leasing;
- whether a deployment, payment, message, or database write already happened;
- state-blob encryption, access control, and deletion.

A production design should separate at least three records:
run_state_blob # encrypted SDK snapshot
approval_request # business approval lifecycle and authorization
idempotency_ledger # effect deduplication, leases, and stored-result reuse
A blob can be large and object-storage friendly. Approval records need frequent queries by operator, tenant, status, and expiry. Idempotency records need strong uniqueness and transaction semantics. Combining all three into one generic agent_runs record creates conflicting access, TTL, and consistency requirements.
The review screen should not render raw RunState tool input as its sole contract. When an interruption is created, generate an immutable review_snapshot containing only fields the reviewer must see, and calculate a canonical digest:
canonical_args = json.dumps(
parsed_arguments,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
arguments_digest = hashlib.sha256(canonical_args.encode()).hexdigest()
The human approves that snapshot and digest. Before execution, the worker extracts the tool name and arguments from RunState and recalculates the digest. A mismatch invalidates the approval and requires a new review. This blocks both parameter substitution and schema drift in which new code applies different defaults to old JSON.
Authorization must also be rechecked at execution time. Store the tenant, reviewer role, scope, target environment, and resource boundary that justified the decision. A user can lose production access while the request is pending, and a change window can close. Production execution should require authorization both when the decision is recorded and when the effect is attempted.
This is not an abstract security preference. A 2026 OpenAI Agents SDK community request for per-tool authorization explicitly separated guardrails from authorization: guardrails inspect content, while authorization decides whether an identity may execute a particular tool call under the current tenant, scope, rate limit, and session conditions. needs_approval=True proves that a human decision is required; it does not prove that the reviewer is authorized for the target resource, and it does not replace the worker’s execution-time permission check. Treat Tool Approval as one decision point inside an authorization system, not as the whole permission system.
For the broader permission, state, and production-governance path, continue with the XBSTACK AI Agent Hub.
5. Cross-Process Approval Requires Rebuilding the Agent Definition
The lab splits one approval across three processes:
- Process A runs the Agent, receives the interruption, writes the RunState, and exits.
- Process B acts as the Approval API, loads the state, records approval, and writes the decided state.
- Process C acts as the worker, rebuilds the Agent, loads the state, and resumes it.

Serialization:
def context_serializer(context: AppContext) -> Mapping[str, Any]:
return {"tenant_id": context.tenant_id}
payload = state.to_json(
context_serializer=context_serializer,
strict_context=True,
)
wrapper = {
"app_state_version": "runstate-approval-lab/v1",
"sdk_state": payload,
}
Loading in another process:
state = await RunState.from_json(
initial_agent,
wrapper["sdk_state"],
context_deserializer=context_deserializer,
strict_context=True,
)
The initial_agent parameter matters. JSON cannot recreate Python functions, asynchronous callbacks, tool implementations, database clients, secret-manager clients, or the executable Agent graph. The worker must build compatible definitions and allow the SDK to resolve serialized identities against them.
The approval process stores the decision:
interruption = state.get_interruptions()[0]
state.approve(interruption)
await persist(state)
The worker resumes:
state = await load_state(agent)
result = await Runner.run(agent, state)
Observed output:
{
"final_output": "Release workflow finished: deployed:2026.07.24",
"effect_count": 1
}
One implementation detail matters: in the 0.18.3 fixture, get_interruptions() still enumerated the original item after state.approve(interruption) and before the Runner resumed. Do not use interruption count as the business approval status. Maintain an application-owned state machine and update pending to approved or rejected with compare-and-set semantics.
Cross-process resume also needs an Agent registry. A worker should not always call the newest build_agent() implementation:
AGENT_FACTORIES = {
"runstate-approval-lab/v1": build_release_agent_v1,
"runstate-approval-lab/v2": build_release_agent_v2,
}
factory = AGENT_FACTORIES.get(record.app_state_version)
if factory is None:
raise StateVersionUnsupported(record.app_state_version)
agent = factory(runtime_dependencies)
Each factory pins Agent names, tool names, parameter schemas, critical prompt revisions, and handoff topology. Keep an old factory until its pending approvals expire, or revoke those approvals during migration. A JSON blob being readable does not prove that current code still has the same tool semantics.
Do not expose a generic endpoint that accepts arbitrary state JSON. Prefer POST /approvals/{approval_id}/resume; the service authenticates the caller, loads the tenant-scoped private record, and lets the worker fetch the blob. External clients should never be allowed to submit a modified executable snapshot or choose an object-storage URI.
6. Rejection Becomes a Tool Result Instead of Executing the Tool
The rejection path starts from the same paused state:
state.reject(
interruption,
rejection_message="Deployment was denied by the release manager.",
)
result = await Runner.run(agent, state)
The result was:
{
"final_output": "Release workflow finished: Deployment was denied by the release manager.",
"effect_count": 0
}
The sensitive tool did not run. The Agent received a model-visible rejection result and could explain, replan, or finish. The SDK also supports a run-level RunConfig.tool_error_formatter and a per-call rejection message override.
A business approval record needs more than a boolean:
| Status | Meaning | Next action |
|---|---|---|
pending | waiting for a human | do not enqueue a worker |
approved | arguments and authorization accepted | enqueue resume work |
rejected | explicitly denied | resume with rejection; do not execute tool |
expired/revoked | timed out or withdrawn | block the old state |
A run can contain multiple interruptions. The docs allow resolving only some and resuming; unresolved items can pause the run again. Model approvals at run_id + tool_call_id granularity rather than setting one approved=true flag for the entire run.
7. The Critical Failure: Replaying One Approved State Executes the Tool Twice
The replay experiment changes the production conclusion.
Worker A loaded the approved state and executed the tool. Instead of using Worker A’s latest state, the original approved snapshot was delivered to Worker B again. The Runner could not know whether this was a queue redelivery, a repeated button click, or a lost acknowledgment. The snapshot said that the call was approved and not completed, so the tool ran again.

Without an idempotency ledger:
{
"retry-a": "deployed:2026.07.24",
"retry-b": "deployed:2026.07.24",
"effect_count": 2
}
With a SQLite unique operation key:
{
"retry-a": "deployed:2026.07.24",
"retry-b": "reused:deployed:2026.07.24",
"effect_count": 1
}
The relevant transaction:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute(
"SELECT result FROM idempotency_ledger WHERE idempotency_key = ?",
(idempotency_key,),
).fetchone()
if existing:
return f"reused:{existing[0]}"
result = perform_external_effect()
connection.execute(
"INSERT INTO idempotency_ledger(idempotency_key, result) VALUES (?, ?)",
(idempotency_key, result),
)
return result
Do not let the model freely invent the production idempotency key. Generate an immutable operation_key when the application creates the approval request, for example:
tenant_id + approval_id + logical_operation + target_resource
Payments, deployments, email providers, and ticket systems should receive that key when their APIs support idempotency. A local Agent database cannot close the crash window in which the remote effect succeeded but the local result was not committed.
This matches the boundary found in the AI SDK 7 interruption and tool-recovery case study: framework state recovery and business idempotency are different layers. The framework knows which call should continue; the business system knows whether the action already happened.
A harder crash window exists after a remote effect succeeds but before the worker stores the local idempotency result. A retry sees no local completion record and can repeat the effect. The response depends on the downstream system:
- pass the application operation key to a native downstream Idempotency-Key when available;
- query by a stable business identifier before creating a resource again;
- mark the operation
uncertainand require manual reconciliation when the remote system cannot deduplicate or be queried; - use a local transaction or transactional outbox when the effect is controlled by the same database.
The idempotency ledger should be a state machine rather than a boolean:
reserved → executing → succeeded
└→ failed_retryable
└→ failed_terminal
└→ uncertain
A worker acquires reserved with a lease. Another worker may take over only after the lease expires and the state permits it. succeeded reuses the stored result; uncertain stops automatic retry. The lab uses BEGIN IMMEDIATE plus a unique key; Postgres can use a unique insert, row lock, or advisory lock. The requirement is an atomic, durable claim to execution rights.
8. Context Is Persisted Data: A Token Can Travel with the State
The official Context guide is explicit: application context can be serialized with a RunState. It is not necessarily an in-memory-only dependency container.
The first experiment used a mapping context:
context = {
"tenant_id": "tenant-a",
"api_token": DEMO_SECRET,
}
Then:
payload = result.to_state().to_json(strict_context=True)
The result was mapping_context_secret_present = true. A mapping already satisfies the strict serialization rule; strict_context=True does not classify sensitive fields for you.
The second experiment used a custom object:
@dataclass
class AppContext:
tenant_id: str
api_token: str
Strict serialization without a serializer failed:
UserError: RunState serialization requires context to be a mapping when strict_context is True.
Provide context_serializer to serialize custom contexts.
The safe serializer persisted only the durable identifier:
def context_serializer(context: AppContext) -> Mapping[str, Any]:
return {"tenant_id": context.tenant_id}
The deserializer re-injected the credential at runtime:
def context_deserializer(payload: Mapping[str, Any]) -> AppContext:
return AppContext(
tenant_id=str(payload["tenant_id"]),
api_token=secret_manager.get("agent-runtime-token"),
)

The final result was safe_serializer_secret_present = false. The docs also warn that context_override can replace context during loading but cannot erase a secret already written into an existing state blob. If a credential was persisted, changing the loaded context does not remediate the historical exposure; rotate the credential and delete or re-encrypt the blob.
A useful split is:
PersistedContext
tenant_id
user_id
request_id
locale
feature_flags_snapshot
RuntimeDependencies
API clients
database connection
secret manager
access token
logger / trace exporter
Only the first group should be eligible for RunState serialization.
Even after filtering Context, RunState can contain original user input, tool arguments, model output, file paths, customer names, or proposed message bodies. “No API key” does not make it ordinary cache data. Treat the blob as sensitive business data:
- encrypt it with a KMS-managed data key;
- isolate object keys by tenant without embedding readable business data;
- store only URI, digest, versions, and key reference in the relational record;
- give Approval API and workers different least-privilege service identities;
- send only a redacted review snapshot to browsers;
- use different TTLs for pending, rejected, and completed states;
- delete blobs, debug exports, and cache copies together;
- audit reads, not just approval decisions.
Do not log complete state during debugging. Log approval ID, schema, byte size, digest, and error category. If a state has already copied a credential into logs, error tracking, backups, or a queue, deleting the primary blob is not sufficient remediation.
9. Long-Lived Approvals Need Both SDK Schema and Application Version Gates
The serialized SDK schema in this lab was $schemaVersion = 1.12. SDK compatibility is only one dimension. An approval created on Friday and accepted on Monday can cross changes such as:
deploy_release(release=...)becomingdeploy_release(artifact_id=...);- a new environment restriction in the prompt;
- a renamed tool;
- revoked production access for the operator;
- a canceled release target;
- a new SDK serialization schema.
Successful RunState.from_json() does not prove that the old approval is still valid.

The lab wraps the SDK state:
{
"app_state_version": "runstate-approval-lab/v1",
"sdk_state": {
"$schemaVersion": "1.12"
}
}
A production approval record should preserve at least:
CREATE TABLE approval_request (
approval_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
operator_scope TEXT NOT NULL,
run_id TEXT NOT NULL,
tool_call_id TEXT NOT NULL,
tool_name TEXT NOT NULL,
arguments_digest TEXT NOT NULL,
app_state_version TEXT NOT NULL,
sdk_schema_version TEXT NOT NULL,
agent_revision TEXT NOT NULL,
state_blob_uri TEXT NOT NULL,
status TEXT NOT NULL,
expires_at TIMESTAMP NOT NULL,
decided_by TEXT,
decided_at TIMESTAMP,
revoked_at TIMESTAMP
);
Before loading the blob, the worker must revalidate tenant, operator permission, argument digest, app version, expiry, and revocation. An incompatible version should create a fresh approval request rather than force an old state through current code.
10. RunState, Session, conversation_id, and previous_response_id Are Different State Layers
The SDK offers several continuation strategies:
| Mechanism | State location | Primary purpose | Next-run input |
|---|---|---|---|
result.to_input_list() | application memory/store | manual history management | prior items + new user input |
session | application store + SDK | conversation history across runs | same session or same backing store |
conversation_id | OpenAI Conversations API | named server-side conversation | same conversation_id |
previous_response_id | OpenAI Responses API | lightweight response chain | previous response ID |
RunState | application-owned blob | resume one interrupted run | restored RunState |
RunState preserves the run’s conversation_id, previous_response_id, and auto_previous_response_id configuration so a resumed approval can continue the same server-managed conversation. However, conversation_id and previous_response_id are mutually exclusive. A local session also cannot be combined with those server-managed mechanisms in the same run.
When a session is used, resume with the same session instance or another instance that points at the same store. RunState resumes the interrupted run; Session appends the resumed turn to the correct conversation history.
None of these identifiers is a business authorization credential:
conversation_idis nottenant_id;previous_response_idis not approval authorization;- a Session ID is not a tool idempotency key;
- a blob URI is not a public resume token.
For broader memory and identity boundaries, see AI Agent Memory Architecture. For high-risk tools, continue with AI Agent Security and MCP security governance.
11. Production Architecture: Approval API, Blob, Queue, Worker, and Idempotency Ledger
A production RunState approval system should separate responsibilities as follows.

Agent API
Run the Agent until completion or interruption. When interrupted, do not send the complete state JSON to the browser. Instead:
- calculate a digest of the tool arguments;
- store the encrypted state blob privately;
- create an
approval_request; - return a minimized, redacted review payload.
Approval API
Authenticate the reviewer, enforce tenant and scope authorization, and transition the record with compare-and-set:
UPDATE approval_request
SET status = 'approved', decided_by = ?, decided_at = CURRENT_TIMESTAMP
WHERE approval_id = ?
AND tenant_id = ?
AND status = 'pending'
AND expires_at > CURRENT_TIMESTAMP;
Only one updated row may enqueue the resume job. This blocks double clicks, duplicate webhooks, and expired approvals.
Queue
Deliver only approval_id, not the entire RunState blob. The worker reloads the record and private blob and revalidates authorization, versions, argument digest, and revocation. Design the queue for at-least-once delivery.
Worker
Choose an Agent factory from app_state_version, rebuild compatible definitions, inject runtime dependencies, call RunState.from_json(), verify the approved arguments, and resume with Runner.run(agent, state).
Idempotency Ledger
Every side-effecting tool acquires an immutable operation key before execution. Prefer downstream-native idempotency. Otherwise, use a unique database constraint, an execution state machine, and stored-result reuse. A worker receiving a message is not completion; acknowledge only after the external effect and recoverable result state have been recorded.
Audit and Trace
Record who approved which argument digest, which Agent revision resumed, which worker executed, what the downstream system returned, and whether the result was reused. Traces help explain SDK execution, but long-lived credentials should not be serialized merely to preserve export. include_tracing_api_key=True is an explicit option and should be treated as a security decision.
Minimum production metrics include:
| Metric | Meaning | Warning signal |
|---|---|---|
approval_pending_age | time waiting for review | rising P95 means the review channel is failing |
approval_expired_total | expired approvals | TTL or notification policy is wrong |
resume_attempts_total | resume attempts per approval | greater than one means redelivery or manual retry |
idempotency_reuse_total | reused completed effects | a spike indicates ACK or worker instability |
state_deserialize_failure | failed blob loads | SDK/app incompatibility or corruption |
argument_digest_mismatch | approved and executed inputs differ | mutation, schema drift, or attack |
effect_uncertain_total | remote outcome unknown | stop automatic retries and reconcile |
state_blob_bytes | serialized size | context, history, or trace is growing unexpectedly |
Correlate logs with approval_id, run_id, tool_call_id, and operation_key without logging complete arguments or state. A trace ID is a useful index, not a replacement for durable business audit records.
Regression tests must go beyond “approval succeeds.” Cover zero effects before approval, one approved effect, zero rejected effects, double-click approval, duplicate state resume, lost worker acknowledgment, context-secret scanning, argument-digest mismatch, expiry, permission revocation, old app versions, corrupted blobs, and partially unresolved interruptions. Assert database and external-effect state, not only final text.
This aligns with the broader AI Agent production-governance guide: the model proposes, the approval service authorizes, the worker executes, the business system enforces idempotency, and the audit layer makes the action replayable.
12. Final Decision: When the Official Example Is Enough
The in-process example is sufficient when all of these are true:
- the reviewer decides immediately in the same CLI or request lifecycle;
- the tool has no irreversible side effect;
- the process remains alive;
- no queue is involved;
- Context contains no durable credential;
- the state does not need to survive code versions.
Move to the durable architecture when any of these are true:
- approval can take minutes, hours, or days;
- frontend, Approval API, and worker are separate services;
- the tool deploys, pays, emails, writes databases, or changes cloud resources;
- queue messages, webhooks, or human actions can repeat;
- tenant authorization must be isolated;
- Agent, Tool, or prompt definitions change over time;
- state blobs need encryption, TTL, revocation, and audit.
Do not enable every high-risk tool at once. Start with read-only or reversible tools and verify blob persistence, notification, version loading, and audit. Then allow one internal tenant to use a low-frequency write tool while deliberately testing queue redelivery, worker restarts, and expiry. Only after idempotency reuse, permission revocation, and stale-state rejection are proven should deployment, payment, or outbound-message tools be enabled.
Rollback needs its own state plan. Reverting the application image is insufficient when the database contains both v1 and v2 pending states. Preserve version routing so old workers can drain old states while new v2 approvals stop being created. Mark incompatible records migration_required, regenerate the proposal, and require a new decision. Never apply an old approval to a new tool schema.
Track rollout gates such as deserialization success, duplicate resume rate, idempotency reuse, effects after rejection, effects after expiry, argument-digest mismatches, and manual reconciliation volume. Any execution after rejection or expiry—or execution despite a digest mismatch—is a safety violation, not an acceptable low error rate.
Before production, verify:
- sensitive tools use
needs_approvalor the corresponding approval mechanism; - the paused path has zero effects;
- state uses a strict, minimal context serializer;
- blobs are encrypted and tenant-scoped;
- approved arguments have an immutable digest;
- records support approved, rejected, expired, and revoked;
- workers rebuild Agents by application version;
- queues are treated as at-least-once;
- tools have stable idempotency keys and unique constraints;
- duplicate resume, rejection, expiry, and incompatible versions have automated tests.
The final result is: RunState moves OpenAI Agents SDK HITL from an in-process pause to a genuinely durable execution boundary. Production safety still depends on the application separating approval authorization, state versioning, context secrets, and side-effect idempotency. A JSON file completes the demo; a complete software system makes it safe to execute production actions.
Reproducible Lab
experiments/openai-agents-runstate-approval-resume/
Run it with:
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python lab.py run-all
.venv/bin/python -m unittest discover -s tests -v
The main evidence is written to:
artifacts/results.json
Official Sources
- OpenAI Agents SDK: Human-in-the-loop
- OpenAI Agents SDK: RunState reference
- OpenAI Agents SDK: Running agents and state strategies
- OpenAI Agents SDK: Sessions
- OpenAI Agents SDK: Context
- PyPI: openai-agents 0.18.3
Continue from one agent pattern to the complete production system
The AI Agent hub organizes architecture, memory, tool use, evaluation, security, deployment and multi-agent coordination into a single learning path.
Next Reading
View Hub →AutoGen Hands-On Tutorial: Multi-AI Agent Conversational Collaboration, Tool Invocation, and Production Deployment Boundaries
Systematically deconstruct AutoGen's practical usage and production deployment boundaries in multi-agent conversational collaboration, covering AgentChat, GroupChat, Planner/Executor/Critic patterns, tool invocation, human-in-the-loop, conversation turn control, evaluation metrics, cost monitoring, and migration risks to the Microsoft Agent Framework.
Practical Guide to AI Log Analysis Agents: Anomaly Clustering, Root Cause Localization, Runbook Matching, and Incident Review Loops
A systematic breakdown of production-grade design methods for AI log analysis agents, covering log ingestion, anomaly clustering, Trace/Metrics alignment, root cause localization, Runbook matching, alert noise reduction, human verification, automated remediation boundaries, incident postmortems, and evaluation metrics. This helps teams build controllable operations AI agent systems.
Practical Guide to AI Contract Review Agents: Clause Extraction, Risk Annotation, Version Comparison, and Legal Review Workflows
This article breaks down the production-grade design of an AI contract review agent, covering OCR recognition, document parsing, clause extraction, standard template comparison, legal risk annotation, version differences, approval workflows, legal review, and audit logs. It helps teams build a traceable contract review assistance system.
Practical Guide to AI Research Agents: Paper Retrieval, Evidence Extraction, Citation Auditing, and Research Knowledge Base Integration
A systematic breakdown of production-grade design methods for AI research agents, covering arXiv / Semantic Scholar / Google Scholar retrieval, paper filtering, abstract parsing, method and experiment extraction, claim auditing, citation verification, research hypothesis generation, human review, and knowledge base capture. This helps teams build trustworthy research automation systems.

Xiaobai
Full-Stack AI Engineer
Xiaobai, a full-stack AI engineer building production Agent systems, product tools and independent software assets.
About Xiaobai & XBSTACK →Liked this article?
Join the newsletter
Every issue condenses production AI engineering changes, real failures, reproducible experiments, useful tools and new XBSTACK assets. No generic news digest and no filler.