nima-hosted VMs
How nima-hosted agent VMs are enabled, what the VM can reach, how nima.yml configures checks, budgets and admission, and how results are published to GitHub.
A nima-hosted VM is an isolated virtual machine that nima starts to run an agent on one of your connected GitHub repositories. The VM is one of two targets for an agent operation. The other target, your own coding agent, needs no VM. For operations in general, see Agent operations.
Enabled by the nima team on request. Hosted VMs are not a self-serve setting: the nima team assigns your workspace a plan that includes agent compute and sets the compute and model prices, and then a workspace owner or admin opts in. Publishing to GitHub from a VM is new and is a separate opt-in (see the last section).
Enabling hosted VMs for a workspace
Hosted VMs need three things, and only the last one is yours to do. Ask the nima team for the first two.
- A plan with agent compute. The nima team assigns a plan that includes the
agent_computefeature. None of the standard plans include it. - Compute and model prices. The nima team sets them. Until they exist, turning hosted compute on fails.
- An owner or admin opts in. Call the MCP tool
update_agent_compute_settingswithenabled: true, orPATCH /v1/agent-compute. Both need theintegrations:writescope. That scope is not offered on the consent screen or in Settings: a workspace owner or admin mints a 30-day key in Slack with/nima mcp-key integrations(or by messaging @nimamcp-key integrations) and confirming. Starting operations needs a second key (mcp-key operations), because the integrations key carries nooperations:write.
The opt-in call usually fails in one of two ways:
| Status | Message | Meaning |
|---|---|---|
| 403 | The organization plan does not include agent compute | The plan step is missing. |
| 409 | Agent compute and model pricing must be configured before dispatch | The price step is missing. |
Any other 409 on enabling means nima's side is not configured yet; ask the nima team.
Saving the settings with hosted compute off (enabled: false) cancels every hosted operation that has not finished, including queued and parked ones. Each ends with the reason compute_disabled, and the response reports how many were cancelled in cancelledOperations. Disabling also stops new budget leases and run credentials. It does not undo commits already made and does not remove GitHub publishing consent, which is a separate switch. Handoffs to your own coding agent are not affected.
Field-level detail is on Agent compute settings; the tools are on Operation tools.
Hosted compute settings and defaults
The hosted compute settings are stored per workspace, and every key has a default that applies until you change it. PATCH /v1/agent-compute replaces each key you send; list keys (allowedKinds, alertThresholdsPercent) are replaced whole.
| Setting | Default | Range |
|---|---|---|
enabled | false | boolean |
defaultTarget | own_harness | own_harness or nima_vm |
allowedKinds | every kind except task | add task to allow free-form report-only jobs |
maxConcurrentOperations | 3 | 1 to 100 |
maxOperationsPerDay | 10 | 1 to 1000 |
monthlyComputeBudgetUsd | 25 | 0 to 1,000,000 |
monthlyAiBudgetUsd | 100 | 0 to 1,000,000 |
defaultOperationBudget | aiUsd 5, activeMinutes 60, maxSessions 10 | aiUsd 0.01 to 10,000, activeMinutes 1 to 1,440, maxSessions 1 to 200 |
maxOperationBudget | aiUsd 25, activeMinutes 120, maxSessions 20 | same shape; the default may not exceed it |
alertThresholdsPercent | [50, 80, 100] | up to 5 integers from 1 to 100 |
allowOperatorExec | false | boolean; lets nima operators run break-glass commands in this workspace's operation containers |
The months are UTC calendar months, and "per day" is the UTC day. An unknown key is a 400 "Invalid agent compute settings".
The alert thresholds are recorded, but nima does not deliver a budget alert to a person today. Do not rely on them to warn you.
Admission and why an operation fails before it starts
Admission is a check that runs after an operation is created and before a VM is assigned. If a check fails, the operation ends as failed with a terminal endReason. It is not queued to retry later.
endReason | Cause |
|---|---|
org_disabled | Hosted compute is off, the kind is not in allowedKinds, the plan no longer includes agent compute, or a repository, publishing or pricing check failed. endDetail carries the message. |
budget_exhausted | The month's compute or AI budget cannot cover the first budget lease. |
concurrency_limit | Live VM operations in the workspace have reached maxConcurrentOperations. nima also caps each workspace at 8 live VM operations, whatever the setting says. Parked operations do not count. |
daily_limit | Operations created since 00:00 UTC have reached maxOperationsPerDay. |
An operation waits instead of failing when nima has paused new placements, when no host capacity is free, or when the workspace already has 3 hosts. A wait for a host that outlasts the placement deadline (about 5 minutes) fails the operation. A denied request that carries a wrong combination (for example a kind that needs a VM while hosted compute is off) is refused at the start call with a 409 instead.
What a nima VM can reach
A nima VM reaches only nima. It has no internet access, holds no credentials and never pushes code itself.
- Network. No external IP. The only destinations are nima's model proxy and nima's MCP proxy. A task that needs a package install or an external service cannot do it and must say so in its report.
- Credentials. The container the agent runs in holds none. Your MCP token never enters it, and model and nima calls go through a per-operation proxy that adds credentials outside the container.
- Tools. The agent can call a fixed list of nima tools: the knowledge, signal, spec, beacon and code tools, plus tools to read its brief, report or ask the requester a question, and wait for checks. It has no operation, settings, key or publishing tools. A
taskcannot callcreate_signalorget_signal_source, and its metered tool calls are capped at 200. - Code. A per-repository snapshot: the tracked files at a commit with dependencies and caches installed, plus the changes up to the operation's head.
- Isolation. One organization per host, gVisor isolation, per-slot CPU, memory and disk limits, and a private network for each operation.
- Cold start. With no warm host, a run takes about 2 to 4 minutes to get going. The first run for a repository or a changed lockfile also waits for a snapshot build. A dependency install that fails ends the run with "couldn't finish preparing the repository".
Only commits on the operation's working branch leave the VM. That branch is nima/operation-<id>, the branch you named for run_checks or browser_check, or, for fix_ci, the open pull request's own branch. Pull requests from forks are not supported.
nima.yml: configuring checks, apps and services
nima.yml is an optional file at the repository root that tells nima how to run your tests and app inside the VM. It never configures the agent. The schema is strict, so a misspelled key fails.
version: 1
test:
command: pnpm test
timeoutMinutes: 20
app:
command: pnpm start
port: 3000
readyPath: /
readyTimeoutSeconds: 120
services:
- name: db
image: postgres:17
env:
POSTGRES_PASSWORD: test
ports: [5432]
healthcheck:
command: ["pg_isready", "-U", "postgres"]
paths:
allow: ["src/**", "test/**"]
park:
exclude: ["node_modules/**"]| Key | What it sets | Limits and defaults |
|---|---|---|
version | Schema version | 1 |
test.command | The command nima runs for checks | 1 to 4096 characters |
test.timeoutMinutes | Time allowed for the command | 1 to 120, default 20 |
app.command, app.port | The app browser_check serves (localhost only) | port 1 to 65535 |
app.readyPath, app.readyTimeoutSeconds | Readiness probe | default / and 120, timeout 1 to 600 |
services | Sibling containers on the operation's private network | at most 8; each has name, image, env, up to 16 ports and a healthcheck |
services[].healthcheck | Readiness command, written as a list of arguments (exec form) and run inside the service container | intervalSeconds 1 to 60 (default 2), retries 1 to 300 (default 30) |
paths.allow | Globs the operation may change; changes outside are rejected | 1 to 64 globs, default ["**"] |
park.exclude | Globs left out of a parked snapshot | up to 64 globs |
For run_checks and browser_check, nima runs the test command itself when the agent finishes and records the verdict from the exit code: 0 is checks_passed, 124 is checks_timed_out, any other non-zero is checks_failed. run_checks needs test.command and browser_check needs an app block. Without them, nima refuses to prepare the run and the operation fails before it is given a VM. A browser_check whose nima.yml has no test.command ends as checks_not_run.
Parking and resuming a VM operation
A parked VM operation is saved to storage and holds no compute, so it costs nothing while it waits. Resuming starts a new session, possibly on another host, and re-checks policy, budgets, repository access and publishing consent.
A VM operation parks for one of six reasons:
| Park reason | Meaning | What releases it |
|---|---|---|
agent_wait | The agent waits for CI or for an answer | any resume cause |
idle | A turn ended and nothing followed for 120 seconds | any resume cause |
host_drain | nima is moving the work to another host | any resume cause |
requested | Someone asked for the park | any resume cause |
limit_reached | A budget cap was hit | raising the limit, or a nima operator |
operator_hold | A nima operator paused it | a nima operator only |
The operation resumes when the requester answers (message_operation or a reply in the Slack thread), when a limit is raised, when a timer the agent set fires (at most 14 days ahead), or when CI finishes. You can also park or resume an operation yourself with POST /v1/operations/:id/park and POST /v1/operations/:id/resume; a resume does not release limit_reached or operator_hold. A parked operation expires after 7 days.
CI wake-ups come from GitHub check_suite and workflow_run completed events on the operation's branch, for operations parked in agent_wait whose head commit matches the last published one. nima's standard GitHub App receives these two events. If GitHub asks you to approve updated permissions for the App, accept them, or CI wake-ups will not reach nima. The separate writer App is not needed for this. See the GitHub integration.
Budgets and limits per operation
Every VM operation has a budget of AI dollars, active minutes and sessions. The default is $5, 60 minutes and 10 sessions, and the workspace maximum defaults to $25, 120 minutes and 20 sessions. Fleet-wide, one operation is also capped at 120 active minutes and 20 sessions.
A VM asks for money in small leases: $0.50 of AI spend, or 600 slot-seconds of compute, renewed as they are used. When a lease is denied, the operation parks with limit_reached. It is not killed, and the worst overspend is one lease.
To continue a parked operation, call raise_operation_limit (or POST /v1/operations/:id/raise-limit) with new absolute caps in aiUsd, activeMinutes or maxSessions. It never lowers a cap and cannot exceed maxOperationBudget (403 "Limit exceeds workspace maximum"). If the operation is parked on limit_reached and a cap it hit was raised, it resumes. Other limits: at most 3 parallel attempts per start, at most 3 hosts per workspace at once, and 3 out-of-memory kills in one session fail it.
Publishing results to GitHub
Publishing to GitHub is done by a separate, optional writer GitHub App, never by the VM and never by the standard nima GitHub App. Live publication is new, so expect rough edges.
Installing the writer App does not turn publishing on. A workspace admin enables it explicitly (set_github_publishing, PATCH /v1/github-writer/publishing), separately from hosted compute. Both need the integrations:write scope.
The kind decides which publish policy is allowed:
| Kind | Allowed publish policy | Needs the writer App |
|---|---|---|
implement_spec | new_draft_pr only | yes |
fix_ci | append_to_pr only | yes |
reproduce_issue | none, comment_only, new_draft_pr | only for comment_only and new_draft_pr |
run_checks, browser_check | none, comment_only | only for comment_only |
task | none only | no |
nima refuses a publish it cannot do rather than downgrading it to a report. The 409 "GitHub publishing is not authorized" carries details.reason, one of: github_write_disabled, writer_not_connected, writer_repository_unavailable, writer_permissions_missing (the writer App lacks contents: write or pull_requests: write; comment_only needs only pull_requests: write), invalid_publish_policy, unsupported_kind.
Before anything leaves the VM, a station gate inspects the exported commits and rejects the result if it:
- edits a workflow file (always refused),
- contains a secret in any commit,
- changes a lockfile (except
fix_ci, where the change is flagged for review), - escapes through a symlink, is oversized, or has a malformed path,
- is not a fast-forward for
append_to_pr, - touches a path outside
paths.allow.
For a report-only task, lockfile changes and changes outside paths.allow are flagged in the report instead of rejected. A task never publishes. When the gate passes, nima publishes with a token minted for exactly one repository and discarded afterwards. Commits are signed as Verified. A new draft PR is opened from nima/operation-<id>; fix_ci appends to the pull request's existing branch.
Agent operations
What an agent operation is, the six kinds, own-harness handoff versus a nima-hosted VM, and how to start, follow and finish one from MCP, Slack or the API.
The nima admin panel
What the web app at app.getnima.io does today: sign-in, onboarding, Integrations and Settings, and what it deliberately does not do.