Docs / First task

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.