Configuration reference
Agent project
An Agent project contains package.json and helmr.config.ts. .helmrignore
is optional and selects which source files are captured; helmr init
generates one. See helmr init.
import { defineConfig } from "@helmr/sdk"
export default defineConfig({
dirs: ["agents"],
ignorePatterns: ["**/*.test.ts"],
})
defineConfig()
function defineConfig(input: HelmrConfigInput): HelmrConfig
interface HelmrConfigInput {
readonly dirs?: readonly string[]
readonly ignorePatterns?: readonly string[]
readonly build?: HelmrBuildInput
}
interface HelmrBuildInput {
readonly builder?: Builder
readonly installCommand?: string
readonly secrets?: readonly string[]
readonly external?: readonly string[]
readonly assets?: readonly string[]
}
| Setting | Default | Description |
|---|---|---|
dirs |
["agents"] |
Directories, relative to the project root, searched for exported Agent and Template definitions. Must be non-empty when set, without duplicates, .., absolute paths or backslashes. |
ignorePatterns |
[] |
Glob patterns excluded from definition discovery only. Supports *, ? and whole-segment **; negation, brackets, braces and extended globs are rejected. |
build.builder |
Helmr’s builder image | Steps that prepare the Linux build environment before dependencies are installed. See Build environment. |
build.installCommand |
Inferred from the package manager | One shell command, run unprivileged with /bin/bash -euo pipefail -c. |
build.secrets |
[] |
Names of variables in the invoking environment. Each is mounted at /run/secrets/NAME during dependency installation only. |
build.external |
[] |
Whole public-registry package names installed beside the generated JavaScript instead of bundled. Declare each in the root package.json. |
build.assets |
[] |
Project-relative file patterns copied into the bundle at their original locations. |
The config accepts only these settings. The default export must be an ordinary
object; {} is valid. Unknown settings, accessors, functions, null and
undefined fail.
Config evaluation
The CLI evaluates helmr.config.ts once, on the machine that runs
helmr build or helmr deploy, before any Linux build environment exists. The
result is passed to every later step; the Linux build does not import the
config again.
- Requires Node.js 22 or newer on
PATH. Helmr does not download Node.js. - The config is ordinary TypeScript. Relative imports, enums, tsconfig
paths, and installed ESM or CommonJS packages (including packages hoisted to a parentnode_modules) resolve as usual. It is transpiled, not type-checked. - Install what the config imports on that machine, at least
@helmr/sdk. Helmr does not install host dependencies, and hostnode_modulesare never copied into the Linux build. A native package imported by the config must be built for the host. - The config is trusted local code, like a package script. It runs with your environment and is not sandboxed. Helmr adds no credentials to it.
- Source is captured before the config is evaluated. Build from a stable checkout; Helmr does not snapshot everything a config might read.
- Agent code cannot import
helmr.config.ts, including through aliases, symlinks, query URLs or dynamic imports. Put shared data and helpers in ordinary modules.
Package manager
The builder uses a top-level packageManager field, or an unambiguous npm,
pnpm, Bun or Yarn lockfile, to choose the install command. A lockfile with a
frozen install is the recommended reproducible setup; it is not required.
build.installCommand replaces this inference.
Source capture
.helmrignore is the only source-selection file; .gitignore and
.dockerignore are not read. The root .git entry is always excluded. The
build fails when the captured source contains a root node_modules or helmr
path, or a .env or .env.* file other than names ending in .example,
.sample or .template.
helmr build copies the selected source into a private directory before
building. Later edits to the checkout do not change the copy. Capture rejects
changes it observes while reading, but it is not an atomic filesystem snapshot.
Files keep their executable bit; timestamps are normalized.
| Limit | Value |
|---|---|
| Regular-file content | 512 MiB |
| Selected entries | 100,000 |
| Path and name observation budget, including ignored children | 128 MiB |
| Path component | 255 bytes, UTF-8 |
| Directory depth | 128 |
| Symlink hops | 40 |
| Symlink target | 4,095 bytes, or the lower host limit |
Relative symlinks must stay inside the project; dangling links are allowed.
Name collisions and Unicode normalization that loses the original spelling
fail. Exclude large caches with .helmrignore.
Installed dependencies have separate limits:
| Limit | Value |
|---|---|
| Regular-file content | 10 GiB |
| Single file | 1 GiB |
| Entries, including the root | 200,000 |
| Path and link names | 128 MiB |
| Intermediate archive stream | 11 GiB |
Build environment
Helmr’s builder image is a digest-pinned Debian environment with the ELF loader, Git, CA certificates, Python, make, a C/C++ toolchain, and Helmr’s pinned Node.js, npm, Corepack and Bun. Lifecycle scripts, development dependencies, native compilation and dependency executables behave as on an ordinary Linux machine. Installation runs as an unprivileged user.
import { builder, defineConfig } from "@helmr/sdk"
export default defineConfig({
dirs: ["agents"],
build: {
builder: builder()
.copy("build/setup.sh", "/opt/setup.sh")
.run(["/bin/sh", "/opt/setup.sh"]),
installCommand: "./scripts/install.sh",
secrets: ["NPM_TOKEN"],
external: ["sharp"],
assets: ["prompts/**"],
},
})
function builder(): Builder
interface Builder {
readonly steps: readonly BuilderStep[]
run(argv: readonly string[]): Builder
copy(source: string, destination: string): Builder
}
builder() takes no arguments and always starts from Helmr’s builder image. It
is separate from a Computer image().
| Step | Rules |
|---|---|
copy(source, destination) |
Copies a file or directory from the captured project source (after .helmrignore, before installation) by project-relative path. Paths are literal: [ and $ mean themselves. source cannot contain *, ? or \; copy the containing directory instead. destination cannot contain \. /opt/helmr, /nix and /workspace are reserved; use locations such as /usr/local or /opt/<name>. |
run(argv) |
Runs an argv array without a shell, as root, with working directory /, HOME=/root, TMPDIR=/tmp and XDG_CACHE_HOME=/root/.cache. Each step starts from that context; files persist between steps, shell state does not. Use a copied script for multi-line setup. |
The steps run once per build. The resulting environment is used for project installation, runtime dependency installation and declaration analysis. Only the generated JavaScript, selected assets and runtime dependencies are included in the deployment bundle; tools installed in the build environment are not. Computer images declare their own runtime packages.
build.secrets values are never written to the config result or the bundle,
and never sent to the control plane. helmr build fails when a named variable
is not set.
Bundling
Helmr bundles all declaration entries into one split JavaScript module graph with a pinned esbuild. Agent code runs that generated JavaScript on Helmr’s managed Node.js runtime; the project’s TypeScript configuration is not read and TypeScript is not transformed at runtime. Source maps include original source content. Original source directories and build-only dependencies are not otherwise included.
At build time, esbuild resolves imports and reads TypeScript configuration
(including package-based extends) within the captured, installed project. Its
TypeScript and JSX transformation rules apply; this is not type-checking. Keep
a type-check command in your own development checks.
Static imports are resolved during bundling. Computed imports and Worker entrypoints must refer to JavaScript in the bundle or in installed runtime packages. A dynamic import whose target is absent fails when reached.
Runtime dependencies
Use build.external for native addons, packages that locate their own files
or executables, and packages looked up only at runtime. Each listed root is
installed even if no static import reaches it, at the exact version found in
the installed project. Pinned npm resolves and installs its runtime dependency
tree once in a fresh build environment; the completed tree is shipped
unchanged. A later build may select different transitive versions.
External roots must be whole package names with public-registry semver
declarations. Workspace, file, Git and alias roots, private registries, and
patch or override mechanisms other than npm overrides are not supported.
npm overrides are passed through: a direct-root override must match the
generated exact version or use npm’s $packageName reference. Keep shared
peers external when they must have a single runtime instance.
Native code has two targets:
- Addons loaded by Agent code run inside Helmr’s managed Node.js: x86_64 Linux,
that Node.js ABI, and the runtime’s own glibc and libstdc++, which are used
instead of the Computer image’s. Other shared libraries an addon links resolve
inside the Computer: the addon’s RPATH, then the image’s
ld.so.cacheand standard library directories. The Computer image must supply them; for example, a project that compiles againstlibvips-devinstallslibvips42in its Computer images. A missing library, or one built against a newer glibc than the runtime’s, fails with the usual loader error when the addon is imported. - Executables that Agent code spawns are ordinary Computer processes. They use the Computer image’s loader and libraries; Helmr injects nothing into them.
One installed tree serves every Computer in a Deployment. A musl-based image can run it when its native code is self-contained.
Assets and file locations
build.assets patterns apply after source capture and .helmrignore. Matches
keep their project-relative locations. Missing matches, symlinks, and collisions
with managed metadata or runtime dependencies fail the build.
Agent code starts in /workspace; the bundle is mounted read-only elsewhere.
/workspace is only the default working directory. Locate shipped files with
Node.js package imports rather than the current directory:
{ "imports": { "#project/*": "./*" } }
import { readFile } from "node:fs/promises"
const prompt = await readFile(
new URL(import.meta.resolve("#project/prompts/system.md")),
"utf8",
)
imports resolves locations; build.assets selects which files ship. The root
package’s name, type, imports and exports are preserved. Paths relative
to a source file’s import.meta.url are not preserved by bundling.
Runtime module rules
The generated JavaScript uses native Node.js resolution. The managed entry
requires a contained, ordinary package.json, rejects ancestor node_modules
lookup, and disables global and NODE_PATH lookup. Modules must resolve within
the bundle or the runtime’s permitted modules; TypeScript and JSX are not
compiled on demand. These rules are not a filesystem sandbox: filesystem
operations and Computer tools keep their normal access. Helmr does not inject
NODE_OPTIONS into other Computer processes.
Definitions
| Surface | Fields | Reference |
|---|---|---|
agent() |
id, computer, turn; optional init, closeAfterIdle (default "7d", or "never"), hibernateAfterIdle (default "never"), maxTurnDuration, triggers |
Agent |
template() |
id, image, resources; optional prepare, refresh, deleteAfterUnused (default "7d", or "never"), secrets, prepareSecrets |
Computer |
triggers.cron() |
trigger ID, expression, { timezone, input, slack? } |
Triggers |
image() |
from, run, copy, copyFrom, workdir, env, user |
Computer |
source |
file(path), directory(path) |
Computer |
Computer Secret bindings reference Secrets by ID and use the same field names in the SDK and REST API:
[
{ "secret": "TOKEN", "env": { "name": "TOKEN", "mode": "raw" } },
{ "secret": "CONFIG", "file": { "path": "/run/secrets/config.json" } }
]
Create Secrets first, then use their IDs. See Computer and Secrets.
Local bundle builder
helmr build and helmr deploy without --bundle use a dedicated Docker
Buildx builder. Its requirements, naming and lifecycle are described in
helmr build.