Files
weichen 45f21e84c7 feat(jevdecide): product shadow client with offline eval gates
Add typed jevdecide HTTP client (circuit, metrics) and coordinator
DecideAssist wiring behind JEV_DECIDE_MODE (default off). Offline eval
and synthetic soak evidence gate realtime/profile before assist rollout.
2026-09-23 17:40:40 +00:00

61 lines
1.3 KiB
Go

package jevdecide
import "sync/atomic"
// RolloutMetrics tracks shadow/assist outcomes (process-local, no prometheus).
type RolloutMetrics struct {
Agree atomic.Int64
Disagree atomic.Int64
JevFail atomic.Int64
AssistHit atomic.Int64
LatencyMs atomic.Int64 // cumulative ms for latency / (agree+disagree+fail)
Calls atomic.Int64
}
// Global metrics singleton for process-wide soak observation.
var Global = &RolloutMetrics{}
// Snapshot returns a copy of current counters.
func (m *RolloutMetrics) Snapshot() map[string]int64 {
if m == nil {
return map[string]int64{}
}
calls := m.Calls.Load()
lat := m.LatencyMs.Load()
avg := int64(0)
if calls > 0 {
avg = lat / calls
}
return map[string]int64{
"agree": m.Agree.Load(),
"disagree": m.Disagree.Load(),
"jev_fail": m.JevFail.Load(),
"assist_hit": m.AssistHit.Load(),
"calls": calls,
"latency_ms_sum": lat,
"latency_ms_avg": avg,
}
}
// Reset clears counters (tests / soak windows).
func (m *RolloutMetrics) Reset() {
if m == nil {
return
}
m.Agree.Store(0)
m.Disagree.Store(0)
m.JevFail.Store(0)
m.AssistHit.Store(0)
m.LatencyMs.Store(0)
m.Calls.Store(0)
}
// RecordCall accumulates one decide latency sample.
func (m *RolloutMetrics) RecordCall(latencyMs int64) {
if m == nil {
return
}
m.Calls.Add(1)
m.LatencyMs.Add(latencyMs)
}