Build it up · Step 8 of 8

Put it in production

Production is four more questions. Where does it run and stay running? Where does state live across redeploys? How does it reach models, browsers, sandboxes and mail without keys in every unit? How do you see what it did? Kuiper answers all four for a Foundry folder, with no Dockerfile.

Kuiper projectKuiper gatewayPeople · appsyour domainFoundry unitglove foundry start/data diskdata · transcripts · VFSPostgresRedis · bucketsSecretssealedModelsyour provider keysBrowsersreal ChromeSandboxeskuiper/coding …Inboxemail in and outDashboard · CLI · MCPlogs · runs · model calls · usage · budgetsHTTPSreads · writesvariablesone keyreads runs and logs
A Foundry agent on Kuiper. The unit is built from your folder or repository and served over HTTPS; Foundry's data, transcripts and memory live on its /data disk; databases and secrets are wired in by variable templates; one gateway key reaches the model providers, browsers, sandboxes and inbox. The dashboard reads the agent's runs through the node, without your app's token leaving Kuiper.

Three changes before you deploy

1. Put state on the disk

Every Foundry unit on Kuiper gets a persistent disk at /data. The starter template writes its state next to the code, which is inside the image, so a redeploy would wipe it. Point Foundry's data, the transcripts and any SQLite memory at one directory you can set per environment:

lib/paths.tstypescript
import { join } from "node:path";

// ".data" on your machine; set it to /data on Kuiper.
export const dataDir = process.env.AGENT_DATA_DIR ?? ".data";
export const dataFile = (name: string) => join(dataDir, name);
foundry.application.tstypescript
import { FileFoundryDataAdapter, defineApplication } from "glove-foundry";
import { dataFile } from "./lib/paths.js";

export const data = new FileFoundryDataAdapter({ file: dataFile("foundry.json") });

export default defineApplication({ name: "release-desk", data /* , requestAuthorization (below) */ });
agents/release/agent.ts (store)typescript
store: ({ conversationId }) => FileStore.open(conversationId, dataFile("transcripts")),

These adapters are single-host, so the unit runs one replica. Kuiper guarantees that for a unit with a disk: a deploy replaces the copy and never overlaps it. That matters for schedules too, because every runtime arms the schedules it finds. For several hosts, use a database-backed data adapter and store; Kuiper's managed Postgres is one command away.

2. Put an authorizer in front

Foundry refuses to listen on a public interface unless foundry.application.ts defines requestAuthorization. Otherwise the inspector, the API and the event streams would be open to anyone. The smallest one accepts a bearer token and lets the health check through:

foundry.application.tstypescript
import { timingSafeEqual } from "node:crypto";
import { Effect } from "effect";

const token = process.env.FOUNDRY_TOKEN;
const same = (a: string, b: string) =>
  a.length === b.length && timingSafeEqual(Buffer.from(a), Buffer.from(b));

export default defineApplication({
  name: "release-desk",
  data,
  requestAuthorization: {
    identifier: "release-desk-token",
    challenge: "Bearer",
    authorize: ({ path, authorization }) =>
      Effect.succeed(
        path === "/health" ||
          (!!token && !!authorization?.startsWith("Bearer ") && same(authorization.slice(7), token)),
      ),
  },
});

Browsers can't send a bearer token, so to open the inspector from one, add a Basic branch; Kuiper's Foundry inspector switch then manages the username and password for you. The Deploy on Kuiper page has that version.

3. Call models through the gateway

Kuiper's model gateway serves each provider's own API at one address and sends calls to your provider keys, which never reach the unit. It records every call with its tokens and estimated cost. Glove keeps its provider and model; it only changes base URL and key:

agents/release/agent.ts (model)typescript
import { Kuiper } from "@kuiper-sh/sdk";

const kuiper = new Kuiper();   // reads KUIPER_GATEWAY_KEY

model: () => createAdapter({
  provider: "anthropic",
  model: "claude-sonnet-4-20250514",
  ...kuiper.models.glove("anthropic"),   // { baseURL, apiKey } for this provider
}),

Deploy

terminalbash
curl -fsSL https://kuiper.sh/install | sh
kuiper login

kuiper projects create release-desk
kuiper use release-desk
kuiper units create release

# Provider keys live at the organisation, never in the unit
kuiper providers add anthropic --key -

# Secrets and variables, as templates
openssl rand -hex 24 | kuiper secrets set FOUNDRY_TOKEN=-
kuiper env set -u release \
  AGENT_DATA_DIR=/data \
  'FOUNDRY_TOKEN=${{secrets.FOUNDRY_TOKEN}}' \
  'KUIPER_GATEWAY_KEY=${{models.API_KEY}}'

kuiper deploy --source . -u release --wait
kuiper logs -u release -f

Kuiper sees a Foundry app (a foundry.config.ts or a glove-foundry dependency) and builds it on Node 22 with your package manager. It starts glove foundry start on port 4141 with a health check, a 5 GiB disk at /data, and the names in .env.example as optional variables. kuiper link --repo … makes every push to a branch deploy, and every deployment is numbered, so kuiper rollback brings any earlier one back.

See it run

  • Inspect card and Agents page. The unit's runs, conversations, events, approvals and activations, read through the node with the unit's own FOUNDRY_TOKEN. Your browser never holds it. If the unit has no token yet, the card offers to generate one.
  • Model calls. kuiper models calls --unit release lists every call with tokens, latency and estimated cost; turn on logging to keep prompts and tool calls too.
  • Logs survive crashes and redeploys, with secret values redacted.
  • Budgets notify or block at a limit you set, per organisation, project or key.
  • Your coding agent can read all of it over Kuiper's MCP server: claude mcp add --transport http kuiper https://app.kuiper.sh/mcp.

Browsers, sandboxes and email

The same gateway key reaches the rest of Kuiper's services once the unit's variables reference them. Inbox gives the agent an email address, with Glove tools from @kuiper-sh/inbox/glove and signed webhooks straight to the unit. Browsers are real Chrome behind Steel's API. Sandboxes come from ready-made images such as kuiper/coding; a Foundry app that hosts Station's own container sandboxes declares capabilities.sandbox in kuiper.json and gets a Docker socket Kuiper polices. Deploy on Kuiper covers each.

Already built for you

PrimitiveWhat you getInstead of building
Zero-config deploysKuiperFoundry and Station apps detected and built with no Dockerfile: Node 22, the right start command, port and health check.kuiper deploy --source . -u UNIT --wait · kuiper link -u UNIT --repo … (push to deploy)a Dockerfile, a process manager and a deploy script
Deployments and rollbacksKuiperNumbered deployments, one live at a time; any earlier one redeploys with its manifest.kuiper deployments · kuiper rollback DEPLOYMENTimage tags and a rollback runbook
Domains and environmentsKuiperHTTPS on every unit, custom domains, and staging environments from their own branches.kuiper domains add HOST · kuiper environments create NAMEcertificates, DNS and a second copy of the stack
Persistent diskKuiperWhere FileFoundryDataAdapter, transcripts, SQLite memory and VFS snapshots live across restarts and redeploys./data on every Foundry unitmounting and backing up a volume
Managed databases and bucketsKuiperPostgres, Redis and S3-compatible buckets inside the project, wired in by variable templates.kuiper db create postgres main · ${{main.DATABASE_URL}}provisioning a database and copying its URL into config
Secrets and variablesKuiperSealed values bound into units, never in the image, redacted from logs.kuiper secrets set NAME=- · kuiper env set 'NAME=${{secrets.NAME}}'a secrets manager integration
Model gatewayKuiperEach provider's own API at one address with your keys, limits and every call recorded with tokens and cost.createAdapter({ provider, model, ...kuiper.models.glove(provider) })provider keys in every unit and your own usage tracking
Kuiper browsersKuiperReal Chrome with profiles, hibernation and recordings, billed by the second.Steel-compatible API at ${{browsers.BASE_URL}}running headless Chrome next to your agent
Kuiper sandboxesKuiperRemote computers from ready-made images with a terminal, files and ports. An app that depends on station-sandbox gets a Docker socket Kuiper polices, for Station's own containers.kuiper.sandboxes.create({ image: "kuiper/coding" }) · capabilities.sandbox in kuiper.jsona container fleet for untrusted code
Kuiper inboxKuiperAn email address per agent that receives, sends, threads and calls your unit's webhook.inboxTools(inbox, { mailbox }) from @kuiper-sh/inbox/glovean email provider, inbound parsing and webhook verification
Agent inspectionKuiperFoundry runs, conversations, events and approvals read into the dashboard through the node, once requestAuthorization accepts Bearer $FOUNDRY_TOKEN. The app's token never reaches a browser.the unit's Inspect card · the project's Agents pageexposing your inspector publicly to see production
Logs and network logsKuiperLogs kept across crashes and redeploys with secrets redacted, plus requests, DNS and connections per unit.kuiper logs -u UNIT -f · kuiper network summarya log shipper and a proxy to see outbound calls
Usage and budgetsKuiperSpend by resource and project, with limits that notify or block.kuiper usage · kuiper budgets create --scope project --limit 50spreadsheets reconciling provider bills
Kuiper over MCPKuiperYour coding agent reads projects, logs and usage and, if you allow it, deploys.claude mcp add --transport http kuiper https://app.kuiper.sh/mcpcopy-pasting logs into your coding agent

Where you are now

You have built every layer: a harness with all seven parts, a Foundry system around it, and production on Kuiper. Most of it was configuration. You wrote tools, prompts and a few adapters; the loop, the state, the queue, the approvals, the API, the inspector, the deploy and the gateway already existed. That ratio is the point. From here:

The whole path

  1. 01A loop and a toolLoop · Tools
  2. 02Show your workSurface
  3. 03Control what it seesContext
  4. 04Make it rememberState
  5. 05Give it a workspaceWorkspace
  6. 06Split the workTeam
  7. 07Make it a systemIdentity · Time · Arrival · Oversight · Access
  8. 08Put it in productionRun · Keep · Reach · Watch