When I started building tools for orchestrating coding agents, I came across numerous issues. Agents like Claude Code and Codex are designed to run locally on your machine and I needed them to run in the cloud triggered by API calls, persisting across requests, and in isolated environments. After running into these issues numerous times, I built the Background Agents SDK, which solves this for CLI coding agents in general and using Daytona as the sandbox provider. I evaluated several options for running agents in isolated environments, and Daytona stood out for a few reasons:
Instant sandbox creation: I can spin up an isolated environment with a single API call
Built-in git support: Cloning repos, switching branches, and pushing changes are all first-class operations
Long-lived persistence: Sandboxes outlive my serverless functions and can run indefinitely with Daytona
Security by design: Credentials like GitHub tokens stay outside the sandbox, so agents can't accidentally (or intentionally) misuse them This meant I could focus on the agent orchestration logic instead of building infrastructure.
Using the Background Agents SDK
I built the Background Agents SDK as a TypeScript library to solve a specific problem: running long-running AI coding agents from serverless applications without managing infrastructure. First, create a Daytona sandbox and clone the repo:
1import { Daytona } from "@daytonaio/sdk"23const daytona = new Daytona({ apiKey: process.env.DAYTONA_API_KEY })45const sandbox = await daytona.create()67await sandbox.git.clone("https://github.com/user/repo.git", "/home/daytona/repo")89await sandbox.git.checkoutBranch("/home/daytona/repo", "fix-auth-bug")
Then create a session and start the agent:
1import { createSession, getSession } from "background-agents"23const session = await createSession("claude", {4 sandbox,5 cwd: "/home/daytona/repo",6 env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },7})89await session.start("Refactor the auth module")
The agent is now running in the sandbox with the repo cloned into it. To use a different agent, just change "claude" to any of the supported agents:
"claude": Claude Code from Anthropic"codex": Codex from OpenAI"gemini": Gemini CLI from Google"goose": Goose from Block"opencode": OpenCode, open-source multi-provider agent"pi": Pi, lightweight open-source agent
Long-Running Agents
Coding agents can run from a few minutes to much longer. For a serverless application, you need to be able to reconnect to running agent sessions to check their status later. When you start a session, save the sandbox ID (sandbox.id) and session ID (session.id). Later, you can reconnect:
1const sandbox = await daytona.get(sandboxId)23const session = await getSession(sessionId, { sandbox })45const { events, running } = await session.getEvents()67for (const event of events) {8 if (event.type === "token") process.stdout.write(event.text)9 if (event.type === "tool_start") console.log(`\n[Tool: ${event.name}]`)10}
The SDK tracks which events you've already seen. Each call to getEvents() returns only the new ones.
Event Types
Since every agent has its own output format, the SDK normalizes agent outputs into a common event structure: token for text output, tool_start and tool_end to mark when a tool begins (with name and input) and finishes (with output), and end when the agent completes the task. Here's what the output of the SDK looks like like when an agent edits a file:
1{ type: "token", text: "I'll update the auth module..." }2{ type: "tool_start", name: "edit", input: { file_path: "src/auth.ts", ... } }3{ type: "tool_end", output: "File updated successfully" }4{ type: "token", text: "Done. The auth module now..." }5{ type: "end" }
Pushing to Git
When getEvents() returns running: false, the agent has finished. You can then push the changes:
1if (!running) {2 await sandbox.git.push("/home/daytona/repo", "x-access-token", githubToken)3}
You can also create a PR using the GitHub API:
1import { Octokit } from "octokit"23const octokit = new Octokit({ auth: githubToken })4await octokit.request("POST /repos/{owner}/{repo}/pulls", {5 owner: "user",6 repo: "repo",7 title: "Refactor auth module",8 head: "fix-auth-bug",9 base: "main",10})
One security benefit is that because Daytona manages the git operations, my GitHub token is never stored in the sandbox. The agent can't push to branches on its own or delete repos, it only has access to the files.
Adding New Agents
Anyone can add new agents to the framework. All you have to do is provide the right information, the agent's CLI commands and documentation, and a coding agent can build the rest using the TESTING.md instructions. The sample output is auto-generated, and the whole process can often be completed in a single session.
Conclusion
The hard parts of running AI agents in the cloud is isolation, persistence, and scaling. Daytona handled all of that, enabling me to build a reusable SDK around all CLI coding agents. If you're building something similar, I'd love to hear about it, and contributions to the SDK are welcome.