Soba Docs

Operating

Environments

Development, preview and production are configurations of a key, not deployments. A development key runs on your own machine and cannot spend money by construction.

Three environments, and the environment is a property of the key:

Compute Purpose
development Your own machine Build and iterate. Never spends money
preview A service identity's machine, or your app's own fallback with a hard cap on runs handed back Rehearse the configuration. CI
production Whatever the end user's plan permits Serve real users

A .env.local holds a development key; your production environment variables hold a production key. That is the whole mechanism. Soba deploys nothing and there is no soba deploy, because a second key already buys everything such a command could.

Why this is a correctness feature, not a tier#

Without it, a key used from a laptop during development is indistinguishable from the key serving real users. A developer who pairs their own machine and tests against it creates a user, a machine and a stream of runs served at user-subscription, which is arithmetically identical to a real customer connecting their own compute.

Connection rate would count developers. Inference you didn't pay for would count your own laptop. Every headline number on the dashboard would be inflated by the people building the product, with no way to subtract them.

So development is excluded from every aggregate, and the billing rule gains one clause: bill from usage where environment = 'production', and nowhere else.

The same user in two environments is two people#

You send the same identifier either way — soba.run({ user: "alice" }) is the same line of code in your test and in production — so Soba files the person under the environment of the key that sent it. alice under a development key and alice under a production key are two end users, with their own machines, their own plan assignment and their own place in the figures.

That is what makes the rule above hold for the person as well as the run. Without it, testing against your own laptop as alice once would file her in development permanently: her production runs would be billed while she herself sat on the unbilled side of the ledger, and the laptop you paired during that test — a machine has no environment of its own, it belongs to its owner — would appear in your production machine list.

Pairing happens per environment

A machine paired by the development alice cannot serve a run addressed to the production one, which is the same rule as everything else here: whoever the key says you are is whose machine you reach.

Preview is not production-like#

Vercel's triad trained everyone to expect that it would be. It cannot be here, and saying so plainly is better than borrowing a promise the architecture cannot keep.

Production's defining property is that the compute belongs to the end user. In preview there are no end users with machines connected. Preview rehearses the configuration (the routing, the ceilings, the plan shape), not the compute.

Preview is never a shared machine

One machine serving many developers' runs is operator pooling, the precise shape the whole architecture exists to avoid. Preview is either your app's own fallback under a cap, or a single service identity that owns its own machine. It is never one person's Claude account serving the team.

A development key cannot reach a metered class#

TypeScript
development: { prefer: ["user-hardware", "user-subscription"],
               allow:  ["user-hardware", "user-subscription"] }

Not by discipline, by construction. The free tier is accident-proofed rather than trusted: a loop in a test cannot bill you, because there is nothing billable in allow. Exercising the metered path is what preview is for, and reaching one from a development key is an explicit opt-in per key.

How the route resolves#

A key's environment sets a ceiling, and the user's plan narrows within it. Neither can widen the other, and the machine then applies its own grant on top, unchanged. See Security model.

Environment Plan Result
development none; you are not a paying user of your own app the environment's route alone
preview none the environment's route, with a period cap required
production the end user's plan the narrower of the two

Two ceilings, not one#

TypeScript
maxCostMicros        // ceiling on ONE run
periodCostCapMicros  // ceiling on the calendar month

Every other ceiling in Soba is per run or per user. periodCostCapMicros is the aggregate brake, and preview is where its absence bites first, because CI runs unattended and nobody is watching a loop on a metered class. It is worth setting on production keys too.

Your first development run#

The default machine grant is read-only tools inside ~/.soba/workspace, and that default is correct: it is what makes a worker safe to point at a control plane nobody trusts. But it means testing an agent against your own project has every file operation refused, in a directory Soba never granted: a first run that fails looking like a broken product rather than a policy you need to widen.

The fix is not a weaker default. It is that you pairing your own machine to your own development key is a different trust situation: the machine owner and the app owner are the same person. So say so, in the file you control:

Terminal
soba-worker login --dev --root .

Pairs, writes a grant scoped to the current project rather than to the scratch workspace, and prints what it granted. Deliberately explicit, deliberately per-directory, and deliberately not the default for anything else.

The free tier that cannot run out#

A development key points at your own Claude Code or Codex. Your machine, your login, your plan, and you are testing. Soba never touches the compute, so there is nothing for it to bill and nothing for it to subsidise.

Credit-metered free tiers expire by design, and the moment they do you are evaluating a bill rather than a product. This one has no meter to exhaust, because the thing being consumed was already bought by the person consuming it.

© 2026 Soba resolved = machine grant ∩ broker request