Self-Hosted Orbs
You can self-host orbs on your own infrastructure. You provide the compute and Amp turns it into orbs.
If you have compute that can
- quickly start new machines
- put them to sleep and resume them equally fast
- keep them around for a long time
- scale them to zero, so no one has to worry about having a lot of them sleeping
then you can replicate the orb experience within your own infrastructure.
Self-hosted orbs are available to every workspace, and they are free for individuals and teams (see Pricing). If you work alone, you can create a workspace for free.
How It Works
You run amp orb provider on a machine in your network. It connects out to Amp and waits for
requests to spawn, pause, or delete orbs. Only outbound HTTPS is needed; Amp never connects in.

How you satisfy those requests is up to you. The orb provider runs a dispatch program that you provide, once per job:
amp orb provider ./dispatch.sh Your dispatch program receives spawn, pause, and delete jobs and creates, resumes, stops,
or removes a machine or container on your platform. On spawn, it starts amp orb bootstrap inside the orb. That process connects out to Amp so Amp can set up the environment and run the
agent’s tools.
When a member creates a thread on your provider, Amp sends a spawn job. When the thread goes
idle or is archived, Amp sends pause, and the next message sends spawn again to resume the
same orb. Amp sends delete when the orb is no longer needed, e.g., when the thread is deleted.
See What Leaves Your Network for the data sent to Amp and the outbound connections your network must allow.
Caution
Not all compute can be turned into orbs. Not all infrastructure meets the requirements to provide a great orb experience.
That’s why Amp-hosted orbs still give the best experience. When they pause, they save disk and memory, so processes keep running when the orb wakes, and new threads start from a project snapshot instead of running setup.
If you run orbs on Docker, for example, the experience is different. Docker cannot save memory. So when a Docker-backed orb wakes, the services behind your portals start again when someone opens a portal, and Terminal sessions and Desktop apps are gone.
The dispatch examples below differ in what an orb keeps when it pauses:
| Infrastructure | Memory | Disk | Project snapshots |
|---|---|---|---|
| Amp-hosted orbs | Yes | Yes | Yes |
| Docker | No | Yes | On one host |
| Apple containers | No | Yes | On one Mac, with container 1.6.0 or later |
| Kubernetes (Helm chart) | No | Yes | Yes |
| E2B | Yes | Yes | Not in the example |
| Modal | No | Yes | Not in the example |
| Vercel | No | Yes | Not in the example |
| Daytona | No | Yes | Not in the example |
| exe.dev | Yes | Yes | Not in the example |
| Boat | No guarantee | Partial | Not in the example |
- Memory: processes keep running when the orb wakes, including the services behind portals, Terminal sessions, and Desktop apps.
- Disk: files and installed software survive a pause, so a waking orb runs
.agents/resume, not.agents/setup. - Project snapshots: new threads start from a prepared copy of the orb and skip
.agents/setupwhile the snapshot is current. See Project Snapshots.
We know not everyone can use Amp-hosted orbs, and some infrastructure does meet every requirement. That’s why we offer self-hosted orbs.
Getting Started
Create a dispatch program for your platform, register a provider in your workspace, and start your first thread. For GKE, EKS, or AKS, you can use Amp’s Helm chart instead of writing a dispatch program.
Create a Dispatch Program
Save one of the examples below as dispatch.sh, dispatch.py, or dispatch.mjs and adapt it to
your platform. The provider passes each job to your program through environment variables.
Each example passes ORB_TOKEN into the orb to connect it to its thread. Most boot ORB_IMAGE;
the Boat example uses Boat’s own base machine instead.
Amp’s default ORB_IMAGE includes the Amp CLI and other dependencies. For ARM64 hosts, use the ARM64 image. You can also bring your own image. See the dispatch contract for the full list of variables and what each job must do.
#!/usr/bin/env bash
# dispatch.sh
set -euo pipefail
# "$ORB_IMAGE" is the Amp orb image.
case "$ORB_OPERATION" in
spawn)
image="$ORB_IMAGE"
docker image inspect "${ORB_SNAPSHOT_ID-}" >/dev/null 2>&1 && image="$ORB_SNAPSHOT_ID"
docker start "$ORB_ID" 2>/dev/null ||
docker run --init --platform linux/amd64 -d --name "$ORB_ID" \
"$image" sleep infinity
docker exec -d --env ORB_TOKEN "$ORB_ID" amp orb bootstrap
;;
pause)
docker stop "$ORB_ID"
[ -z "${ORB_SNAPSHOT_ID-}" ] || docker commit "$ORB_ID" "$ORB_SNAPSHOT_ID"
;;
delete)
docker rm -f "$ORB_ID"
;;
esac Docker does not save memory, so processes in the orb stop when it pauses. The container keeps its
files, so a waking orb runs .agents/resume, not .agents/setup. Project snapshots are local
images, so they work only on this host.
# Needs the E2B CLI, `npm install e2b`, and E2B_API_KEY. Build the template once:
echo 'FROM us-central1-docker.pkg.dev/amp-prod-0/amp-orb/amp-orb:latest' \
> e2b.Dockerfile
e2b template create amp-orbs --dockerfile e2b.Dockerfile \
--cpu-count 4 --memory-mb 8192 --ready-cmd true
export E2B_TEMPLATE=amp-orbs #!/usr/bin/env node
// dispatch.mjs
import { Sandbox } from 'e2b'
const {
ORB_OPERATION, ORB_ID, ORB_TOKEN, AMP_URL, E2B_TEMPLATE,
} = process.env
const metadata = { orbId: ORB_ID }
const [existing] = await Sandbox.list({
query: { metadata, state: ['running', 'paused'] },
}).nextItems()
if (ORB_OPERATION === 'spawn') {
const timeoutMs = 60 * 60 * 1000
const sandbox = existing
? await Sandbox.connect(existing.sandboxId, { timeoutMs })
: await Sandbox.create(E2B_TEMPLATE, {
metadata, timeoutMs, lifecycle: { onTimeout: 'pause' },
})
const envs = { ORB_TOKEN }
if (AMP_URL) envs.AMP_URL = AMP_URL
const bootstrap = await sandbox.commands.run('amp orb bootstrap', {
user: 'user', envs, background: true, timeoutMs: 0,
})
await bootstrap.disconnect()
} else if (ORB_OPERATION === 'pause' && existing) {
await Sandbox.pause(existing.sandboxId)
} else if (ORB_OPERATION === 'delete' && existing) {
await Sandbox.kill(existing.sandboxId)
} E2B preserves the disk and memory on pause. This example also pauses on timeout
after an hour rather than deleting the orb. It finds running and paused orbs by
metadata, so the provider does not need to save sandbox IDs locally. It ignores ORB_SNAPSHOT_ID, so every new thread runs .agents/setup.
Amp supplies a Helm chart for GKE, EKS, and AKS.
It runs the orb provider and a dispatch program that creates orbs as pods and saves paused orbs to your storage bucket, so you do not write a dispatch program yourself. The bucket holds the orb’s files, not its memory, so processes in the orb stop when it pauses.
See Kubernetes to set it up.
#!/usr/bin/env bash
# dispatch.sh
set -euo pipefail
export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
# "$ORB_IMAGE" is the Amp orb image.
ids=$(container list --all --quiet)
exists() { grep -Fxq "$ORB_ID" <<<"$ids"; }
case "$ORB_OPERATION" in
spawn)
image="$ORB_IMAGE"
container image inspect "${ORB_SNAPSHOT_ID-}" >/dev/null 2>&1 && image="$ORB_SNAPSHOT_ID"
if exists; then
container start "$ORB_ID"
else
container run --detach --init --rosetta \
--platform linux/amd64 --cpus 4 --memory 8G \
--name "$ORB_ID" "$image" sleep infinity
fi
# A bare --env NAME copies the value from the host.
container exec --detach --env ORB_TOKEN --env AMP_URL \
"$ORB_ID" amp orb bootstrap
;;
pause)
if exists; then
container stop "$ORB_ID"
[ -z "${ORB_SNAPSHOT_ID-}" ] || container commit "$ORB_ID" "$ORB_SNAPSHOT_ID"
fi
;;
delete)
if exists; then
container delete --force "$ORB_ID"
fi
;;
*)
exit 1
;;
esac Apple containers do not save memory, so processes in the orb stop when it pauses. Saving project
snapshots with container commit needs container 1.6.0 or later. Snapshots are local images, so
they work only on this Mac.
#!/usr/bin/env python3
# dispatch.py
import os
import modal
# Needs `pip install modal` plus MODAL_TOKEN_ID and MODAL_TOKEN_SECRET.
APP = "amp-orbs"
app = modal.App.lookup(APP, create_if_missing=True)
snapshots = modal.Dict.from_name("amp-orb-snapshots", create_if_missing=True)
orb_id = os.environ["ORB_ID"]
try:
sandbox = modal.Sandbox.from_name(APP, orb_id)
except modal.exception.NotFoundError:
sandbox = None
match os.environ["ORB_OPERATION"]:
case "spawn":
if sandbox is None:
# Modal has no pause, so resume from the last snapshot.
snapshot = snapshots.get(orb_id)
if snapshot:
image = modal.Image.from_id(snapshot)
else:
image = modal.Image.from_registry(os.environ["ORB_IMAGE"])
sandbox = modal.Sandbox.create(
"sleep", "infinity", app=app, name=orb_id, image=image,
cpu=4, memory=(16384, 16384), timeout=24 * 60 * 60,
)
# Modal execs run as root; point bootstrap at the image user's home.
bootstrap = "setsid amp orb bootstrap </dev/null >/dev/null 2>&1 &"
sandbox.exec(
"bash", "-c", bootstrap,
env={
"ORB_TOKEN": os.environ["ORB_TOKEN"],
"AMP_URL": os.environ.get("AMP_URL"),
"HOME": "/home/user", "USER": "user",
},
).wait()
case "pause":
if sandbox:
image_id = sandbox.snapshot_filesystem(timeout=120).object_id
snapshots[orb_id] = image_id
sandbox.terminate()
case "delete":
if sandbox:
sandbox.terminate()
snapshots.pop(orb_id, None) Modal’s filesystem snapshot keeps the orb’s files but not its memory, so processes in the orb stop
when it pauses. This example ignores ORB_SNAPSHOT_ID, so every new thread runs .agents/setup.
#!/usr/bin/env node
// dispatch.mjs
import { APIError, Sandbox, Snapshot } from '@vercel/sandbox'
const { ORB_OPERATION, ORB_ID, ORB_IMAGE, ORB_TOKEN, AMP_URL } = process.env
const credentials = {
token: process.env.VERCEL_TOKEN,
teamId: process.env.VERCEL_TEAM_ID,
projectId: process.env.VERCEL_PROJECT_ID,
}
if (ORB_OPERATION === 'spawn') {
const sandbox = await Sandbox.getOrCreate({
...credentials,
name: ORB_ID,
// Copy Amp's image to VCR, then set ORB_IMAGE to its VCR URL.
image: ORB_IMAGE,
resources: { vcpus: 4 },
timeout: 60 * 60 * 1000,
persistent: true,
snapshotExpiration: 0,
keepLastSnapshots: { count: 1, expiration: 0, deleteEvicted: true },
})
const setup = await sandbox.runCommand({
cmd: '/bin/bash',
args: ['-c', 'hostname localhost && mkdir -p /vercel/sandbox'],
sudo: true,
})
if (setup.exitCode !== 0) throw new Error(await setup.stderr())
await sandbox.runCommand({
cmd: '/usr/bin/setpriv',
args: [
'--reuid=user', '--regid=user', '--init-groups', '--',
'/usr/bin/env', `ORB_TOKEN=${ORB_TOKEN}`,
...(AMP_URL ? [`AMP_URL=${AMP_URL}`] : []),
'HOME=/home/user', 'USER=user', '/bin/bash', '-lc',
'exec amp orb bootstrap',
],
sudo: true,
detached: true,
})
} else if (ORB_OPERATION === 'pause' || ORB_OPERATION === 'delete') {
try {
const sandbox = await Sandbox.get({
...credentials, name: ORB_ID, resume: false,
})
if (ORB_OPERATION === 'delete') {
if (sandbox.status === 'running') await sandbox.stop()
for await (const saved of await sandbox.listSnapshots()) {
if (saved.status !== 'created') continue
const snapshot = await Snapshot.get({
...credentials, snapshotId: saved.id,
})
await snapshot.delete()
}
await sandbox.delete()
} else if (sandbox.status === 'running') await sandbox.stop()
} catch (error) {
if (!(error instanceof APIError && error.response.status === 404)) {
throw error
}
}
} Vercel saves the orb’s files when it stops, but not its memory, so processes in the orb stop when it
pauses. This example ignores ORB_SNAPSHOT_ID, so every new thread runs .agents/setup.
#!/usr/bin/env python3
# dispatch.py
import os
from uuid import uuid4
from daytona import (
Daytona, DaytonaNotFoundError, CreateSandboxFromImageParams,
Resources, SessionExecuteRequest, SandboxState,
)
daytona = Daytona()
operation = os.environ['ORB_OPERATION']
try:
sandbox = daytona.get(os.environ['ORB_ID'])
except DaytonaNotFoundError:
sandbox = None
if operation == 'spawn':
if sandbox is None:
sandbox = daytona.create(CreateSandboxFromImageParams(
name=os.environ['ORB_ID'], image=os.environ['ORB_IMAGE'],
os_user='user', resources=Resources(cpu=4, memory=8, disk=10),
auto_stop_interval=0, auto_delete_interval=-1,
), timeout=240)
elif sandbox.state != SandboxState.STARTED:
sandbox.start()
env = {key: os.environ[key] for key in ('ORB_TOKEN', 'AMP_URL')
if key in os.environ}
sandbox.update_env(env, unset=['AMP_URL'])
session = str(uuid4())
sandbox.process.create_session(session)
sandbox.process.execute_session_command(session, SessionExecuteRequest(
command="bash -lc 'exec amp orb bootstrap'", run_async=True,
))
elif operation == 'pause' and sandbox:
if sandbox.state == SandboxState.STARTED:
sandbox.stop()
elif operation == 'delete' and sandbox:
sandbox.delete() Daytona keeps the orb’s files when it stops, but not its memory, so processes in the orb stop when
it pauses. This example ignores ORB_SNAPSHOT_ID, so every new thread runs .agents/setup.
#!/usr/bin/env bash
# dispatch.sh
set -euo pipefail
# Pause/resume currently requires access enabled by exe.dev support.
status=$(ssh exe.dev ls "$ORB_ID" --json |
jq -r --arg id "$ORB_ID" '.vms[] | select(.vm_name == $id) | .status')
case "$ORB_OPERATION" in
spawn)
if [[ -z "$status" ]]; then
ssh exe.dev new --name="$ORB_ID" --image="$ORB_IMAGE" \
--cpu=2 --memory=8G --no-email
elif [[ "$status" == paused ]]; then
ssh exe.dev resume "$ORB_ID"
fi
{
printf 'export ORB_TOKEN=%q\n' "$ORB_TOKEN"
if [[ -n "${AMP_URL:-}" ]]; then
printf 'export AMP_URL=%q\n' "$AMP_URL"
fi
cat <<'BOOTSTRAP'
sudo -u user --preserve-env=ORB_TOKEN,AMP_URL bash -lc \
'cd "$HOME"; nohup amp orb bootstrap </dev/null >.amp-bootstrap.log 2>&1 &'
BOOTSTRAP
} | ssh "$ORB_ID.exe.xyz" bash -s
;;
pause)
if [[ "$status" == running ]]; then
ssh exe.dev pause "$ORB_ID"
fi
;;
delete)
if [[ -n "$status" ]]; then
ssh exe.dev rm "$ORB_ID"
fi
;;
esac exe.dev saves memory on pause, so processes keep running when the orb wakes. This example ignores ORB_SNAPSHOT_ID, so every new thread runs .agents/setup.
Install the Boat CLI and sign in with boat login. The
controller needs Bash, jq, and OpenSSH. Enable snapshots in Boat’s settings and keep zero data retention off.
#!/usr/bin/env bash
# dispatch.sh
set -euo pipefail
export BOAT_NO_UPDATE=1
# Keep this directory on persistent storage.
STATE="${BOAT_ORB_STATE:-$HOME/.amp-boat-orbs}"
mkdir -p "$STATE"
trap 'echo "Boat $ORB_OPERATION $ORB_ID failed at line $LINENO" >&2' ERR
MAP="$STATE/$ORB_ID"
id=""
state=""
if [[ -s "$MAP" ]]; then
id=$(cat "$MAP")
state=$(boat info "$id" --json | jq -er '.sandbox.state')
fi
case "$ORB_OPERATION" in
spawn)
if [[ -z "$id" ]]; then
# Read all JSON Lines before parsing. Increase the TTL for longer turns.
out=$(boat new --json --ttl 3600 --no-env)
printf '%s\n' "$out" | jq -ser 'map(select(.event == "ready"))[0].id // empty' > "$MAP"
id=$(cat "$MAP")
else
while [[ "$state" == stopping ]]; do
sleep 2
state=$(boat info "$id" --json | jq -er '.sandbox.state')
done
if [[ "$state" == stopped || "$state" == error ]]; then
boat resume "$id" >/dev/null
fi
fi
{
printf 'export ORB_TOKEN=%q AMP_URL=%q\n' "$ORB_TOKEN" "${AMP_URL:-https://ampcode.com}"
printf '%s\n' \
'export PATH="$HOME/.local/bin:$HOME/.amp/bin:$PATH"' \
'command -v amp >/dev/null || curl -fsSL https://ampcode.com/install.sh | bash' \
'nohup amp orb bootstrap </dev/null >~/.amp-bootstrap.log 2>&1 &'
} | boat ssh "$id" bash -se -o pipefail
;;
pause)
if [[ -n "$id" && "$state" != stopped ]]; then
[[ "$state" == stopping ]] || boat stop "$id" >/dev/null
until [[ "$(boat info "$id" --json | jq -er '.sandbox.state')" == stopped ]]; do
sleep 2
done
fi
;;
delete)
if [[ -n "$id" ]]; then
boat delete "$id" --yes
rm -f "$MAP"
fi
;;
esac Boat keeps home files for 14 days after a pause, but not /tmp or ~/.cache, and memory may not
survive. This example uses Boat’s base machine instead of ORB_IMAGE and ignores ORB_SNAPSHOT_ID, so every new thread runs .agents/setup.
Set Up a Provider
Open Settings → Orbs for your workspace, click Add Orb Provider, and give the provider a name such as
our-k8s-orbs. Members pick the provider by this name when they start a thread.

Copy the
aop_live_…token. It is shown once.Make your dispatch program executable and run the orb provider with the token in its environment and the absolute path of your program, under a service manager that restarts it after a reboot:
chmod +x dispatch.sh export AMP_ORB_PROVIDER_TOKEN="<copied token>" amp orb provider /opt/orbs/dispatch.sh
Run exactly one amp orb provider per token. Two of them, for example on two machines or a
forgotten terminal next to the service, each claim a share of the jobs and each start their own
orbs, so a thread’s orb ends up on one while its next job goes to the other. A provider tells Amp
when it starts and stops, and Amp notices one that was killed. If another provider is still
connected when one starts, it asks for confirmation at a terminal; without a terminal it logs the
other provider and waits for it to stop, so a rollout that stops the old one first needs nothing
special. Pass --allow-multiple only if your dispatch program shares its state across all of them.
Select the provider in workspace settings to make it the default for new threads.
Self-hosted orbs are on by default. A workspace admin can turn them off with the Self-Hosted Orbs setting in Settings → Orbs. While they are off, members cannot start orbs on the workspace’s providers and the providers stop receiving jobs; the providers and their tokens are kept.
Start Your First Thread
In the new thread composer, click Orb, open the type dropdown, and choose your provider under Self-Hosted.


From the CLI, run:
amp -ox "your first message" --orb-provider our-k8s-orbs Submit your prompt. Amp sends a request to your provider to spawn an orb, clones your repository,
runs .agents/setup, and starts the agent.
Dispatch Contract
The orb provider runs your program once per job, with no arguments. The job arrives as environment variables:
| Variable | Value |
|---|---|
ORB_OPERATION | spawn, pause, or delete. |
ORB_ID | The orb’s ID, such as amp-<thread id>. Use it as the machine name so repeated jobs find the same machine. |
ORB_IMAGE | The image to boot: your ORB_IMAGE override or Amp’s default. |
ORB_SNAPSHOT_ID | The project snapshot to boot from on spawn, or to save on pause. Unset when the job has none. |
ORB_TOKEN | spawn only. A short-lived credential that authorizes this orb’s connection to its thread. Pass it into the orb. |
AMP_URL | spawn only. The Amp server to connect to when it is not ampcode.com. Pass it into the orb with the token. |
Each operation does the following:
| Operation | What your program does |
|---|---|
spawn | Make the orb run. Start it again if it exists. Otherwise create it from ORB_SNAPSHOT_ID if you have that snapshot, or from ORB_IMAGE. Then start amp orb bootstrap inside it as the image’s default user, detached, with ORB_TOKEN and AMP_URL set. |
pause | Stop the orb and keep its disk. When ORB_SNAPSHOT_ID is set, also save a copy of the disk under that ID for later spawn jobs. If your platform cannot keep the disk, delete the orb instead; the next spawn boots a fresh one and Amp sets the repository up again. If your platform also saves memory, processes keep running after the next spawn. Otherwise they stop with the orb. |
delete | Remove the orb and its disk. |
Make every operation safe to repeat and finish within five minutes; your program’s output goes to the provider log, and spawn output also shows in the thread’s setup log.
Who Should Self-Host
We built self-hosted orbs for enterprises that can’t use the orbs we host, because regulations might restrict where their code runs, because they need access to internal networks, or because they need custom hardware attached.
That’s why the interface is so open: we want to support all the infrastructure our enterprise customers might have, as long as it meets the qualitative requirements.
The infrastructure needs to provide fast-to-start, resumable machines that scale to zero.
We only support our end of the dispatch contract, not every possible piece of infrastructure you can plug in.
We recommend you self-host orbs when:
- Your repository checkout and saved orb disks must stay in your cloud account.
- Your builds need private services that are reachable only from your network.
- Your rules require compute in a specific cloud, region, or account.
- You need custom hardware attached to orbs.
If you’re on an Enterprise plan, let us know and we’ll help you set it up.
What Leaves Your Network
Amp runs the agent loop and model inference on its own servers. Your orb runs the agent’s tools, such as reading and editing files and running commands. The repository checkout, build output, and saved orb disks stay on your infrastructure, and orbs can reach the private services your network allows.
During a thread, the orb sends Amp what those tools produce: the file contents the agent reads, command output, the diff shown in the thread’s Changes tab, and screenshots and other files attached to the thread. Amp stores this with the thread.
Amp never connects into your network and needs no cloud credentials or access to your cluster or storage, so you do not need to allow Amp’s server IP addresses. Allow this outbound access. Proxies must allow long-lived connections.
| Source | Destination | Purpose |
|---|---|---|
| Orb provider | ampcode.com, HTTPS 443 | Receiving jobs and reporting results |
| Orb provider and orbs | static.ampcode.com, HTTPS 443 | Amp CLI updates |
| Orbs | ampcode.com, HTTPS and WSS 443 | Bootstrap and tool connections |
| Orbs | tunnel.ampcode.com, WSS 443 | Amp Tunnel for portals, Terminal, and Desktop |
| Orbs | Your Git host, HTTPS 443 | Cloning and pushing the repository |
| Orbs | storage.googleapis.com, HTTPS 443 | ripgrep download when the image has no rg |
Portals, Terminal, and Desktop use Amp Tunnel. A portal on a self-hosted orb can be opened only by a signed-in Amp user with access to the thread.
Docker Image
Amp publishes two orb images. Both include the Amp CLI and the tools for portals, Terminal, and
Desktop. When the provider’s environment does not set ORB_IMAGE, Amp passes the x64 image to
every spawn job.
| Image | Platform |
|---|---|
us-central1-docker.pkg.dev/amp-prod-0/amp-orb/amp-orb:latest | Linux x64, the default |
us-central1-docker.pkg.dev/amp-prod-0/amp-orb/amp-orb-arm64:latest | Linux ARM64 |
For ARM64 hosts, such as Apple silicon Macs or ARM64 servers, set ORB_IMAGE to the ARM64 image
in the provider’s environment:
export ORB_IMAGE=us-central1-docker.pkg.dev/amp-prod-0/amp-orb/amp-orb-arm64:latest
amp orb provider ./dispatch.sh In the Docker example, replace --platform linux/amd64 with --platform linux/arm64. Also add --shm-size=1g, because the browser in the orb needs more than Docker’s default 64 MB of shared
memory.
Changing ORB_IMAGE affects only new orbs. Existing orbs and project snapshots keep the image they
started from.
Bring Your Own Image
Set ORB_IMAGE in the provider’s environment to start orbs from your own image instead of the
default Amp orb image. Amp passes the value to every spawn job.
The easiest way is to build on the Amp orb image and add what every orb needs, such as language
toolchains or internal CA certificates. Keep project-specific setup in .agents/setup.
An image built from another base must run Linux x64 or ARM64 with glibc, so Alpine does not work. It needs:
| Requirement | Needed for |
|---|---|
| Bash, coreutils, util-linux, and CA certificates | Every orb |
Git at /usr/bin/git or /opt/git/bin/git, 2.34 or later to sign commits | Every orb |
The Linux amp binary for the image’s platform at /usr/local/bin/amp | Every orb |
A user user and group as the image’s default user, with a writable /home/user | Every orb |
Passwordless sudo for user (user ALL=(ALL) NOPASSWD: ALL) | Every orb |
| Node.js 18 or later | Portals and external agents |
| tmux 2.9 or later | Terminal and external agents |
| curl | External agents |
/opt/amp-wayland from the Amp orb image, xwayland, dbus, gsettings-desktop-schemas, xsettingsd, wlr-randr, pulseaudio, ffmpeg, and fonts-dejavu-core | Desktop |
| ripgrep, gh, and openssh-client | Recommended |
Amp uses passwordless sudo inside the orb to configure system Git settings and prepare runtime files, as well as manage Amp services on images that use systemd.
Project Snapshots
A project snapshot is a saved copy of a prepared orb, with the repository cloned and .agents/setup already run, so later threads skip setup. Amp uses snapshots on self-hosted
providers the same way it does on Amp-hosted orbs, as long as your dispatch program supports
them. Of the examples above, Docker, Apple containers, and the Kubernetes chart do. See The Orb Lifecycle for when Amp saves and reuses them.
To save a snapshot, Amp sends a pause job with ORB_SNAPSHOT_ID set and then a spawn job
for the same orb. Store a copy of the orb under that ID on pause, and boot from the copy when a
later spawn names it.
If your dispatch program ignores ORB_SNAPSHOT_ID, every thread starts from the image and runs .agents/setup.
To list or delete a project’s snapshots, run:
amp projects snapshots list <project>
amp projects snapshots delete <project> Bring Your Own Repository Checkout
By default, Amp clones the project’s repository into each new orb and sets up Git and gh with
short-lived GitHub tokens for the member who created the thread. If your orb image already
contains the repository and its own Git credentials, turn off Repository Setup in the
provider’s Edit dialog in workspace settings.


Amp then does not clone or fetch the repository, change the orb’s Git credential helpers or gh login, or get a GitHub token for the orb. It still sets the commit author, runs the pre-setup
script and .agents/setup, and starts the agent. The setting applies to orbs Amp sets up after
you change it and does not apply to repositories hosted on Amp.
Your image must have a Git checkout of the project’s repository at /home/user/workspace/repo with origin and origin/HEAD set, so the Changes view and Ship can find the base branch.
Keeping the checkout up to date is your job. Pushes and pull requests use the credentials in the
orb, so use a credential helper that gets a fresh token each time Git asks; a token that lasts an
hour can expire during a long thread.
Pricing
Self-hosted orbs are free for every workspace until October 29. After that, they stay free on the Hobby, Individual, and Teams plans, and Enterprise workspaces pay 100% of the standard Amp-hosted orb price for the orb’s CPUs and memory. Volume discounts are available; contact the Amp team.
You also pay for the infrastructure that runs your orbs. Your thread usage shows each orb’s runtime and size, even when its cost is $0.
Prices are at the standard credit rate; an Enterprise workspace pays in its credits at its rate. An orb is billed by the minute while it is connected to a thread, with the CPUs and memory it reports. Only the thread’s orb is billed; its agent tools sandbox is not.
Troubleshoot
If an orb does not start, read the orb provider’s log and the thread’s setup log. If the cause is not on your side, send a bug report from the thread and include the log lines.