Documentation
¶
Index ¶
- Constants
- Variables
- func Available() string
- func BodyErrorStatus(code int) int
- func DoWithRetry(ctx context.Context, cfg RetryConfig, fn func() (statusCode int, err error)) error
- func IsRetryBudgetExhausted(err error) bool
- func IsRetryableStatus(code int) bool
- func NewHTTPClient() *http.Client
- func NewHTTPTransport() *http.Transport
- func NewStreamScanner(r io.Reader) *bufio.Scanner
- func ReadResponseBody(r io.Reader, maxBytes int64) ([]byte, error)
- func ReadResponseBodyTruncated(r io.Reader, maxBytes int64) (data []byte, truncated bool, err error)
- func Register(name string, factory ProviderFactory)
- func RegisterAuditDecorator(d func(Provider) Provider)
- func RegisterBreaker(key string, b *CircuitBreaker)
- func RegisteredNames() []string
- func ResetAllBreakers()
- func RetryBudgetExhaustedTotal() int64
- func SetHTTPMiddleware(fn func(http.RoundTripper) http.RoundTripper) func(http.RoundTripper) http.RoundTripper
- func ValidEffort(v string) bool
- func WithRetryBudget(ctx context.Context, b *RetryBudget) context.Context
- func WrapErrWithRequestID(ctx context.Context, err error) error
- type BackgroundActivity
- type BackgroundPolicyConfig
- type BreakerConfig
- type BreakerStatus
- type CircuitBreaker
- func (b *CircuitBreaker) Allow() bool
- func (b *CircuitBreaker) Config() BreakerConfig
- func (b *CircuitBreaker) RecordFailure(err error)
- func (b *CircuitBreaker) RecordSuccess()
- func (b *CircuitBreaker) Reset()
- func (b *CircuitBreaker) SetClock(now func() time.Time)
- func (b *CircuitBreaker) Status() BreakerStatus
- type CircuitState
- type Options
- type Provider
- type ProviderConfig
- type ProviderFactory
- type Result
- type RetryBudget
- type RetryConfig
Constants ¶
const DefaultRetryBudget = 10
DefaultRetryBudget is the per-task ceiling applied when neither the task nor the configuration overrides it. With three internal retries per provider call (the default DoWithRetry config), ten attempts cover at least three back-to-back provider invocations before the task is declared failed — enough headroom for normal flake recovery, not enough to drain a budget on a single deeply-stuck task.
const MaxErrorBodyBytes int64 = 64 << 10
MaxErrorBodyBytes caps error-path body reads. Error responses are echoed into user-facing error messages; we do not want to embed megabytes of stack traces or HTML into a wrapped error.
const MaxResponseBytes int64 = 32 << 20
MaxResponseBytes caps a fully-buffered provider response body. The largest realistic AI completion (max output tokens × ~4 bytes/char + thinking + JSON overhead) sits in the low single-digit MB range; 32 MiB is generous enough that legitimate responses are never refused while bounding the damage from a misbehaving server (or proxy injecting a huge HTML error page) that would otherwise OOM the process via io.ReadAll.
const MaxStreamLineBytes = 4 << 20
MaxStreamLineBytes caps a single line in a streamed response (SSE event or NDJSON record). bufio.Scanner's default ceiling is 64 KiB; legitimate provider events can exceed that — Anthropic occasionally emits a single thinking_delta of several hundred KiB, and OpenAI's reasoning summaries can do the same. 4 MiB leaves comfortable headroom while still bounding the damage from a misbehaving server that emits one huge line.
Variables ¶
var EffortLevels = []string{"low", "medium", "high", "xhigh", "max"}
EffortLevels are the valid reasoning-effort levels accepted by Options.Effort, ordered from least to most effort. They mirror the claude CLI's --effort flag values.
var ErrCircuitOpen = errors.New("provider: circuit breaker is open")
ErrCircuitOpen is returned by DoWithRetry when the breaker rejects a request.
var ErrResponseTooLarge = errors.New("provider: response body exceeds maximum allowed size")
ErrResponseTooLarge is returned by ReadResponseBody when the body exceeds the supplied cap.
var ErrRetryBudgetExhausted = errors.New("provider: retry budget exhausted")
ErrRetryBudgetExhausted is returned by DoWithRetry when the per-task retry budget bound to ctx has been fully consumed. It is a distinct failure mode from a plain provider error: the upstream may be perfectly healthy, but the calling task has spent its allotment of provider attempts and the orchestrator must surface this as task-level failure rather than retrying further.
Callers branch on it with errors.Is(err, ErrRetryBudgetExhausted).
Functions ¶
func Available ¶
func Available() string
Available returns a comma-separated list of registered providers.
func BodyErrorStatus ¶
BodyErrorStatus maps an HTTP status to the status a DoWithRetry closure should report when reading or parsing the response body fails. A 2xx status would be classified as a non-retryable client success, but a body failure after a 2xx is a network-level transport error — report 0 so it is retried and counted against the circuit breaker.
func DoWithRetry ¶
DoWithRetry calls fn up to cfg.MaxAttempts times, sleeping with exponential backoff between attempts. fn returns the HTTP status code (0 for non-HTTP errors) and an error. Retries stop when fn returns nil, the context is done, or the error comes from a non-retryable HTTP status.
When ctx carries a request ID (via pkg/reqid) it is included in the returned error wrappers so a single failure can be traced from the orchestrator log → provider error → HTTP-level audit trail without having to correlate timestamps. Errors that already wrap the request ID (the inner fn might do its own wrapping) are not double-tagged: only the outermost retry/breaker error gets the prefix.
func IsRetryBudgetExhausted ¶
IsRetryBudgetExhausted reports whether err is, or wraps, ErrRetryBudgetExhausted. Provided as a small convenience so callers do not have to import errors and remember to use errors.Is.
func IsRetryableStatus ¶
IsRetryableStatus returns true for HTTP status codes worth retrying. 429 = rate limited, 500/502/503/504 = transient server errors.
func NewHTTPClient ¶
NewHTTPClient returns an *http.Client tuned for long-lived AI provider calls.
Why a shared helper: the bare zero-value http.Client used historically had no transport timeouts, so a hung TCP connection (silently dead peer, network blip) could block a Complete() call for up to two hours waiting on the OS keepalive default. Even with context cancellation, the goroutine is stuck inside the syscall until the read returns.
We deliberately do NOT set Client.Timeout because that would cap the entire request including streaming response body reads — a long Anthropic SSE completion legitimately takes minutes and must not be cut off mid-token. Instead we set transport-level timeouts that catch the failure modes that matter: TCP connect hang, TLS handshake hang, server stalling before the first response byte, and dead peers (via TCP keepalive).
func NewHTTPTransport ¶
NewHTTPTransport returns an *http.Transport with the same timeout policy as NewHTTPClient. Exposed separately so callers that need a custom Client (e.g. to set a Client.Timeout for a non-streaming request) can reuse the transport.
func NewStreamScanner ¶
NewStreamScanner returns a *bufio.Scanner configured to read SSE / NDJSON streamed responses. It uses a 64 KiB initial buffer (cheap) and grows up to MaxStreamLineBytes per line. A line that exceeds the cap surfaces as bufio.ErrTooLong via Scanner.Err() — callers should wrap that error with provider context so users can tell streaming-cap-exceeded apart from a transport failure.
func ReadResponseBody ¶
ReadResponseBody reads up to maxBytes from r and returns the buffer. If the body is larger than maxBytes it returns ErrResponseTooLarge with the limit included so the user-facing error is actionable.
Implementation note: we read maxBytes+1 through io.LimitReader; if the final length is greater than maxBytes the cap was exceeded. This avoids a false positive when the body is exactly maxBytes long.
func ReadResponseBodyTruncated ¶
func ReadResponseBodyTruncated(r io.Reader, maxBytes int64) (data []byte, truncated bool, err error)
ReadResponseBodyTruncated reads at most maxBytes from r and reports whether the underlying body was longer. Unlike ReadResponseBody, oversize is not an error — the caller wants whatever diagnostic content is available (typical use: error-path bodies that get embedded in a wrapped error message). The returned buffer is at most maxBytes long.
func Register ¶
func Register(name string, factory ProviderFactory)
Register adds a provider factory to the global registry.
func RegisterAuditDecorator ¶
RegisterAuditDecorator installs the audit-log wrapper used by Build. Idempotent: calling it twice replaces the prior decorator. Pass nil to disable audit logging (useful in tests that don't want a state.db).
func RegisterBreaker ¶
func RegisterBreaker(key string, b *CircuitBreaker)
RegisterBreaker installs a custom-configured breaker under the given key. Replaces any existing entry. Useful in tests and for tuning per provider.
func RegisteredNames ¶
func RegisteredNames() []string
RegisteredNames returns the names of all registered providers as a slice.
func ResetAllBreakers ¶
func ResetAllBreakers()
ResetAllBreakers wipes the breaker registry. Test helper.
func RetryBudgetExhaustedTotal ¶
func RetryBudgetExhaustedTotal() int64
RetryBudgetExhaustedTotal returns the number of Take calls that have returned ErrRetryBudgetExhausted since process start. Used by the metrics layer to expose retry_budget_exhausted_total without having to plumb a callback through every provider implementation.
func SetHTTPMiddleware ¶
func SetHTTPMiddleware(fn func(http.RoundTripper) http.RoundTripper) func(http.RoundTripper) http.RoundTripper
SetHTTPMiddleware installs (or removes, when fn == nil) the wrapper applied to every transport returned by NewHTTPClient. Returns the previous wrapper.
Concurrency: SetHTTPMiddleware may be called at any time, including from inside an init() before any provider is constructed. Already-built http.Client instances are NOT retroactively wrapped — only future NewHTTPClient calls pick up the change. In practice every provider builds its client in NewProvider() at startup, after init() has run.
func ValidEffort ¶
ValidEffort reports whether v is a valid Options.Effort value. The empty string is valid and means "provider default".
func WithRetryBudget ¶
func WithRetryBudget(ctx context.Context, b *RetryBudget) context.Context
WithRetryBudget returns a copy of ctx that carries b. When b is nil the original ctx is returned unchanged, so installing a missing budget at an outer layer does not clobber an inner one.
func WrapErrWithRequestID ¶
WrapErrWithRequestID is the public helper for provider implementations (anthropic, openai, ollama, claudecode) to tag a direct error with the request ID carried by ctx. Streaming and one-shot paths that don't go through DoWithRetry use this so every error returned to the caller is traceable end-to-end.
Returns err unchanged when ctx carries no ID or err is nil.
Types ¶
type BackgroundActivity ¶
type BackgroundActivity struct {
// Detected is how many processes were still running once the harness had
// exited and a short grace window had passed.
Detected int
// Commands names what was running, for operator diagnosis. Bounded, and
// truncated to the kernel's 15-character process names.
Commands []string
// Waited is how long cloop blocked waiting for the work to finish.
Waited time.Duration
// Drained is true when the background work finished on its own within the
// budget. The task's output can then be trusted: the work it started is
// genuinely complete.
Drained bool
// Terminated is how many processes were killed after outliving the
// budget. Zero when Drained, or when the operator opted to keep orphans.
Terminated int
}
BackgroundActivity describes processes an agent harness left running when it exited, and what cloop did about them.
This exists because an agent that starts a long job in the background and then reports success is indistinguishable, from its output alone, from one that actually finished the job. The distinction matters whenever a later task consumes the result: a training run still writing its checkpoint looks exactly like a finished one to the task that reads the model file.
func (*BackgroundActivity) Incomplete ¶
func (b *BackgroundActivity) Incomplete() bool
Incomplete reports whether background work outlived its budget, meaning the task that started it has not actually finished.
func (*BackgroundActivity) Summary ¶
func (b *BackgroundActivity) Summary() string
Summary renders the activity as one line for annotations, logs and the UI.
type BackgroundPolicyConfig ¶
type BackgroundPolicyConfig struct {
Disabled bool
GraceSeconds int
WaitMinutes int
KeepOrphans bool
}
BackgroundPolicyConfig carries pkg/config's background settings to the claudecode factory in units the factory can apply directly.
A plain struct here rather than an import of pkg/config because the dependency runs the other way round: the provider tree is imported by pkg/config's consumers, and a leaf provider must not be made to depend on the configuration package to compile.
type BreakerConfig ¶
type BreakerConfig struct {
// FailureThreshold is the number of failures within FailureWindow that
// trigger a transition from closed to open. Default: 5.
FailureThreshold int
// FailureWindow is the rolling window in which failures are counted.
// Default: 60s.
FailureWindow time.Duration
// Cooldown is the duration the breaker stays open before transitioning
// to half-open and admitting a probe request. Default: 30s.
Cooldown time.Duration
}
BreakerConfig configures a CircuitBreaker.
type BreakerStatus ¶
type BreakerStatus struct {
Key string `json:"key"`
State string `json:"state"`
ConsecutiveFails int `json:"consecutive_failures"`
RecentFailures int `json:"recent_failures"`
OpenedAt time.Time `json:"opened_at,omitempty"`
CooldownRemaining time.Duration `json:"cooldown_remaining"`
LastFailure time.Time `json:"last_failure,omitempty"`
LastError string `json:"last_error,omitempty"`
}
BreakerStatus is a point-in-time snapshot of a breaker for surfacing in the UI or metrics layer. Safe to copy across goroutines.
func AllBreakerStatuses ¶
func AllBreakerStatuses() []BreakerStatus
AllBreakerStatuses returns a snapshot of every registered breaker, sorted by key. The UI/metrics layer can render this directly.
type CircuitBreaker ¶
type CircuitBreaker struct {
// contains filtered or unexported fields
}
CircuitBreaker tracks failures for one logical endpoint (typically a single upstream provider) and short-circuits new requests when the upstream is failing repeatedly. The zero value is not usable; call NewCircuitBreaker.
func GetBreaker ¶
func GetBreaker(key string) *CircuitBreaker
GetBreaker returns the singleton breaker for the given key, creating one with default config if needed. Safe for concurrent use.
func NewCircuitBreaker ¶
func NewCircuitBreaker(key string, cfg BreakerConfig) *CircuitBreaker
NewCircuitBreaker creates a breaker for the given key (used in Status).
func (*CircuitBreaker) Allow ¶
func (b *CircuitBreaker) Allow() bool
Allow checks whether a request should be admitted. It mutates the breaker's state to handle cooldown expiry (open → half-open) and probe gating. Callers MUST follow Allow()==true with exactly one of RecordSuccess or RecordFailure when the request completes.
func (*CircuitBreaker) Config ¶
func (b *CircuitBreaker) Config() BreakerConfig
Config returns the effective configuration (with defaults applied).
func (*CircuitBreaker) RecordFailure ¶
func (b *CircuitBreaker) RecordFailure(err error)
RecordFailure marks the most recent admitted request as failed. May transition the breaker from closed → open (after threshold failures within the rolling window) or half-open → open (probe failed).
func (*CircuitBreaker) RecordSuccess ¶
func (b *CircuitBreaker) RecordSuccess()
RecordSuccess marks the most recent admitted request as successful. In half-open state this closes the breaker and clears all failure history.
func (*CircuitBreaker) Reset ¶
func (b *CircuitBreaker) Reset()
Reset returns the breaker to closed state and clears all history. Intended for tests and explicit operator action.
func (*CircuitBreaker) SetClock ¶
func (b *CircuitBreaker) SetClock(now func() time.Time)
SetClock overrides the breaker's time source. Test-only — not concurrency safe with in-flight Allow/RecordSuccess/RecordFailure calls.
func (*CircuitBreaker) Status ¶
func (b *CircuitBreaker) Status() BreakerStatus
Status returns a point-in-time snapshot for observation. Side-effect free except that it expires an open breaker into half-open if the cooldown has passed and there is no in-flight probe (mirrors what Allow would do).
type CircuitState ¶
type CircuitState int
CircuitState is the operational state of a circuit breaker.
const ( // CircuitClosed means requests flow through normally. CircuitClosed CircuitState = iota // CircuitOpen means requests are rejected without being attempted. CircuitOpen // CircuitHalfOpen means a single probe request is allowed through to test recovery. CircuitHalfOpen )
func (CircuitState) String ¶
func (s CircuitState) String() string
type Options ¶
type Options struct {
Model string
MaxTokens int
Timeout time.Duration
SystemPrompt string
WorkDir string
// OnToken, if set, is called for each token chunk as it streams in.
// When set, the provider should use streaming mode.
// The full output is still returned in Result.Output.
OnToken func(token string)
// OnBackgroundWait, if set, is called once when the provider starts
// waiting for work an agent harness left running after it exited. The
// activity passed in is provisional: Waited and Drained are not yet known.
//
// It exists so a caller can surface the wait as it happens. Without it a
// task blocked for twenty minutes on a training run the agent forgot to
// wait for is indistinguishable, from the outside, from a task that is
// simply slow — which is the confusion this whole mechanism exists to end.
OnBackgroundWait func(activity BackgroundActivity)
// Inference parameters — nil means "use provider default".
// Temperature controls randomness (0 = deterministic, higher = more creative).
// TopP is nucleus sampling threshold (0–1).
// FrequencyPenalty reduces repetition (OpenAI only, 0–2).
Temperature *float64
TopP *float64
FrequencyPenalty *float64
// ExtendedThinking enables reasoning/thinking mode.
// For Anthropic: sends the "thinking" block with budget_tokens=ThinkingBudget.
// For OpenAI o1/o3/o4-mini: sets reasoning_effort based on ThinkingBudget.
ExtendedThinking bool
// ThinkingBudget is the token budget for reasoning/thinking content.
// Anthropic: budget_tokens for the thinking block (default 8000).
// OpenAI: maps to reasoning_effort ("low"/<4000, "medium"/<12000, "high"/>=12000).
ThinkingBudget int
// Effort is the model reasoning-effort level for providers that support
// it. Valid values: "low", "medium", "high", "xhigh", "max"; empty means
// provider default. Currently honored by claudecode (passed as the claude
// CLI's --effort flag); other providers ignore it.
Effort string
// ResumeSession asks a provider that keeps a conversation to continue the
// one a previous Result.SessionID named instead of starting a new one; the
// prompt is then the next message in it. Providers that keep none ignore
// it. Honoured by claudecode (the CLI's --resume), which is how a task
// whose agent ended its turn mid-work is given the turn back with its
// context intact (Task 20349).
ResumeSession string
// ReadOnly asks a provider that runs an agent harness to give it only
// tools that read — no edit, no write, no shell. The review gate sets it
// (Task 20357): a reviewer that could rewrite the work it is judging
// would not be reviewing it. Honoured by claudecode (--tools); HTTP
// providers run no tools and ignore it.
ReadOnly bool
// Env adds variables to the environment of an agent harness process, on
// top of the one it inherits. The review gate uses it to hold the agent's
// pushes (Task 20357). Honoured by claudecode; HTTP providers spawn no
// process and ignore it.
Env []string
}
Options for a completion request.
type Provider ¶
type Provider interface {
Complete(ctx context.Context, prompt string, opts Options) (*Result, error)
Name() string
DefaultModel() string
}
Provider is the interface all AI backends must implement.
func Build ¶
func Build(cfg ProviderConfig) (Provider, error)
Build creates a provider by name using the global registry.
The returned provider is wrapped (innermost → outermost) in:
real provider → WithPanicSafety → WithRequestIDTracing → redaction → audit decorator
so a panic inside the underlying SDK becomes an ordinary error, every error returned to the caller is tagged with the request ID carried in the call context, a credential this process was lent is scrubbed from everything the call returns or streams, and every call (success or failure) lands in the per-project audit log. This is the single chokepoint every cloop command goes through, so wrapping here gives the behaviour to all 80+ Complete call sites without touching them.
The audit decorator runs OUTSIDE request-ID tagging so it sees the final tagged error message; that message is what ends up in the audit row. The decorator is best-effort and can never block the call.
Redaction is inside the audit decorator, not outside it, and the order is the point: the decorator writes the response it receives into provider_calls, so it must receive the scrubbed one. With redaction outermost — as it was until Task 20378's end-to-end test read the table — the caller got a clean response while the audit row kept the credential.
func WithPanicSafety ¶
WithPanicSafety wraps p so that a panic raised inside p.Complete is converted into an ordinary error rather than crashing the host process.
Most cloop commands call p.Complete from a single foreground goroutine (e.g. the long-running `cloop run` orchestrator, ad-hoc commands like `cloop scope`/`cloop critique`/`cloop ask`). A nil-pointer deref inside a third-party SDK or a bad JSON cast inside a provider's response parser would otherwise tear the entire process down — losing every queued task, every cached state mutation that hadn't been flushed, and the WS server.
The Build factory below applies this wrapper unconditionally, so every provider returned to the rest of the codebase is already panic-safe. Per-package safeComplete helpers in pkg/orchestrator and pkg/multiagent remain in place as defense-in-depth; with this wrapper they should now be unreachable on the panic branch but still correct.
On panic, the full stack trace is logged to stderr at the recovery point and the returned error matches the existing "provider panic in <name>" shape so callers that already match against that string keep working.
func WithRedaction ¶ added in v0.0.2
WithRedaction returns p with credential scrubbing applied, or p unchanged when there is nothing to scrub — which is the ordinary case for a project with no grants, and must cost nothing.
func WithRequestIDTracing ¶
WithRequestIDTracing decorates p so that every error returned from p.Complete is automatically tagged with the request ID carried by ctx (via pkg/reqid). Successful results pass through unchanged.
The wrapper sits OUTSIDE retry/breaker logic — retries internal to the provider already use DoWithRetry, which tags its own outermost error. What this layer adds: error paths that bypass retry (streaming-only providers, claudecode subprocess execution, request-construction failures) also receive the tag, so a future log aggregation never sees an untagged error from a request that did flow through the middleware.
In addition, when ctx carries no request ID at all the wrapper mints a fresh one (via reqid.EnsureContext) and binds it to the context handed down to the inner provider. This guarantees every provider call in the process is traceable, even when invoked from a CLI command that did not go through the HTTP middleware (e.g. `cloop run` from a shell).
The wrapper is idempotent: applying it twice has the same effect as applying it once. The factory.go Build pipeline composes it with WithPanicSafety so callers get both behaviours by default.
type ProviderConfig ¶
type ProviderConfig struct {
// Which provider to use
Name string
// Anthropic settings
AnthropicAPIKey string
AnthropicBaseURL string
// OpenAI settings
OpenAIAPIKey string
OpenAIBaseURL string
// Ollama settings
OllamaBaseURL string
// Mock settings
MockResponsesFile string
// ClaudeCodeBackground tunes the handling of work the claude CLI leaves
// running after it exits (Task 20205). The zero value detects it, waits
// for it, and terminates whatever outlives the wait.
ClaudeCodeBackground BackgroundPolicyConfig
}
ProviderConfig holds settings for all providers, used by the factory.
type ProviderFactory ¶
type ProviderFactory func(cfg ProviderConfig) (Provider, error)
ProviderFactory is a function that creates a Provider from a ProviderConfig.
type Result ¶
type Result struct {
Output string
Duration time.Duration
Provider string
Model string
InputTokens int // tokens in the prompt/input
OutputTokens int // tokens in the completion/output
// ThinkingTokens is the number of tokens used for reasoning/thinking content
// (Anthropic extended thinking, OpenAI reasoning tokens). Estimated from output.
ThinkingTokens int
// Background reports work the harness left running after it exited, for
// providers that spawn one. nil means none was detected — which is also
// what the HTTP providers report, since they spawn no processes at all.
//
// A non-nil value with Drained false means the output above cannot be
// trusted as a finished result: the harness described work it had only
// started. Callers that gate other work on this one must treat that as
// incomplete rather than as success.
Background *BackgroundActivity
// SessionID names the conversation this call ran in, for a provider that
// can continue it through Options.ResumeSession. Empty when it cannot.
SessionID string
}
Result from a completion request.
type RetryBudget ¶
type RetryBudget struct {
// contains filtered or unexported fields
}
RetryBudget caps the total number of provider attempts a single logical task is allowed to consume across all DoWithRetry calls made from goroutines that share its context. A "task" here is anything the orchestrator scopes a budget to — typically one entry in pm.Plan.Tasks, but sub-agent fan-out, consensus voting, and heal retries also share the same budget so a misbehaving task cannot escape the cap by spawning helpers.
All methods are safe for concurrent use. Multiple goroutines may share a single *RetryBudget; the counter is updated with sync/atomic so parallel sub-calls cannot race past the limit.
The zero value is NOT usable; call NewRetryBudget. A nil *RetryBudget is treated as "no budget configured" — Take returns nil so existing call sites that do not wire a budget keep working unchanged.
func NewRetryBudget ¶
func NewRetryBudget(limit int) *RetryBudget
NewRetryBudget returns a budget allowing up to limit total provider attempts. limit values <= 0 are clamped to DefaultRetryBudget so a hand-constructed Config{Task.RetryBudget: 0} or a forgotten override never produces a no-cap budget.
func RetryBudgetFromContext ¶
func RetryBudgetFromContext(ctx context.Context) *RetryBudget
RetryBudgetFromContext returns the budget bound to ctx, or nil when none is present. Callers MUST treat a nil return as "no budget configured" and proceed normally.
func (*RetryBudget) Limit ¶
func (b *RetryBudget) Limit() int
Limit returns the maximum number of attempts the budget allows. A nil receiver returns 0 ("no budget configured").
func (*RetryBudget) Remaining ¶
func (b *RetryBudget) Remaining() int
Remaining returns the number of attempts still available, never negative. A nil receiver returns 0.
func (*RetryBudget) Take ¶
func (b *RetryBudget) Take() error
Take consumes one attempt from the budget. It returns nil while the budget has room and ErrRetryBudgetExhausted (without actually rolling back the counter) once the limit has been crossed.
A nil receiver returns nil — call sites that have not wired a budget behave exactly as they did before this type existed. Concurrent calls are safe: even if N goroutines invoke Take simultaneously, at most limit of them will observe a nil return.
func (*RetryBudget) Used ¶
func (b *RetryBudget) Used() int
Used returns the number of attempts that have been consumed so far. May exceed Limit() if Take has been called past exhaustion (the over- budget calls return ErrRetryBudgetExhausted but the counter still advances so audit logs show the true demand).
type RetryConfig ¶
type RetryConfig struct {
MaxAttempts int // total attempts including the first (default: 3)
InitialDelay time.Duration // base delay before the first retry (default: 1s)
MaxDelay time.Duration // cap on delay between retries (default: 30s)
// BreakerKey, if non-empty, gates the call through a circuit breaker
// looked up from the global registry (or created with default config).
// When the breaker is open the call short-circuits with ErrCircuitOpen
// without consuming an attempt or contacting the upstream.
BreakerKey string
}
RetryConfig controls retry behavior for provider HTTP requests.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package anthropic provides direct access to the Anthropic Messages API.
|
Package anthropic provides direct access to the Anthropic Messages API. |
|
Package cached provides a CachedProvider decorator that wraps any Provider and transparently caches responses to avoid redundant identical API calls.
|
Package cached provides a CachedProvider decorator that wraps any Provider and transparently caches responses to avoid redundant identical API calls. |
|
Package claudecode wraps the claude CLI binary as a provider.
|
Package claudecode wraps the claude CLI binary as a provider. |
|
Package fallback provides a Provider that tries a list of providers in order, returning the first successful result.
|
Package fallback provides a Provider that tries a list of providers in order, returning the first successful result. |
|
Package mock provides a deterministic offline provider for CI and testing.
|
Package mock provides a deterministic offline provider for CI and testing. |
|
Package ollama provides access to locally running Ollama models.
|
Package ollama provides access to locally running Ollama models. |
|
Package openai provides access to the OpenAI Chat Completions API and any OpenAI-compatible API (e.g.
|
Package openai provides access to the OpenAI Chat Completions API and any OpenAI-compatible API (e.g. |