The durable runtime for AI agents.
Define tasks in plain TypeScript. Each run gets its own microVM, pauses whole for session input, then resumes with every input and output attached.
$ helmr deploy
Deployment dpl_4e21 queued
v12
$ helmr run review-pr --payload prNumber=482
run_8f3a
$ helmr ps
RUN ID TASK STATUS INPUT CHANNEL
run_8f3a review-pr waiting approval
$
One TypeScript file. Image, sandbox, task, secrets, session input.
Codex here — or Claude Code, the AI SDK, any agent SDK that runs on Node. Follow-up input stays attached to the session, not the start payload.
1import { cache, image, queue, sandbox, source, streams, task } 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 cache: [{ mountPath: "/root/.bun/install/cache", cache: cache("bun") }]12 })13 14const sbx = sandbox("repo-agent")15 .image(base)16 .resources({ cpu: 2, memory: "4Gi" })17 18const reviews = queue({ id: "review/pr", concurrencyLimit: 5 })19 20export const reviewPr = task({21 id: "review-pr",22 sandbox: sbx,23 queue: reviews,24 secrets: [{ name: "OPENAI_API_KEY", env: "OPENAI_API_KEY" }],25 run: async (event, ctx) => {26 const codex = new Codex()27 const thread = codex.startThread({ workingDirectory: process.cwd() })28 const turn = await thread.run("Review this PR and propose a patch.")29 30 const approval = streams.input("approval", {31 schema: z.object({ approved: z.boolean() })32 })33 await sendSlackApproval({34 sessionId: ctx.session.id,35 stream: approval.id36 })37 // The microVM freezes here — filesystem, memory, process.38 const decision = await approval.wait({39 timeout: "30m",40 tags: ["approval", "github-review"],41 metadata: { subject: "Post this review to GitHub?" },42 correlationId: `pr:${event.prNumber}`43 }).unwrap()44 if (decision.approved) await postReview(event.prNumber, turn.finalResponse)45 }46}) Pause. Schedule. Start. Reuse. Each one is a few lines.
Pause for session input
const approval = streams.input("approval", {
schema: z.object({ approved: z.boolean() })
})
const decision = await approval.wait({
timeout: "30m",
tags: ["approval", "github-review"]
}).unwrap() The microVM freezes whole while it waits. A webhook, UI, or operator appends input and resumes it mid-instruction.
Run on a schedule
export const nightly = schedules.task({
id: "nightly-audit",
cron: {
pattern: "0 9 * * 1-5",
timezone: "Asia/Tokyo"
},
run: async (event, ctx) => {
return runAudit(ctx)
}
}) Cron lives in the task definition and deploys with it. One file owns the code and the schedule.
Start from anywhere
const started = await sessions.start(
reviewPr,
{ prNumber: 482 },
{ idempotencyKey: pr.head.sha }
) Your product, a webhook, CI, or another task — deduplicated with idempotency keys.
Reuse the workspace
const workspace = workspaces.open(started.session.workspaceId)
const exec = await workspace.exec(["bash", "-lc", "bun test"], {
cwd: "/workspace"
})
await exec.wait() The session records the workflow. The workspace stays available for follow-up execs and terminals.
Run on our cloud, or in your AWS.
Early, open, Apache 2.0. Read the source before you trust it with your repos.
Helmr Cloud coming soon
A managed fleet. Projects, environments, immutable deployments — no infrastructure to run.
Read the docsYour AWS account
The identical control plane and workers in your VPC, Apache 2.0. Credentials never leave your boundary.
Self-hosting guide