Skip to main content
Glama

Run a job on a rented machine (up to your spending limit)

submitJob

Estimate first — budgetUsd is optional there — then ask the user for budgetUsd (never choose one for them) and call submitJob only after the user has approved the estimate at that budget. Run a containerized workload on a rented cloud machine sized to the spec — GPU, and as much disk as minDiskGb asks for, so the local machine's disk and bandwidth are irrelevant (the machine downloads datasets itself). Queues the job and returns a durable executionId immediately (status "queued"); Symbioza provisions, runs, collects the output files and tears down, all under the budgetUsd ceiling. Write every deliverable to /workspace/artifacts/ — only that directory is collected and returned by getArtifact; a command that saves its model to the working directory or any other path finishes with nothing delivered. image must be one of the images we have verified launch on our machines: pytorch/pytorch:2.4.1-cuda12.1-cudnn9-runtime, pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime, tensorflow/tensorflow:2.15.0. Any other image is refused with code invalid_spec, action fix_spec and a supportedImages list, before anything is booked or charged. budgetUsd is what you are willing to spend on the job, and you are never billed more than that. Call estimateExecution first if you have not priced this spec — it also tells you whether this account can cover the budget, and whether it holds free compute credit still to claim. A refusal here carries a machine-readable code (unauthorized, invalid_spec, budget_too_low, insufficient_balance, access_required, secret_conflict, no_compute, unavailable, refused) and one action (authenticate, fix_spec, raise_budget, add_funds, claim_credit, add_access, relax_spec_or_retry, retry_later, contact_support), so branch on those rather than on the sentence; a refusal books no machine and charges nothing. insufficient_balance with action claim_credit means free credit is waiting on the account — claim it at https://symbioza.dev/app and resubmit; insufficient_balance also carries shortfallUsd and topupUrl. access_required and secret_conflict carry the same access block estimateExecution returns, with a Symbioza setup link for each missing saved secret: give the user that link, and after they say it is saved call listSecrets and submit again. On acceptance each attached set is pinned to its id. OPTIONAL: if your workload can count its own work, write /workspace/artifacts/.symbiosa/metrics.json with {"unit": "configuration", "units_planned": 100, "units_completed": 100, "elapsed_seconds": 4821} — unit is free text naming what you counted, partial progress is valid evidence on a failed run, and the whole file is optional (omit it and throughput is simply recorded as UNKNOWN; do not invent a unit to fill it in). It is recorded as observed evidence about your workload and never affects what you are charged; a missing, malformed or incoherent file changes nothing about the run. You may disconnect and poll getStatus later. If an attempt fails and the job is moved to a replacement machine, the newest checkpoint streamed off the previous box (files named ckpt_step.pt, N = digits) is pushed onto the new box under /workspace/.symbiosa/resume/ (outside the artifacts directory) and SYMBIOSA_RESUME is set to its absolute path there — your command must load that file to continue, or the attempt restarts from the beginning. The variable is unset on a first attempt, when no checkpoint was kept, when kept checkpoints span more than one directory (multi-arm layouts are not restored), when checkpoint.enabled is false, when the box lacks disk headroom, or when the push failed; restore is best-effort salvage and never fails a run. Work since the last streamed checkpoint is lost, and the push's hash check proves the transfer, not that the checkpoint itself is intact. Only when the user explicitly requires a particular model for their own benchmark, set gpu.gpuNameExact: true alongside gpu.gpuName to pin the CARD MODEL: only that model is booked and every substitute is refused, including a faster one, because when the measured quantity is the hardware a better card is as wrong as a slower one; it holds the model across every run. pinHost is accepted and ignored. The same model is sold at different board powers under one name, so for an energy or efficiency measurement also declare gpu.minPowerLimitW (watts): hosts below it are refused, and a host publishing no power figure is refused rather than assumed un-throttled. Declare gpu.minCudaVersion (e.g. 13.0) when your image pins a torch/flash-attn build against a CUDA version: only hosts whose driver advertises at least that CUDA are booked, and a host that publishes no CUDA ceiling is refused rather than booked. Declare datasetGb as the total data the box will ingest, including data it downloads itself (not only what datasets[] stages), so a host too slow to pull it in time is filtered before booking. With a clientRequestId, the same key with the same spec returns the same job, marked duplicate: true, instead of booking and charging for a second one; the same key with a different spec is refused with invalid_spec. Without a key, an identical submission (same spec and budget) from the same account returns the same job marked duplicate: true and deduplicated: true and books nothing while that job is still queued or running, at any time after it failed, was cancelled or was lost (then the response carries a hint instead of starting a second paid attempt), and within 10 minutes of its acceptance if it delivered. Keep one clientRequestId for one submission across every resend. To run a failed, cancelled or lost job again, call submitJob with retryOf set to its executionId and a new clientRequestId: that starts a fresh attempt at once, checked against the budget and balance again, even with the same spec, and its response names the earlier job as retryOf; a retryOf that is not a failed, cancelled or lost job of this account is refused with invalid_spec. A new clientRequestId always starts a new job. Pass the specDigest estimateExecution returned and a spec or budget changed since that estimate is refused with invalid_spec, so what runs is what you were quoted. A job whose command, image or environment names cryptocurrency-mining software or a mining pool is refused under the Terms of Service (code refused, action contact_support); nothing is booked and nothing is charged.

Input Schema

NameRequiredDescriptionDefault
specYesThe job spec: image + command run on a rented box sized by minDiskGb (and gpu, when declared), under budgetUsd. Supply the workload: the image, the command, its inputs, the expected outputs and budgetUsd. Symbioza sizes the machine — do not ask the user to choose CPU cores, RAM, VRAM, a GPU model, a provider or a region, and do not derive a resource minimum from a thread count, a worker count or a measurement taken somewhere else. gpu.minVramGb, minRamGb and minCpuCores are optional expert floors: declare one only when the workload has evidence for it, and omit it otherwise — an omitted floor is sized by Symbioza, never treated as zero. The gpu block is optional: leave it out and the job is still GPU work with Symbioza sizing the card; declare it to carry minCudaVersion or an evidenced floor.
authKeyNoUsually omit this. The customer door — the OAuth-signed-in HTTPS connector at https://symbioza.dev/mcp — authenticates the session itself, so no key is passed per call. Only a self-hosted or test transport passes a key here.
retryOfNoOnly to run a failed, cancelled or lost job again, on purpose: that job's executionId, sent with a new clientRequestId. The retry starts at once as a new job, checked against the budget and balance again, may carry the same spec, and its response names the earlier job as retryOf. Refused with invalid_spec when the job is not this account's, was delivered, or is still queued or running.
budgetUsdNoOptional cross-check: must EQUAL spec.budgetUsd or the submit is rejected. Never overrides it.
specDigestNoOptional, recommended: the specDigest estimateExecution returned for this spec — if the spec or its budgetUsd changed since that estimate, the submit is refused with invalid_spec and nothing is booked or charged.
clientRequestIdNoOptional, recommended: a key of your own for this submission, 1 to 128 printable ASCII characters — keep it for every resend of the same submission (after a timeout or a lost response): the same key with the same spec returns the same job whatever its outcome: the same executionId with duplicate: true, nothing booked or charged twice; the same key with a different spec is refused with invalid_spec, so use a new key for a new spec or for a retryOf retry.

Output Schema

NameRequiredDescriptionDefault
codeNoMachine-readable refusal code: unauthorized, invalid_spec, budget_too_low, insufficient_balance, access_required, secret_conflict, no_compute, unavailable, refused
hintNoOn a repeated submission whose job ended without delivering (failed, cancelled or lost): nothing new was booked, and this says how to run it again — submitJob with retryOf and a new clientRequestId
accessNoOn access_required or secret_conflict: what the spec's secrets need, with setup links
actionNoThe one next step: authenticate, fix_spec, raise_budget, add_funds, claim_credit, add_access, relax_spec_or_retry, retry_later, contact_support
reasonNoWhy, in a customer-safe sentence
statusNo"queued" on acceptance
retryOfNoThe earlier job this one retries: set when submitJob was called with retryOf, on the retry and on every resend of it
rejectedNoPresent and true when the call was refused; nothing was booked or charged
topupUrlNoOn insufficient_balance: the link to give the user for that credit
duplicateNotrue when a repeated submit returned the job the first call started: a clientRequestId retry, or an identical keyless submission
executionIdNoYour durable job id
deduplicatedNotrue when no clientRequestId was sent and an identical submission from this account (still queued or running, ended without delivering at any age, or delivered and accepted within the last 10 minutes) was matched; nothing was booked
shortfallUsdNoOn insufficient_balance: whole dollars of credit this job still needs
supportedImagesNoOn an unverified-image refusal: the images the door accepts

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / spec / properties / pinHost / description
      Previous value: -"Set true for a measurement/benchmark workload whose numbers are only comparable on one machine. The job then FAILS if its box is lost mid-run instead of being rerouted to a different one — a silent host swap would make the results meaningless. Default false: a lost box may be replaced by another machine."New value: +"Accepted for compatibility and ignored. A machine that is lost or never starts may be replaced by another one, inside budgetUsd. To keep a benchmark on one GPU model, set gpu.gpuName with gpu.gpuNameExact."
  2. Changed1 schema field changed
    • changedInput schema / properties / spec / properties / gpu / properties / count / description
      Previous value: -"GPUs on ONE machine. Must be 1 — a higher count is REFUSED, never silently reduced to one card. Omit it."New value: +"Exact number of GPUs on ONE machine (never N separate machines). Omit it for one GPU, or declare 2; any other count is REFUSED, never silently changed. Declare it only when the workload runs that many processes on a single host (for example torchrun --nproc_per_node=N); minVramGb then applies to each GPU."
  3. Changed3 schema fields changed
    • changedInput schema / properties / spec / properties / gpu / properties / count / description
      Previous value: -"GPUs on ONE machine. Must be 1 — a higher count is REFUSED, never silently reduced to one card. Omit it unless you mean several cards on a single host (that is not N separate machines)."New value: +"GPUs on ONE machine. Must be 1 — a higher count is REFUSED, never silently reduced to one card. Omit it."
    • changedInput schema / properties / spec / properties / gpu / properties / count / maximum
      Previous value: -9007199254740991New value: +16
    • changedInput schema / properties / spec / properties / gpu / properties / minVramGb / description
      Previous value: -"Optional. A VRAM floor the box must meet, only when the workload has an evidenced one (a measured peak, a known model footprint). Omit it when unknown: Symbioza then sizes the GPU itself. Never ask the user to choose a tier, and never derive a number from an unrelated run."New value: +"Optional. A VRAM floor the box must meet, PER GPU, only when the workload has an evidenced one (a measured peak, a known model footprint). Omit it when unknown: Symbioza then sizes the GPU itself. Never ask the user to choose a tier, and never derive a number from an unrelated run."
  4. Changed7 schema fields changed
    • changedInput schema / properties / spec / properties / env / description
      Previous value: -"Environment variables for the container. They are stored with the job spec in clear on our control plane and are included in our routine backups, so do not put credentials or other secrets here — pass them in at run time or fetch them from your own store inside the command."New value: +"Non-sensitive configuration for the container (e.g. MODEL_NAME, EPOCHS). They are stored with the job spec in clear on our control plane and are included in our routine backups, so NEVER put credentials here: API keys, tokens, passwords and cloud keys belong in saved Secrets, attached with `secrets`."
    • addedInput schema / properties / spec / properties / secrets
      Added value: +{
      +  "description": "Saved secret sets this job needs, by name (e.g. [{\"name\":\"hf-training\"}]) or by the access it needs ([{\"type\":\"huggingface\",\"purpose\":\"Access a gated model\"}]). Every variable in each set is injected into the container at run time; values are never in the spec, the response or the chat. A missing set comes back with a Symbioza setup link for the user.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "fields": {
      +        "description": "For a custom requirement only: the environment variable NAMES the workload expects (e.g. [\"MODEL_API_TOKEN\"]). Names only, never values.",
      +        "items": {
      +          "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$",
      +          "type": "string"
      +        },
      +        "maxItems": 32,
      +        "type": "array"
      +      },
      +      "id": {
      +        "description": "The saved set's id (sec_…). Symbioza pins it at submit; you normally give the name.",
      +        "pattern": "^sec_[A-Za-z0-9_-]{22}$",
      +        "type": "string"
      +      },
      +      "name": {
      +        "description": "The saved secret set's name, e.g. \"hf-training\". Read the names with listSecrets.",
      +        "pattern": "^[a-z0-9][a-z0-9_-]{0,62}$",
      +        "type": "string"
      +      },
      +      "purpose": {
      +        "description": "A short reason shown to the user on the setup page, e.g. \"Access a gated model\". Never a credential.",
      +        "maxLength": 160,
      +        "minLength": 1,
      +        "type": "string"
      +      },
      +      "type": {
      +        "description": "What access the workload needs: huggingface, object-storage, wandb or custom. With no name, Symbioza uses your one saved set of this type, or returns a setup link when there is none.",
      +        "enum": [
      +          "huggingface",
      +          "object-storage",
      +          "wandb",
      +          "custom"
      +        ],
      +        "type": "string"
      +      }
      +    },
      +    "type": "object"
      +  },
      +  "maxItems": 16,
      +  "type": "array"
      +}
    • addedOutput schema / properties / access
      Added value: +{
      +  "additionalProperties": {},
      +  "description": "On access_required or secret_conflict: what the spec's secrets need, with setup links",
      +  "properties": {
      +    "complete": {
      +      "description": "true when every set the spec names is saved and nothing collides; submitJob refuses otherwise",
      +      "type": "boolean"
      +    },
      +    "conflicts": {
      +      "description": "A variable two attached sets (or a set and env) both define",
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "nextAction": {
      +      "anyOf": [
      +        {
      +          "type": "string"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ],
      +      "description": "add_access | fix_spec | null"
      +    },
      +    "ready": {
      +      "items": {
      +        "additionalProperties": {},
      +        "properties": {
      +          "fields": {
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "name": {
      +            "type": "string"
      +          },
      +          "type": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "name",
      +          "type",
      +          "fields"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "required": {
      +      "items": {
      +        "additionalProperties": {},
      +        "properties": {
      +          "candidates": {
      +            "description": "ambiguous: the saved set names that could serve; ask the user which",
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "message": {
      +            "type": "string"
      +          },
      +          "name": {
      +            "type": "string"
      +          },
      +          "purpose": {
      +            "type": "string"
      +          },
      +          "setupUrl": {
      +            "description": "missing: the Symbioza link where the user saves it; give it as returned",
      +            "type": "string"
      +          },
      +          "status": {
      +            "description": "missing | ambiguous | type_mismatch",
      +            "type": "string"
      +          },
      +          "type": {
      +            "anyOf": [
      +              {
      +                "type": "string"
      +              },
      +              {
      +                "type": "null"
      +              }
      +            ]
      +          }
      +        },
      +        "required": [
      +          "status",
      +          "type",
      +          "message"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    }
      +  },
      +  "required": [
      +    "complete",
      +    "ready",
      +    "required",
      +    "conflicts",
      +    "nextAction"
      +  ],
      +  "type": "object"
      +}
    • changedOutput schema / properties / action / description
      Previous value: -"The one next step: authenticate, fix_spec, raise_budget, add_funds, claim_credit, relax_spec_or_retry, retry_later, contact_support"New value: +"The one next step: authenticate, fix_spec, raise_budget, add_funds, claim_credit, add_access, relax_spec_or_retry, retry_later, contact_support"
    • changedOutput schema / properties / code / description
      Previous value: -"Machine-readable refusal code: unauthorized, invalid_spec, budget_too_low, insufficient_balance, no_compute, unavailable, refused"New value: +"Machine-readable refusal code: unauthorized, invalid_spec, budget_too_low, insufficient_balance, access_required, secret_conflict, no_compute, unavailable, refused"
    • addedOutput schema / properties / shortfallUsd
      Added value: +{
      +  "description": "On insufficient_balance: whole dollars of credit this job still needs",
      +  "type": "number"
      +}
    • addedOutput schema / properties / topupUrl
      Added value: +{
      +  "description": "On insufficient_balance: the link to give the user for that credit",
      +  "type": "string"
      +}
  5. First observed

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say it is a non-read-only, open-world, non-idempotent write. The description adds substantial context beyond that: what teardown destroys (only /workspace/artifacts/ is collected), that a refusal books nothing and charges nothing, the full code/action taxonomy to branch on, confirm-first billing semantics, ToS mining refusal, and best-effort checkpoint salvage with its many skip conditions. None of this contradicts the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is correctly front-loaded with the estimate-first instruction, but the body is a single monolithic paragraph that runs to hundreds of words, mixing critical gating rules (artifacts path, refusals) with dense edge-case detail (checkpoint salvage conditions, multi-arm layouts). Most sentences earn their place, yet the lack of structure and some repetition make it hard to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a high-complexity tool with nested schema, full annotations and an output schema (so return values need no explanation), the description covers everything an agent needs to call it correctly: pricing prerequisite, refusal branching, dedup/retry semantics, artifact placement, budget enforcement and checkpoint resume.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline would be 3; the description nevertheless adds real meaning for key parameters — budgetUsd as a never-exceeded ceiling, retryOf's valid-state constraint, clientRequestId's resend/dedup contract, specDigest's quote-binding effect, and the rationale behind gpu.gpuNameExact, minPowerLimitW and datasetGb.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a precise verb and resource: it queues a containerized workload on a rented cloud machine and returns a durable executionId, with the lifecycle (provision, run, collect, teardown) spelled out. It clearly distinguishes itself from siblings by routing pricing to estimateExecution and polling to getStatus.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit ordered workflow (estimate first, ask the user for budgetUsd, submit only after approval) and names alternatives for each situation: retryOf + new clientRequestId for re-running a failed job, the same clientRequestId for resends, specDigest to bind the quote. When-to-use and when-not are unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.