INTEGRATION

OpenAI Agents SDK + Actae.

Actae gives the OpenAI Agents SDK two independent things: a runner facade that persists the SDK's native RunState so a run can resume in a new process, and a tracing processor that mirrors every trace and span as durable events.

Two integrations, two jobs

It is worth separating them, because they answer different questions:

IntegrationWhat it doesCan it resume a run?
actae.openai.run / resume / forkPersists the SDK’s serializable RunState at the run boundaryYes — restore and continue, or fork
actae.openai.tracing()Mirrors traces and spans as openai.* events + versioned snapshotsNo — observability and comparison

Use both together freely: the runner facade for durability, tracing for span-level inspection. Tracing alone does not restore a run; the runner facade does.

Durable runs that resume

bash
pip install 'actae-client[openai-agents]'
python
from actae_client import Actae
from agents import Agent, Runner

actae = Actae.from_env()
agent = Agent(name="researcher", instructions="...")

# First run: execute and persist the native RunState.
result = await actae.openai.run(agent, "Summarize this ticket.", run_id="ticket-42")

# New process, same run_id: restore the persisted RunState and continue.
result = await actae.openai.resume(agent, run_id="ticket-42")

run delegates to the SDK’s Runner.run, then saves result.to_state().to_json() as a native checkpoint on the run’s channel. resume loads the latest persisted state with RunState.from_json and passes it back to the runner — no re-execution of completed turns.

Because the context object of some agents (for example a SQLite session or a custom tool registry) is not JSON-serializable, the facade accepts context_serializer, context_deserializer, and strict_context so you control exactly how context crosses the boundary. With strict_context the facade fails loudly rather than silently dropping context that cannot round-trip.

Fork a run

actae.openai.fork copies a persisted run’s RunState into a new channel. Resume the fork with a modified agent — a different model or instruction — and the earlier turns are inherited, not re-run.

python
# Fork ticket-42 into a new fork, then continue it there.
fork_channel = await actae.openai.fork("ticket-42", "ticket-42-model-b")

result = await actae.openai.resume(
    agent_b, run_id="ticket-42-model-b", channel_id=fork_channel,
)

This is a real resume boundary: the fork records a lineage marker and an immutable fork receipt, so the divergence between the original and the fork is auditable. See forking & recovery for the receipt and boundary-mode semantics.

Trace and span mirroring

The tracing processor implements agents.tracing.TracingProcessor and records a durable history of each run:

  • openai.trace.start — trace id, workflow name, group id
  • openai.span.end — one per span: agent runs, generations, function/tool calls, guardrails, handoffs, custom spans
  • openai.trace.end — an atomic event plus a cursor-aligned snapshot carrying the complete span tree
python
from actae_client.adapters.openai_agents import install_actae_tracing

processor = install_actae_tracing(client)     # once, before Runner.run
result = await Runner.run(agent, "Hello")

snapshot = await client.latest_state("openai_agents")
for span in snapshot["state"]["spans"]:
    print(span["type"], span["name"], span.get("duration_ms"))

await processor.flush()                        # await pending writes on shutdown

Tracing callbacks are synchronous, so writes are posted to a dedicated background event loop rather than blocking the SDK. The processor never raises into the SDK; failures are caught and logged. Span extraction is duck-typed against the SDK’s span objects, so minor SDK version changes do not break it. Set include_inputs_outputs=False to keep names, timings, and errors while stripping prompt/output payloads.

The tracing processor can also fork a completed run’s snapshot for comparison via fork_trace(trace_id, new_channel). Because the SDK’s tracing path is stateless, that is fork-for-comparison (diff two runs structurally), not fork-for-resume — use actae.openai.fork for resume.

TypeScript

The TypeScript SDK exposes the same concepts with actae.openai.run, resume, fork, and tracing, built on the SDK’s RunState.toJSON / fromString.

typescript
import { Actae } from '@actae/sdk';

const actae = Actae.fromEnv();
await actae.openai.run(agent, input, { runId: 'ticket-42' });
await actae.openai.resume(agent, { runId: 'ticket-42' });

const forkChannel = await actae.openai.fork('ticket-42', 'ticket-42-model-b');
await actae.openai.resume(agentB, { runId: 'ticket-42-model-b', channelId: forkChannel });

Limitations, stated plainly

  • Resume needs a serializable context. If your runner uses a non-serializable context, provide a serializer or the run cannot be restored exactly. The facade never fabricates context.
  • Tracing is not resume. The span mirror is for observability; only the runner facade restores execution.
  • The extra is optional. The module imports without openai-agents installed; constructing or installing the adapter then raises ImportError with the install command.
  • Upstream tracing still works. install_actae_tracing adds a processor alongside the SDK’s existing ones rather than replacing them.

Related: sessions & adapters and idempotent tool executions.