Documentation
¶
Overview ¶
Package policy defines Halos's harness-neutral policy model: the YAML documents an org keeps in its policy repo, and the in-memory form every other package (compiler, gateway, delivery, eval) consumes.
Index ¶
- Constants
- Variables
- func ChannelName(ring, exp, variant string) string
- func Compile(org *Org) ([]byte, error)
- func DefaultModelFor(provider string) string
- func GateMode(m string) string
- func GeneratedFor(rollout string) string
- func HasErrors(issues []Issue) bool
- func HasTokenShape(s string) bool
- func KindServes(kind, proto string) bool
- func KnownMetric(name string) bool
- func LooksSecret(v string) bool
- func ParseBake(s string) (time.Duration, error)
- func ServesNatively(tool, kind string) bool
- func SimpleGatewayName(org string) string
- func SplitModel(id, def string) (provider, model string)
- func ToolModelFor(tool, provider, defaultID string) (string, bool)
- func ValidName(s string) bool
- type Axis
- type Credential
- type Egress
- type Experiment
- type ExperimentType
- type Gateway
- type GatewayAuth
- type Guardrail
- type HarnessSpec
- type Hook
- type Hooks
- type Identity
- type Issue
- type Kind
- type MCP
- type MCPServer
- type Membership
- type Meta
- type MetricDef
- type MetricGoal
- type Metrics
- type ModelChoice
- type ModelRoute
- type Models
- type Org
- type Permissions
- type Profile
- type Provider
- type Ring
- type Rollout
- type RolloutBaseline
- type RolloutChange
- type RolloutGates
- type RolloutPreset
- type RolloutStep
- type RolloutStrategy
- type Root
- type RouteTarget
- type ScorecardGate
- type SelfService
- type Severity
- type SignalSource
- type Simple
- type Stopping
- type Subject
- type Telemetry
- type Toggle
- type ToggleClient
- type TogglePatch
- type ToggleRule
- type ToggleTraffic
- type ToolSpec
- type Upstream
- type Variant
- type WireFitness
Constants ¶
const ( RolloutDraft = "draft" RolloutActive = "active" RolloutPaused = "paused" RolloutAborted = "aborted" RolloutCompleted = "completed" )
Rollout statuses. Only active rollouts are driven by the controller.
const ( SimpleProfile = "default" ProviderMulti = "multi" SafetyStrict = "strict" SafetyStandard = "standard" SafetyRelaxed = "relaxed" )
Simple-mode names. The profile every generated ring uses is SimpleProfile.
const ( EffectOn = "on" EffectOff = "off" )
Toggle effects for ToggleRule.Effect.
const ( GateOff = "off" GateWarn = "warn" GateEnforce = "enforce" )
Gate modes for Ring.Posture and Ring.VersionGate.
const APIVersion = "halos.dev/v1"
APIVersion is the stable document version this build writes.
const APIVersionV1Alpha1 = "halos.dev/v1alpha1"
APIVersionV1Alpha1 is the pre-GA document version. It is still read, with semantics identical to APIVersion and a deprecation warning from Validate; `halo migrate` rewrites it. Removed only in halos.dev/v2 (see docs/reference/compatibility).
const LabelGeneratedBy = "halos.dev/generated-by"
LabelGeneratedBy marks a document a tool generated for one owner, with the value GeneratedFor(rollout): `halo model switch --canary` labels the gateway.yaml override it writes, and the rollout's completion deletes it.
const MaxChannelLen = 100
MaxChannelLen bounds a delivery channel name. Channels become OCI tags ("ring-<channel>.pointer", tag limit 128) and halod state keys, so a name that would exceed this is shortened by hashing (see ChannelName).
const RootFile = "halos.yaml"
RootFile is the policy repo's root document.
const SnapshotVersion = "halos.dev/snapshot/v1"
SnapshotVersion identifies the envelope format written by Compile.
const ToggleDateLayout = "2006-01-02"
ToggleDateLayout is the layout of Toggle.Expires.
const ToggleSaltPrefix = "halos/toggles/"
ToggleSaltPrefix namespaces percent-rollout hashing per toggle, so a toggle's cohort is independent of the ring and of every experiment (see internal/assign).
Variables ¶
var ExtraGuardrails []Guardrail
ExtraGuardrails run after the built-in guardrails. They can only add issues: the built-ins have already been judged and recorded, so an extra can never remove or downgrade one (ADR-0007). Set once at start-up.
var MetricRegistry = []MetricDef{ {Name: "halo.task.success", Unit: "ratio", Description: "Fraction of tasks that ended successfully (eval verdict).", Sources: map[string]string{"claude-code": "halo eval trial verdict", "codex": "halo eval trial verdict", "gemini-cli": "halo eval trial verdict"}, Source: []SignalSource{SourceEval}, PerUnit: "one eval trial (pass = 1)"}, {Name: "halo.cost.usd_per_session", Unit: "usd", Description: "Model spend per session.", Sources: map[string]string{"claude-code": "claude_code.cost.usage", "codex": "codex.api_request events (tokens x price)", "gemini-cli": "gemini_cli.api_response (tokens x price)"}, Source: []SignalSource{SourceOnlineCLI}, PerUnit: "unit's total halo.cost.usd / its distinct sessions"}, {Name: "halo.tokens.per_session", Unit: "tokens", Description: "Input+output tokens per session.", Sources: map[string]string{"claude-code": "claude_code.token.usage", "codex": "codex.api_request events", "gemini-cli": "gemini_cli.token.usage"}, Source: []SignalSource{SourceOnlineCLI}, PerUnit: "unit's input+output halo.tokens / its distinct sessions"}, {Name: "halo.api.error_rate", Unit: "ratio", Description: "Fraction of model API requests that failed (non-2xx or stream error).", Sources: map[string]string{"claude-code": "claude_code.api_error / (claude_code.api_request + claude_code.api_error) events", "codex": "codex.api_request events (http.response.status_code, error.message)", "gemini-cli": "gemini_cli.api_error / (gemini_cli.api_response + gemini_cli.api_error)", "gateway": "halo.gateway.requests{status_class!=2xx} / halo.gateway.requests"}, Source: []SignalSource{SourceOnlineGateway, SourceOnlineCLI}, PerUnit: "unit's failed / total model API requests"}, {Name: "halo.edit.accept_rate", Unit: "ratio", Description: "Fraction of proposed code edits the user accepted.", Sources: map[string]string{"claude-code": "claude_code.code_edit_tool.decision", "codex": "codex.tool_decision events", "gemini-cli": "gemini_cli.tool_call (decision)"}, Source: []SignalSource{SourceOnlineCLI}, PerUnit: "unit's accepted / total edit decisions"}, {Name: "halo.latency.p50_ms", Unit: "ms", Description: "Median latency of successful model requests.", Sources: map[string]string{"claude-code": "claude_code.api_request duration_ms", "codex": "codex.api_request duration_ms (2xx)", "gemini-cli": "gemini_cli.api_response duration_ms", "gateway": "halo.gateway.latency_ms{status_class=2xx} histogram"}, Source: []SignalSource{SourceOnlineGateway, SourceOnlineCLI}, PerUnit: "median over the unit's successful requests (gateway: interpolated from histogram buckets)"}, {Name: "halo.latency.p95_ms", Unit: "ms", Description: "95th percentile latency of successful model requests.", Sources: map[string]string{"claude-code": "claude_code.api_request duration_ms", "codex": "codex.api_request duration_ms (2xx)", "gemini-cli": "gemini_cli.api_response duration_ms", "gateway": "halo.gateway.latency_ms{status_class=2xx} histogram"}, Source: []SignalSource{SourceOnlineGateway, SourceOnlineCLI}, PerUnit: "p95 over the unit's successful requests (gateway: interpolated from histogram buckets)"}, {Name: "halo.session.duration_s", Unit: "s", Description: "Wall-clock session length.", Sources: map[string]string{"claude-code": "claude_code.active_time.total", "codex": "codex.session events", "gemini-cli": "gemini_cli.session events"}, PerUnit: "not yet derived: no online source"}, {Name: "halo.tool.error_rate", Unit: "ratio", Description: "Fraction of tool calls that errored.", Sources: map[string]string{"claude-code": "claude_code.tool_result events (success)", "codex": "codex.tool_result events (success)", "gemini-cli": "gemini_cli.tool_call (success)"}, Source: []SignalSource{SourceOnlineCLI}, PerUnit: "unit's failed / total tool calls"}, {Name: "halo.eval.judge.score", Unit: "ratio", Description: "LLM-judge rubric score (0..1, higher is better) of a model response, graded by `halo eval online` on halo-shadow pairs.", Sources: map[string]string{"shadow": "halo eval online -> halo.eval.judge.score{halo.unit=<pair id>}, halo.source=eval"}, Source: []SignalSource{SourceOnlineEval}, PerUnit: "mean judge score of one shadow pair's response for that arm (unit = pair)", Better: "increase"}, }
MetricRegistry lists every metric experiments may reference; Validate rejects names not in this list.
var RolloutPresets = map[string]RolloutPreset{ "fast": {Name: "fast", TeamBake: "4h", RingBake: "1d", Rings: []Ring{presetRing("ring0-team", 0, Membership{}), presetRing("ring1-ga", 1, Membership{Default: true})}, Canary: []RolloutStep{canary(25, "4h", 100)}}, "standard": {Name: "standard", TeamBake: "1d", RingBake: "2d", Rings: []Ring{presetRing("ring0-team", 0, Membership{}), presetRing("ring1-canary", 1, Membership{Percent: 5}), presetRing("ring2-early", 2, Membership{Percent: 25}), presetRing("ring3-ga", 3, Membership{Default: true})}, Canary: []RolloutStep{canary(5, "12h", 200), canary(25, "1d", 500), canary(50, "1d", 1000)}}, "careful": {Name: "careful", TeamBake: "2d", RingBake: "3d", Rings: []Ring{presetRing("ring0-team", 0, Membership{}), presetRing("ring1-canary", 1, Membership{Percent: 1}), presetRing("ring2-early", 2, Membership{Percent: 5}), presetRing("ring3-broad", 3, Membership{Percent: 25}), presetRing("ring4-ga", 4, Membership{Default: true})}, Canary: []RolloutStep{canary(1, "1d", 200), canary(5, "2d", 500), canary(25, "2d", 2000), canary(50, "3d", 4000)}}, }
RolloutPresets are fast, standard and careful.
Functions ¶
func ChannelName ¶
ChannelName is the delivery channel that serves variant of client-axis experiment exp to devices in ring: "<ring>.x-<exp>.<variant>". When that exceeds MaxChannelLen the experiment/variant part is replaced by the first 20 hex chars of sha256(exp NUL variant). Names are ValidName, so the result only uses [a-z0-9._-] and fits the OCI tag grammar.
The ring release names its channels in its signed manifest; halod recomputes the name and refuses a mismatch. Validate rejects policies where a channel name collides with a ring or another channel.
func Compile ¶
Compile renders org as deterministic JSON (same input => same bytes: maps are key-sorted by encoding/json, no timestamps) for the gateway plane.
func DefaultModelFor ¶
DefaultModelFor is the model `halo init` gives the default alias on provider.
func GeneratedFor ¶
GeneratedFor is the LabelGeneratedBy value of documents generated for rollout.
func HasTokenShape ¶
HasTokenShape reports whether s contains a well-known credential shape (vendor token prefixes such as sk-ant-, ghp_, xoxb-; AWS access key ids; PEM private key blocks) outside ${VAR} references. It is the low-false- positive subset of looksSecret, safe to run over whole rendered files.
func KindServes ¶
KindServes reports whether an upstream of kind can serve a client speaking proto. Pass-through kinds (orchestrator, anthropic, openai, gemini) keep the client's wire as-is. bedrock additionally serves anthropic-messages clients (the request is translated and the event stream converted back); vertex serves only anthropic-messages; azure-openai only openai-responses; gemini clients are served only by kinds gemini and orchestrator.
func KnownMetric ¶
KnownMetric reports whether name is in MetricRegistry.
func LooksSecret ¶
LooksSecret reports whether v looks like a literal credential (see looksSecret).
func ServesNatively ¶
ServesNatively reports whether upstream provider kind answers tool's wire (true for tools the gateway does not route).
func SimpleGatewayName ¶
SimpleGatewayName is the generated Gateway document's name.
func SplitModel ¶
SplitModel splits "<provider>/<model>" when the prefix is a known provider (Bedrock ARNs keep their slashes); otherwise the model runs on def.
func ToolModelFor ¶
ToolModelFor reports whether tool needs its own start model because defaultID (the default alias's model, on provider) runs on a provider that cannot answer the tool's wire, and if so the vendor model to use ("<provider>/<model>"). Tools the gateway does not route (copilot-cli) never need one.
Types ¶
type Credential ¶
type Credential struct {
Env string `yaml:"env,omitempty" json:"env,omitempty"`
File string `yaml:"file,omitempty" json:"file,omitempty"`
Scheme string `yaml:"scheme,omitempty" json:"scheme,omitempty"`
}
Credential locates a secret on the halo-proxy host (kinds openai, azure-openai, gemini): an environment variable name or a file path (exactly one). Scheme is api-key (default for azure-openai) or bearer (default for openai).
type Egress ¶
type Egress struct {
// AllowedDomains is the only egress permitted from managed environments (firewall + harness sandbox).
AllowedDomains []string `yaml:"allowedDomains,omitempty" json:"allowedDomains,omitempty"`
}
type Experiment ¶
type Experiment struct {
Meta `yaml:",inline"`
Type ExperimentType `yaml:"type" json:"type"`
Axis Axis `yaml:"axis" json:"axis"`
Status string `yaml:"status,omitempty" json:"status,omitempty"` // draft | running | paused | concluded
// Rings the experiment draws users from.
Rings []string `yaml:"rings" json:"rings"`
Variants []Variant `yaml:"variants" json:"variants"`
// Shadow only: fraction of eligible requests mirrored (0-1).
SampleRate float64 `yaml:"sampleRate,omitempty" json:"sampleRate,omitempty"`
Metrics Metrics `yaml:"metrics" json:"metrics"`
Stopping Stopping `yaml:"stopping" json:"stopping"`
// Salt keeps assignment independent across experiments; defaults to Name.
Salt string `yaml:"salt,omitempty" json:"salt,omitempty"`
}
func (*Experiment) Arms ¶
func (e *Experiment) Arms() (control, treatment *Variant, ok bool)
Arms returns the backing experiment's control and treatment variants.
func (*Experiment) ResolveVariant ¶
func (e *Experiment) ResolveVariant(s Subject, ring string) *Variant
ResolveVariant returns the experiment variant for a subject, or nil if the subject's ring is not enrolled or the experiment isn't running.
type ExperimentType ¶
type ExperimentType string
ExperimentType is how an experiment exposes users.
const ( ExperimentAB ExperimentType = "ab" ExperimentCanary ExperimentType = "canary" ExperimentShadow ExperimentType = "shadow" )
type Gateway ¶
type Gateway struct {
Meta `yaml:",inline"`
// BaseURL is what clients are pointed at (Kong's public listener).
BaseURL string `yaml:"baseURL" json:"baseURL"`
// Protocol per harness: anthropic-messages | bedrock-invoke | openai-responses | gemini.
Protocols map[string]string `yaml:"protocols,omitempty" json:"protocols,omitempty"`
// Auth: how clients obtain a credential for the auth gateway.
Auth GatewayAuth `yaml:"auth" json:"auth"`
// Models maps a stable alias (what clients request) to upstream targets.
Models map[string]ModelRoute `yaml:"models" json:"models"`
// Upstreams are named backends (e.g. the orchestrator, direct Anthropic).
Upstreams map[string]Upstream `yaml:"upstreams" json:"upstreams"`
// Engine is the data plane that enforces this policy: halo-proxy (default)
// or kong. Multi-target routes (weights, failover) are halo-proxy-only; Kong
// uses each route's first target.
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
}
Gateway describes how harness traffic reaches models.
func (*Gateway) HarnessWire ¶
HarnessWire is the wire harness h speaks to this gateway ("" = the gateway does not route it).
func (*Gateway) WireFit ¶
func (g *Gateway) WireFit(alias, wire string) (WireFitness, []string)
WireFit reports whether alias routes to an upstream that answers wire, and lists each candidate target as "upstream (kind)". It is the check `halo validate` applies to a harness's start model (guardHarnessWire).
type GatewayAuth ¶
type GatewayAuth struct {
// HelperCommand prints a short-lived token (used as Claude apiKeyHelper, Codex env_key source, ...).
HelperCommand string `yaml:"helperCommand,omitempty" json:"helperCommand,omitempty"`
TTLSeconds int `yaml:"ttlSeconds,omitempty" json:"ttlSeconds,omitempty"`
// IdentityHeader is the header the auth gateway sets with the verified user id.
IdentityHeader string `yaml:"identityHeader,omitempty" json:"identityHeader,omitempty"`
}
type Guardrail ¶
Guardrail is an org-wide policy check. ponytail: guardrails are plain Go funcs, not OPA (ADR-0007). An org-specific evaluator (Rego later) plugs in through ExtraGuardrails; no other code changes.
type HarnessSpec ¶
type HarnessSpec struct {
// Version is an exact CLI version pin (e.g. "2.1.280"). Required for rings.
Version string `yaml:"version" json:"version"`
// Model is this harness's default model alias, replacing models.default
// for it (e.g. codex-default, so codex does not ask for a Claude alias).
// Must be a gateway.models alias, and in models.allowed when enforced.
Model string `yaml:"model,omitempty" json:"model,omitempty"`
// Overrides are raw, harness-native keys merged last. Escape hatch; the Go guardrails still check them (ADR-0007).
Overrides map[string]any `yaml:"overrides,omitempty" json:"overrides,omitempty"`
}
type Identity ¶
type Identity struct {
Issuer string `yaml:"issuer,omitempty" json:"issuer,omitempty"`
ClientID string `yaml:"clientID,omitempty" json:"clientID,omitempty"`
// Audience expected in gateway bearer tokens; defaults to ClientID.
Audience string `yaml:"audience,omitempty" json:"audience,omitempty"`
// UserClaim / GroupsClaim name the JWT claims for subject id and groups.
UserClaim string `yaml:"userClaim,omitempty" json:"userClaim,omitempty"` // default "email"
GroupsClaim string `yaml:"groupsClaim,omitempty" json:"groupsClaim,omitempty"` // default "groups"
// AdminGroups may approve requests and manage experiments in the portal.
AdminGroups []string `yaml:"adminGroups,omitempty" json:"adminGroups,omitempty"`
}
Identity is the org's OIDC provider. Any compliant IdP works (Okta, Entra ID, Google, Keycloak, Ping, Auth0...). Used by halo-server (portal login), halo-proxy / halo-kong (verifying the caller) and halod enrollment.
type Issue ¶
Issue is one validation finding. Path is like "rings[ring1].membership.percent".
func TrafficOverlaps ¶
TrafficOverlaps is the guard's finding for gateways, which log it when they load a snapshot that was never validated (they still route first by name).
type Kind ¶
type Kind string
Kind names a policy document type.
const KindRollout Kind = "Rollout"
KindRollout is a phased rollout of one change (see Rollout).
const KindToggle Kind = "Toggle"
KindToggle is a feature toggle document: one capability a platform team can turn on for a targeted cohort, and kill instantly, without a new release.
type Membership ¶
type Membership struct {
// Users are explicit user ids always in this ring (checked before Groups).
Users []string `yaml:"users,omitempty" json:"users,omitempty"`
// Groups are IdP groups always in this ring.
Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
// Percent of remaining users hashed into this ring (0-100, basis points precision).
Percent float64 `yaml:"percent,omitempty" json:"percent,omitempty"`
// Default marks the catch-all ring (GA).
Default bool `yaml:"default,omitempty" json:"default,omitempty"`
// OptIn lets developers join this ring themselves from the portal
// (recorded as a group membership request; admins may auto-approve).
OptIn bool `yaml:"optIn,omitempty" json:"optIn,omitempty"`
}
type Meta ¶
type Meta struct {
APIVersion string `yaml:"apiVersion" json:"apiVersion"`
Kind Kind `yaml:"kind" json:"kind"`
Name string `yaml:"name" json:"name"`
Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
}
Meta is common to every document.
type MetricDef ¶
type MetricDef struct {
Name string
Unit string // unit of the value, e.g. ratio, ms, usd
Description string
Sources map[string]string
// Source lists where values come from. When a metric has both online
// sources, internal/promote prefers the gateway for an experiment that has
// gateway data (exact cohort attribution), else the CLI events.
Source []SignalSource
// PerUnit defines one analysis sample: experiments compare the per-unit
// values of the two arms. CLI unit = user.id (Codex: user.account_id, else
// conversation.id; Gemini: installation.id); gateway unit = salted hash
// of the session id (else of the verified subject).
PerUnit string
// Better is the direction of improvement ("increase" | "decrease"); when
// set, goals on this metric must use it. Empty = not enforced.
Better string
}
MetricDef is a normalised halo.* metric. The telemetry collector maps each harness's native signal (Sources, keyed by adapter name) onto Name.
func LookupMetric ¶
LookupMetric returns the registry entry for name.
func (MetricDef) HasSource ¶
func (m MetricDef) HasSource(s SignalSource) bool
HasSource reports whether the metric can be measured by s.
func (MetricDef) ShadowOnly ¶
ShadowOnly reports whether the metric is only measured on shadow pairs.
type MetricGoal ¶
type MetricGoal struct {
// Metric is a normalised halo.* metric name, e.g. halo.task.success, halo.cost.usd_per_session.
Metric string `yaml:"metric" json:"metric"`
// Direction: increase | decrease
Direction string `yaml:"direction" json:"direction"`
// MaxRegression (guardrails): relative worsening that aborts, e.g. 0.05 = 5%.
MaxRegression float64 `yaml:"maxRegression,omitempty" json:"maxRegression,omitempty"`
}
type Metrics ¶
type Metrics struct {
Primary MetricGoal `yaml:"primary" json:"primary"`
Guardrails []MetricGoal `yaml:"guardrails,omitempty" json:"guardrails,omitempty"`
}
type ModelChoice ¶
type ModelChoice []string
ModelChoice is one model id, or several in failover order.
func (*ModelChoice) UnmarshalYAML ¶
func (m *ModelChoice) UnmarshalYAML(n *yaml.Node) error
UnmarshalYAML accepts a scalar or a list of scalars.
type ModelRoute ¶
type ModelRoute struct {
Upstream string `yaml:"upstream,omitempty" json:"upstream,omitempty"`
// Model is the provider model id / Bedrock inference profile ARN.
Model string `yaml:"model,omitempty" json:"model,omitempty"`
Targets []RouteTarget `yaml:"targets,omitempty" json:"targets,omitempty"`
}
ModelRoute is one alias's routing: either a single upstream+model, or an ordered/weighted list of Targets (mutually exclusive).
func (ModelRoute) Candidates ¶
func (r ModelRoute) Candidates() []RouteTarget
Candidates returns the route's targets (a single-target route is one).
func (ModelRoute) Primary ¶
func (r ModelRoute) Primary() ModelRoute
Primary is the route reduced to its first-priority target, for consumers that cannot fail over (halo-kong, halo-shadow).
type Org ¶
type Org struct {
Name string
Identity Identity
SelfService SelfService
Gateway *Gateway
Profiles map[string]*Profile
Rings []*Ring // ordered, earliest ring first
Experiments []*Experiment
Toggles []*Toggle `json:",omitempty"` // omitted when empty: existing snapshots keep their digest
// Rollouts are omitted when empty, like Toggles.
Rollouts []*Rollout `json:",omitempty"`
// Deprecations are load-time warnings (one per file still on a deprecated
// apiVersion) that Validate reports. Not part of the snapshot.
Deprecations []Issue `json:"-"`
}
Org is the fully loaded policy repo.
func Load ¶
Load reads every *.yaml/*.yml under dir (multi-document files supported), dispatches by kind, and returns the assembled Org. Decoding is strict: unknown fields are errors, reported with the yaml line ("line N") and file.
func ParseSnapshot ¶
ParseSnapshot reads a Compile envelope (verifying version and digest) or, for backwards compatibility, a bare json.Marshal(*Org) document.
func (*Org) ClientExperiments ¶
func (o *Org) ClientExperiments(ring string) []*Experiment
ClientExperiments returns the running client-axis experiments that enroll ring, in policy order. Validate allows at most one per ring.
func (*Org) ResolveProfile ¶
ResolveProfile returns the named profile with its extends chain applied (root ancestor first, child last). Merge semantics:
- scalars: child wins when set (non-zero). ponytail: zero means "unset", so a child cannot turn a parent's true bool back to false; add pointer bools if needed.
- slices: child replaces the parent's list when non-empty, EXCEPT permissions.deny, mcp.denied and hooks.items, which union (see below).
- maps: merged per key; struct values (e.g. harnesses) merge recursively, other values (e.g. overrides) are child-wins per key.
The result is a deep-ish copy: mutating it does not affect the Org. Extends cycles and missing parents are errors.
func (*Org) ResolveRing ¶
ResolveRing returns the ring a subject belongs to. Rings are walked in order: explicit user ids win, then explicit group membership, then each ring claims its Percent of the hash space (cumulatively, so ring1 5% + ring2 25% = buckets [0,500) and [500,3000)), then the Default ring catches everyone else.
type Permissions ¶
type Permissions struct {
// Mode: default | acceptEdits | plan | auto
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Allow []string `yaml:"allow,omitempty" json:"allow,omitempty"`
Deny []string `yaml:"deny,omitempty" json:"deny,omitempty"`
Ask []string `yaml:"ask,omitempty" json:"ask,omitempty"`
DisableBypass bool `yaml:"disableBypass,omitempty" json:"disableBypass,omitempty"`
// Sandbox: harness sandbox policy — off | workspace-write | read-only
Sandbox string `yaml:"sandbox,omitempty" json:"sandbox,omitempty"`
// SandboxRequired fails closed: the harness must refuse to run (Claude:
// sandbox.failIfUnavailable) rather than fall back to unsandboxed
// execution when its sandbox dependencies are missing.
SandboxRequired bool `yaml:"sandboxRequired,omitempty" json:"sandboxRequired,omitempty"`
}
func SafetyPreset ¶
func SafetyPreset(name string) (Permissions, bool, error)
SafetyPreset returns the permission, MCP and hook settings of preset name ("" = standard). No preset ever sets bypassPermissions, and all set disableBypass.
type Profile ¶
type Profile struct {
Meta `yaml:",inline"`
Extends string `yaml:"extends,omitempty" json:"extends,omitempty"`
// Harnesses enabled by this profile, keyed by adapter name.
Harnesses map[string]HarnessSpec `yaml:"harnesses" json:"harnesses"`
Models Models `yaml:"models" json:"models"`
Permissions Permissions `yaml:"permissions" json:"permissions"`
MCP MCP `yaml:"mcp" json:"mcp"`
Hooks Hooks `yaml:"hooks" json:"hooks"`
Telemetry Telemetry `yaml:"telemetry" json:"telemetry"`
Egress Egress `yaml:"egress" json:"egress"`
// Instructions is org-wide memory (managed CLAUDE.md / AGENTS.md / GEMINI.md).
Instructions string `yaml:"instructions,omitempty" json:"instructions,omitempty"`
// Env is extra environment applied to every harness.
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
}
Profile is desired harness behaviour, independent of any one CLI.
type Provider ¶
type Provider struct {
Name string `yaml:"name" json:"name"`
Region string `yaml:"region,omitempty" json:"region,omitempty"`
Project string `yaml:"project,omitempty" json:"project,omitempty"`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
}
Provider names a model provider and its settings.
type Ring ¶
type Ring struct {
Meta `yaml:",inline"`
Order int `yaml:"order" json:"order"`
Profile string `yaml:"profile" json:"profile"`
// Release pins the ring to an immutable published release digest; empty = build from Profile.
Release string `yaml:"release,omitempty" json:"release,omitempty"`
Membership Membership `yaml:"membership" json:"membership"`
// Posture gates gateway access on the device posture halod reports to
// halo-server (fresh, drift-free, on this ring's release): off | warn | enforce.
// Empty means warn. Takes effect only on gateways wired to halo-server.
Posture string `yaml:"posture,omitempty" json:"posture,omitempty"`
// VersionGate checks the CLI version in the User-Agent against the ring's
// pinned harness version: off | warn | enforce. Empty means warn.
VersionGate string `yaml:"versionGate,omitempty" json:"versionGate,omitempty"`
}
Ring is an ordered rollout cohort pointing at a profile.
type Rollout ¶
type Rollout struct {
Meta `yaml:",inline"`
Axis Axis `yaml:"axis" json:"axis"`
// Status: draft | active | paused | aborted | completed ("" = draft).
Status string `yaml:"status,omitempty" json:"status,omitempty"`
// Step is the live step's name, set by merged advance PRs ("" = not started).
Step string `yaml:"step,omitempty" json:"step,omitempty"`
// Experiment is the backing two-arm experiment: exposure, evidence, kill
// switch. Optional when every step is progressive or blue-green.
Experiment string `yaml:"experiment,omitempty" json:"experiment,omitempty"`
Change RolloutChange `yaml:"change" json:"change"`
Baseline RolloutBaseline `yaml:"baseline,omitempty" json:"baseline,omitempty"`
Steps []RolloutStep `yaml:"steps" json:"steps"`
}
Rollout describes how ONE change rolls out: ordered steps, each with a target ring and percent, a minimum bake, gates, and a failure action.
Exposure inside a ring is carried by the backing Experiment (its control and treatment weights, or its shadow sample rate), so assignment stays in internal/assign and the existing kill switch is the traffic-axis rollback. Ring-wide steps move Ring.release. Every edit is proposed as a PR.
func (*Rollout) EffectiveStatus ¶
EffectiveStatus is Status with the draft default.
type RolloutBaseline ¶
type RolloutBaseline struct {
// Release is the client-axis switch-back target (required with ring-wide steps).
Release string `yaml:"release,omitempty" json:"release,omitempty"`
}
RolloutBaseline is the known-good state a rollback returns to.
type RolloutChange ¶
type RolloutChange struct {
// Client axis: the release digest ring pointers move to, and its label.
Release string `yaml:"release,omitempty" json:"release,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
// Traffic axis: the gateway model alias whose route becomes the treatment
// variant's route for it when the rollout completes.
Alias string `yaml:"alias,omitempty" json:"alias,omitempty"`
}
RolloutChange is what rolls out.
type RolloutGates ¶
type RolloutGates struct {
// Guardrails compare the backing experiment's arms (internal/stats
// sequential test). A breach triggers OnFailure at any time.
Guardrails []MetricGoal `yaml:"guardrails,omitempty" json:"guardrails,omitempty"`
// Scorecard reads a `halo eval run --output json` scorecard.
Scorecard *ScorecardGate `yaml:"scorecard,omitempty" json:"scorecard,omitempty"`
// Approval holds the step until a human advances it (`halo rollout advance`).
Approval bool `yaml:"approval,omitempty" json:"approval,omitempty"`
}
RolloutGates must all pass before a step advances.
type RolloutPreset ¶
type RolloutPreset struct {
Name string
Rings []Ring
// Canary is the treatment ramp on the backing experiment, in order.
Canary []RolloutStep
// TeamBake is the bake on ring0; RingBake on each later ring before GA.
TeamBake, RingBake string
}
RolloutPreset is a named ring layout plus the step template intent commands stamp into Rollouts. Rings carry no team members (Expand fills ring0).
func LookupRolloutPreset ¶
func LookupRolloutPreset(name string) (RolloutPreset, error)
LookupRolloutPreset returns preset name ("" = standard).
type RolloutStep ¶
type RolloutStep struct {
Name string `yaml:"name" json:"name"`
Strategy RolloutStrategy `yaml:"strategy" json:"strategy"`
// Ring is required for progressive/blue-green. Percent steps expose the
// backing experiment's rings; Ring, if set, must be one of them.
Ring string `yaml:"ring,omitempty" json:"ring,omitempty"`
// Percent: canary = treatment share; holdout = share kept on control;
// dark-launch = share of requests mirrored; progressive/blue-green = 100.
Percent float64 `yaml:"percent,omitempty" json:"percent,omitempty"`
// Bake is the minimum time at this step: Go duration or N"d" (e.g. "14d").
Bake string `yaml:"bake,omitempty" json:"bake,omitempty"`
MinSamples int `yaml:"minSamples,omitempty" json:"minSamples,omitempty"`
Gates RolloutGates `yaml:"gates,omitempty" json:"gates,omitempty"`
// OnFailure: rollback | pause (default rollback).
OnFailure string `yaml:"onFailure,omitempty" json:"onFailure,omitempty"`
}
RolloutStep is one phase of a rollout.
func (RolloutStep) BakeDuration ¶
func (s RolloutStep) BakeDuration() (time.Duration, error)
BakeDuration parses Bake ("" = 0).
func (RolloutStep) EffectiveGuardrails ¶
func (s RolloutStep) EffectiveGuardrails(exp *Experiment) []MetricGoal
EffectiveGuardrails is what step s is judged on: the backing experiment's guardrails, each overridden by the step's guardrail on the same metric, then the step's other guardrails. An active rollout owns its experiment (the experiment loop skips it), so dropping the experiment's guardrails here would silently disable their auto-rollback. Ring-wide steps have no control arm and keep only their own (none: validated).
func (RolloutStep) EffectivePercent ¶
func (s RolloutStep) EffectivePercent() float64
EffectivePercent is Percent with the ring-wide default (100) applied.
func (RolloutStep) FailureAction ¶
func (s RolloutStep) FailureAction() string
FailureAction is OnFailure with its default.
func (RolloutStep) RingWide ¶
func (s RolloutStep) RingWide() bool
RingWide reports whether the step moves a ring pointer instead of experiment weights.
type RolloutStrategy ¶
type RolloutStrategy string
RolloutStrategy is how one step exposes the change.
const ( // StrategyProgressive moves Step.Ring's release pointer to Change.Release (client axis). StrategyProgressive RolloutStrategy = "progressive" // StrategyCanary gives Percent of the backing experiment's users the treatment. StrategyCanary RolloutStrategy = "canary" // StrategyBlueGreen switches Step.Ring from the pre-staged standby // Change.Release atomically; rollback switches back to Baseline.Release. StrategyBlueGreen RolloutStrategy = "blue-green" // StrategyDarkLaunch mirrors Percent of requests to the treatment via // halo-shadow; no user sees it (traffic axis). StrategyDarkLaunch RolloutStrategy = "dark-launch" // StrategyHoldout keeps Percent of users on control for Bake to measure // the long-term effect; everyone else gets the treatment. StrategyHoldout RolloutStrategy = "holdout" )
type Root ¶
type Root struct {
APIVersion string `yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"`
Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
Org string `yaml:"org" json:"org"`
// Identity configures OIDC for the portal, halo-proxy and enrollment (any IdP).
Identity Identity `yaml:"identity,omitempty" json:"identity,omitempty"`
// SelfService configures the developer portal (kiosk).
SelfService SelfService `yaml:"selfService,omitempty" json:"selfService,omitempty"`
// Simple mode fields (tools, provider, models, ...): see simple.go.
Simple `yaml:",inline"`
}
Root is the halos.yaml document.
type RouteTarget ¶
type RouteTarget struct {
Upstream string `yaml:"upstream" json:"upstream"`
Model string `yaml:"model" json:"model"`
Weight float64 `yaml:"weight,omitempty" json:"weight,omitempty"`
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
// TimeoutSeconds bounds connect + wait for response headers (default: the
// proxy's upstreamHeaderTimeout). Streams are not time-limited afterwards.
TimeoutSeconds int `yaml:"timeoutSeconds,omitempty" json:"timeoutSeconds,omitempty"`
}
RouteTarget is one provider target of a route. Targets sharing a Priority (lower first; default 0) form a tier: within a tier Weight splits traffic (sticky per identity+session) and the rest of the tier is the failover order; later tiers are tried only after earlier ones fail.
type ScorecardGate ¶
type ScorecardGate struct {
// File is the scorecard JSON, relative to the policy dir.
File string `yaml:"file" json:"file"`
Variant string `yaml:"variant" json:"variant"`
// MinPass1 is the absolute pass@1 floor (0 = none).
MinPass1 float64 `yaml:"minPass1,omitempty" json:"minPass1,omitempty"`
// MaxRegression is the largest allowed pass-rate drop vs the scorecard's
// control (0.02 = 2pp); a significantly worse comparison always fails.
MaxRegression float64 `yaml:"maxRegression,omitempty" json:"maxRegression,omitempty"`
}
ScorecardGate passes when Variant's eval scorecard clears the bars.
type SelfService ¶
type SelfService struct {
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
// Launchers offered to developers: devcontainer | codespaces | coder | laptop.
Launchers []string `yaml:"launchers,omitempty" json:"launchers,omitempty"`
// Requestable items: kinds of access developers may request (approval opens a policy PR).
Requestable []string `yaml:"requestable,omitempty" json:"requestable,omitempty"` // mcp-server | model | ring-opt-in | harness
// Catalog lists MCP servers developers may request that aren't in their profile yet.
Catalog []MCPServer `yaml:"catalog,omitempty" json:"catalog,omitempty"`
// EnrollmentTTLSeconds bounds one-time laptop bootstrap tokens (default 900).
EnrollmentTTLSeconds int `yaml:"enrollmentTTLSeconds,omitempty" json:"enrollmentTTLSeconds,omitempty"`
}
SelfService configures the developer portal ("kiosk").
type SignalSource ¶
type SignalSource string
SignalSource is where a registry metric's values come from.
const ( // SourceOnlineCLI: the harness's own OTEL export (metrics and log events), // attributed to a cohort by the halo.* resource attributes the profile sets. SourceOnlineCLI SignalSource = "online-cli" // SourceOnlineGateway: halo-proxy's per-request OTLP metrics. The gateway // assigns ring/variant itself, so it is the only online source for // traffic-axis experiments (the CLI never learns a gateway-assigned variant). SourceOnlineGateway SignalSource = "online-gateway" // SourceEval: offline replay (`halo eval`) only; never observed on live traffic. SourceEval SignalSource = "eval" // SourceOnlineEval: `halo eval online` LLM-judge grades of halo-shadow // pairs, received on the collector's authenticated eval receiver // (halo.source=eval). Pairs exist only for shadow experiments (and the // dark-launch steps of rollouts, which run their experiment as shadow). // Trusted for verdicts and PRs; never for the kill switch (gateway only). SourceOnlineEval SignalSource = "online-eval" )
type Simple ¶
type Simple struct {
// Tools enables harnesses, each pinned to an exact CLI version: a bare
// version, or {version, model} where model is one of the Models aliases
// that tool starts on instead of "default" (e.g. codex on an OpenAI model).
Tools map[string]ToolSpec `yaml:"tools,omitempty" json:"tools,omitempty"`
// Provider is where models run: a name (anthropic | bedrock | vertex |
// openai | gemini | multi) or {name, region, project, url}.
Provider *Provider `yaml:"provider,omitempty" json:"provider,omitempty"`
// Models maps an alias (what clients ask for; "default" is required) to a
// provider model id, or to a list of ids tried in failover order. An id may
// be prefixed "<provider>/" to pick another provider (required with multi).
Models map[string]ModelChoice `yaml:"models,omitempty" json:"models,omitempty"`
// Gateway is the base URL clients are pointed at (halo-proxy).
Gateway string `yaml:"gateway,omitempty" json:"gateway,omitempty"`
// Telemetry is the OTLP/HTTP endpoint (telemetry is always on).
Telemetry string `yaml:"telemetry,omitempty" json:"telemetry,omitempty"`
// Team is ring0: IdP groups, or user ids containing "@". Default:
// identity.adminGroups, else "<org>-ai-platform".
Team []string `yaml:"team,omitempty" json:"team,omitempty"`
// Safety preset: strict | standard (default) | relaxed. None bypasses permissions.
Safety string `yaml:"safety,omitempty" json:"safety,omitempty"`
// Rollout preset: fast | standard (default) | careful. Sets the rings and
// the step template `halo upgrade start` and `halo model switch --canary` use.
Rollout string `yaml:"rollout,omitempty" json:"rollout,omitempty"`
}
Simple is halos.yaml's simple mode: a handful of intent-level fields that Load expands, deterministically and in memory, into the Gateway, the "default" Profile and the preset Rings. Explicit documents of the same kind/name are overlaid on the expansion field by field (see overlay), so a repo can drop to full power one document at a time; `halo explain` prints the result and `halo eject` writes it out.
type Stopping ¶
type Stopping struct {
// Method: msprt | fixed
Method string `yaml:"method" json:"method"`
Alpha float64 `yaml:"alpha,omitempty" json:"alpha,omitempty"`
MinSamples int `yaml:"minSamples,omitempty" json:"minSamples,omitempty"`
MaxDays int `yaml:"maxDays,omitempty" json:"maxDays,omitempty"`
MaxSpendUSD float64 `yaml:"maxSpendUSD,omitempty" json:"maxSpendUSD,omitempty"`
}
type Telemetry ¶
type Telemetry struct {
Enabled bool `yaml:"enabled" json:"enabled"`
OTLPEndpoint string `yaml:"otlpEndpoint,omitempty" json:"otlpEndpoint,omitempty"`
Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"` // grpc | http/protobuf
LogPrompts bool `yaml:"logPrompts,omitempty" json:"logPrompts,omitempty"`
Attributes map[string]string `yaml:"attributes,omitempty" json:"attributes,omitempty"`
}
type Toggle ¶
type Toggle struct {
Meta `yaml:",inline"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
// Owner is the team or person who answers for this toggle (required).
Owner string `yaml:"owner" json:"owner"`
// Expires (YYYY-MM-DD) is when the toggle should be removed; past it, validate warns.
Expires string `yaml:"expires,omitempty" json:"expires,omitempty"`
// Default is the state when no rule matches.
Default bool `yaml:"default" json:"default"`
// Axis: client = a settings fragment delivered in the signed release;
// traffic = a model route switch applied by the gateway.
Axis Axis `yaml:"axis" json:"axis"`
// Rules are evaluated in order; the first rule that matches decides.
Rules []ToggleRule `yaml:"rules,omitempty" json:"rules,omitempty"`
// Client is the payload of a client-axis toggle.
Client *ToggleClient `yaml:"client,omitempty" json:"client,omitempty"`
// Traffic is the payload of a traffic-axis toggle.
Traffic *ToggleTraffic `yaml:"traffic,omitempty" json:"traffic,omitempty"`
}
Toggle is a named switch with ordered targeting rules.
type ToggleClient ¶
type ToggleClient struct {
Harnesses map[string]TogglePatch `yaml:"harnesses" json:"harnesses"`
}
ToggleClient maps harness name to the fragment applied when the toggle is on.
type TogglePatch ¶
type TogglePatch struct {
MCPServers []MCPServer `yaml:"mcpServers,omitempty" json:"mcpServers,omitempty"`
Hooks []Hook `yaml:"hooks,omitempty" json:"hooks,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
// Overrides are harness-native keys, restricted to the same allowlist as profile overrides.
Overrides map[string]any `yaml:"overrides,omitempty" json:"overrides,omitempty"`
}
TogglePatch is what an "on" client toggle adds to one harness's resolved profile. It goes through the same adapter and guardrails as a profile, so an unsupported part produces the adapter's warning, never a silent drop.
type ToggleRule ¶
type ToggleRule struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Rings []string `yaml:"rings,omitempty" json:"rings,omitempty"`
Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
Users []string `yaml:"users,omitempty" json:"users,omitempty"`
// Percent (0-100) of the matching subjects, hashed with the toggle's own
// salt. Omitted = no percent condition; an explicit 0 matches nobody.
Percent *float64 `yaml:"percent,omitempty" json:"percent,omitempty"`
// Effect is the state when the rule matches: "on" (default) or "off".
Effect string `yaml:"effect,omitempty" json:"effect,omitempty"`
}
ToggleRule matches a subject when EVERY condition it sets holds (rings, groups and users each match if the subject is in any listed value). A rule with no condition matches everyone.
type ToggleTraffic ¶
type ToggleTraffic struct {
Routes map[string]ModelRoute `yaml:"routes" json:"routes"`
}
ToggleTraffic maps a gateway model alias to the route used while the toggle is on.
type ToolSpec ¶
type ToolSpec struct {
Version string `yaml:"version" json:"version"`
Model string `yaml:"model,omitempty" json:"model,omitempty"`
}
ToolSpec is one tool's pin and, optionally, its own default model alias.
type Upstream ¶
type Upstream struct {
// URL is the base URL. Optional for kind vertex (derived from region).
URL string `yaml:"url,omitempty" json:"url,omitempty"`
// Kind: orchestrator | anthropic | bedrock | vertex | openai | azure-openai | gemini
Kind string `yaml:"kind" json:"kind"`
// Project is the Google Cloud project id for kind vertex.
Project string `yaml:"project,omitempty" json:"project,omitempty"`
// APIVersion is the api-version query for kind azure-openai; empty uses the
// versionless /openai/v1 surface.
APIVersion string `yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"`
// Credential names where halo-proxy reads the provider key (kinds openai,
// azure-openai, gemini). Policy never holds the secret itself.
Credential *Credential `yaml:"credential,omitempty" json:"credential,omitempty"`
// Region is the AWS SigV4 region for kind bedrock. halo-proxy signs a
// bedrock upstream only if its host ends in an AWS suffix (.amazonaws.com,
// .amazonaws.com.cn, .api.aws) or is listed in the proxy's signHosts; it
// never signs for other hosts. The region is this value when set, else
// parsed from the host (e.g. bedrock-runtime.<region>.amazonaws.com).
Region string `yaml:"region,omitempty" json:"region,omitempty"`
// Serves lists the client wires an orchestrator upstream answers
// (anthropic-messages | bedrock-invoke | openai-responses | gemini). An
// orchestrator may translate, so validate only trusts what is declared here;
// other kinds' wires are known.
Serves []string `yaml:"serves,omitempty" json:"serves,omitempty"`
}
type Variant ¶
type Variant struct {
Name string `yaml:"name" json:"name"`
// Weight is the share of the experiment's users (weights are normalised).
Weight float64 `yaml:"weight" json:"weight"`
// Traffic axis: model alias -> route override.
Routes map[string]ModelRoute `yaml:"routes,omitempty" json:"routes,omitempty"`
// Client axis: profile to apply.
Profile string `yaml:"profile,omitempty" json:"profile,omitempty"`
Control bool `yaml:"control,omitempty" json:"control,omitempty"`
}
type WireFitness ¶
type WireFitness int
WireFitness is how well a model alias's route can answer a client wire.
const ( WireNo WireFitness = iota // no target can answer it (or the alias is unknown) WireMaybe // only orchestrators that do not declare it: they may translate WireYes // a target's provider (or a declaring orchestrator) answers it )