MainSDK
Search⌘K
Interfaces

AI and agents

Remote GPU and long jobs

compute

Run GPU and long jobs on your own Modal, Replicate or RunPod account, or a local program, as workflows with progress, cancel, cost limits and results in files.

Building for 0.6ships as a experimental APIPhase 2 · Cloud and Teams, 1.0Spec computeFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

Run GPU and long jobs on your own Modal, Replicate or RunPod account, or a local program, as workflows with progress, cancel, cost limits and results in files.

At a glance

compute:
  heartbeat: 60s
  poll: 15s
  results: { label: personal, max-size: 2GB, types: ["image/*"] }
  limits: { per-tenant: $50/month }
  targets:
    upscale:
      driver: replicate
      model: nightmareai/real-esrgan:f121d640bd286e1fdc67f9799164c1d5be36ff74576ee11c803ae5b665dd46aa
      region: us
      timeout: 30m
      price: $0.0014/s
      max-cost: $2

Calls

  • compute.run(target, input, { files?, maxCost?, timeout?, agent?, idempotencyKey? })
  • compute.get(jobId)
  • compute.list({ target?, state?, cursor?, limit? })
  • compute.cancel(jobId, { reason })
  • compute.step(name, target, input, opts?)
  • compute.callback(call, request)

Guarantees

Whatever the driver.

  • MainSDK never hosts models; jobs run on your provider account
  • A job never costs more than its limit; the worst case is checked before submit
  • Jobs survive restarts and can be cancelled at the provider
  • Labelled data goes only to a target in the tenant's region

Drivers

4 drivers. Select one to see its line in main.yaml.

main.yamlLocal program (system.run) (default)
compute:
  driver: local

Change this one line to swap providers; your calls stay the same.Built-in and local drivers integration

Codes

CodeWhere it appearsMeaning
MAIN-COMPUTE-0001Checkmainsdk check, before deployA target not declared under compute.targets
MAIN-COMPUTE-0008Checkmainsdk check, before deployA target isn't pinned: no model version, app or endpoint id
MAIN-COMPUTE-0013Checkmainsdk check, before deployA target has no price or timeout, or a timeout over 24h
MAIN-COMPUTE-0002RuntimeError responses and logsRefused before submit: the worst case (price times timeout) passes maxCost, a compute.limits ceiling or the agent's budget (402)
MAIN-COMPUTE-0003RuntimeError responses and logsThe running cost reached maxCost; the job was cancelled at the provider
MAIN-COMPUTE-0004RuntimeError responses and logsNo heartbeat for three periods; the job is lost and was cancelled at the provider
MAIN-COMPUTE-0005RuntimeError responses and logsThe provider failed submit, status or cancel after retries (502)
MAIN-COMPUTE-0006RuntimeError responses and logsThe job passed its timeout and was cancelled
MAIN-COMPUTE-0007RuntimeError responses and logsLabelled input to a target outside the tenant's region (403)
MAIN-COMPUTE-0009RuntimeError responses and logsAn output is too large or of a type not in compute.results.types (413 or 415)
MAIN-COMPUTE-0010RuntimeError responses and logsNo job with that id in the current tenant (404)
MAIN-COMPUTE-0011RuntimeError responses and logsCancel of a finished job (409)
MAIN-COMPUTE-0012RuntimeError responses and logsA provider callback failed verification (400)

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
compute@1 contract and pinned targetsPhase 20.6compute#contract
Jobs as workflow runsPhase 20.6compute#run
Wait on a job as one workflow stepPhase 20.6compute#step
Cost limits before and during a jobPhase 20.6compute#cost
Heartbeat, progress and lost jobsPhase 20.6compute#heartbeat
Results into filesPhase 20.6compute#results
Region check for labelled inputPhase 20.6compute#region
Verified provider callbacksPhase 20.6compute#callbacks
Modal, Replicate, RunPod and local driversPhase 20.6compute#drivers

Quick start

main.yaml
compute:
  heartbeat: 60s
  limits: { per-tenant: $50/month }
  targets:
    upscale:
      driver: replicate
      model: nightmareai/real-esrgan:f121d640bd286e1fdc67f9799164c1d5be36ff74576ee11c803ae5b665dd46aa
      region: us
      timeout: 30m
      price: $0.0014/s
      max-cost: $2
app/example.ts
import { compute, type ComputeRunOptions } from "mainsdk/experimental/compute";

declare const image: NonNullable<ComputeRunOptions["files"]>[number];   // a file stored with `files`

// refused before submit if the worst case could pass $2
const { jobId } = await compute.run("upscale", { scale: 4 }, { files: [image], maxCost: "$2" });
const job = await compute.get(jobId);

Calls

CallWhat it does
compute.run(target, input, { files?, maxCost?, timeout?, agent?, idempotencyKey? })Submits a job to a target; refuses it first if the worst-case cost passes the limit.
compute.get(jobId)Returns a job's state, progress, cost and result.
compute.list({ target?, state?, cursor?, limit? })Pages through the current tenant's jobs.
compute.cancel(jobId, { reason })Cancels a job at the provider and records why.
compute.step(name, target, input, opts?)Runs a job as a step inside a workflow and returns its result.

Configuration

main.yaml
compute:
  heartbeat: 60s        # no sign of life for three of these and the job is lost
  poll: 15s             # how often the workflow asks the provider
  results: { label: personal, max-size: 2GB, types: ["image/*", "video/*", "audio/*", "application/json"] }
  limits: { per-tenant: $50/month, total: $500/month }
  targets:
    upscale:
      driver: replicate      # modal | replicate | runpod | local
      model: nightmareai/real-esrgan:<64 hex>
      region: us             # required unless driver is local
      timeout: 30m           # at most 24h
      price: $0.0014/s
      max-cost: $2
      retries: 0             # 0 to 5

A target with no price or timeout stops the app from starting (MAIN-COMPUTE-0013). A local target names a program that is also declared under permissions.processes.

What you get

text
compute.run(target, input, { files?, maxCost?, timeout?, agent?, idempotencyKey? })
compute.get(jobId)
compute.list({ target?, state?, cursor?, limit? })
compute.cancel(jobId, { reason })
compute.step(name, target, input, opts?)   // as one step of a workflow
compute.callback(call, request)            // verify a provider callback and wake the run

A job is queued, running, completed, failed, cancelled or lost. It is a run of the built-in workflow mainsdk.compute (check, submit, poll, fetch, finish), so it survives a restart. Progress is published on the realtime channel compute:<jobId> and the cost on the meter metric compute.cost, in micro-dollars.

Examples

Cancel a job and read its result

app/jobs.ts
import { compute } from "mainsdk/experimental/compute";

export async function finish(jobId: string) {
  const job = await compute.get(jobId);
  if (job.state === "running") await compute.cancel(jobId, { reason: "no longer needed" });
  return job.result;
}

Wait inside a workflow

app/flows.ts
import { compute } from "mainsdk/experimental/compute";

export async function upscaleStep() {
  return compute.step("upscale-step", "upscale", { scale: 4 });
}

Errors and checks

CodeWhenFix
MAIN-COMPUTE-0001A target is not declared under compute.targets (also a check on a literal in code)Declare it in main.yaml, or fix the name
MAIN-COMPUTE-0002The worst case (price times timeout) passes maxCost, a limit or the agent's budgetRaise the limit on purpose, or shorten the timeout
MAIN-COMPUTE-0003The running cost reached maxCost; the job was cancelledRaise maxCost or fix the job
MAIN-COMPUTE-0004No sign of life for three heartbeats; the job is lostCheck the provider; set retries
MAIN-COMPUTE-0005The provider failed or did not answerRetry later
MAIN-COMPUTE-0006The job passed its timeoutRaise timeout or shorten the job
MAIN-COMPUTE-0007Labelled input and a target outside the tenant's regionUse a target in the tenant's region
MAIN-COMPUTE-0008A target is not pinned (check)Add the version hash, app and function, or endpoint id
MAIN-COMPUTE-0009An output is too large or of a type not allowedAdjust results on purpose
MAIN-COMPUTE-0010No job with that id in this tenantUse a job id from this tenant
MAIN-COMPUTE-0011Cancel of a finished jobNothing to cancel
MAIN-COMPUTE-0012A provider callback failed verificationCheck the webhook secret
MAIN-COMPUTE-0013A target has no price, no timeout, or a timeout over 24hGive it a price and a timeout of 24h or less

Swapping drivers

main.yaml
compute:
  targets:
    upscale:
      driver: runpod   # was replicate
      endpoint: abc123

Your calls stay the same. See Swap a driver.

Testing

FakeComputeProvider, ParkingFlows and computeConformance come from mainsdk/testing. See Run a GPU job and Test your app.

Security and privacy

  • MainSDK never hosts models; jobs run on your provider account, and a driver declares that it never trains on inputs.
  • Output is fetched only from the driver's declared hosts, then stored through files with owner scope, scanning and limits.
  • Labelled data goes only to a target in the tenant's region.
  • get, list and cancel see only the current tenant's jobs; a callback only wakes the run, it never carries state.

FAQ

Can a job cost more than I set? No. The worst case is reserved before submit and a running job is cancelled when its cost reaches maxCost.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet