Computer

This page lists the Computer authoring API and the HelmrClient methods for Computers and Commands. For what a Computer is and how it persists, see Computers. For step-by-step instructions, see Prepare a Computer and Run commands.

template()

function template(config: Omit<TemplateDefinition, "kind">): TemplateDefinition

interface TemplateDefinition {
  readonly kind: "template"
  readonly id: string
  readonly image: ImageBuilder
  readonly resources: { readonly cpu: number; readonly memory: string; readonly disk?: string }
  readonly network?: NetworkPolicy
  readonly prepareNetwork?: NetworkPolicy
  readonly prepare?: (prepare: PrepareContext) => MaybePromise<void>
  readonly refresh?: { readonly every: Duration }
  readonly deleteAfterUnused?: Retention
  readonly secrets?: readonly SecretBinding[]
  readonly prepareSecrets?: readonly SecretBinding[]
}

Returns a frozen definition with kind: "template". Throws TypeError when id is blank or resources.cpu/resources.memory is missing.

Field Required Description
id yes Definition ID, matching ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$.
image yes Root filesystem, built with image().
resources.cpu yes Positive number of CPUs; must resolve to whole milliCPU (for example 0.5, 1, 2).
resources.memory yes Positive integer with MiB or GiB, for example "4GiB".
resources.disk no Must be "32GiB" in this release; omit it to use that size.
network no Outbound network maximum for Computers created from this Template. Omission keeps ordinary IPv4 connectivity under installation restrictions.
prepareNetwork no Replaces the network policy during prepare; omission inherits network. Requires a callback.
prepare no Preparation function. Runs with a PrepareContext to set up the Computer’s filesystem (for example cloning a repository and installing dependencies) before the Computer is used.
refresh.every yes, with refresh Best-effort interval between background preparations after promotion. A usable build already in progress satisfies the interval; missed intervals are coalesced. A new Computer does not start from a prepared image older than three intervals; an older one is prepared again first. Without refresh, images have no age limit.
deleteAfterUnused no Deletes a Computer created from this Template after it stays unused this long: a Duration or "never". Default "7d". A Computer always uses its origin Template’s value; to keep a long-lived Computer longer, create it from a Template with a longer period.
secrets no Secret bindings available to processes on the Computer.
prepareSecrets no Secret bindings available to prepare. Nonempty bindings require a callback.
interface PrepareContext {
  readonly signal: AbortSignal
  exec(command: string | readonly string[], options?: { cwd?: string; env?: Record<string, string> }): Promise<void>
}
import { template, image } from "@helmr/sdk"

export const repoComputer = template({
  id: "app",
  image: image("app").from("node:24-bookworm"),
  resources: { cpu: 2, memory: "4GiB" },
  prepareSecrets: [{ secret: "SOURCE_TOKEN", env: { name: "SOURCE_TOKEN", mode: "raw" } }],
  secrets: [{
    secret: "MODEL_API_KEY",
    env: { name: "MODEL_API_KEY", mode: "protected", allowedOrigins: ["https://api.example.com"] },
  }],
  async prepare(build) {
    await build.exec(["git", "clone", "https://github.com/acme/app", "/app"])
    await build.exec(["npm", "ci"], { cwd: "/app" })
  },
  refresh: { every: "1d" },
})

Outbound network policy

export type AllowedDomain =
  | string
  | { readonly domain: string; readonly ipRanges: readonly string[] }

export type NetworkPolicy =
  | { readonly blockAll: boolean; readonly allowDomains?: never }
  | { readonly allowDomains: readonly AllowedDomain[]; readonly blockAll?: never }
Value Outbound access
Omitted or { blockAll: false } Ordinary IPv4 TCP, UDP and ICMP, subject to mandatory denies and the installation’s approved private ranges.
{ blockAll: true } No external guest traffic or external DNS.
{ allowDomains: [...] } Matching TLS SNI names on TCP 443, subject to each entry’s address bounds.
{ allowDomains: [] } Full blocking; normalized to { blockAll: true }.

The two branches are exclusive, including blockAll: false. Empty objects, null, bare booleans and unknown fields are invalid.

allowDomains accepts at most 256 unique normalized patterns. Names use ASCII DNS A-labels with at least two labels, normalized to lowercase with one final dot removed. The maximum length is 253 characters, including a leading *.; labels contain 1–63 letters, digits or hyphens without leading/trailing hyphens. IP literals, numeric-only names, localhost names, Unicode, URLs, ports, paths and whitespace are invalid. Use explicit IDNA A-labels for international names.

*.example.com matches api.example.com and a.b.example.com, but excludes example.com. Exact names grant no subdomains. A wildcard can appear only once, at the beginning, before a multi-label suffix; * and *.com are invalid. Overlapping entries are positive grants: any one matching entry must satisfy its own address bounds. An exact entry cannot narrow a broader wildcard.

Strings permit public IPv4 destinations only. Objects require 1–64 unique canonical IPv4 CIDRs in ipRanges; host bits are rejected. Those ranges bound all addresses for that entry and grant no direct-IP or non-SNI access. Private RFC1918 destinations additionally require containment in the installation’s approved business ranges, which default to empty. Management, metadata, host, peer and other mandatory denies always apply. IPv6 and other non-global address classes have no business exception.

With a callback, prepareNetwork replaces the entire preparation policy or inherits network when omitted. Without a callback, preparation is fully blocked, and explicit prepareNetwork or nonempty prepareSecrets is invalid. Seed stages always block guest egress. The resolved phase policy contributes to preparation cache identity. Image-building host networking and project config.build.secrets are separate from VM preparation.

Protected Secret origins must match the phase’s network policy. Full blocking rejects protected bindings. Allowlisted origins require port 443 and a matching concrete hostname; their later connections must satisfy the matching entry’s address bounds. Ordinary connectivity retains explicit HTTPS origin ports. Raw and file bindings never grant additional network access.

See Execution policies for authoring, placement, MCP and trust boundaries.

Images

function image(key: string): ImageBuilder

interface ImageBuilder {
  readonly key: string
  from(ref: string): ImageBuilder
  run(argv: readonly string[]): ImageBuilder
  copy(source: SourceFile | SourceDirectory, destination: string): ImageBuilder
  copyFrom(destination: string, source: ImageBuilder, sourcePath: string): ImageBuilder
  workdir(path: string): ImageBuilder
  env(key: string, value: string): ImageBuilder
  user(name: string): ImageBuilder
}

const source: {
  file(path: string): SourceFile
  directory(path: string): SourceDirectory
}

Each method returns a new builder with one more step.

Step Description
from(ref) Base image reference, for example "node:24-bookworm-slim".
run(argv) Runs an argv array without a shell.
copy(source, destination) Copies a project file or directory, selected with source.file() or source.directory().
copyFrom(destination, image, sourcePath) Copies sourcePath from another image() into destination.
workdir(path), env(key, value), user(name) Set the working directory, an environment variable, or the user for later steps and processes.

Native addons loaded by Agent code and the shared libraries they need are covered in Configuration.

Secret bindings

type SecretBinding = { readonly secret: string } & (
  | { readonly env: { readonly name: string; readonly mode: "protected"; readonly allowedOrigins: readonly string[] } }
  | { readonly env: { readonly name: string; readonly mode: "raw" } }
  | { readonly file: { readonly path: string } }
)
Rule Value
Bindings per Computer At most 64
Placement Exactly one of env or file
env.name Matches ^[A-Za-z_][A-Za-z0-9_]*$; unique per Computer. Names starting with HELMR_ or LD_, and NODE_OPTIONS, NODE_PATH, NODE_ICU_DATA, OPENSSL_CONF, OPENSSL_MODULES, OPENSSL_ENGINES, GCONV_PATH, LOCPATH, SSL_CERT_FILE, SSL_CERT_DIR, NODE_EXTRA_CA_CERTS, NODE_USE_SYSTEM_CA are reserved.
protected mode Requires 1–16 exact HTTPS allowedOrigins; at most 256 origins per Computer.
raw mode Does not accept allowedOrigins.
file.path Absolute, normalized, at most 4096 bytes. Not under /workspace, /var/lib/helmr, /dev, /opt/helmr, /proc, /sys, /run/helmr or /.helmr-old-root. Paths must not repeat or nest.

Bindings reference Secrets by stable ID and never contain values. The same field names (secret, allowedOrigins) are used in REST request bodies. See Secrets for the protection modes.

Client: Templates

client.templates.get(id: string, query?: { deploymentId?: string }, options?): Promise<TemplateInfo>
client.templates.list(query?: { deploymentId?: string; cursor?: string; limit?: number }, options?): Promise<TemplatePage>

TemplateInfo includes template, deploymentId, resources, normalized network, resolved prepareNetwork, and prepareSecrets. List items have the same fields except deploymentId, which belongs to the page. The preparation policy is explicit or inherited when a callback exists, and { blockAll: true } otherwise. Omitted runtime network reads as { blockAll: false }.

Omitting deploymentId reads the current Deployment; a list cursor keeps its original Deployment. limit is 1–100. resources contains normalized milliCpu, memoryMiB and optional diskMiB. These are pinned configuration views, not a live acknowledgement of packet enforcement. To inspect an existing Computer, select its own template and deploymentId; see Inspect the pins.

Client: Computers

client.computers.create(request: ComputerCreateRequest, options?): Promise<ComputerRef>
client.computers.get(id: string, options?): Promise<Computer>
client.computers.list(query?: ComputerListQuery, options?): Promise<CursorPage<ComputerListItem>>
client.computers.ref(id: string): ComputerRef

interface ComputerCreateRequest {
  readonly template: string
  readonly key?: string
  readonly secrets?: readonly SecretBinding[]
  readonly idempotencyKey?: string
}

create uses the current Deployment. template is the identifier declared in template({ id, ... }). key is an optional immutable lookup value; it does not make creation get-or-create. Unused retention comes from the Template definition.

ComputerListQuery is either { secret?, cursor?, limit? } or an exact lookup { key }, which does not accept secret, cursor or limit. ComputerListItem is a Computer without secrets.

secret lists only non-deleted Computers that received a raw value of that Secret. Each item then has secretExposure: "received" when a process or Command on the Computer received the value directly, or "image" when its image was prepared with the value, so the Computer may hold it. Use it after revoking or rotating a Secret to find the raw copies that stay on Computers.

const holders = await client.computers.list({ secret: "DATABASE_PASSWORD" })

ComputerRef

Method Returns Description
get(options?) Computer Current state, including Secret bindings.
sessions.list({ cursor?, limit? }?, options?) CursorPage<ComputerUsage> Sessions currently using the Computer, including pending cleanup; limit 1–100.
commands.list({ cursor?, limit? }?, options?) CursorPage<ComputerUsage> Commands currently using the Computer, including pending cleanup; limit 1–100.
exec(request, options?) CommandRef Admits a Command. See Commands.
delete({ idempotencyKey?, dryRun? }?, options?) { computerId, sessionsCancelled, commandsCancelled } Requests deletion. Deleting cancels its Sessions and the Sessions they started, and its unfinished Commands. dryRun reports the counts without changing anything.
interface ComputerCommandRequest {
  readonly command: readonly string[]
  readonly idempotencyKey?: string
  readonly cwd?: string
  readonly env?: Readonly<Record<string, string>>
  readonly stdin?: Uint8Array
  readonly timeout?: Duration
}

exec() returns after admission; use the returned Command’s wait() to observe completion. timeout limits execution, defaults to 5m, and accepts 1ms through 24h. It is separate from the timeout on CommandRef.wait(). Without idempotencyKey, each call admits a new Command; reuse a key to retry one admission.

ComputerUsage is { id, usageState, createdAt }, where usageState is admitted, running, waiting, parked, draining or unreconciled. These are current occupancy summaries, not full history or Session/Command lifecycle statuses. Each collection has a separate cursor.

Computer

Field Type
id, template, deploymentId string
key string, optional
status "available" | "deleting" | "deleted"
residency "cold" | "starting" | "running" | "parking" | "parked" | "restoring" | "unavailable"
executionWait { reason: "scheduling" | "environment_limit" | "saving" } or null
error { code, message }, present when residency is unavailable
deleteAfterUnused Duration ("never" when disabled), from the origin Template
unusedSince string | null
secrets SecretBinding[]
createdAt, updatedAt string

Client: Commands

client.commands.ref(id: string): CommandRef

interface CommandRef {
  readonly id: string
  get(options?): Promise<CommandInfo>
  wait(options?: { signal? }): Promise<CommandOutcome>
  wait(options: { timeout: Duration; signal? }): Promise<TimedCommandWaitResult>
  cancel(options?): Promise<CommandCancelReceipt>
  readonly logs: {
    list(query?: { cursor?: string; limit?: number }, options?): Promise<CommandLogReadPage>
    stream(query?: { after?: string }, options?): AsyncIterable<CommandLogRecord>
  }
}
Member Description
get() CommandInfo: { id, computerId, status, processReconciled, outcome? }. status is pending, starting, running, stopping, exited, failed, cancelled, timed_out or lost.
wait() Resolves with the CommandOutcome. With timeout, resolves with { status: "timeout" } or { status: "settled", outcome }; a timeout does not cancel the Command.
cancel() Requests termination. Returns { id, targetId, status: "accepted" }; repeated calls return the same receipt.
logs.list() One page: { items, outputState, nextCursor? }, where outputState is open, closed or unavailable. limit 1–100.
logs.stream() Retained output followed by new output until the output closes. Resume with after: record.cursor.

The Command process scope ends when its main process exits, including exit code 0, or when it is cancelled or times out. Remaining descendants are terminated; backgrounding a child does not extend the scope. For example, sh -c 'server &' does not leave a server running after the shell exits. Run the program as the Command’s main process and retain its Command handle when you need to observe or cancel it within the execution limit.

Commands share the Computer’s filesystem, but each has a private process scope; they do not provide a shared shell or cross-command process-ID visibility. Commands that have not finished, and finished Commands awaiting physical cleanup, prevent idle checkpoint capture. These boundaries apply to external Commands; native children inside an Agent Session follow the Session’s lifecycle.

An execution timeout or cancel() requests termination of the Command scope. A client wait timeout or disconnection does not cancel an admitted Command. Cancelling a terminal Command leaves its outcome unchanged. processReconciled reports physical cleanup separately from the outcome; a cancellation receipt alone does not establish that cleanup has finished.

CommandOutcome has commandId, terminalAt and a kind:

kind Extra fields
exited exitCode
cancelled, timed_out none
system_failed failure.reason: guest_failure, dispatch_failed or scope_termination_failed

CommandLogRecord has cursor and stream (stdout or stderr), and is one of:

kind Fields
output content: Uint8Array, observedAt
gap fromSequence, throughSequence (records that are no longer available)

Output chunks are bytes, not necessarily whole lines or characters. Ordering holds within each stream only.

const command = await client.computers.ref(computerId).exec({
  command: ["npm", "test"],
  idempotencyKey: "test:revision-42",
})
for await (const record of command.logs.stream({}, { signal })) {
  if (record.kind === "output") process.stdout.write(record.content)
}
const outcome = await command.wait()

Agent code that needs to run local processes uses ordinary child processes; a HelmrClient inside Agent code acts with its own API key.

executionWait describes an observed wait for accepted runnable work. scheduling means placement is pending, environment_limit means the Environment’s execution limit prevents admission, and saving means work is waiting for a save or checkpoint transition. Held work is not reported as waiting for capacity. null does not promise guest readiness; use residency and errors too. The HTTP field is execution_wait. Existing polling refreshes this observation.