loc bengaluru, ist | local --:-- srijanshukla18@gmail.com
[post]/ai/durable-agent-sessions-openai-sdk-docker-temporal

Durable agent sessions with the OpenAI SDK, Docker, and Temporal

/ 8 min read· ai

From a survey of the agent-platform field through strict sandbox requirements to a durable agent session: one OpenAI SDK run in Docker, hardened with a Temporal workflow.

Companion to Putting an LLM Inside a Workflow: Three Runtime Designs - this is the story of how one concrete build landed on the third runtime.

I spent a while staring at the agent-platform landscape before building anything. The field is crowded and getting more so. Every contender promises roughly the same thing: framework-flavored developer experience, durable execution, managed sandboxes, channels, approvals, evals - all deployable like a web app. But the details diverge fast.

Here’s what I looked at:

  • Cloudflare Agents - full-stack TypeScript runtime with durable state, scheduling, sandboxes, channels, approvals, global deployment.
  • Mastra - the DX pick. TypeScript-first, suspend/resume workflows, memory, multi-agent, evals, chat channels.
  • LangGraph + LangSmith - the incumbent. Mature stateful graphs, persistence, human-in-the-loop, tracing.
  • Anthropic Managed Agents - sandboxes, sessions, skills, MCP, vaults. Claude-centric, weaker on arbitrary channels.
  • OpenAI Agents SDK - lightweight loop-and-tools. No bundled channels or deployment.
  • Eve by Vercel - TypeScript framework plus durable workflows, sandboxed compute, OAuth, channels, approvals, subagents, evals, observability, Vercel deployment. In beta.
  • Cloudflare OS - open-source browser workspace with agent sessions, persistent state, curated skills, Gatekeepers for least-privilege, Gadgets (persistent generated apps). Code runs in V8 isolates, not Linux containers.
  • Google ADK + Agent Runtime and Microsoft Agent Framework + Durable Task - enterprise equivalents on their respective clouds.
  • CrewAI + AMP - Python-first crews and flows for business automation.

Smaller composable options floating around: PydanticAI + Temporal, Inngest AgentKit, Claude Agent SDK. E2B showed up as the alternative to self-run Docker - hosted sandboxes over an API.

The survey set the bar. But my actual requirements pointed somewhere narrower.

The session abstraction

Before picking runtimes or vendors, I needed to nail down what I was actually building. The answer kept coming back to one shape:

Agent Session
├── objective
├── identity
├── context
├── capabilities
├── workspace
├── durable state
├── event history
├── artifacts
├── approvals
├── cost and limits
└── final result

Incident investigation, repo changes, file analysis, internal ops, research, test runs, artifact generation - the domain varies, the session doesn’t. That’s the abstraction I wanted to build around.

The actual requirements

I’ll be specific. One durable agent session per task, triggerable from anywhere - user message, API call, webhook, schedule. OpenAI Agents SDK’s SandboxAgent for the model loop. Ephemeral Docker for execution. Skills as markdown directories, tools as an explicit allowlist, memory backed by a simple.md file. No subagents for version one. And one continuous visible session, even when the runtime retries internally.

The constraints produced this input shape:

type AgentSessionInput = {
  sessionId: string;
  objective: string;
  context?: {
    text?: string;
    files?: string[];
    references?: string[];
  };
  skills: string[];
  memoryFile?: string;
  permissions: {
    tools: string[];
    networkAllowlist: string[];
    writablePaths: string[];
    requiresApproval: string[];
  };
  limits: {
    timeoutSeconds: number;
    maxModelTurns: number;
    maxToolCalls: number;
    cpu: number;
    memoryMb: number;
  };
  model: {
    name: string;
    baseUrl?: string;
  };
};

Starting simple: one SDK run in Docker

Before any workflow layer entered the picture, the design was a single constrained run:

TASK ─► SandboxAgent (Docker) ─► result
          ├── tools: read, search, shell, tests
          ├── Skills + simple.md (read-only)
          └── /workspace: input ro · working rw · output rw

The sandbox layout made the trust boundary physical:

/workspace/
├── input/        read-only
├── working/      writable
├── output/       writable
├── task.md       read-only
├── simple.md     read-only
└── skills/       read-only

simple.md held the stable context - environment details, conventions, known constraints, previously approved facts. The agent could propose memory updates in its result; a trusted process or human approved them afterward.

The trust model was straightforward. Privileged work never ran inside the container. The model requested a tool, a trusted controller checked policy, asked for approval when required, used a scoped credential, sanitized the response, and returned only the result:

model requests tool ─► policy check ─► approval if needed
  ─► scoped credential ─► sanitized result back

A PR review task, for example, looks like this end-to-end:

User requests PR review


Trusted controller service
  - verifies GitHub webhook
  - fetches PR metadata
  - checks out PR
  - creates task.md


Ephemeral Docker sandbox
  - repository
  - task.md
  - review skill
  - no GitHub credentials
  - no network


OpenAI SandboxAgent
  - shell tool
  - skills
  - structured review output


Trusted controller
  - validates findings
  - displays draft
  - optionally publishes to GitHub

Untrusted model, unprivileged sandbox, trusted gates on both sides.

This baseline worked fine for a single run. What it didn’t survive: crashes, multi-hour tasks, approval waits, duplicate triggers. That’s what pushed me toward the workflow layer.

Wrapping it in Temporal

The workflow kept the same agent and sandbox. It just made them durable:

AgentSessionWorkflow

CREATED


prepareSession()


provisionSandbox()


runAgent()

   ├── approval required ──► WAITING_FOR_APPROVAL
   │                            │
   │                            ▼
   │                         resumeAgent()


validateResult()


persistArtifacts()


destroySandbox()


COMPLETED

Each stage is a Temporal Activity with a single responsibility:

prepareSession
- Resolve input files and context
- Load selected skills
- Load simple.md
- Calculate effective permissions

provisionSandbox
- Create ephemeral Docker container
- Mount workspace
- Apply CPU, memory and network restrictions
- Return sandbox ID

runAgent
- Start SandboxAgent
- Stream normalized events
- Execute allowed tool calls
- Return structured result

executeExternalTool
- Perform privileged calls outside the sandbox
- Use narrowly scoped credentials
- Apply approval rules

persistArtifacts
- Save output files, logs and structured results

destroySandbox
- Remove the container and temporary workspace

Human approval becomes a Temporal Signal. That’s the key detail - a multi-day approval wait survives process restarts because it’s a workflow signal, not a queue or a polling job. The UI renders one continuous session to the user while Temporal retries an activity underneath, driven by a normalized event stream stored per session and sequence number.

Two scoping decisions kept this manageable. First, Temporal owns coarse boundaries - not every token or shell command. The entire agent attempt is one retriable activity, rerun from scratch on failure. Second, the SDK stays disposable behind a runner interface:

interface AgentRunner {
  run(
    session: AgentSession,
    environment: ExecutionEnvironment,
  ): AsyncIterable<AgentEvent>;
}

Right now:

AgentRunner
  └── OpenAI SandboxAgent

But the point is that later it could be:

AgentRunner
  ├── Claude Agent SDK
  ├── OpenAI successor runtime
  ├── Cloudflare agent runtime
  ├── self-hosted model harness
  └── some future capable model that barely needs a framework

Only the runner file knows which SDK is wired up. Workflow, sandbox, skills, memory, events, artifacts - all independent.

The control plane is what actually matters

Here’s what I’ve come to believe: the model, the agent loop, the sandbox vendor - those will keep churning. The durable value is everything around the intelligence.

Identity and authorization. Per-resource capability grants (“read accounts in territory A, draft but not publish, never export to Slack”), never raw credentials. Memory split into instructions, knowledge, session state, preferences, and audit history with provenance - not one writable simple.md file that the agent can stomp on. A stable domain event API instead of raw model output. Bounded cost through token budgets, revision limits, and concurrent-work caps as workflow-level counters rather than per-call heuristics.

Build as if intelligence gets cheap. Build only the substrate that proven consequential workflows actually demand.

Mapping to the three runtime designs

This build is the third design from the companion piece: the model drives an open loop with tools, and the workflow supervises the boundary.

The Docker baseline without Temporal was closer to the first design - a single call, minus the durability. The fixed-pipeline middle ground (specialists with reviewer loops and policy gates) fits work like remediation PRs where the artifact shape is known but its content needs iteration.

Three levels:

  • Fully specified input and output: one call, code-computed context.
  • Specified artifact, unspecified content: bounded specialist loops, code-applied artifacts.
  • Unspecified path: an agent loop in a sandbox, approvals before side effects.

Same spine across all three - prepare → context → model → validate → persist. Only the middle moves: who owns the loop.