Temporal
Durable agent workflows, with opsen as the sandbox — and no opsen code to write.
Temporal's OpenAI Agents integration takes any
BaseSandboxClient. openai-agents-opsen is one,
so it registers directly — there is no Temporal-specific opsen package
to install.
pip install openai-agents-opsen 'temporalio[openai-agents]'On the worker
from temporalio.contrib.openai_agents import (
OpenAIAgentsPlugin,
SandboxClientProvider,
)
from openai_agents_opsen import OpsenSandboxClient
plugin = OpenAIAgentsPlugin(
sandbox_clients=[
SandboxClientProvider("opsen", OpsenSandboxClient(
task_id="nightly-report",
budget_usd=2.00,
)),
],
)Each provider gets its own set of Temporal activities, prefixed with its name, so several sandbox backends can share one task queue. Registering opsen alongside another provider is supported and occasionally useful — run the cheap steps one place and the metered ones here.
In the workflow
from agents import RunConfig
from agents.sandbox import SandboxRunConfig
from temporalio.contrib.openai_agents.workflow import temporal_sandbox_client
run_config = RunConfig(
sandbox=SandboxRunConfig(
client=temporal_sandbox_client("opsen"),
),
)The workflow refers to the provider by name. It holds no credentials and no client object, which is what makes the workflow deterministic and replayable.
Why this combination is worth having
Temporal exists because long agent runs fail halfway. But a retried workflow re-runs steps, and every re-run spends money again — a durable execution layer makes the failure survivable and makes the cost worse.
A budget_usd on the opsen client is a ceiling
on the whole thing rather than on one attempt, because the cap belongs to
the session and the session is what the activities share. A workflow that
retries into a loop is refused rather than allowed to bill for each
attempt.
SandboxClientProvider experimental. It has been stable in
practice, but pin your temporalio version rather than
tracking latest.Costs, per workflow
opsen.task_cost("nightly-report")Use the workflow id as the task_id and every
activity, every retry and every model call it made add up under one
number.