The code-first runtime for AI agents.

Define agent work in TypeScript. Run it in isolated Linux microVMs with durable workspaces and human input.

One TypeScript file.Image, Sandbox, Task, human input.

Codex here — or Claude Code, the AI SDK, any agent SDK that runs on Node. Durable continuation uses Actor input or independently completable Tokens; one-shot Task input remains an explicit start payload.

review-pr.tstypescript
 1import { image, sandbox, source, task, tokens } from "@helmr/sdk" 2import { Codex } from "@openai/codex-sdk" 3import { z } from "zod" 4  5const base = image("repo-agent") 6  .from("node:24-bookworm-slim") 7  .workdir("/workspace") 8  .run(["sh", "-ceu", "apt-get update && apt-get install -y git ripgrep"]) 9  .copy("/workspace/package.json", source.file("package.json"))10  .run(["bun", "install"])11 12export const repoSandbox = sandbox({ id: "repo-agent" })13  .image(base)14  .resources({ cpu: 2, memory: "4GiB" })15 16export const reviewPr = task({17  id: "review-pr",18  payload: z.object({ prNumber: z.number().int().positive() }),19  run: async (event, ctx) => {20    const codex = new Codex()21    const thread = codex.startThread({ workingDirectory: process.cwd() })22    const turn = await thread.run("Review this PR and propose a patch.")23 24    const approval = await tokens.create({ timeout: "30m" })25    await sendSlackApproval({26      callbackUrl: approval.callbackUrl27    })28    // The microVM freezes here — filesystem, memory, process.29    const decision = await approval.wait({30      schema: z.object({ approved: z.boolean() }),31      timeout: "30m",32      tags: ["approval", "github-review"],33      metadata: { subject: "Post this review to GitHub?" }34    }).unwrap()35    if (decision.approved) await postReview(event.prNumber, turn.finalResponse)36  }37})

Workflows as functions. Typed in, typed out, durable in between.

Write the Task

task()typescript
export const reviewPr = task({
  id: "review-pr",
  payload: z.object({ prNumber: z.number() }),
  run: async ({ prNumber }) => {
    return draftReview(prNumber)
  }
})

One exported const with a typed payload and a run function. That is the whole unit Helmr deploys.

Start it from anywhere

client.tasks.start()typescript
const run = await client.tasks.start<typeof reviewPr>(
  "review-pr",
  {
    payload: { prNumber: 482 },
    workspace
  }
)

Your product, a webhook, or CI starts a deployed Task with a typed payload against an existing Workspace.

Put it on a cron

schedules.task()typescript
export const nightly = schedules.task({
  id: "nightly-audit",
  cron: {
    pattern: "0 9 * * 1-5", timezone: "Asia/Tokyo"
  },
  workspace: { sandbox: repoSandbox },
  run: async (_, ctx) => runAudit(ctx)
})

The schedule lives beside the code and ships with it. Every fire gets a fresh Workspace.

Wait for a human

tokens.create()typescript
const approval = await tokens.create({ timeout: "30m" })

const decision = await approval.wait({
  schema: z.object({ approved: z.boolean() }),
  timeout: "30m",
  tags: ["approval", "github-review"]
}).unwrap()

The microVM freezes whole while it waits — filesystem, memory, process. A scoped callback completes one value.

Steerable agent sessions. Input and output, kept across Runs.

Define the Actor

actor()typescript
export const reviewer = actor({
  id: "reviewer",
  async run(session) {
    const input = await session.input
      .receive({ idleTimeout: "30m" })
      .unwrap()

    await session.output.append({
      type: "progress",
      message: "Reviewing",
      input
    })
  }
})

Send input and read output

client.sessions.ref()typescript
// From your product or webhook:
const session = client.sessions.ref(sessionId)

await session.input.send({
  type: "steer",
  instruction: "Please also update the tests."
}, { idempotencyKey: "slack:thread-1:message-7" })

const { records } = await session.output.list()

for (const record of records) {
  render(record.sequence, record.data)
}

Send a correction from Slack, your product, or the CLI. The Session history survives waits and continuation Runs.

Persistent VM filesystems. The same /workspace, Run after Run.

Define the environment

image() + sandbox()typescript
const base = image("repo-agent")
  .from("node:24-bookworm-slim")
  .workdir("/workspace")
  .run(["npm", "install", "-g", "@openai/codex"])

export const repoSandbox = sandbox({
  id: "repo-agent"
})
  .image(base)
  .resources({
    cpu: 2,
    memory: "4GiB"
  })

Choose the base image, install CLIs, and set the working directory and resources behind the Workspace.

Create the Workspace

client.sandboxes.createWorkspace()typescript
const workspace = await client.sandboxes.createWorkspace(
  "repo-agent",
  {
    key: `github-pr:${pr.id}`,
    idempotencyKey: `workspace:${pr.id}`
  }
)

const result = await workspace.exec({
  command: ["bash", "-lc", "bun test"],
  cwd: "/workspace",
  idempotencyKey: `verify:${pr.id}`
})

Create it once, run bounded commands, and attach later Task and Actor Runs to the same files.

Run on our cloud, or in your AWS.

Early, open, Apache 2.0. Read the source before you trust it with your repos.

apache-2.0go control planetypescript sdkfirecracker microVMsopentofu modules

Helmr Cloud coming soon

A managed fleet. Projects, environments, immutable deployments — no infrastructure to run.

Read the docs

Your AWS account

The identical control plane and workers in your VPC, Apache 2.0. Credentials never leave your boundary.

Self-hosting guide