Skip to main content
Modal runs code in isolated cloud sandboxes. A coding agent’s tools work in a sandbox checkout while its model loop runs in the Rebuno agent. Each tool call passes through policy and becomes a recorded step. The workspace is a resource: later dispatches reopen it, and forks create separate sandboxes from its checkpoints.

Register the workspace

The handler registers a sandbox with a checkpoint policy:
The example’s ModalResource implements three operations:
  • create(checkpoint_ref=None) creates a sandbox from the selected snapshot image or a fresh checkout, and configures its Git branch.
  • open(binding) connects to the sandbox ID recorded for this execution.
  • checkpoint(handle) captures a filesystem snapshot and returns its image ID.
The checkpoint policy is optional. Without it, dispatches and session turns still reopen the same sandbox, but forks start from a fresh checkout. With it, the SDK captures a baseline, then checkpoints after every fifth live tool call that declares a workspace change, and on completion. Rebuno stores the sandbox binding and image IDs; Modal stores the files. Each new sandbox gets a unique Git branch, and shell commands push the current branch. Reopening a workspace preserves its branch. Modal sandboxes cannot pause. The sandbox runs until its one-hour timeout, including while a call waits for approval or after a worker dies. Missing sandboxes or selected snapshots fail the execution.

Wrap the tools

shell and write_file declare workspace changes. read_file uses the default of none:
The model also uses shell to commit and push the branch. These tools use safe_to_retry, so an interrupted command can run again. See idempotency when adding commands with effects that must not repeat.

Keep credentials out of the sandbox

Cloning and pushing need a GitHub token. The driver stores it in a named Modal secret and creates the sandbox with an outbound policy that sets the Authorization header on requests to github.com:
Modal resolves the secret outside the sandbox, so git works as usual and nothing in the sandbox can read the token. The secret name comes from a hash of the token, so each token gets its own secret and running sandboxes keep theirs. The driver applies the policy when opening a sandbox, so resumed workspaces use the token the worker currently holds. Outbound policies are an experimental Modal API. Opening the pull request is a separate tool that calls GitHub’s API from the agent:
The policy covers only github.com, so the sandbox can push a branch but cannot use that token to call the API. Protect the default branch with a GitHub ruleset that requires a pull request.

Sessions

The handler reads the previous completed turn with previous() and returns its conversation in Result.state:
Executions in the same session reuse the workspace and Git branch, so later pushes update the same pull request. A session turn that starts after the sandbox’s timeout fails. See Sessions.

Fork the workspace

A fork creates a separate sandbox from the selected snapshot image. At an uncovered point it uses the newest earlier checkpoint; copied tool results still replay through the requested event and may describe changes missing from that sandbox. See Fork coverage. Snapshots restore sandbox files; GitHub branches and pull requests persist.

Write the policy

Sandbox tools are allowed. Opening a pull request waits for approval.

Run it

examples/integrations/sandbox/modal has the full agent, resource driver, policy, and dev kernel config. Authenticate with modal token new, or set MODAL_TOKEN_ID and MODAL_TOKEN_SECRET. Set REPO (the repository as owner/name), GITHUB_TOKEN, LLM_MODEL, LLM_BASE_URL, and LLM_API_KEY. The token needs read and write access to the repository’s contents and pull requests. Start the kernel and agent from that directory:
Create an execution:
The open_pr call appears in rebuno exec watch. See Approvals to approve it. Continue the same session with:
To see a re-dispatch, stop the agent after a few tool calls and start it again. The kernel dispatches the execution once its lease expires, two minutes by default. lease_timeout_seconds sets a shorter lease for the agent.