feat: conversion-driven extensions — resolvers, DefineTool, hooks, ops controls
CI / Tidy (push) Successful in 9m31s
CI / Build & Test (push) Successful in 10m13s

Phase 9a (ADR-0014): Registry.RegisterResolver for dynamic tiers;
DefineTool[Args] typed tools; Usage cache/reasoning detail fields wired
through anthropic/openai/google; WithPromptCaching (Anthropic
cache_control); agent supervision hooks (WithMaxStepsFunc, WithSteer,
WithCompactor, WithToolErrorLimits + ErrToolLoop); health
Bench/Unbench/Snapshot; ChainConfig.Observer failover events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 13:30:06 +02:00
parent 04b21fdad2
commit 0147a79d18
21 changed files with 767 additions and 29 deletions
+53
View File
@@ -15,6 +15,8 @@
package health
import (
"slices"
"strings"
"sync"
"time"
)
@@ -133,6 +135,57 @@ func (t *Tracker) ReportFailure(key string) (backedOff bool) {
return true
}
// Bench manually benches a key until the given time, regardless of its
// failure history (ops surfaces: ".failover bench"). A zero time unbenches.
func (t *Tracker) Bench(key string, until time.Time) {
t.mu.Lock()
defer t.mu.Unlock()
e, ok := t.entries[key]
if !ok {
e = &entry{}
t.entries[key] = e
}
e.until = until
}
// Unbench clears a key entirely: bench window, failure count, and backoff
// history.
func (t *Tracker) Unbench(key string) {
t.mu.Lock()
defer t.mu.Unlock()
delete(t.entries, key)
}
// Status describes one tracked key (diagnostics).
type Status struct {
Key string
// Until is the end of the current bench window (zero = not benched).
Until time.Time
// ConsecutiveFailures since the last success or bench trigger.
ConsecutiveFailures int
// Benches is the consecutive backoff count driving the exponent.
Benches int
}
// Snapshot returns the status of every currently-benched key, sorted by
// key, evaluated at the tracker's clock.
func (t *Tracker) Snapshot() []Status {
t.mu.Lock()
defer t.mu.Unlock()
now := t.cfg.Clock()
out := make([]Status, 0, len(t.entries))
for key, e := range t.entries {
if now.Before(e.until) {
out = append(out, Status{
Key: key, Until: e.until,
ConsecutiveFailures: e.consecutiveFailures, Benches: e.backoffs,
})
}
}
slices.SortFunc(out, func(a, b Status) int { return strings.Compare(a.Key, b.Key) })
return out
}
// BackedOffUntil returns the end of the key's current backoff window, or the
// zero time when the key is not backed off. Useful for diagnostics and error
// messages.