Files
weichen c07d4020d0 feat(intentflow): I2 shadow tier, project allowlist, and rollout metrics
Persist validated_only shadow plans without executing, gate elevated tiers
behind INTENT_PLAN_PROJECT_ALLOWLIST, and keep legacy chat routing for shadow.
2026-09-23 15:10:26 +00:00

87 lines
2.9 KiB
Go

package intentflow
import "sync/atomic"
// RolloutMetrics is the in-process I2 counter set used for shadow reconciliation
// and allowlist gates. Durable plan facts still flow through intent.plan.* events.
type RolloutMetrics struct {
PlansCreated atomic.Int64
ShadowPlans atomic.Int64
AutoReadPlans atomic.Int64
ConfirmPlans atomic.Int64
AllowlistDenied atomic.Int64
CounterfactualWouldExecute atomic.Int64
CounterfactualWouldConfirm atomic.Int64
CounterfactualWouldClarify atomic.Int64
CounterfactualWouldReject atomic.Int64
CounterfactualPassthrough atomic.Int64
ReconciliationDiffs atomic.Int64
}
var globalMetrics RolloutMetrics
// Metrics returns the process-wide rollout counters.
func Metrics() *RolloutMetrics { return &globalMetrics }
// Snapshot is a plain copy for tests and admin surfaces.
type MetricsSnapshot struct {
PlansCreated int64 `json:"plans_created"`
ShadowPlans int64 `json:"shadow_plans"`
AutoReadPlans int64 `json:"auto_read_plans"`
ConfirmPlans int64 `json:"confirm_plans"`
AllowlistDenied int64 `json:"allowlist_denied"`
CounterfactualWouldExecute int64 `json:"cf_would_execute"`
CounterfactualWouldConfirm int64 `json:"cf_would_confirm"`
CounterfactualWouldClarify int64 `json:"cf_would_clarify"`
CounterfactualWouldReject int64 `json:"cf_would_reject"`
CounterfactualPassthrough int64 `json:"cf_passthrough"`
ReconciliationDiffs int64 `json:"reconciliation_diffs"`
}
func (m *RolloutMetrics) Snapshot() MetricsSnapshot {
if m == nil {
return MetricsSnapshot{}
}
return MetricsSnapshot{
PlansCreated: m.PlansCreated.Load(),
ShadowPlans: m.ShadowPlans.Load(),
AutoReadPlans: m.AutoReadPlans.Load(),
ConfirmPlans: m.ConfirmPlans.Load(),
AllowlistDenied: m.AllowlistDenied.Load(),
CounterfactualWouldExecute: m.CounterfactualWouldExecute.Load(),
CounterfactualWouldConfirm: m.CounterfactualWouldConfirm.Load(),
CounterfactualWouldClarify: m.CounterfactualWouldClarify.Load(),
CounterfactualWouldReject: m.CounterfactualWouldReject.Load(),
CounterfactualPassthrough: m.CounterfactualPassthrough.Load(),
ReconciliationDiffs: m.ReconciliationDiffs.Load(),
}
}
func ResetMetricsForTest() {
globalMetrics = RolloutMetrics{}
}
const (
cfWouldExecute = "would_execute"
cfWouldConfirm = "would_confirm"
cfWouldClarify = "would_clarify"
cfWouldReject = "would_reject"
cfPassthrough = "would_passthrough"
)
func recordCounterfactual(route string) {
m := Metrics()
switch route {
case cfWouldExecute:
m.CounterfactualWouldExecute.Add(1)
case cfWouldConfirm:
m.CounterfactualWouldConfirm.Add(1)
case cfWouldClarify:
m.CounterfactualWouldClarify.Add(1)
case cfWouldReject:
m.CounterfactualWouldReject.Add(1)
default:
m.CounterfactualPassthrough.Add(1)
}
}