First task
A Helmr task project has a helmr.config.ts file and one or more exported task modules.
helmr init --dir ./my-helmr-tasks
helmr init creates:
helmr.config.ts, which tells Helmr where to find tasks.package.json, which declares the Helmr SDK dependency.tasks/hello.ts, a starter task.
The starter shape is:
import { cache, image, sandbox, source, task } from "@helmr/sdk"
const runtime = image("hello")
.from("node:24-bookworm-slim")
.workdir("/app")
.run(["npm", "install", "-g", "bun@1.3.10"])
.copy("/app/package.json", source.file("package.json"))
.run(["bun", "install"], {
cache: [{ mountPath: "/root/.bun/install/cache", cache: cache("hello-bun") }],
})
const sb = sandbox("hello")
.image(runtime)
.workspace("/app")
export const hello = task({
id: "hello",
sandbox: sb,
run: async () => ({ ok: true }),
})
Tasks declare their runtime before they run: image, sandbox, workspace mount, resources, secrets, max duration, payload type, and return value.
Use ctx for read-only execution context and module-level APIs for operations:
import { logger } from "@helmr/sdk"
import { writeFile } from "node:fs/promises"
import { z } from "zod"
const helloPayload = z.object({
name: z.string().optional(),
})
export const hello = task({
id: "hello",
sandbox: sb,
maxDuration: 300,
payload: helloPayload,
run: async (payload, ctx) => {
const greeting = `hello ${payload.name?.trim() || "Helmr"}`
await writeFile("hello.txt", `${greeting}\nrun=${ctx.run.id}\n`)
logger.info({ message: "wrote greeting", path: "hello.txt" })
return { greeting, runId: ctx.run.id }
},
})
Use payload for payload-bearing tasks. Helmr accepts schemas that validate through Standard Schema v1; Zod v4 schemas satisfy this contract and can be passed directly. Keep payload for audit-safe inputs such as PR numbers, repository names, ticket ids, and flags. Do not put tokens or credentials in payload; declare secrets and bind them at run time.