Open source · Apache-2.0 · v0.3.0

Ship AI coding tools
like software.

One policy repo becomes signed releases, rolled out ring by ring, gated by evals and live telemetry, and killed automatically when a guardrail measured at the gateway breaks (with halo-server's kill list on).

Works with Claude Code · Codex · Gemini CLI · Copilot CLI · Cursor CLI · OpenCode

~/acme-policy
halo init --org acme
create halos.yaml
next: halo explain --policy-dir .   (the full policy this expands to)
next: halo validate --policy-dir .
halo upgrade start claude-code 2.1.286  # needs HALO_REGISTRY + HALO_KEY, and ring3-ga already published as the baseline
Signing ring ring1-canary.x-claude-code-2.1.286-canary.next → version claude-code-2.1.286-x-claude-code-2.1.286-canary.next (digest sha256:534762a6afe095d3bfd4991ea507241f9a301c68dd2ba3257f2555ff8325670a, seq 1790874803)
ok published 127.0.0.1:5056/acme/halos vclaude-code-2.1.286 (sha256:8350dc789e85dfde1450643bc88f3818294f9fbf47b6c4389946644cca0d65ba); no ring pointer moved
ok published channel ring1-canary.x-claude-code-2.1.286-canary.next (sha256:534762a6afe095d3bfd4991ea507241f9a301c68dd2ba3257f2555ff8325670a): reached only once the experiment runs
create experiments/claude-code-2.1.286-canary.yaml
create profiles/claude-code-2.1.286.yaml
create rings/ring0-team.yaml
create rings/ring1-canary.yaml
create rings/ring2-early.yaml
create rings/ring3-ga.yaml
create rollouts/claude-code-2.1.286.yaml
next: start it: merge this, then `halo rollout advance claude-code-2.1.286 --reason ...` opens the first step's PR
next: after it completes, set halos.yaml tools.claude-code to 2.1.286 so new releases keep the pin
halo rollout simulate claude-code-2.1.286 --scenario regression
Simulating claude-code-2.1.286 (scenario regression, tick 1h, 1000 users/h, seed 1; PRs assumed merged at once)

AT      STEP        DECISION  WHY
T+0     -           advance   not started: propose step ring0-team
T+1h    ring0-team  hold      bake: baking, 23h left
T+1d    ring0-team  advance   all gates passed: propose step canary-5
T+1d1h  canary-5    hold      bake: baking, 11h left; samples: 950/50 of 200 per arm; guardrail:halo.tool.error_rate: inconclusive, upper...
T+1d4h  canary-5    rollback  primary failed: primary halo.tool.error_rate significantly worse (effect +0.09415, p=0.0000) (threshold not...

Outcome: rollback after 1d4h
Real output, recorded 2026-10-01 with halo a23f464 against a local registry (HALO_REGISTRY and HALO_KEY set, --plain-http omitted from the display, and ring3-ga already published as the rollback baseline).Transcript
  1. 1
    halo init --org acme

    Write policy: one git repo of YAML for CLI versions, models, permissions, MCP servers and rings.

  2. 2
    halo release publish

    A human merges; halo signs an immutable release into your OCI registry.

  3. 3
    halo rollout advance

    Rings point at releases; halod applies them on every machine. Each step is gated and reversible.

The problem

AI coding tools change weekly. Your org finds out last.

01

Every laptop runs a different agent.

CLI versions, models, permissions and MCP servers drift per machine, per team, per week.

02

Upgrades hit everyone at once.

A new CLI or model reaches 100% of engineers on day one, with no canary and no numbers.

03

Rollback is a Slack message.

When it regresses, someone notices days later and asks everyone to downgrade by hand.

How it works

From one PR to every laptop.

Your platform team owns one repo. Developers keep running their CLI and change nothing.

  1. 01

    Write policy

    A git repo of Halos YAML: CLI versions, models, permissions, MCP servers, rings and rollouts. halo init scaffolds it; CI validates, plans and evals every PR.

  2. 02

    Ship a signed release

    A human merges; halo release publish signs an immutable, content-addressed release into your OCI registry. Nothing moves until a ring points at it.

  3. 03

    Roll out by ring

    Rings point at releases and halod applies its ring's release on every machine. Each rollout step bakes, counts samples, checks its gates and opens a PR a human merges.

  4. 04

    Roll back on its own

    A gateway-measured breach trips the signed kill switch; gateways pick it up within ~10 s. Other breaches open a rollback PR.

The platform team owns a git policy repo. A pull request is validated, planned and evaluated in CI, a human merges, and halo publishes a signed immutable release. Rings point at releases; halod applies them on every machine and the gateway routes model traffic. Developers keep running their CLI as usual.

See it run

Real commands, real output. Recorded, not mocked.

Scaffold a policy repo in simple mode, then validate it.
localhost:18080 · Halos console (make demo)
Recorded on make demo: sign in as alice and kill the opus-5-5-canary experiment. Then open the github-mcp toggle, propose a ramp to 25% (a policy PR; a local branch in the demo), kill the toggle with a reason, and find both kills in the audit log. Kills take effect at the next kill-list poll: gateways every 10 s by default (2 s in the demo), halod every 60 s by default.

Recorded from the real binaries; the orange cursor ring is the only overlay. Scripts: record-media.sh · record-console.mjs

See it decide

Watch a model rollout advance, or roll itself back.

Six steps: a dark launch, a canary ramp and a 14-day holdout. Flip the scenario to inject a latency regression.

halo rollout simulate opus-5-5-upgrade

gateway alias opus -> treatment route of experiment opus-5-5-canary

  1. 1dark-launch
    dark-launch · bake 2d10% mirrored, none served
    • latency.p50_ms<=10%
    • latency.p95_ms<=15%
    • eval:opus-5-5
    pending
  2. 2canary-1
    canary · bake 6h1% on treatment
    • latency.p50_ms<=10%
    • latency.p95_ms<=15%
    • api.error_rate<=5%
    pending
  3. 3canary-5
    canary · bake 12h5% on treatment
    • latency.p50_ms<=10%
    • latency.p95_ms<=15%
    • api.error_rate<=5%
    pending
  4. 4canary-25
    canary · bake 1d25% on treatment
    • latency.p50_ms<=10%
    • latency.p95_ms<=15%
    • api.error_rate<=5%
    pending
  5. 5canary-50
    canary · bake 1d50% on treatment
    • latency.p50_ms<=10%
    • latency.p95_ms<=15%
    • api.error_rate<=5%
    • approval
    pending
  6. 6holdout
    holdout · bake 14d95% on treatment
    • latency.p50_ms<=10%
    • latency.p95_ms<=15%
    • api.error_rate<=5%
    • approval
    pending
T+0advance

Decisions from halo rollout plan|simulate opus-5-5-upgrade --policy-dir examples/acme-corp --output json, recorded 2026-10-01 (halo a23f464). Synthetic evidence, real state machine.Data

Security

Built for the people who say no.

Signed, immutable releases

ed25519-signed bundles and ring pointers in your OCI registry. halod refuses a foreign key and keeps the last good release.

Identity, not headers

Cohorts come from a verified JWT. Client x-halo-* headers are stripped at the gateway.

Guardrails that can't ship off

bypassPermissions and danger-full-access never render, in any ring or profile.

Humans own the irreversible

No auto-merge, no auto-promote. Rollback is the only automatic direction.

Enterprise controls: RBAC, SCIM 2.0, org units, cost budgets, SIEM audit export, workload and agent identity, HA on Postgres, vendor admin console sync.

Security model and threat model →

Quickstart

The whole stack, one command.

Console with a mock login, gateway, shadow, mock models, Grafana, and a seeded repo with a running experiment, rollout and toggle.

terminal
# needs Go and Docker
git clone https://github.com/dshakes/halos && cd halos
make demo        # prints the URLs; make demo-down removes it

# or start your own policy repo
make build && export PATH=$PWD/bin:$PATH
mkdir acme && cd acme && halo init --org acme && halo validate