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.

Input Schema

TableJSON 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

TableJSON 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.4/5.0
Behavior5/5

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

With annotations already declaring readOnlyHint=false, openWorldHint=true, idempotentHint=false, and destructiveHint=false, the description still adds substantial behavioral context beyond them: immediate queuing and durable executionId, budget ceiling enforcement, artifact collection only from /workspace/artifacts/, image allowlist refusals, refusal codes and actions, checkpoint restore semantics, deduplication rules, and that a refusal books nothing and charges nothing. It does not contradict any annotation.

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

Conciseness2/5

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

The description is extremely long and reads as a wall of text. While it front-loads the critical estimate-then-submit workflow and many sentences carry real information, it repeats schema descriptions and covers numerous edge cases at length, making it poorly sized for an agent to scan. Conciseness suffers despite the useful content.

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?

Given the tool's complexity, the rich input schema, the annotations, and the presence of an output schema, the description is more than complete. It covers the entire lifecycle: estimate, approval, budget, submission, refusal codes, artifact location, checkpoint resume, retry, deduplication, and secret access. Nothing material is missing for an agent to invoke the tool correctly.

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

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3. The description adds some cross-parameter context, such as clientRequestId deduplication, retryOf semantics, specDigest enforcement, and budgetUsd cross-checking, but the schema already documents these parameters in exhaustive detail. Overall it does not add enough meaning beyond the schema to merit a 4.

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 specific verb and resource: running a containerized workload on a rented cloud machine sized to the spec, queuing it, returning an executionId, and provisioning/teardown under budgetUsd. It distinguishes this from siblings by naming estimateExecution, getStatus, getArtifact, listSecrets, and retryOf behavior. An agent can tell exactly what the tool does and how it differs from alternatives.

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 explicit sequencing and alternatives: call estimateExecution first if the spec has not been priced, ask the user for budgetUsd, and only call submitJob after the user approves the estimate. It also names when to use retryOf versus a new clientRequestId, and when to branch on refusal codes rather than retrying blindly. This is explicit when-to-use and when-not-to-use guidance.

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.