Run Devin Outposts sessions on Tensorlake sandboxes.
The orchestrator watches a Devin Outposts queue, claims sessions for one
outpost, creates or resumes a Tensorlake Firecracker microVM for each session,
launches the pinned devin-remote binary inside that sandbox, then releases and
suspends or terminates the sandbox when the session sleeps or ends.
Devin's agent loop (inference and planning) keeps running in Devin's cloud. Command execution, file edits, and repository access happen inside a Tensorlake sandbox that you own and can inspect.
The orchestrator runs inside a Tensorlake sandbox: a launcher command on
your machine starts it in a long-lived dispatcher sandbox, so no long-running
orchestrator process remains on your machine. Observe it with --status and
--logs. See Run the orchestrator.
A Makefile wraps every command below (make connect, make build,
make build-dispatcher, make orchestrator-sandbox, make status,
make logs).
- A Devin account with Outposts enabled.
- A Tensorlake account and API key that can build images and manage sandboxes, from cloud.tensorlake.ai.
- Python 3.10+ on the machine that runs the setup and launcher commands (connect, image builds, sandbox launch). The orchestrator itself runs inside a Tensorlake sandbox.
- A Devin organization administrator who can approve the outpost connection and manage service users.
Tensorlake sandboxes are Linux Firecracker microVMs, so an outpost served this way is Linux only. Run one orchestrator process per outpost.
Install the package:
python3 -m venv .venv
. .venv/bin/activate
pip install -e .
cp .env.example .envoutposts-connect fills in the Devin settings. Add your Tensorlake API key
before building the image. After the build, copy the printed image name into
IMAGE_NAME in .env before starting the orchestrator.
Run the connection command on the same machine as your browser:
outposts-connect --platform linuxThe command opens Devin's connection page. A Devin organization administrator
confirms the outpost name and platform and clicks Connect. Devin redirects
the browser to a temporary listener on http://localhost:8765/callback; the
command exchanges that one-time code directly with Devin and writes
DEVIN_OUTPOSTS_TOKEN, DEVIN_API_URL, and OUTPOST_ID to this project's
.env. The listener then exits. The machine token does not pass through the
browser, and nothing is written to a global environment store.
The command and browser must run on the same machine because the callback uses
localhost. If the machine you launch the orchestrator sandbox from is headless,
run the connection on a workstation and transfer .env securely, or use manual
setup. See Cognition's
Outposts partner integration documentation
for the authorization flow.
| Token | Scope | Used for |
|---|---|---|
DEVIN_OUTPOSTS_TOKEN |
account.outposts.machine |
Created with a service user by outposts-connect; used for queue list, watch, claim, and release. Sandboxes never receive this token; each remote gets its claim's connect token. |
TENSORLAKE_API_KEY |
Tensorlake API access | Image build and sandbox lifecycle calls. The Tensorlake SDK reads it from the environment; it is never placed on a command line. |
GIT_TOKEN |
Repo-read access, only if REPOS needs it |
Optional repo pre-clone step. |
DEVIN_OUTPOSTS_TOKEN is intentionally named after Devin's Outposts
machine-serving contract. Do not treat it as interchangeable with a generic
DEVIN_API_TOKEN from unrelated Devin API scripts.
If the connection flow is unavailable, install the Devin CLI and create an
outpost with a Devin v3 service-user token (prefix cog_) whose service
user has the Use outpost machine and Manage outposts permissions:
| Token | Scope | Used for |
|---|---|---|
| Devin outpost-create token | account.outposts.orchestrator |
One-time outpost creation with devin worker outpost create. Remove it from your shell after the outpost exists. |
export DEVIN_API_URL="https://api.devin.ai"
export DEVIN_OUTPOST_CREATE_TOKEN="replace-with-outpost-create-token"
devin worker outpost create tensorlake-linux \
--platform linux \
--description "Tensorlake Linux sandboxes for Devin Outposts" \
--api-url "$DEVIN_API_URL" \
--token "$DEVIN_OUTPOST_CREATE_TOKEN"Do not add /opbeta to DEVIN_API_URL; the queue client appends that path.
Copy the returned outpost ID into OUTPOST_ID in .env, and set
DEVIN_OUTPOSTS_TOKEN to a service-user key with the account.outposts.machine
scope.
The orchestrator creates each serving sandbox from a registered Tensorlake
image. build-devin-outposts-image installs the required pieces on top of a
Tensorlake Ubuntu base: ca-certificates, curl, git, and the
/opt/.devin and /var/log/devin directories. It also attempts to install the
GitHub CLI and Chromium on a best-effort basis. The command prints the recipe
hash and image name.
The image name is derived from a hash of the build recipe as
devin-outposts-tl-{sha8}, so editing a step produces a new name and re-running
with an unchanged recipe reuses the existing image. Set IMAGE_NAME before the
build to pin an explicit name instead.
set -a
. ./.env
set +a
build-devin-outposts-imageIf the console script is unavailable but the package dependencies are installed, run this instead:
python -m devin_outposts.build_imageCopy the printed image name into IMAGE_NAME in .env before starting the
orchestrator. The build allocates the CPU, memory, and disk from SANDBOX_CPUS,
SANDBOX_MEMORY_MB, and SANDBOX_DISK_MB (defaults: 2 vCPU, 8 GiB, 10 GiB), and
each concurrently served session runs one sandbox.
The gh and Chromium steps are best-effort so the image always builds. If your
sessions must build or test code or drive a browser, harden those steps in
devin_outposts/build_image.py and add language toolchains there. The base
image is tensorlake/ubuntu-systemd; override it with IMAGE_BASE.
After .env has DEVIN_OUTPOSTS_TOKEN, TENSORLAKE_API_KEY, OUTPOST_ID, and
IMAGE_NAME, launch the orchestrator. DEVIN_API_URL is optional and defaults
to https://api.devin.ai.
The orchestrator runs inside a long-lived Tensorlake sandbox launched from a dispatcher image. Your machine runs the build and launcher commands, but it does not need to keep an orchestrator process alive. Build that image once, then launch:
build-devin-outposts-dispatcher-image # once; or: make build-dispatcher
devin-outposts-orchestrator-sandbox # launch/resume; or: make orchestrator-sandboxObserve and manage it:
devin-outposts-orchestrator-sandbox --status # make status
devin-outposts-orchestrator-sandbox --logs # make logs
devin-outposts-orchestrator-sandbox --terminate # make stopDevin Outposts is watch-based: the orchestrator must be running and watching
the queue to claim sessions, so this is not truly scale-to-zero. Tensorlake
keeps a named sandbox up to your plan's maximum idle window and then suspends
it. While it is suspended, it cannot claim sessions.
devin-outposts-orchestrator-sandbox is idempotent: it resumes the sandbox and
makes sure the process is running. Schedule that command with cron or a
Tensorlake schedule to restore the dispatcher after the idle ceiling. The
dispatcher sandbox holds TENSORLAKE_API_KEY and the Devin machine token. It
also receives GIT_TOKEN when configured, so treat it as a trusted
control-plane host.
Create Devin sessions from the Devin UI or Slack and select the outpost you created. The orchestrator will claim matching sessions from that outpost.
The orchestrator log (make logs) only shows lifecycle events (claim, sandbox
created, serving, released). The session's processing output — every tool call
devin-remote executes — is written inside the serving sandbox to
/tmp/devin-outposts/<session>.log. Use session_logs.py to read it:
python session_logs.py # list this outpost's serving sandboxes
python session_logs.py <dvo-name> # dump the last 500 lines
python session_logs.py <dvo-name> -f # stream live (ctrl-C to stop)The sandbox name is the dvo-... string the orchestrator prints when it claims
a session. Live mode attaches a tail -F over a PTY websocket — the SDK's
streaming channel — and follows the session lifecycle: it reconnects when the
websocket idles out, waits while the sandbox is suspended (Devin put the
session to sleep) and re-attaches on resume, and exits when the sandbox is
terminated because the session ended.
devin_outposts.config— defines the environment contract for orchestration.devin_outposts.connect— authorizes an outpost through Devin and writes its machine credentials to.env.devin_outposts.queue— provides the thin typed client for the early-access queue API.devin_outposts.queue_shapes— normalizes drifting beta queue payload shapes.devin_outposts.sandbox— names sandboxes and looks them up by name across restarts (Tensorlake has no labels).devin_outposts.worker— prepares and supervises the remote viasandbox.run.devin_outposts.worker_linux— launches the pinned, checksum-verified Linux remote.devin_outposts.orchestrator— reconciles queue state with sandbox and worker lifecycle; runs inside the dispatcher sandbox.devin_outposts.build_image— builds the hash-named per-session worker image.devin_outposts.dispatcher_image— builds the image that runs the orchestrator inside a sandbox.devin_outposts.launch_orchestrator_sandbox— launches/observes the orchestrator sandbox from your machine.
See docs/ARCHITECTURE.md for the component and lifecycle design.
| Variable | Required | Notes |
|---|---|---|
DEVIN_OUTPOSTS_TOKEN |
Yes | Service-user key scoped for machine serving, account.outposts.machine. Sent as a bearer token to the queue API. Sandboxes never receive it; each remote gets only its claim's connect token. |
DEVIN_API_URL |
No | Devin API base without /opbeta. Defaults to https://api.devin.ai. |
TENSORLAKE_API_KEY |
Yes | Read by the Tensorlake SDK for image build and sandbox lifecycle. |
OUTPOST_ID |
Yes | The outpost ID written by outposts-connect or returned by manual creation. Run one orchestrator process per outpost. |
IMAGE_NAME |
Yes for the orchestrator | Registered Tensorlake image used to create serving sandboxes. build-devin-outposts-image derives this when the variable is blank; the orchestrator needs the actual name in .env. |
TENSORLAKE_ORGANIZATION_ID |
No | Pin the org when your account spans more than one. |
TENSORLAKE_PROJECT_ID |
No | Pin the project when your account spans more than one. |
TENSORLAKE_NAMESPACE |
No | Pin the namespace; defaults to the SDK default. |
MAX_CONCURRENT_SESSIONS |
No | Default 5. The orchestrator does not serve more sessions than this at once. |
ACCEPTOR_ID |
No | Leave blank for a generated ID that persists in STATE_DIR. Set it only when you need a stable, unique name for this instance. Do not share one acceptor ID across instances. |
STATE_DIR |
No | Stores the watch cursor, generated acceptor ID, and the sandbox→session index. The launcher pins it to /root/.devin-outposts-orchestrator inside the dispatcher sandbox, so any value set here is ignored. |
SANDBOX_CPUS |
No | vCPUs per serving sandbox. Default 2. Match your image build. |
SANDBOX_MEMORY_MB |
No | Memory per serving sandbox. Default 8192. Tensorlake allows 1024–8192 MB per CPU core. |
SANDBOX_DISK_MB |
No | Disk per serving sandbox. Default 10240. |
SANDBOX_TIMEOUT_SECS |
No | Idle threshold before Tensorlake auto-suspends an idle named sandbox. Default 1800. 0 requests your plan maximum. The supervise loop sends traffic more often than this, so a serving sandbox stays awake. |
SANDBOX_NO_INTERNET |
No | true blocks outbound internet from serving sandboxes. Keep false: the remote needs its outbound connection to Devin's gateway. |
SANDBOX_USER |
No | User the remote runs as. Default root. |
SANDBOX_HOME |
No | Home directory for the remote and its state. Default /root. |
WORKDIR |
No | Working directory for repo clones and the remote launch. Default /root/workspace. |
DEVIN_CHROME_PATH |
No | Browser path inside sandboxes. Default /usr/bin/chromium. |
REPOS |
No | Comma-separated clone URLs for optional pre-clone into WORKDIR. |
GIT_USERNAME |
No | Optional username for private repos cloned from REPOS. |
GIT_TOKEN |
No | Optional token for private repos cloned from REPOS. The launcher forwards it to the trusted dispatcher sandbox. |
| Queue state | Orchestrator action |
|---|---|
phase=pending, session_status=pending or running |
Claim with this ACCEPTOR_ID, create the sandbox (or resume an existing one), clone REPOS when configured, then launch the pinned devin-remote. |
phase=claimed, acceptor_id matches this orchestrator |
Re-attach on startup. If the sandbox or worker is gone, release the claim and suspend or terminate according to the latest queue status. |
session_status=suspended |
Release the claim and suspend the sandbox. Tensorlake preserves the filesystem, memory, and running processes so the same session resumes in place. |
session_status=terminated |
Release any held claim and terminate the sandbox. |
| Queue entry missing or delete event | Treat as suspended: suspended sessions leave the queue entirely, so release any held claim and suspend the sandbox while keeping its state. |
Claim conflict 409 |
Another acceptor won the compare-and-set claim. The orchestrator leaves the session alone. |
Tensorlake suspend/resume is the key difference from stop/start on other
providers: a resumed session picks up with its memory and processes intact, not
just its disk. docs/ARCHITECTURE.md covers the watch
loop, serve sequence, reattach after restart, and the janitor.
Tensorlake sandboxes have no labels, and Sandbox.connect resolves by sandbox
ID rather than name, so the deterministic sandbox name is the reconciliation
key:
- Name:
dvo-{outpost8}-{session}-{hash}— an 8-char hash of the outpost ID as a prefix, a sanitized session fragment, and a 16-char hash of the session ID. - The outpost prefix lets the janitor scope a
Sandbox.listsweep to this outpost; the session hash keeps distinct sessions from colliding. - A
sandbox_index.jsoninSTATE_DIRmaps sandbox name → session ID so the janitor and reattach logic can map a listed sandbox back to its Devin session after an orchestrator restart.
Use REPOS when the worker needs repository state before the remote launches.
REPOS=https://github.com/example/private-app.git,https://github.com/example/shared-lib.git
GIT_USERNAME=replace-with-git-username
GIT_TOKEN=replace-with-git-tokenWhen REPOS is configured, the orchestrator creates WORKDIR and clones those
URLs before starting the worker. Credentials are passed to git through a
GIT_ASKPASS helper written into the sandbox, so the token never appears on a
command line or in git config. Treat GIT_TOKEN like any other secret. The
launcher forwards it to the dispatcher sandbox and then to each serving sandbox
that needs to clone a configured repository.
Run this before relying on the orchestrator in your own environment:
- Build the image with
build-devin-outposts-imageand copyIMAGE_NAME. - Create a fresh sandbox from that image and verify
git --version. If your workflow depends on the best-effort tools, verifygh --versionand Chromium as well. - Create a Devin session on the outpost.
- Confirm the orchestrator claims it, creates a sandbox, starts the worker, and
completes a smoke prompt that writes a file under
WORKDIR. - Let the session sleep, then message it again. Confirm the same sandbox resumes and the file still exists.
- Terminate the session. Confirm the orchestrator terminates the sandbox.
- Set a stable
ACCEPTOR_IDin.env, terminate the dispatcher sandbox during an active session (devin-outposts-orchestrator-sandbox --terminate), relaunch it, and confirm the orchestrator re-attaches to entries claimed by thatACCEPTOR_ID. (Terminating the dispatcher discards its generated state, so a pinned acceptor ID is required for this check.)
- Outposts APIs are in early access. The queue client is intentionally thin; expect endpoint shapes to change.
- The current Devin session-create API may not expose outpost selection. If so, create sessions from the Devin UI or Slack and select the outpost there.
- The remote runs as
rootin a per-session isolated microVM by default. SetSANDBOX_USER/SANDBOX_HOMEif your image ships a different user. - The default image installs
ghand Chromium best-effort. Validate the browser and desktop tools against your tenant before relying on Devin's browser or computer-use features, and add language toolchains for build/test sessions. - The image recipe (
devin_outposts/build_image.py) is the main thing to verify against your Tensorlake tenant, along with the sandboxSANDBOX_USER/HOMEdefaults.
- Keep real values in
.env, a trusted launch environment, or a secret manager. - Keep
.env.exampleplaceholders fake. - Do not paste tokens into session prompts, logs, docs, generated artifacts, or issue comments.
- Secrets are passed into subprocesses and the remote through environment variables, never argv.