Cory Policht
← All references
REF · ADR-019-COST-GOVERNANCE-POLICY · ADR

Adr 019 Cost Governance Policy

source: AI-Dev-Platform/ADR/ADR-019-Cost-Governance-Policy.mdscrubbed extract

Scrubbed extract of AI-Dev-Platform/ADR/ADR-019-Cost-Governance-Policy.md — cited by the Chronicle (04-the-proving-ground). Redactions: phone / service detail / secrets / clearance / personal / internal hosts.


category: Work project: AI-Dev-Platform status: accepted created: 2026-06-18 number: ADR-019 title: Cost Governance Policy tags:

  • adr
  • decision-record
  • cost-governance
  • routing
  • omnigent
  • meta-harness
  • policy

ADR-019: Cost Governance Policy

Status

Accepted — 2026-06-18

Context

[[ADR-020-Meta-Harness-Omnigent-Mapping]] canonicalizes a five-phase plan to promote project-level abstractions into platform-level contracts. Phase 1 — “Policy Decoupled from Prompt” (IP-OMNIGENT-001 Phase 1) — targets the model-cost-guard skill.

Today cost governance lives entirely in the agent’s context window. The model-cost-guard skill (in bears-eat-beets) encodes a watcher table — session-token warn/stop thresholds, per-request budget overrun, per-tier saturation, and provider quota headroom — as prompt text. That has three structural problems:

  1. Cost-of-cost. Every governed session pays tokens to carry the policy. The policy competes for context with the actual task.
  2. Unenforceable cross-harness. A prompt-level guard only governs the harness whose prompt contains it. A meta-harness ([[ADR-020-Meta-Harness-Omnigent-Mapping]]) routing across MAF, Claude Code, and Codex cannot enforce one budget from inside any single prompt.
  3. Invisible to the control plane. ADP-Orchestration-UI cannot render budget state it cannot read; a threshold buried in a prompt is not a queryable service-layer fact.

The platform already has a routing seam in AIService.Core: TierResolver resolves a session’s active ModelTier (Local / Budget / Mid / Premium), enforcing the [[ADR-015-Sensitive-Data-Classification-And-Handling]] §3b invariant that a session with a sensitive document is locked to the Local tier. RoutingUseCase consumes that resolution. This is the natural enforcement point for cost policy — it sits before any ILLMProvider dispatch and is already inside the hexagonal Core.

Decision

Promote the model-cost-guard watcher thresholds into a CostGovernancePolicy domain value object in AIService.Core, and consult it inside TierResolver before dispatch. The skill becomes a read-only client that surfaces policy state to agents; it no longer enforces it.

The CostGovernancePolicy type

CostGovernancePolicy is an immutable record in AIService.Core/Domain/ValueObjects/. It captures the five thresholds from the model-cost-guard watcher table, validated at construction via a Create static factory (a private constructor guarantees every instance is well-formed), with a canonical Default baseline mirroring the historical table:

public sealed record CostGovernancePolicy
{
    public decimal SessionBudgetWarnThreshold { get; } // 0.75 — warn at 75% of session token budget
    public decimal SessionBudgetStopThreshold { get; } // 0.95 — hard-stop + notify at 95%
    public decimal PerRequestBudgetDowngrade { get; }  // 1.25 — downgrade one rung at 125% of per-request budget
    public decimal TierSaturationDowngrade { get; }    // 0.90 — downgrade one rung at 90% tier capacity
    public decimal ProviderQuotaRouteAway { get; }     // 0.10 — route away below 10% provider quota headroom

    public static CostGovernancePolicy Default { get; } // = Create(0.75, 0.95, 1.25, 0.90, 0.10)

    public static CostGovernancePolicy Create(decimal warn, decimal stop, decimal perRequest,
        decimal saturation, decimal quota); // validates ranges; stop must be strictly above warn

    public CostGovernanceAction EvaluateSessionBudget(decimal budgetFractionUsed); // Allow | Warn | HardStop
    public bool ShouldDowngrade(decimal perRequestBudgetMultiple, decimal tierSaturationFraction);
    public bool ShouldRouteAway(decimal providerQuotaHeadroomFraction);
}

Construction validation enforces that all four fraction thresholds sit in their valid range (warn and quota in the exclusive interval (0, 1); stop and saturation in (0, 1]), the per-request multiplier is >= 1.0, and the stop threshold is strictly greater than the warn threshold. Invalid input fails fast with ArgumentOutOfRangeException / ArgumentException rather than producing a misconfigured policy.

The type carries no infrastructure concerns — no [BsonId], no JSON binding attributes. When V1 ships the policy as static YAML configuration, a dedicated infrastructure-layer mapper translates the config shape into CostGovernancePolicy.Create(...), preserving the hexagonal boundary ([[ADR-001-Architecture-Overview]]).

Tier limits and the governance ladder

The thresholds map onto the existing four-tier ModelTier ladder. Downgrade and route-away both move the resolved tier exactly one rung toward the cheapest tier (Premium → Mid → Budget → Local, never below Local), mirroring the model-cost-guard “move exactly one rung” constraint. The session-budget ladder is independent of tier and gates dispatch outright:

Signal Threshold (default) Action
Session tokens used >= 0.75 Warn (no tier change)
Session tokens used >= 0.95 Hard-stop + notify operator
Per-request cost multiple >= 1.25 Downgrade one rung
Tier in-flight saturation >= 0.90 Downgrade one rung
Provider quota headroom < 0.10 Route away (downgrade one rung)

Integration with routing

TierResolver gains ResolveWithGovernance(SessionContext, CostGovernancePolicy, CostGovernanceSignals), additive alongside the existing ResolveActiveTier(SessionContext) so no current caller breaks. It returns a RoutingDecision(ModelTier Tier, CostGovernanceAction Action, bool Locked). The precedence is invariant-first:

  1. The [[ADR-015-Sensitive-Data-Classification-And-Handling]] §3b sensitive-document lock is evaluated first. A sensitive session is pinned to Local and is exempt from every cost-driven tier change. The budget action is still surfaced (so the operator sees budget state), but the tier never moves off Local.
  2. For non-sensitive sessions, a session-budget HardStop short-circuits dispatch — the decision returns HardStop and the caller refuses to route and notifies, rather than silently downgrading.
  3. Otherwise, route-away (low provider quota) or downgrade (cost overrun / tier saturation) moves the resolved tier one rung down.

CostGovernanceSignals (session-budget fraction used, per-request budget multiple, tier saturation fraction, provider quota headroom) is the live input set captured per routing decision; a None baseline (full headroom, zero pressure) lets callers resolve without live signals when none are available, in which case every governance check passes.

Consequences

Positive

  1. Policy leaves the prompt. Cost governance is a service-layer fact, queryable by the control plane and enforceable across every harness the meta-harness routes to.
  2. Token savings. Governed sessions no longer carry the watcher table in context.
  3. Invariant safety preserved. The ADR-015 §3b sensitive lock takes strict precedence over cost optimization; a sensitive session can never be cost-downgraded off the Local tier.
  4. Backwards compatible. ResolveWithGovernance is additive; the existing ResolveActiveTier path and its callers are untouched. Hard-stop is explicit and observable, never a silent halt.
  5. Hexagonal boundary intact. CostGovernancePolicy is a pure domain value object; configuration enters via an infrastructure mapper, not by coupling Core to YAML or persistence.

Negative

  1. model-cost-guard role change. Consumers that treat the skill as an enforcement mechanism must now treat it as a read-only observer. This is a behavioral change for running sessions (also called out in [[ADR-020-Meta-Harness-Omnigent-Mapping]] Consequences §2).
  2. Signal sourcing is deferred. CostGovernanceSignals defines the contract, but live per-request cost, tier in-flight counts, and provider quota headroom are not yet wired from telemetry. Until they are, callers pass CostGovernanceSignals.None and only the session-budget ladder (fed by existing token accounting) is live. This is intentional staging, not a gap to be hidden.
  3. Static V1 only. V1 ships a single static policy. Per-account / per-project policy resolution and dynamic thresholds are future work, aligned with the [[ADR-MLOPS-001-Router-Model-Architecture]] V1 → V2 progression.

Risks and Mitigations

Risk Mitigation
A cost downgrade silently degrades output quality below task needs Downgrade moves exactly one rung and never below Local; hard-stop is explicit and notified. The model-cost-guard skill surfaces the decision so the operator sees it.
Sensitive session escapes the Local tier via a cost path ResolveWithGovernance evaluates the ADR-015 §3b lock before any cost logic; a unit test (SensitiveSession_LockedToLocal_EvenUnderMaxPressure) asserts it under maximal pressure.
Misconfigured policy (stop below warn, out-of-range fraction) routes incorrectly Create validates ranges and ordering, failing fast at construction; tests cover every rejection path.
  • [[ADR-020-Meta-Harness-Omnigent-Mapping]] — Phase 1 of the Omnigent adoption sequence; this ADR is its implementation record.
  • [[ADR-001-Architecture-Overview]] — Hexagonal architecture; CostGovernancePolicy is a Core domain value object, configuration enters via an infrastructure mapper.
  • [[ADR-010-Dual-Gate-MAF]] — CostGovernancePolicy feeds the existing ModelRoutingService/TierResolver, not a new gate type.
  • [[ADR-015-Sensitive-Data-Classification-And-Handling]] — §3b sensitive-document Local lock takes strict precedence over all cost-driven tier changes.
  • [[ADR-MLOPS-001-Router-Model-Architecture]] — the V1 static policy router is the same artifact framed here for cost governance.

References

  • Task: obsidian-sync/Tasks/Work/implement-cost-governance-policy-aiservice.md
  • Source: omnigent_mapping_analysis.md §1 “Policy Decoupled from Prompt”
  • Skill: bears-eat-beets/catalog/skills/model-cost-guard/SKILL.md
  • Implementation: AIService.Core/Domain/ValueObjects/CostGovernancePolicy.cs, AIService.Core/Application/Routing/TierResolver.cs