briqbriq
Concepts

Lifecycle and TTL

States, transitions, idle auto-stop, and why nothing runs forever.

States

creating → running → stopped → destroyed
                 ↘        ↗
                   error
StateMeaningBilling
creatingMachine requested, image pulling, healthcheck pendingCompute, from start
runningHealthcheck passed (or no healthcheck and the process is up)Compute per second
stoppedMachine stopped, rootfs and volume keptNothing
destroyedGone. Volume deleted unless detached firstNothing
errorProvider or image failure. Reason in errorNothing after the transition

Transitions are idempotent: calling briq_stop on a stopped briq is a no-op that returns the current state, not an error. The same goes for starting a running briq, which matters because a briq can auto-stop between two of an agent's calls. Invalid transitions (start a destroyed briq) return invalid_state (HTTP 409).

Idle auto-stop

A briq follows the agent's working rhythm, not the wall clock. Every call that touches it — briq_exec, the file tools, briq_logs, briq_expose — marks it as in use. When nothing has touched it for idleMinutes (default 60), the reconciler stops it.

A stopped briq is not billed. Its disk is kept, so it comes back with its data where it was. On sizes up to 2 GB of memory (nano and small) the machine is suspended with a memory snapshot rather than fully stopped, so the start takes about a second instead of a cold boot; larger sizes cold-boot, and a snapshot that cannot be resumed falls back to one too.

This is the difference between paying for a database while an agent thinks, and paying for it only while the agent uses it. An agent that runs a five-minute task against a Postgres and then moves on stops paying an hour later, without having to remember to clean up.

Each briq carries both deadlines: idleStopsAt (moves every time you use it) and stopsAt (the TTL, fixed). MCP results show whichever comes first.

Coming back

You do not have to notice that a briq idled out. The next call that needs it running — an exec, a file read or write, an expose — starts it again and then does what you asked. The agent sees a slightly slower call, not an error.

That resume is deliberate about why the briq stopped. Only an idle stop restarts on access. A briq you stopped yourself, one that hit its TTL, and one stopped by a spend cap all stay stopped, and the call returns invalid_state naming the reason and pointing at briq_start — a spend cap that restarted itself on the next call would not be a cap.

Holding a briq open

briq only sees its own control plane. Traffic to a public URL, or a long job running inside the briq with nothing calling out, looks exactly like idleness. Two things handle that:

  • briq_expose turns the idle auto-stop off for that briq, automatically. Once a port has a public URL, people can be using it without briq ever knowing, so the TTL takes over as the only deadline. The expose result says so.
  • Set it yourself with idleMinutes: 0 at run time, or on a live briq with briq_extend({ briq, idleMinutes: 0 }). The same call sets it back to a real number later.

You never need briq_extend for ordinary work — using a briq already pushes its idle deadline back. Extend is for the TTL, and for these cases where the work is invisible to briq.

Where the work happens

Provider calls never run inside an HTTP request. briq_run validates the policy, writes the briq row as creating, and enqueues a job. The job talks to the provider, waits for readiness, and moves the row to running. If your client times out, the machine is still tracked and still subject to the TTL and the reconciler.

TTL and stopsAt

Every briq has a ttlMinutes (default 480, maximum 10080) and a computed stopsAt. When the clock passes it, the reconciler stops the briq, whether or not it is in use. It is the backstop under the idle auto-stop: idle time ends a forgotten briq, the TTL ends a busy one.

The default TTL is 480 minutes (8 hours) and the maximum is 10080 (7 days), capped per key by maxTtlMinutes. It sits well above the idle deadline on purpose: if the two were equal, a briq would always hit its TTL first and the idle auto-stop could never fire.

A briq stopped by its TTL stays stopped until you start or destroy it. It costs nothing while it sits there, so there is no rush, but briq_destroy is still the right call when you are finished.

  • Extend with briq_extend({ briq, minutes }); the new TTL is still capped by the key's maxTtlMinutes.
  • Every response about a briq includes stopsAt, and every MCP tool result ends with it, so a model running a long job can extend before the deadline rather than lose its work.

The reconciler

A worker runs every 30 seconds. It lists machines at the provider per team, compares them to the database, and:

  • emits usage events for metering and spend-cap enforcement,
  • enforces stopsAt,
  • enforces spend caps per key,
  • stops briqs that have been idle for idleMinutes,
  • destroys orphans (machines briq does not know about).

A briq stopped in the same pass keeps the most specific reason it earned: ttl, then cap, then idle.

This is why a provider incident, a crashed API process or a lost HTTP response cannot leak a machine for more than a reconciler cycle.