Two integrations, two jobs
It is worth separating them, because they answer different questions:
actae.openai.run / resume / forkPersists the SDK’s serializable RunState at the run boundaryYes — restore and continue, or forkactae.openai.tracing()Mirrors traces and spans as openai.* events + versioned snapshotsNo — observability and comparisonUse 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
bashpip 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.
# 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 idopenai.span.end— one per span: agent runs, generations, function/tool calls, guardrails, handoffs, custom spansopenai.trace.end— an atomic event plus a cursor-aligned snapshot carrying the complete span tree
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 shutdownTracing 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.
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-agentsinstalled; constructing or installing the adapter then raisesImportErrorwith the install command. - Upstream tracing still works.
install_actae_tracingadds a processor alongside the SDK’s existing ones rather than replacing them.
Related: sessions & adapters and idempotent tool executions.