policy

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

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

View Source
const (
	RolloutDraft     = "draft"
	RolloutActive    = "active"
	RolloutPaused    = "paused"
	RolloutAborted   = "aborted"
	RolloutCompleted = "completed"
)

Rollout statuses. Only active rollouts are driven by the controller.

View Source
const (
	SimpleProfile  = "default"
	ProviderMulti  = "multi"
	SafetyStrict   = "strict"
	SafetyStandard = "standard"
	SafetyRelaxed  = "relaxed"
)

Simple-mode names. The profile every generated ring uses is SimpleProfile.

View Source
const (
	EffectOn  = "on"
	EffectOff = "off"
)

Toggle effects for ToggleRule.Effect.

View Source
const (
	GateOff     = "off"
	GateWarn    = "warn"
	GateEnforce = "enforce"
)

Gate modes for Ring.Posture and Ring.VersionGate.

View Source
const APIVersion = "halos.dev/v1"

APIVersion is the stable document version this build writes.

View Source
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).

View Source
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.

View Source
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).

View Source
const RootFile = "halos.yaml"

RootFile is the policy repo's root document.

View Source
const SnapshotVersion = "halos.dev/snapshot/v1"

SnapshotVersion identifies the envelope format written by Compile.

View Source
const ToggleDateLayout = "2006-01-02"

ToggleDateLayout is the layout of Toggle.Expires.

View Source
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

View Source
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.

View Source
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.

View Source
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

func ChannelName(ring, exp, variant string) string

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

func Compile(org *Org) ([]byte, error)

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

func DefaultModelFor(provider string) string

DefaultModelFor is the model `halo init` gives the default alias on provider.

func GateMode

func GateMode(m string) string

GateMode is m with the default (warn) applied.

func GeneratedFor

func GeneratedFor(rollout string) string

GeneratedFor is the LabelGeneratedBy value of documents generated for rollout.

func HasErrors

func HasErrors(issues []Issue) bool

HasErrors reports whether any issue is an error.

func HasTokenShape

func HasTokenShape(s string) bool

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

func KindServes(kind, proto string) bool

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

func KnownMetric(name string) bool

KnownMetric reports whether name is in MetricRegistry.

func LooksSecret

func LooksSecret(v string) bool

LooksSecret reports whether v looks like a literal credential (see looksSecret).

func ParseBake

func ParseBake(s string) (time.Duration, error)

ParseBake parses a Go duration or a whole number of days ("14d").

func ServesNatively

func ServesNatively(tool, kind string) bool

ServesNatively reports whether upstream provider kind answers tool's wire (true for tools the gateway does not route).

func SimpleGatewayName

func SimpleGatewayName(org string) string

SimpleGatewayName is the generated Gateway document's name.

func SplitModel

func SplitModel(id, def string) (provider, model string)

SplitModel splits "<provider>/<model>" when the prefix is a known provider (Bedrock ARNs keep their slashes); otherwise the model runs on def.

func ToolModelFor

func ToolModelFor(tool, provider, defaultID string) (string, bool)

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.

func ValidName

func ValidName(s string) bool

ValidName reports whether s is a safe policy identifier (ring, profile, experiment, variant, model alias, upstream, MCP server).

Types

type Axis

type Axis string

Axis is where an experiment's variants differ.

const (
	AxisClient  Axis = "client"  // variants are different profiles/releases on the machine
	AxisTraffic Axis = "traffic" // variants are different model routes at the gateway
)

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

func (g *Gateway) HarnessWire(h string) string

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

type Guardrail func(*Org) []Issue

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 Hook

type Hook struct {
	// Event: PreToolUse | PostToolUse | SessionStart | Stop | UserPromptSubmit
	Event   string `yaml:"event" json:"event"`
	Matcher string `yaml:"matcher,omitempty" json:"matcher,omitempty"`
	Command string `yaml:"command" json:"command"`
}

type Hooks

type Hooks struct {
	ManagedOnly bool   `yaml:"managedOnly,omitempty" json:"managedOnly,omitempty"`
	Hooks       []Hook `yaml:"items,omitempty" json:"items,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

type Issue struct {
	Severity Severity
	Path     string
	Message  string
}

Issue is one validation finding. Path is like "rings[ring1].membership.percent".

func TrafficOverlaps

func TrafficOverlaps(o *Org) []Issue

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).

func (Issue) String

func (i Issue) String() string

type Kind

type Kind string

Kind names a policy document type.

const (
	KindProfile    Kind = "Profile"
	KindRing       Kind = "Ring"
	KindExperiment Kind = "Experiment"
	KindGateway    Kind = "Gateway"
)
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 MCP

type MCP struct {
	// ManagedOnly blocks every server not listed here.
	ManagedOnly bool        `yaml:"managedOnly,omitempty" json:"managedOnly,omitempty"`
	Servers     []MCPServer `yaml:"servers,omitempty" json:"servers,omitempty"`
	Denied      []string    `yaml:"denied,omitempty" json:"denied,omitempty"`
}

type MCPServer

type MCPServer struct {
	Name    string            `yaml:"name" json:"name"`
	URL     string            `yaml:"url,omitempty" json:"url,omitempty"`
	Command []string          `yaml:"command,omitempty" json:"command,omitempty"`
	Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
}

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

func LookupMetric(name string) (MetricDef, bool)

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

func (m MetricDef) ShadowOnly() bool

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 Models

type Models struct {
	Default string   `yaml:"default" json:"default"`
	Allowed []string `yaml:"allowed,omitempty" json:"allowed,omitempty"`
	// Enforce rejects models outside Allowed rather than just hiding them.
	Enforce bool `yaml:"enforce,omitempty" json:"enforce,omitempty"`
}

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

func Load(dir string) (*Org, error)

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

func ParseSnapshot(b []byte) (*Org, error)

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

func (o *Org) ResolveProfile(name string) (*Profile, error)

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

func (o *Org) ResolveRing(s Subject) *Ring

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.

func (*Org) Validate

func (o *Org) Validate() []Issue

Validate runs semantic checks over the whole Org, then the built-in guardrails and any ExtraGuardrails. It never returns nil-vs-empty ambiguity: no issues = empty slice.

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.

func (*Provider) UnmarshalYAML

func (p *Provider) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML accepts a bare name or a mapping, strictly (unknown keys are errors).

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

func (r *Rollout) EffectiveStatus() string

EffectiveStatus is Status with the draft default.

func (*Rollout) StepIndex

func (r *Rollout) StepIndex(name string) int

StepIndex returns the index of the named step, or -1.

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.

func ReadRoot

func ReadRoot(dir string) (*Root, error)

ReadRoot strictly decodes dir's halos.yaml.

func (*Root) Expand

func (r *Root) Expand() (*Org, error)

Expand returns the documents simple mode generates for root: the Gateway, the "default" Profile and the preset Rings. It is pure and deterministic; the result still has to pass Validate (Load overlays explicit documents first).

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 Severity

type Severity string

Severity of a validation Issue.

const (
	SeverityError   Severity = "error"
	SeverityWarning Severity = "warning"
)

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.

func (*Simple) Enabled

func (s *Simple) Enabled() bool

Enabled reports whether any simple-mode field is set.

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 Subject

type Subject struct {
	ID     string
	Groups []string
}

Subject is a user as seen by the control plane (from the IdP / auth gateway).

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.

func (*Toggle) Expired

func (t *Toggle) Expired(now time.Time) bool

Expired reports whether the toggle's expiry date has passed (a toggle with no or an unparsable expiry never expires; validate reports those).

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.

func (TogglePatch) Apply

func (tp TogglePatch) Apply(p *Profile, h string) *Profile

Apply returns a copy of p with the patch merged in for harness h: servers and hooks are appended (deduped), env and overrides merged key by key.

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.

func (*ToolSpec) UnmarshalYAML

func (t *ToolSpec) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML accepts a bare version or {version, model}, strictly.

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"`
}

func (Upstream) Endpoint

func (u Upstream) Endpoint() string

Endpoint is the upstream base URL, deriving the Vertex AI host from Region when URL is unset ("global" has no region prefix).

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
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL