Auto-scaler: bare-metal backend
The bare-metal backend provisions agents as host child processes (child_process.spawn). Use it for workloads that cannot run in containers (GPU access, specialized hardware) or when container overhead is unacceptable. For fields shared across all backends, see Common configuration.
Bare-metal-specific fields
Section titled “Bare-metal-specific fields”Label-set-level fields:
binaryPath— Filesystem path to the agent binary. The scaler spawns this process for each job.image— Akici-agentcontainer image. It has two uses, described in Container jobs below.
Every bare-metal label set needs at least one of the two. A set with only
binaryPath spawns a host process, which is the classic bare-metal pool.
Container jobs
Section titled “Container jobs”A job may name its own container image with the container field. KiCI runs
such a job with its own Node build mounted read-only, so the image needs neither
Node nor git. A bare-metal agent is a plain host process and carries no such
build, so the label set’s image is where it comes from:
binaryPathandimage— The pool spawns the agent process as usual. When that agent takes acontainerjob, it starts the job’s container and copies the Node build out ofimageinto a named volume, which it then mounts into the job container. The copy runs once per agent image on that host and is reused afterwards.imageonly — The pool runs the job’s own image as the agent, with the same Node build mounted in. There is no host process. Use this for a pool that only ever runs container jobs. The agent runs inside the job’s image here, so that image must also shipgitandbash; the agent refuses to start without them.binaryPathonly — No Node build is available to inject, so acontainerjob runs on the image’s ownnode. That works for an image that ships one, such asnode:24-slim.
The host needs docker or podman for any of this. See Container jobs for the job-side contract.
Scaler-level field:
enforceCgroups— Whentrue, wrap each agent in a transientsystemd-run --user --scope --slice=kici-scalerwithCPUQuota=/MemoryMax=derived from the resolved resource limits. Default:false(advisory limits only). Linux-only; on macOS / Windows the flag silently no-ops with a startup warning. See cgroup enforcement.
Process management
Section titled “Process management”Processes are spawned in detached process groups ({ detached: true }) to enable clean killing of entire process trees. Environment variables are passed directly to the spawned process:
KICI_ORCHESTRATOR_URL— Orchestrator WebSocket URLKICI_AGENT_ID— Pre-generated agent ID for correlationKICI_LABELS— Comma-separated label setKICI_SCALER_MANAGED=1— Scaler-managed flagKICI_EXECUTION_MODE=bare-metal— Execution modeKICI_PORT=0— Random port assignmentKICI_RUNTIME_IMAGE— (set only when the label set declares animage) Where the agent materializes the KiCI runtime from when it nests a job container. A label set withbinaryPathalone leaves it unset, which is why acontainerjob on such a pool runs on the image’s ownnodeKICI_AGENT_TOKEN— (optional) Ephemeral auth token when auth is configuredKICI_BACKPRESSURE_MODE— (optional) Log backpressure mode from label set config- Host variables forwarded through the
KICI_AGENT_ENV_prefix - Any additional
enventries from the label set config, which win on a conflict
Agent lifecycle
Section titled “Agent lifecycle”All bare-metal agents are single-use: the agent process is spawned for one job, then killed after the job completes or the agent disconnects. Process group kill sequence: SIGTERM, wait 5s, SIGKILL.
cgroup enforcement
Section titled “cgroup enforcement”By default, bare-metal resource limits are advisory — they drive the cap math (per-scaler / global / machine-pool budgets) but no cgroup is created. Set enforceCgroups: true on the scaler entry to wrap each agent in a transient systemd-run --user --scope --slice=kici-scaler with CPUQuota= / MemoryMax= derived from the resolved limits. This is Linux-only; on macOS and Windows the flag silently no-ops with a startup warning. The requests/limits model is described in Common configuration → Resource limits.
enforceCgroups governs process mode only. A container-mode agent (image only) takes its ceilings from the container runtime instead — memory, CPU and a process cap, exactly as the container backend applies them.
Network access
Section titled “Network access”What isolates an agent depends on how its label set launches it.
- Process mode (
binaryPath) has no network isolation. The agent runs as a child process with full host filesystem and network access. AnetworkPolicyon such a label set is not enforced, and a startup warning names it. Use this mode in trusted environments only. - Container mode (
imageonly — see Container jobs) joins the agent container to the isolatedkici-agent-netnetwork and applies the same per-address nftables rules as the container backend: the RFC1918 and cloud-metadata drops, plus the label set’snetworkPolicy.
See Agent execution security for the isolation trade-offs across backends.
Remote orchestrator configuration (macOS / Windows)
Section titled “Remote orchestrator configuration (macOS / Windows)”When running a multi-orchestrator cluster, remote Mac or Windows machines need bare-metal scaler entries to advertise their capabilities to the cluster. Without scaler config, the remote orchestrator’s heartbeats will show empty capabilities, and the cluster coordinator won’t route jobs to it.
How it works
Section titled “How it works”- The remote orchestrator connects to the Platform relay as a peer in the cluster.
- On connection (and via periodic heartbeats), it advertises its scaler capacity — including the label sets it can handle and available concurrency.
- The cluster coordinator uses this advertised capacity to make informed routing decisions: when a job needs
runsOn: ['macos'], it checks which peers have matching labels with available capacity. - If no peer handles the required labels, the coordinator returns a clear error: “No orchestrator in cluster handles labels: macos”. If peers exist but are at capacity, it says: “Peers with matching labels exist but are at capacity”.
macOS example
Section titled “macOS example”# scalers.yaml on the Mac orchestratorversion: 1scalers: - name: macos-bare-metal type: bare-metal maxAgents: 2 labelSets: - labels: [macos, darwin, bare-metal] binaryPath: /Users/youruser/kici/agent/kici-agentWindows example
Section titled “Windows example”# scalers.yaml on the Windows orchestratorversion: 1scalers: - name: windows-bare-metal type: bare-metal maxAgents: 2 labelSets: - labels: [windows, bare-metal] binaryPath: C:\kici\agent\kici-agent.exeFor a non-Linux bare-metal pool, prefer declaring the structured platform: { os, arch } field. It is the canonical way to taint a Windows / macOS / ARM pool so unqualified Linux jobs are never routed to it, and it works even when the pool’s plain labels use a non-canonical name. See Automatic platform taint in the common config reference.
Key notes
Section titled “Key notes”-
Warm pool support: Bare-metal scalers accept a
warmPoolblock and keep its agents ready like every other backend (see Warm pool). Starting a bare-metal process takes seconds, so a warm pool saves little here — the defaultsize: 0is the right choice for most bare-metal pools. ThemaxAgentsfield controls maximum concurrency (how many simultaneous jobs can run). -
Intermittent availability: Remote orchestrators (especially developer laptops) may be intermittently available. When the machine is off or disconnected, jobs requiring its labels will fail with a clear error message (“No orchestrator in cluster handles labels: …”). This is expected behavior — the cluster coordinator handles it gracefully.
-
Capability advertisement is automatic: Once the scaler config is in place and the orchestrator is running, it automatically advertises its capabilities via heartbeats. No additional configuration is needed on the coordinator side.
-
Label matching: Jobs use
runsOnlabel sets (e.g.,runsOn: ['macos', 'arm64']). The coordinator matches these against thelabelSetsin each scaler’s config. All labels in the job’srunsOnmust be present in the scaler’s label set for a match.
Example
Section titled “Example”version: 1globalMaxAgents: 5
scalers: - name: gpu-machines type: bare-metal maxAgents: 3 labelSets: - labels: ['linux', 'gpu', 'cuda'] binaryPath: '/opt/kici/kici-agent' resources: memory: '16g' cpus: 8