Build a provider

Build a custom AgentBox provider as a plugin on @madarco/agentbox-provider-sdk and register it with agentbox plugin add — no changes to AgentBox itself

AgentBox ships seven built-in providers (local-docker, remote-docker, hetzner, digitalocean, daytona, vercel, e2b), but the provider surface is open: you can build your own provider as a plugin — its own npm package — and run agents on any cloud or infrastructure with agentbox --provider <name>. A plugin needs zero changes to AgentBox itself.

SHARE YOUR PROVIDER

Once your provider works end to end, open a PR against madarco/agentbox to add a short page for it under Community Providers in these docs — so other users can discover and install it.

You build against one public package, @madarco/agentbox-provider-sdk. It re-exports the whole provider-facing surface with AgentBox's internals inlined, so your plugin never imports AgentBox internals — that indirection is the stable seam AgentBox can refactor behind without breaking published plugins.

Two reference packages live in the repo — read the first, copy the second:

  • examples/agentbox-provider-sample — a stub backend that throws on provision. The smallest thing that plugs in; shows the contract.
  • examples/agentbox-provider-example — a real, working provider (Vercel-backed) built only on the SDK. Exercises the whole surface: prepare, buildAttach, an id-addressed checkpoint, and the box-runtime split.

The shape of a provider

Implement the thin CloudBackend (~13 methods over your cloud's SDK), wrap it with createCloudProvider to get the full box lifecycle for free, and export a providerModule:

import {
  createCloudProvider,
  type CloudBackend,
  type ProviderModule,
} from '@madarco/agentbox-provider-sdk';

const backend: CloudBackend = {
  name: 'myprovider',
  async provision(req) {
    /* create the VM/sandbox, return { sandboxId } */
  },
  async get(id) {
    /* … */
  },
  async start(h) {},
  async stop(h) {},
  async pause(h) {},
  async resume(h) {},
  async destroy(h) {},
  async state(h) {
    return 'running';
  },
  async exec(h, cmd, opts) {
    /* … */
  },
  async uploadFile(h, local, remote) {},
  async downloadFile(h, remote, local) {},
  async listFiles(h, dir) {
    return [];
  },
  async previewUrl(h, port) {
    return { url: `https://…:${port}` };
  },
  // optional: createSnapshot/deleteSnapshot (checkpoints), list (prune),
  // refreshPreviewUrl, signedPreviewUrl, attachArgv, renewTimeout, …
};

const provider = createCloudProvider(backend, {
  defaultResources: { cpu: 2, memory: 4, disk: 40 },
});

export const providerModule: ProviderModule = {
  provider,
  doctorChecks: async () => [{ label: 'credentials', status: 'ok', detail: 'configured' }],
  // optional: backend, ensureCredentials, readCredStatus, currentBaseFingerprintLive
};

Only provider and doctorChecks are required — createCloudProvider supplies the entire lifecycle (workspace seeding, ctl launch, relay wiring, preview URLs, checkpoints, file copy) on top of the thin CloudBackend. "A cloud is one file."

What the package must ship

  • Name it agentbox-provider-<name> (or @scope/agentbox-provider-<name>).
  • Declare the contract version in package.json:
    { "agentbox": { "providerApiVersion": 3 } }
  • Depend on @madarco/agentbox-provider-sdk (^3).
  • Export a providerModule (or providerModules for a multi-provider package).

Describe your provider

Declare a descriptor and AgentBox's UIs can render your provider properly — a real name in the create pickers, the right credential form, and correct capability gating:

import type { ProviderDescriptor } from '@madarco/agentbox-provider-sdk';

const descriptor: ProviderDescriptor = {
  name: 'myprovider',
  kind: 'cloud',
  label: 'MyProvider (cloud microVM)',
  loginHint: 'paste an API token from the MyProvider console',
  credentials: {
    envKeys: ['MYPROVIDER_TOKEN'], // presence = "configured"
    fields: [{ key: 'token', label: 'API token' }], // the form a UI renders
  },
  bake: { required: true, approxMinutes: '5-10' },
  capabilities: {
    checkpoints: true,
    checkpointReboots: false,
    ssh: false,
    persistentSsh: false,
    directBoxSsh: false,
    inbound: false,
    directGit: true,
    resync: true,
    prune: true,
    vnc: true,
    dind: true,
    pauseSemantics: 'freeze',
    hubRoutable: true,
  },
  blurb: 'MyProvider microVMs',
  sizeDesc: 'Per-provider override of `box.size` for myprovider.',
  imageDesc: 'Per-provider override of `box.image` for myprovider.',
};

export const providerModule: ProviderModule = { provider, backend, descriptor /* … */ };

agentbox plugin add snapshots this, so the CLI, hub and tray all read it without importing your package.

Declare what code can't reveal. AgentBox doesn't infer capabilities from your Provider object: createCloudProvider gives every cloud provider a checkpoint, setInbound and enableDirectGit, so their presence says which scaffold you used, not what you support. Three values are cross-checked against your CloudBackend (which you wrote, so it means something) — prune must equal !!backend.list, inbound must equal !!backend.setInbound, and timeoutModel must equal backend.timeoutModel.

Three capabilities are easy to get wrong:

  • pauseSemantics'freeze' if pause preserves the running processes, 'stop' if it powers the box off and only the disk survives. It's a labelling hint, not a reason to hide a pause control (powering a VPS down is what stops the billing).
  • persistentSsh — whether the per-box SSH identity outlives the CLI call. A gateway handing out an expiring token doesn't qualify, even though SSH works right now.
  • checkpointRebootstrue if capturing a snapshot stops and restarts the box, so the CLI confirms before yanking a live agent.

Already published a plugin?

You don't have to change anything. descriptor is optional and the SDK's contract version didn't move, so an existing plugin loads exactly as before — AgentBox derives what it can from your module and defaults the rest to preserve current behavior. Declaring one just upgrades you from a bare provider name to a real label, credential form and capability gating.

Cloud overrides (optional)

A cloud provider that bakes its own base image typically overrides three optional capabilities on top of createCloudProvider — the SDK re-exports the helpers each one needs, so they're all buildable on the SDK alone:

  • prepare — boot a builder sandbox, run your installer, snapshot it. Bake the host's static agent config in with stageAllAgentStatic, which returns one entry per agent the host has (plus the shared ~/.agents skills tree), each with the extractDir to unpack it at; persist the result in your own ~/.agentbox/<name>-prepared.json.
  • buildAttach — for a provider with no SSH, render the shared inner tmux command with renderInnerCommand + hostTermForCloud and return your transport's argv.
  • checkpoint — if your snapshots are id-addressed (an opaque id you can't name, like Vercel/E2B), override the whole capability and store the snapshot id in the manifest via writeCloudCheckpointManifest and friends. If they're name-addressed, just implement backend.createSnapshot/deleteSnapshot and skip the override.

For the box-side runtime (a VPS-style provider that installs files onto a throwaway host), pull ctl.cjs and the shims from the running CLI with resolveSharedRuntimeAsset('ctl.cjs') so they stay version-locked — don't vendor your own. Ship only your provider-specific pieces (an installer script, a custom-system-CLAUDE.md). Providers that build from a Dockerfile don't need any of this.

Credentials

Persist your API token however you like; the convention is a 0600 ~/.agentbox/secrets.env entry read on demand (see the built-in providers' env-loader.ts / credentials.ts). Your plugin manages its own base image in ~/.agentbox/<name>-prepared.json — AgentBox does not pin a plugin's image into its config.

Develop & test locally

Working from a clone of the repo, against the bundled example provider:

# build the SDK, then build + register the example provider
pnpm --filter @madarco/agentbox-provider-sdk build
cd examples/agentbox-provider-example && npm install && npm run build
node ../../apps/cli/dist/index.js plugin add .      # register it (a path works)
node ../../apps/cli/dist/index.js doctor            # shows the provider's group

# verify the SDK artifact in isolation (packs + installs the tarball, asserts exports)
pnpm --filter @madarco/agentbox-provider-sdk pack:test

For your own package, point its @madarco/agentbox-provider-sdk dependency at a local build (an npm link or a file: path) while iterating, then agentbox plugin add <path>.

Operate

npm i -g agentbox-provider-myprovider        # or install anywhere resolvable
agentbox plugin add agentbox-provider-myprovider   # validates + records it (a path also works)
agentbox plugin list                         # `!` marks one this CLI cannot load
agentbox doctor                              # shows your provider's group
agentbox create --provider myprovider        # first create triggers ensureCredentials
agentbox plugin update --dry-run             # what an update would change, and why
agentbox plugin update                       # move every plugin to its newest compatible release
agentbox plugin remove myprovider            # unregister (does not uninstall the package)

TRUST

A provider plugin runs in-process with full host + credential access — it is trusted code, exactly like the CLI. agentbox plugin add is the consent boundary: it names the package and version and warns before recording. Only add plugins you trust; AgentBox does not sandbox plugin code.

Consent covers the package, not one version of it: once added, agentbox self-update and agentbox plugin update install newer releases published under that same npm name without asking again — the same trust model as npm update. --skip-plugins opts out per run. A plugin you no longer trust should be removed (agentbox plugin remove <name>), not merely left un-updated.

Compatibility & publishing

The CLI loads a plugin only if its providerApiVersion is in the CLI's supported set — the SDK's exported SDK_API_VERSION is the gate. An incompatible plugin is refused at plugin add, and fails at load with an error naming the version it targets; it never crashes the CLI. A plugin registered before a gate change stays in the registry and is marked ! by agentbox plugin list.

agentbox self-update (and agentbox plugin update) moves each registered plugin to the newest published version whose SDK major the CLI supports — never simply to @latest, so a plugin that works today is not upgraded onto a build this CLI cannot load. Publish agentbox.providerApiVersion in your package.json and that resolution needs no guessing; the @madarco/agentbox-provider-sdk dependency range is the fallback. Plugins registered from a local path, and npm linked checkouts, are left alone.

v4 is the current contract; v2 and v3 are not loaded

Two clean breaks landed together — v3 was never in a release, so a v2 provider moves straight to v4. Rebuild against ^4 and set providerApiVersion: 4.

v3 removed the three per-agent staging helpers. A v2 provider bakes a snapshot containing exactly claude, codex and opencode, so on a host with any other agent it produces a base that silently lacks it. Replace stageClaudeStaticForUpload / stageCodexStaticForUpload / stageOpencodeStaticForUpload with the single stageAllAgentStatic, which stages whatever agents the host actually has.

v4 made agent settings generic. PrepareOptions.claudeInstall is replaced by the agentSettings map, resolveAgentInstall takes that agent's settings rather than a mode string, Provider.baseFingerprint takes no arguments (an agentless base folds no agent setting), and claudeInstallFingerprint is gone. A v3 provider silently drops every agent setting, which is why this is a break rather than a default.

Both are refusals rather than degradations on purpose: either would produce a wrong artifact, which is worse than a missing feature.

Publish your package to npm under the agentbox-provider-<name> name and users install it like any other. For the full authoring reference — the complete CloudBackend contract, the backend conformance test suite, and publishing notes — see docs/provider-plugins.md and the agentbox-provider-example package.

On this page