Repository navigation
Releases: dshakes/halos
Release list
v0.5.2
Upgrade notes
- npm: the packages are
@dshakesnotbot/haloand@dshakesnotbot/halo-<os>-<arch>(npx @dshakesnotbot/halo quickstart). This is the first version on npm. - halo-server now rate-limits each signed-in user and service token on console/API routes (20/s, burst 100, 429 with
Retry-After). Automation that bursts harder should back off on 429 or raise-api-rate-limit/-api-rate-burst(0disables). halo doctor --output json(and the MCPdoctortool) can now report"status": "skip"for optional CLIs that are not installed; consumers that switch onok|warn|failmust accept it.- Binaries are built with go1.26.10 (12 reachable standard-library vulnerabilities fixed).
Added
- halo-server sends
Strict-Transport-Security(browsers honour it only over HTTPS); every Prometheus/Grafana alert carries arunbook_urlto the operations guide. - halo-server rate-limits each signed-in user and service token on console/API routes (
-api-rate-limit20/s,-api-rate-burst100; 429 withRetry-After;0disables). Fleet, gateway and SCIM traffic is not charged. halo --version(same output ashalo version).
Changed
halo doctor(and the MCPdoctortool and--output json) report an optional CLI that is not installed with the new check statusskipinstead ofok; it is informational, never a warning or failure, and does not affectok. JSON consumers that switch onstatus(ok,warn,fail) must acceptskip.halo onboard local|installcollapse a harness's adapter warnings (more than two) into one count line,--verboselists them all and--output jsonis unchanged;halo planprints a per-file summary first and(none)for a first release's empty side.- The npm packages publish as
@dshakesnotbot/halo(and@dshakesnotbot/halo-<os>-<arch>): the@dshakesnpm scope does not exist, so v0.5.1 never reached npm. Usenpx @dshakesnotbot/halo quickstart. - Every command that writes only with
--applyalso accepts--dry-run(its default), matchingquickstart,enable,ejectandmodel switch; the two flags are mutually exclusive.
Fixed
halo onboard companynames every missing required flag in one error (--gateway,--issuer,--registry,--policy-repo,--portal-url) instead of failing one unnamed field per run; README points at--help.halo quickstartclones the release tag that matches the binary (cached per version), notmain, and says when the Docker daemon is not running.- A
kind: Halosdocument outsidehalos.yamland an unknownhalo model switchalias now say how to fix them.
Security
- Build toolchain go1.26.6 -> go1.26.10: fixes 12 standard-library vulnerabilities govulncheck reports as reachable (net/http, crypto/tls, crypto/x509, net/textproto, os). Every binary and image is rebuilt with it.
v0.5.1
Upgrade notes
- Releases need an
NPM_TOKENsecret: the release gate fails without it (RELEASING.md).
Added
npx @dshakes/halo: the release workflow's newnpmjob repackages the verified release archives as@dshakes/halo(a Node launcher) and one@dshakes/halo-<os>-<arch>package per platform, published with npm provenance. CI builds the packages from the snapshot release and runshalo versionthrough them. Releases now need anNPM_TOKENsecret (RELEASING.md).
Documentation
- README capability table, docs site and diagrams cover v0.5.0: admin credentials, scoped RBAC, external signing keys and FIPS, the OpenAPI document, promotion PRs, notifiers, the Helm ops kit, fleet sizing and compliance mapping. New guide for the Terraform and OpenTofu provider (
dshakes/halos, separate repository). The release-pipeline, trust-zones, security-boundaries and architecture diagrams show key URIs (file, AWS KMS, external signer) instead of a fixed ed25519 key.
v0.5.0
Upgrade notes
- Rollout-step approvals recorded before the upgrade stop counting (the approval key now includes the step's and the experiment's rings): give them again.
- Upgrade every
halo-serverreplica, thehaloCLI,halo mcp serveandhalo controller runbefore adding the firstidentity.roleBindingsentry. halo-servertakes an exclusive lock on--data-dir: a second process on the same directory refuses to start.- FIPS builds refuse webhook, audit-export, cloud-webhook and telemetry-salt secrets under 14 bytes at startup; standard builds warn.
- When shadow pair keys are configured, unencrypted pair rows are refused; pass
--allow-plaintext-pairs(MCPallow_plaintext_pairs) once to migrate a store sealed part way through. POST /api/v1/service-tokensrejects unknown, repeated or case-variant keys with 400.
Added
- Fleet-scale load test for
halo-server(make load-fleet,test/load/fleet,.github/workflows/load-fleet.yml): simulatedhaloddevices enroll, poll the signed kill list, fetch rings and post reports against file and Postgres modes, with latency percentiles, error rates and server CPU and memory; a manual or monthly 10,000-device workflow with a Postgres service container. Sizing guidance with the measured numbers is in the Operations guide ("Sizing halo-server"): up to 10,000 devices per replica, one replica more than needed, memory and Postgres storage per device. - Scoped role bindings for halo-server (ADR-0037):
identity.roleBindingsgrant a console role to groups and users over a scope of rings and units (a unit includes its descendants; rings and units intersect). Every route declares how its resource is scoped (routeScopes); a scoped grant covers a resource only when it lies wholly inside the scope, derived from policy and server records, never the request. Lists (fleet, devices, releases, experiments, rollouts, toggles, kill list, freeze, requests, service tokens) are filtered, unknown resources deny scoped callers with the same 403 as out of scope, and the audit log, budgets, policy and a few admin routes stay global only. Service tokens take ascopewithin their creator's grants (halo service-token create --scope-ring/--scope-unit); scoped approvals count toward ADR-0030 quorums only while their scope covers the change; scoped actions are audited withauthzRole/authzScope/authzBinding./api/v1/meand/capabilitiesreturnbindings;halo whoamishows console roles and scopes.identity.roles,adminGroupsand existing tokens are unchanged. Upgrade every halo-server replica, thehaloCLI andhalo mcp servebefore adding the first binding: older binaries refuse such policies and scoped tokens. - Controller notifications beyond Slack: Microsoft Teams (Workflows/Power Automate webhook, Adaptive Card; the retired Office 365 connector URLs are refused), PagerDuty (Events API v2:
triggerwith a stablededup_keyper rollout or experiment for rollbacks, kill-switch holds and paused rollouts,resolvewhen a human merges or advances, or the kill is lifted; informational events never page) and the signed generic webhook, which can now receive all-clears. Per-sink event filters (--notify-<sink>-events,server.controller.notify.<sink>.events), bounded retries with backoff on 429 and 5xx that honourRetry-After(never on other 4xx),halo_controller_notifications_total{sink,result}and aHaloControllerNotifyFailingalert. Secrets stay in files and Secrets (--notify-teams-url-file,--notify-pagerduty-routing-key-file). Slack output is unchanged. See the notifications guide. - PagerDuty and webhook all-clears also fire when an experiment or rollout is deleted from policy while an incident is open, so a deleted name no longer stays paged forever.
- GitLab merge requests and a GitHub REST path for every change Halos proposes (promotion, rollback, rollout, controller, console proposals,
halo upgrade, MCPpropose_*):--forge gh|github|gitlaband--forge-base-urlonhalo exp promote,halo rollout advance|pause|rollback,halo controller run,halo upgrade check|watch,halo mcp serveandhalo-server; portal configpolicyForge/policyForgeBaseURL; Helmserver.controller.policyRepo.forge,forgeBaseURL,gitlabToken. GitLab uses REST v4 withGITLAB_TOKEN(apiscope, nested group paths, self-managed hosts), GitHub usesGITHUB_TOKEN/GH_TOKEN(GitHub Enterprise Server via base URL); both are idempotent by marker and send no merge, auto-merge or approve call (a human still merges). The default is unchanged: with no flag the forge is autodetected from the origin remote (gitlabif the host names it, else theghCLI). Tested againsthttptestfakes only, not a real GitLab or GHES (UNVERIFIED).halo doctorreportsGITLAB_TOKENinstead ofghwhen the policy repo's origin is a GitLab host,--forge-base-urlunder a URL prefix (https://host/gitlab/api/v4) strips that prefix from the remote project path, and the MCPpropose_*tools now embed a change marker and return the existing open PR/MR instead of opening a second one (merged or closed ones never block a new proposal). See the new GitLab and GitHub Enterprise Server guide. - An OpenAPI 3.1 description of halo-server's HTTP API:
api/openapi.yaml, published at/halos/openapi.yamlon the docs site and served unauthenticated and read-only atGET /api/v1/openapi.yaml. It lists every route with auth schemes, request and response schemas derived from the Go types, status codes, the RBAC permission each route needs (x-halo-permission, from the route table) and a stability marker (x-halo-stability,stableon every operation under the existing/api/v1promise). Generated bymake openapi(also run bymake docs-gen);go test ./internal/server -run OpenAPIfails when a route is registered without documentation, a permission differs from the route table, an example stops decoding into its Go type, the file is not valid OpenAPI 3.1, or a handler answers a status or body the file does not describe. - Signing keys in AWS KMS or an HSM (ADR-0038):
--key-uri file://|awskms://|exec://on every signing command (HALO_KEY_URI),halo-server --killswitch-key-uri, andhalo keys public --key-urito export the public key and key id.awskms://signs withECC_NIST_P256orECC_NIST_EDWARDS25519keys through the KMS API (no new dependency);exec://runs an external signer speaking the documentedhalos.signer.v1JSON protocol (PKCS#11, GCP, Azure, Vault adapters). Every remote signature is verified against the key before use. Guide: Signing keys in a KMS or HSM. - ECDSA P-256 (
ecdsa-p256-sha256) release, pointer, attestation and kill-list signatures, verified byhalod,halo-proxy,halo-kong, the portal and exports next to ed25519. Each signature is checked under its recorded algorithm only; unknown algorithms are refused. Manifests and kill envelopes record the signing key id.--co-key-urico-signs with the old key of the other algorithm while rotating, sohalods that trust only it (including older releases ofhalod) keep verifying. Releases signed before this change verify unchanged. - FIPS 140-3 builds:
*_fips_*archives and-fipsimage tags built withGOFIPS140=v1.0.0(Go Cryptographic Module),make build-fips/make test-fips, and a CI job running the signing, verification, TLS, session and encryption packages underGODEBUG=fips140=only. Guide: FIPS 140-3 builds. Halos itself holds no FIPS certificate. - Service-account API tokens for halo-server (ADR-0036):
halo service-token create|list|revokeand/api/v1/service-tokens. A token (halo_sat_+ 80 hex digits, for secret scanners) holds one role no stronger than its creator's, always expires (--service-token-max-ttl, default 90 days), is stored only as SHA-256, revocable alone on every replica, and audited asservice-account:<name>:<id>. Only an interactive admin session can create one. The CLI's server-backed commands andhalo mcp serve --serverreadHALO_TOKEN, which wins overHALO_SESSION. Postgres: migration 4 addshalo_service_tokens(runhalo-server migrate);halo-server importcopies a data dir's tokens. - Break-glass local admin:
halo-server breakglass-hash(PBKDF2-HMAC-SHA256),--breakglass-hash-file,--breakglass-user,--breakglass-enabled(Helmserver.breakGlass). Sealed unless enabled;POST /auth/breakglassgives a fixed 30-minute admin session; every login, failure and request is a high-severity audit entry, and a login whose audit entry cannot be written is refused. SIEM export marks high-severity entries (OCSF severity High, syslog critical). Runbook: Admin access guide. - Console session policy:
--session-ttl(default 12h, unchanged) and a sliding--session-idle-timeout(default 1h; 0 turns it off), Helmserver.session. - Policy
identity.groupsOverage(denydefault, orgraphto read groups from Microsoft Graph,--entra-graph-urlfor national clouds) andidentity.requireAcr/identity.requireAmrfor the console login. Okta and Entra ID setup guides. - Helm chart operations kit.
monitoring.prometheusRule.enabledrenders aPrometheusRule(only when the cluster serves the CRD) fromdeploy/helm/halos/files/prometheus-rules.yaml, now the single source of the alert list: the Grafana alerting provisioning (deploy/observability/grafana/provisioning/alerting/halos-alerts.yaml) is generated from it bymake obs-assets, andgo test ./internal/obsassetsfails when the rules, the generated file, the dashboards or the operations guide drift, or when a rule or panel names a metric no b...
v0.4.1
Fixed
- Codex: egress allowlists now work. Halos rendered
[experimental_network.domains], which codex 0.99.0 ignores (its proxy then refused every host), plus a managednetwork_access = falsethat outranked the user's opt-in, so no allowlisted host was reachable from a sandboxed command. Releases now render theallowed_domains/denied_domainslists (read by 0.99.0 and 0.166.0) and leavenetwork_accessto the user under an allowlist. Relativesandbox.protectedPathsare no longer rendered as/**/globs, which codex 0.166.0 refuses for every sandboxed command; they warn instead (Codex has no workspace-relativedeny_read), and pins before 0.122.0 warn thatdeny_readis ignored. A newuat-clischeck runs the rendered files through the realcodex sandbox linux. Re-publish releases with Codex egress or protected paths.
Changed
- Release warnings name the remaining local overrides found by testing the current CLIs (gemini-cli 0.66.0, opencode 1.19.5, codex 0.166.0, whose managed keys all matched): any
SANDBOXenvironment variable skips Gemini CLI's sandbox and its default Seatbelt profile does not restrict network;OPENCODE_PERMISSIONor an agent'spermission.webfetchin user config overrides OpenCode's managedwebfetch. halo doctorwarns whenSANDBOXis set, which makes Gemini CLI skip its sandbox; the egress and sandbox guide lists the Gemini and OpenCode overrides and Gemini CLI's root-owned system settings requirement.
v0.4.0
Added
-
Cloud agent governance (ADR-0034) for the Claude Code GitHub Action and cloud sessions, Codex cloud, the Copilot cloud agent, Cursor cloud agents and the Gemini CLI GitHub Action.
halos.yamlcloudAgentssets per vendor: enabled, allowed repositories and models,maxConcurrentper user and group, budgets (ADR-0017 fields), sandbox network mode,identity: workload(gateway credential through the ADR-0023 exchange instead of a repository secret) andonOverLimit: alert|pause;identitiesmap GitHub logins to Halos users.halo cloud-agents renderwrites SHA-pinned,permissions: {}workflows (halos-claude.yml,halos-gemini.yml,copilot-setup-steps.yml) checked against the never-bypass invariant, and releases carry them signed inmanifest.jsoncloudAgents(never written byhalod). halo-server receives GitHubworkflow_runwebhooks (POST /api/v1/cloud-agents/webhooks/github,X-Hub-Signature-256HMAC,--cloud-webhook-secret-file), stores tasks in the audit chain (cloudagent.task; finished ones also as session ledger records with sourcecloudagents), raises alerts (halo_server_cloud_agent_alerts_total) and, withonOverLimit: pause, reversibly disables the workflow or archives the Cursor agent.GET /api/v1/cloud-agents(read) andhalo cloud-agents listshow tasks and alerts;POST /api/v1/cloud-agents/kill(operate) andhalo cloud-agents kill --scope user|ring|experimentcancel a user's, ring's or experiment's tasks through the GitHub and Cursor APIs, dry run by default, reason required, audited before any call. An experiment kill reports its users' active cloud tasks.halo admin plan|exportadd the console steps for Claude cloud sessions, Codex cloud, Cursor and Copilot repository access, and read each repository's Copilot cloud agent firewall. -
Egress and sandbox policy (ADR-0032). A profile
sandboxblock (mode=permissions.sandbox,writableRoots,protectedPaths) and an extendedegressblock (default: allow|deny,deniedDomains,presets: package-registries|vcs|model-providersbesideallowedDomains; deny wins,*.xis subdomains only).deniedDomainsandprotectedPathsunion downextendsand across units; a unit switching egress to allow, dropping a denied domain or protected path, or adding a writable root is a loosening. Validation rejects bypass-shaped combinations: a writable root of/,~or a glob, over a protected path or underread-only, and modeoffwith egress or path rules. Rendered natively: Claude Codesandbox.networkallowed/denied domains withallowManagedDomainsOnly,sandbox.filesystemandRead/Edit/WebFetch(domain:)deny rules; Codexrequirements.toml[experimental_network]and[permissions.filesystem] deny_read,writable_roots; Copilot CLI managedsandboxwithuserPolicy.network/filesystem(pins >= 1.0.93); Gemini CLItools.sandbox(macOS); OpenCodewebfetchand path deny rules; Cursor warns (per-user files only). Newsandboxandegresscapabilities inhalo harnesses.halodreports per-user settings that weaken the managed posture (~/.codex/config.tomldanger-full-access,~/.gemini/settings.jsontools.sandbox: false,~/.cursor/sandbox.jsoninsecure_none) asposture:drift with a fix;halo doctorshows each installed harness's effective sandbox and egress posture, including environment overrides.halo egress test <url> --ring R [--unit U]names the deciding rule and the mechanism each harness enforces it with (exit 1 on deny). Guide: Egress and sandbox. Existing allowlist profiles now also render Codex[experimental_network]withnetwork_access = false, andpermissions.sandboxnow renders Gemini CLItools.sandboxon macOS. -
Gateway data-loss prevention and prompt-injection defense (ADR-0033). Policy
gateway.dlpmakes halo-proxy and halo-kong scan every model-call request (user text, tool results, files; Anthropic, Bedrock, OpenAI Responses, Gemini) with twelve high-precision secret detectors (AWS, GitHub, GitLab, Slack token and webhook, Stripe, Anthropic, OpenAI, JWT, PEM private keys, GCP service-account keys, key-name plus high-entropy) and org RE2 patterns, andmonitor,redact([REDACTED:<detector>:<hash8>], the JSON otherwise byte-for-byte unchanged, also in the shadow mirror) orblock(HTTP 400 in the caller's wire format). Tool results are scored for prompt-injection signals (instruction overrides, fake turns, invisible Unicode, HTML-comment imperatives, exfiltration URLs, permission-bypass tokens) andmonitored orblocked above a threshold; model content is never modified.rulesset the actions per ring and org unit. halo-proxy also watches streamed responses for echoed secrets (monitor only, bytes unchanged). Bounded bymaxScanBytesand a 250 ms budget (200 KB request: about 5 ms p99). Findings never carry the secret:halo_proxy_dlp_*andhalo_kong_dlp_findings_totalmetrics, adlprequest-log attribute, audit entriesdlp.secret/dlp.injectionthrough the newPOST /api/v1/gateway/dlp/events(--gateway-dlp-token-file; halo-proxydlp.url, halo-kongdlp_url) so the SIEM exporter carries them, anddlpSecrets/dlpInjectionsin the session ledger.halo dlp test <file>dry-runs the scan locally against a policy, ring and unit. -
Policy copilot (ADR-0035): the model proposes, Halos verifies, a human approves.
halo copilot draft "<intent>"has a model draft whole policy files and loops on Halos' own checks (bypass-token scan over every written byte, validation, a release plan per ring, the impact preview) until they pass or a hard ceiling (--max-iterations,--max-tokens,--max-cost-usd) stops it;--applycommits a passing draft to a new localhalos/copilot-*branch and never pushes.halo copilot experiment "<hypothesis>"takes the arms and metrics from the model and the baseline, sd and MDE from the human;stats.SampleSizecomputes the per-arm sample size and duration, and Halos renders astatus: draftexperiment.halo copilot explain release|diff|experimentrejects any point that does not cite a Halos fact key.halo copilot nextsuggests next steps from verdicts, eval scorecards, ROI and budgets, each with evidence and the exact command. Model replies are validated against a Go-side schema and retried; each call logs token counts only (prompts with--debug); the default model isclaude-opus-5-5through the policy's gateway with the eval judge's client (eval.HTTPLLMnow reports token usage).--offlineuses deterministic templates. MCP:copilot_draft,copilot_experiment(dry run by default; commits need--allow-writesand a reason),copilot_explain,copilot_nextand thecopilotprompt; the calling agent is the model, so they never call a provider. -
Console: a Rollouts page (step timeline, per-gate status, bake countdown, halt and rollback events, ring-to-release pointers) and a Cost & budgets page (spend against limit per scope, window, warn/enforce mode and
alertAtthresholds); Fleet gains per-ring compliance, a drift count per device and an "in sync" filter; Device lists the drifted settings; Overview links to both new pages. Read-only; no server API changes. -
Copilot CLI lines of code in
halo.loc.github.copilot.code.lines_addedandgithub.copilot.code.lines_removedare mapped tohalo.locwith atypeattribute (added/removed), like Claude Code's lines-of-code metric. Codex's native lines metric stays unmapped (vendor metric name not verified). -
Randomized delivery ROI (ADR-0027):
halo roi ingest github|fileimports pull-request outcomes (cycle time, review rounds, lines changed, a revert/hotfix change-failure proxy) read-only from GitHub GraphQL or a generic CSV/JSONL export into ClickHousehalo_delivery_prsor a local JSONL store.halo roi report --experiment Xmaps authors to Halos subjects with an identity roster, recovers each developer's variant with the fleet's own assignment, and estimates per-arm effects with developer-clustered bootstrap CIs, an SRM check, novelty time slices, an honestinsufficient_powerstate and cost per merged PR;--ringscomparisons are labelled observational. The MCP toolroi_reportis read-only, and promotion PRs (halo exp promote --roi-identities,propose_promotion) carry an informational ROI section that never affects the verdict.internal/statsgainsClusterBootstrapDiffandSRM. -
Agent session ledger (ADR-0031): one vendor-neutral, identity-linked record per agent session (who and the delegated agent chain, harness and version, release/ring/experiment variant, models, MCP servers and tools, hashed or dropped repos and files, tokens, cost, start/end, outcome, evidence sources and confidence).
halo sessions importreads halo-proxy GenAI spans and Claude Code OTel events from ClickHouse, the Anthropic Compliance API, the GitHub enterprise audit log and JSONL; records are redacted fail-closed (no field can hold prompt text; paths dropped or HMAC-hashed) and stored by halo-server in the hash-chained audit log (session.record), so they are tamper-evident and reach the SIEM through the ADR-0016 exporter. Identity is joined server-side from the SCIM directory only.halo sessions list|show|export,GET /api/v1/sessions[/{id}](audit role),POST /api/v1/sessions(admin), the read-only MCP toolsession_ledger, and the ClickHouse tablehalo_session_ledger(--write-clickhouse). -
Change governance (ADR-0030).
halos.yamlgovernance.freezesdecla...
v0.3.0
Added
- Vendor admin sync (ADR-0025):
halos.yamlvendorAdmindrives the vendors' cloud admin consoles from the GA ring's profile.halo admin plandiffs Gemini Code Assist (logging, release channel) and GitHub Copilot (content exclusion, CLI policy) against the live consoles and exits 2 on drift.halo admin applywrites the settings that have a public API, after confirmation, and never loosens a control.halo admin exportwrites Claude server-managed settings, Codex requirements and config, and a Cursor model list, with manual steps for consoles that have no write API. The MCP tooladmin_planis read-only. - Telemetry: the collector normalises Copilot CLI metrics into
halo.*(github.copilot.tool.call.countbecomeshalo.tool.calls; itsgen_ai.client.*metrics are stampedhalo.harness=copilot-clibyservice.name, where they used to be mislabelled as Gemini CLI). Cursor CLI and OpenCode emit no usable OTel and are documented as unmapped, with halo-proxygen_ai.*metrics as their per-request source. - README, landing page, docs and diagrams cover all six harnesses and the enterprise controls.
- High-availability halo-server on Postgres (ADR-0021):
--database-url-fileruns several replicas behind one load balancer, with advisory-lock migrations and a fenced leader lease for the rollout controller. Gateway budget counters (atomic batch dedupe), the workload-token replay cache, the SCIM directory (deprovisioning holds on every replica), SIEM export cursors (one forwarder per sink under a lease), pending-request uniqueness and verdicts are shared across replicas. Failed-auth limiters stay per replica (up to N× the limit).halo audit verify --database-url-file; Helmserver.gatewayTokensfor per-capability gateway tokens. - Agent identity and delegation (ADR-0024):
POST /api/v1/token/exchangewithactor_idmints short-lived delegated agent credentials (RFC 8693actchain, root principal as subject, down-only model scope, depth/cycle/TTL limits, audited). halo-proxy and halo-kong route them as the root, enforceagents.*per call and stampx-halo-actor.agents.remoteis an A2A allowlist with pinned AgentCard digests, checked byhalo agents verifyandPOST /api/v1/agents/egress. - Multi-turn agent shadowing (ADR-0014, amends ADR-0004). A shadow experiment with
turns: allmirrors every step of a sampled session, not only the first turn. Each step replays the real prefix (developer prompt, production's actions, real tool results and terminal output) to both arms, and nothing either arm returns is executed.halo eval onlinereads each step without a judge across Anthropic, Bedrock (invoke and Converse), OpenAI Responses and Gemini:- tool and argument agreement with the control;
- fidelity to the action production actually took, recovered from the session's next mirrored step and prefix-checked so subagent and compacted conversations are skipped (the control's fidelity is the measured noise floor);
- invalid tool calls (a tool the request did not offer, or a missing required argument);
- premature stops;
- divergence rate and first divergent step;
- agreement by depth.
halo.eval.agent.fidelityandhalo.eval.agent.invalid_call_rateare registered metrics, one sample per hashed session and arm, usable as shadow-experiment guardrails inhalo exp analyzeand the controller. Shadow evidence still never promotes.stats.ClusterBootstrapMean/PairedClusterBootstrap.- Session-to-eval harvesting (ADR-0020).
halo eval harvestturns shadow steps into redacted step-level regression cases. It selects steps where the candidate diverged, made an invalid call, stopped early or lost on judge score, plus a depth-stratified sample. Cases are written 0600 with hashed sessions; redaction is mandatory and fail-closed.halo eval steps runreplays them against a model route and grades the next action with session-clustered CIs. MCP:eval_harvest(preview without--allow-writes). - Role-based access control for halo-server (ADR-0015).
identity.rolesmaps IdP groups toviewer,operator,approver,auditorandadmin.adminGroupsstill grants admin.- Every console and API route is enforced from one deny-by-default permission table, and every denial is audited as
authz.deny. /api/v1/meand/api/v1/capabilitiesreport the caller's roles and permissions, and the console hides actions accordingly. New--dev-insecure-rolesfor demos.
- Audit export to SIEM (ADR-0016).
--audit-configforwards every audit entry at least once and in order to Splunk HEC, an HMAC-signed HTTPS webhook (Datadog-ready) or RFC 5424 syslog over TLS.- Formats are native (hash-verifiable) or OCSF API Activity.
- Each sink has its own cursor and backoff, and chain checks halt export on tampering. Metrics:
halo_server_audit_forward_*. - The audit log rotates by size or age into segments the hash chain runs across.
GET /api/v1/auditstreams instead of loading the log into memory, andhalo audit verify --data-dirchecks every segment. Helm:server.audit.
- Org hierarchy (ADR-0018).
- The new
Unitkind covers business units and teams with delegated owners, member selectors and overlays. - Enforced security fields can only be tightened, and
halo validatereports each loosening with its field path. - New
halo units codeowners, andhalo explain --user, which shows the effective config with the layer that set each field. - Unit files ship inside the signed release, and
halodapplies them.
- The new
- Cost budgets and quotas at the gateway (ADR-0017).
- Optional
gateway.pricingandgateway.budgets: user, group, ring or org scope; day, week, month or rolling window; USD and/or tokens;warnorenforce, withalertAt. - halo-proxy meters streamed usage and answers 429 with
Retry-Afterin each CLI's wire format. - halo-server holds the counters (
POST /api/v1/gateway/budget/{check,usage}, andGET /api/v1/budgets, which needs the read role).budgets.failModeisopenorclosed. - New
halo budget status, andhalo_proxy_budget_*metrics.
- Optional
- SCIM 2.0 provisioning (ADR-0022).
- halo-server serves
/scim/v2for Okta and Entra ID. - Deactivating, deleting or renaming a user immediately revokes their sessions, device tokens and pending enrollments, audited as actor
scim. --scim-authoritativemakes directory groups decide rings, toggles and roles.- halo-proxy
directory.urland halo-kongdirectory_urlmake the directory authoritative at the gateway too; deprovisioned users get default routing.
- halo-server serves
- Workload identity federation for CI and cloud agents (ADR-0023).
- New
workloadspolicy section. - halo-server
POST /api/v1/token/exchange(RFC 8693) trades a GitHub Actions, GitLab, Buildkite, CircleCI or cloud OIDC token for an ed25519-signed gateway credential that lasts at most 1h. Exchanges are audited, and a token'sjticannot be replayed. - halo-proxy reads
identity.workloadPubkeyFileand halo-kong readsworkload_pubkeyto accept these credentials. - New
halo ci tokenandhalo ci config. Workloads are excluded from experiments by default. - Examples in
examples/ci-agents.
- New
- Agent extension governance. Profiles can govern
extensions.{marketplaces,blockedMarketplaces,plugins,skills,subagents,outputStyle,statusLine}andlogin.- These render only where the vendor documents a key (Claude Code, Codex, Gemini CLI, Copilot CLI) and warn in the release manifest everywhere else.
mcp.deniednow renders for Gemini (mcp.excluded) and Copilot (deniedMcpServers). For Copilot,mcp.managedOnlywith no servers rendersallowedMcpServers: [].- The harness matrix gains an
extensionscapability.
- OpenTelemetry GenAI semantic conventions (semconv v1.40.0, Development stability).
- halo-proxy exports
gen_ai.client.token.usage,gen_ai.client.operation.durationandgen_ai.server.time_to_first_tokenalongside the unchangedhalo.gateway.*series, on by default. - Opt-in
telemetry.genai.tracessends one content-freechat {model}span per call. halo telemetry collector-configgains atraces/gatewaypipeline.
- halo-proxy exports
Security
- Bump golang.org/x/text to v0.41.0 (GO-2026-6629, GO-2026-5970, reachable via the Postgres store); halo-server closes its data-dir files when startup fails part way.
- Workload token exchange matches a rule before recording the subject token's
jti, so tokens anyone can mint from a shared CI issuer can no longer fill the replay cache and refuse every tenant;workload.token.deniedis audited at most once per issuer and client IP per minute, the rest counted inhalo_server_workload_denied_audit_suppressed_total. - A login right after a session revocation is stamped a whole second past the revocation epoch, so credentials delegated from it are no longer refused as revoked; sessions issued before the revocation stay dead.
- A workload rule removed by a refused policy stays in place as a deny tombstone, so its tokens never fall through to a broader rule below it.
halo adminvendor API clients never follow redirects (net/http would resendAuthorizationover http or to a subdomain); a 3xx is an error.- Units can no longer set any
GIT_*env key except commit identity, norCC,CXX,CGO_*, Go module sources (GOPROXY, `GO...
v0.2.1
Fixed
- halo-server's JSON request bodies (experiment status, kill switch, portal enrollment, access requests, toggles) now reject any bytes after the JSON value. The decoder stopped at the end of the first value, so a body larger than the size cap was accepted whenever its JSON ended before the cap. Found by the nightly
FuzzAPIJSONDecodersrun; the crasher is kept as a regression seed.
v0.2.0
Added
- Your own API gateway (
Gateway.engine: external, simple modegatewayEngine: external,halo onboard local --gateway-engine external,halo onboard company --gateway-kind external): for a company whose model traffic already goes through its own gateway (Kong, an auth gateway, an orchestrator, Bedrock). Halos runs no data plane there, so Claude Code, Codex and Gemini CLI are rendered with each alias's provider model id instead of the alias, routes need no Halos upstream (simple mode generates none, so a provider's Halos-side requirements such as a Vertex project do not apply), Claude Code's built-in fallback tiers are pinned to the default model whenmodels.enforceis set (nothing behind an external gateway rejects an unlisted model), and validation warns that the gateway-side posture and version gates, kill switch and model-axis experiment metrics do not apply.halo onboard companynow also recordskongin the generated policy.
Changed
- The nightly soak's p99 gate is armed:
test/load/baseline.jsonis the result of a green 60-minute run onubuntu-latest(run 37253557695: 0 errors at ~1245 rps, p99 53.4 ms, all SSE streams intact, no heap or goroutine growth).
Fixed
- Claude Code: a profile may set
ANTHROPIC_CUSTOM_HEADERS(the oneANTHROPIC_*variable the reserved-env guardrail now allows), and those headers (e.g. a tenant or routing header a company gateway requires) are no longer overwritten by Halos'sx-halo-ring/x-halo-releaseheaders: they are kept, with Halos's appended, and CR orx-halo-*lines in them are rejected. - The
.halos-backupof a replaced managed file keeps the original's permissions; it was root-only, so a manual restore left Claude Code unable to read the file. halo onboard localstops with explicit next steps (compare in a scratch root, deliver through MDM, or point at your own gateway) when a managed file on the machine was not written by Halos.halo onboard install --applyreplaced a managed config file it had not written (for example a Claude Codemanaged-settings.jsonan MDM had pushed), keeping only a.halos-backupand previewing it as a plainupdate. Such a file is now planned asforeignand--applyrefuses and writes nothing unless--replace-existingis set; the MCPlocal_installtool never replaces one. Halos records the hash of each file it writes at<file>.halos-sha256to tell its own files from rewritten ones, on every install (an earlier.halos-backupdoes not count as ownership), and a second replacement keeps the earlier backup and writes a timestamped one. A file with no hash is never adopted silently, even one that matches the render (an MDM push can match): files installed byhalo0.1.1 or earlier report as foreign on the next change; confirm they are yours and re-run with--replace-existing, which also records the hash of matching files.- The nightly soak's load tool minted a one-hour JWT for a 60-minute soak, so the first scheduled run (3 Oct) ended in 401s at t+58 m with every other gate green; the token now outlives the run.
- CI's released-
halojobs validateexamples/acme-corpagain; thetest/ci/policy-v1alpha1fixture is gone.
v0.1.1
Fixed
- Homebrew and Scoop publishing. GoReleaser accepts a tap or bucket token only as
{{ .Env.NAME }}; theindex .Envform passedgoreleaser checkand every prerelease (which skip the tap and bucket) and failed on the first final tag.v0.1.0's release job stopped at that step: its GitHub release assets, signed checksums and0.1.0images were published, but it has no Homebrew formula, no Scoop manifest and no provenance attestation.v0.1.1is otherwise identical and is the release the installers and docs point at.
v0.1.0
The first general-availability release. Everything in this section and in the two release candidates ships here. Verified in CI on every change, and run end to end before this tag: Linux, macOS and Windows builds; the installers, halod as a launchd daemon and as a Windows SYSTEM scheduled task; the GitHub Action, the GitLab template and the dev container Feature; Helm on a kind cluster with pod-kill, rolling-upgrade and eviction fault injection under load; halo-kong in open-source Kong 3.9.3; the real pinned Claude Code, Codex, Gemini CLI and Copilot CLI driven headlessly through halo-proxy; halo-proxy against the real Anthropic and OpenAI APIs nightly; a headless browser click-through of every console page.
GA scope. Linux and macOS hosts, dev containers, the four CLIs' managed configuration, halo-proxy with Anthropic and OpenAI upstreams, and halo-kong on open-source Kong are GA. Windows hosts, MDM-delivered laptops (Jamf, Kandji, Intune), Codespaces and Coder, the Codex and Copilot CLI onboarding entry points, and the Bedrock, Vertex and Azure OpenAI upstreams are supported but not yet field-verified: each is built, unit- and fixture-tested, and exercised in CI as far as a hosted runner can, but has not run against a real tenant, device fleet or cloud account. See Known gaps.
Added
- Device posture gate (
Ring.posture: off|warn|enforce, defaultwarn):halo-proxyandhalo-kongaskhalo-server(GET /api/v1/gateway/posture, gateway token, rate-limited) whether the calling device'shalodreported it stale, drifted or off its ring's release, and refuse model calls inenforce. Verdicts are cached (1 m) and the last verdict serves for a 15 m grace window whenhalo-serveris unreachable, after which requests pass and are countedoutcome="unknown".halo_proxy_gate_totalmetrics. ADR-0011. - CLI version gate (
Ring.versionGate: off|warn|enforce, defaultwarn): the gateway reads the CLI version from theUser-Agentand compares it with the ring's pinned versions (ring profile and the variant profiles of running client-axis experiments). This covers Codex, Gemini CLI and Copilot CLI, which do not enforce a pin themselves. User-Agent formats come from recorded headers and vendor source, cited in ADR-0011. - Immutable managed files:
halod.yamlimmutable: truesetschflags schg(macOS) /chattr +i(Linux) after each atomic write and clears it before a rewrite; a missing tool or permission is logged once and skipped. - Policy API v1:
apiVersion: halos.dev/v1is the stable document version; every kind and field is frozen as it was in v1alpha1.halo migrate --policy-dir DIR [--dry-run]rewrites v1alpha1 documents in place, keeping comments and formatting (idempotent).halo initand the intent commands write v1;schemas/*.schema.jsondescribe v1 andschemas/v1alpha1/keeps the old ones. Compatibility states the v1 guarantees, the deprecation policy and the supported version skew acrosshalo,halod,halo-serverandhalo-proxy. ADR-0012. - Agentic onboarding from whichever AI CLI you have.
halo quickstartbrings up the demo and prints a tour.halo doctorreports installed CLIs and versions, tools, which credential variables are set (never their values), policy validity and gateway reachability, with a fix per problem.halo onboard local|install|proxy|verifytakes a machine from nothing to a validatedhalos.yaml, a dry-run (then--apply) install of the managed config, an optional loopbackhalo-proxy, and a realclaude -p/codex exec/gemini -p/copilot -pround trip.halo onboard companyinterviews an admin (IdP, gateway, provider, delivery) and generates the policy repo, Helm values, OIDC client, enrollment config, CI workflow and smoke eval, then stops before anything outward. All--output json, idempotent, dry-run by default. MCP tools of the same names plus anonboardprompt; entry points for Claude Code (/halos:halo-onboard), Codex ($halos-onboard), Gemini CLI (extension) and Copilot CLI (.github/mcp.json). New Start here page. - Helm HA:
halo-proxyruns with probes, a PodDisruptionBudget, topology spread, an optional HPA, a hardenedsecurityContextand an optional NetworkPolicy;values.schema.jsonpinshalo-serverandhalo-shadowto one replica because each is a single writer over local JSONL files (documented in the chart).make uat-k8snow kills a proxy pod mid-traffic, performs a rolling upgrade and tests the PDB under 200 rps with zero client-visible errors required. - Operations docs: Operations (HA model, SLOs, Prometheus alert rules on the real metric names, backup and restore of every
--data-dirfile and key, runbook for killing a change, rolling back a ring, rotating release and kill-switch keys, a lost signer state file, gateway or server down) and Support policy (SemVer scope per surface, supported versions, upgrade orderhalo-server → halo-shadow → halo-proxy/kong → halod → halo, EOL). - Nightly soak (
soak.yml): 60 minutes oftest/loadagainst a realhalo-proxy, failing on errors, cut SSE streams, goroutine or heap growth, and p99 regression against a committed baseline (records only untiltest/load/baseline.jsonis committed). - Fuzzing: 16 native Go fuzz targets over every parser of untrusted input (policy YAML, JWTs, ring pointers, bundle tar and manifest, kill lists, SSE, identity headers, webhook signatures, server JSON, audit chain, shadow replay,
halodpaths andhalod.yaml), run briefly on every PR and for 10 minutes per target nightly. An OpenSSF Scorecard workflow and aSECURITY.mdwith the disclosure process and supported versions. - Security model, bound to tests: every claim in the security model names the test that proves it, and
TestSecurityModelClaimsBoundfails if a named test disappears. - CI verifies every delivery path on every PR:
install.ps1,enroll.ps1andhalod service installon Windows;install.sh,enroll.shand launchd on macOS; the composite Action on three OSes; the GitLab template undergitlab-ci-local; the dev container and the Feature under the devcontainer CLI; Playwright clicks through every console page and button (make demo-e2e). - One-command releases:
scripts/release.sh [--dry-run] [--yes] <tag>runs the preflight (main, clean, HEAD == origin/main, SemVer, CHANGELOG section, green CI,go test,goreleaser check) and pushes the tag;release.ymlgates, builds, attests provenance and then verifies the published artifacts (cosign bundle, checksums, attestation, image signatures,install.sh, tap and bucket);release-drift.ymlfails daily if the tap or bucket drift from the latest release. RELEASING.md. - Demo kiosk:
make demoserves a pinnedhalodwith its checksum and configures every launcher, so "Set up my laptop" and the dev container launcher work end to end locally. Unconfigured launchers never show a raw server error:GET /api/v1/capabilitiesreports each launcher's availability, the kiosk shows only usable ones, and admins see what to set. Harness cards list only the model aliases that harness can use (the same wire check ashalo validate). halo eval onlinereports per-arm error rate, mean latency and output tokens over every shadow pair, errored ones included, and exportshalo.eval.shadow.*(ADR-0004: regressions in refusal rate, latency and cost are now caught, not only judge scores).- Guardrails
guardSandboxEgress(a ring profile withpermissions.sandboxRequiredmust set a non-emptyegress.allowedDomains) andguardGARing(a default ring that ships hooks must make them managed-only);guardNoBypassnow also scans profileenvand harness overrides.ExtraGuardrailslets an org add checks that can only add issues.halo release publishrefuses a ring pinned to a differentrelease:digest. Every ADR 0001–0010 carries an Implementation status table mapping each commitment to code and a test, with dated addenda where a decision was superseded. halo planno longer requires--against: a first release has no baseline.
Changed
halo initand the intent commands writehalos.dev/v1.- Release candidates are published as GitHub prereleases and never move the
:latestimage tag or the Homebrew and Scoop manifests; only a finalvX.Y.Ztag does. - The Scoop manifest is
halo.json; the Homebrew formula lives in the tap'sFormula/directory.
Deprecated
apiVersion: halos.dev/v1alpha1still loads with identical semantics and a validation warning per file. It is removed only inhalos.dev/v2.
Fixed
halodon Windows:state.jsonis written with the managedvardirectory's DACL instead of an owner-only one, so the SYSTEM scheduled task no longer rejects a state file first written by an admin during enrollment.- GitLab template:
.halosno longer sets a job-levelHALOS_VERSIONdefault that shadowed the pipeline-level pin. halo exp analyzecould fail withjson: unsupported value: -Infwhen a guardrail's two arms had no variance yet; the sequential guardrail now reports the exact estimate instead of an unbounded interval.halo-proxyno longer logs a spurious export error when a telemetry flush is cut short by shutdown.test/loadpaces requests with-rps; an unthrottled run exhausted the kind ingress's ephemeral ports and reported harness 502s as chart failures.
Security
Each finding...