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.