Skip to content

Getting started with Helix

Helix turns Spiral’s real-time optimisation into a complete workforce-management system, shaped to your operation through configuration and code extensions. This guide takes you from nothing to a running Helix with your own first domain file. For the architecture behind it, see the Helix technical overview.

  • podman or Docker — Helix is delivered as a single container image and runs on either. Podman (rootless) is our default; Docker is equally supported, including Docker Desktop. With both installed, podman is chosen — set HELIX_CONTAINER_RUNTIME=docker to pick.
  • Access to the Honeywick container registry and the @helix/client-sdk npm package (we provide credentials).
  • A Spiral licence (SPIRAL_LICENCE) — the optimiser will not start without it.
  • Node.js — to build your client extension.

Helix ships registry-only: one container image carries everything the runtime needs (the Spiral optimiser, the CRUD/dispatch backend, the web apps) plus the host-side launcher, helixctl.

Terminal window
podman login registry.gitlab.com # or: docker login registry.gitlab.com
./helix/runtime/helixctl pull # latest core for your pinned SDK line

If you don’t yet have helixctl, fetch the bootstrap stub from the image:

Terminal window
podman run --rm --entrypoint cat \
registry.gitlab.com/honeywick-consulting/helix/helixcore:sdk-0.1 /app/helixctl-bootstrap \
> helix/runtime/helixctl && chmod +x helix/runtime/helixctl

The same command works with docker in place of podman. Every helixctl command below is runtime-agnostic — it uses whichever runtime it finds. Run helixctl check to see which one it resolved, and whether anything else is missing — including the host’s kernel, which must be Linux 4.18 or newer: a container brings its own userland but runs on the host’s kernel, and 4.18 is what the image’s node 24 needs. (Helix packs and unpacks its own archives inside the container rather than with the image’s GNU tar, whose current Ubuntu build needs Linux 5.6 — so 5.6 is not required.) It also reports the runtime’s image storage driver: on overlay, overlay2 or fuse-overlayfs every container shares the one image, while vfs (what a rootless daemon silently falls back to when no layering driver is available) makes every container a full copy of the image, so check flags it and says how to switch.

Your client repo pins a floating SDK tag (e.g. sdk-0.1) in helix/runtime/VERSION — so helixctl pull always brings the latest core built for your SDK line, not a fixed version.

You customise Helix in your own client repo, against the published @helix/client-sdk and the image — the helixCore source is not needed. From the repo root (the directory that holds helix/):

Terminal window
./helix/runtime/helixctl <CLIENT> start dev # e.g. ./helix/runtime/helixctl AA start dev

This runs the container as a single node, mounts your helix/, runs vite build --watch on your extension, and prints a URL. The loop:

  1. Edit your domain file (<CLIENT>.domain.json), clientConfig.tsx, or a component.
  2. The bundle rebuilds in ~1 second.
  3. Refresh the browser.

To ship: npm run build:client → publishHelixClient.sh <version> → helixctl import then start on the node → helixctl <CLIENT> start demo.

Most behaviour is data, not code — authored in your domain file. Reach for the code extension (clientConfig.tsx, backend hooks) only for what configuration can’t express.

The domain file (<CLIENT>.domain.json) is a stream of JSONC directives, read in order to build the reference data and rules for your operation:

DirectivePurpose
includePull in an environment variable or file. Domain and map files are referenced by bare filename, resolved relative to the client directory.
setGlobal settings — resolutions and the ignore lists (fields kept out of Spiral).
defineReference data — stopParams, resourceParams, projectParams, customerParams, contractParams. Each array entry is keyed by key into reference.define.<type>.
helixApplication-level Helix config (e.g. Contact-Centre search paths).

A minimal domain needs little more than a set block and one stop type:

// my.domain.json — a starting point
{"set":{ "atResolution":"5km", "hostClockResolution":"00:01:00" }},
{"define":{"stopParams":[
{ "key":"default",
"helix":{ "icon":"Car", "tag":"J" } // how the stop appears in the apps
}
]}},
{"define":{"resourceParams":[ { "key":"default" } ]}}

Each stopParams entry’s helix block drives the UI for that stop type — icon, tag, per-state colours, the add-stop menu, warning and option templates. Add more types with "clone":"default" to inherit and extend. The reference documents every field.

From here you grow the file — more stop, resource and customer types, then business rules and quality gates. The How-To guides cover the client extension and backend hooks in depth.