toolwrap

package
v0.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 10, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

README

toolwrap — Composable Middleware for LLM Tool Execution

toolwrap is a library that applies composable middleware to LLM tool calls, following the same func(next) next pattern used by HTTP middleware stacks. Each middleware implements the Middleware interface and can be used independently, composed into chains, or mixed with custom implementations.

Architecture

          ┌──────────────────────────────────────────────────────────────────┐
          │                        Wrapper.Call()                           │
          │                                                                  │
          │  ToolCallContext{ToolName, Args, OriginalArgs, Justification}   │
          └─────────────┬────────────────────────────────────────────────────┘
                        │
                        ▼
 ┌────────────────────────────────────────────────────────────────────────────┐
 │                        Middleware Chain                                    │
 │                                                                            │
 │  [ContextMode] → PanicRecovery → [Tracing] → [Metrics] → Emitter →        │
 │  Logger → Audit → [Timeout] → [RateLimit] → [CircuitBreaker] →            │
 │  [Concurrency] → [Retry] → LoopDetection → FailureLimit →                 │
 │  SemanticCache → [Validation] → [Sanitize] → HITLApproval →               │
 │  ContextEnrich → execute()                                                │
 │                                                                            │
 │  Always-on middlewares run unconditionally.                                │
 │  [Bracketed] middlewares are opt-in via MiddlewareConfig.                  │
 └────────────────────────────────────────────────────────────────────────────┘

Core Types

// Handler processes a single tool call.
type Handler func(ctx context.Context, tc *ToolCallContext) (any, error)

// Middleware wraps a Handler with cross-cutting behaviour.
type Middleware interface {
    Wrap(next Handler) Handler
}

// MiddlewareFunc is the function adapter for Middleware.
type MiddlewareFunc func(next Handler) Handler

// CompositeMiddleware composes a slice of Middlewares into one.
type CompositeMiddleware []Middleware

Using as a Library

Quick Start — Default Chain
svc := toolwrap.NewService(auditor, approvalStore)
wrapped := svc.Wrap(tools, toolwrap.WrapRequest{
    EventChan: evChan,
    ThreadID:  threadID,
    RunID:     runID,
})
// wrapped tools now have all core middlewares applied
Custom Chain — Pick and Choose
mw := toolwrap.CompositeMiddleware{
    toolwrap.PanicRecoveryMiddleware(),
    toolwrap.TimeoutMiddleware(30 * time.Second),
    toolwrap.RateLimitMiddleware(toolwrap.RateLimitConfig{
        GlobalRatePerMinute: 60,
        PerToolRatePerMinute: map[string]float64{"web_search": 10},
    }),
    toolwrap.RetryMiddleware(toolwrap.RetryConfig{MaxAttempts: 3}),
    toolwrap.TracingMiddleware(),
}

w := &toolwrap.Wrapper{Tool: myTool}
w.SetMiddleware(mw) // or pass via NewWrapper + MiddlewareDeps
result, err := w.Call(ctx, args)
Single Middleware — Standalone Use

Every middleware works independently. You can wrap any Handler directly:

handler := toolwrap.TimeoutMiddleware(5 * time.Second).Wrap(myHandler)
result, err := handler(ctx, tc)
Config-Driven — YAML/TOML
toolwrap:
  context_mode:
    enabled: true     # opt-in; set to true to activate
    threshold: 20000    # compress responses above this char count
  timeout:
    enabled: true
    default: 30s
    per_tool:
      execute_code: 120s
      web_search: 15s
  rate_limit:
    enabled: true
    global_rate_per_minute: 60
    per_tool_rate_per_minute:
      web_search: 10
  circuit_breaker:
    enabled: true
    failure_threshold: 5
    open_duration: 30s
  concurrency:
    enabled: true
    global_limit: 10
    per_tool_limits:
      web_search: 3
  retry:
    enabled: true
    max_attempts: 3
    initial_backoff: 500ms
    max_backoff: 10s
  metrics:
    enabled: true
    prefix: "myapp.tools"
  tracing:
    enabled: true
  sanitize:
    enabled: true
    replacement: "[REDACTED]"
    per_tool:
      read_file: ["API_KEY", "password", "secret"]

Middleware Reference

Always-On (Core)

These middlewares are always included in the default chain and have no config toggle.


PanicRecoveryMiddleware

File: mw_loop.go · Position: Outermost

Recovers from panics in any downstream handler (e.g. OTel span.End on a closed channel) and converts them into a structured error. Without this, a single panicking tool would crash the entire server process.

toolwrap.PanicRecoveryMiddleware()
Aspect Detail
State Stateless
Per-tool N/A
Short-circuits On panic → returns error

LoopDetectionMiddleware

File: mw_loop.go

Detects when the LLM calls the same tool with identical arguments more than 3 times consecutively and blocks further calls. Uses SHA1-based fingerprinting with a bounded sliding window (10 entries). Prevents infinite loops where a stuck agent re-issues the same call.

toolwrap.LoopDetectionMiddleware()
Aspect Detail
State Per-instance mutex + []string history
Per-tool Implicit — fingerprint includes tool name
Threshold 3 consecutive identical calls
Short-circuits On loop → returns error

FailureLimitMiddleware

File: mw_loop.go

Blocks a tool after 3 consecutive failures (any args), preventing the LLM from burning tokens on a service that's down or rate-limited. A single success resets the counter. Unlike CircuitBreaker, there is no automatic recovery timer — the tool stays blocked until success.

toolwrap.FailureLimitMiddleware()
Aspect Detail
State map[string]int (tool → failure count)
Per-tool ✅ independent counter per tool name
Threshold 3 consecutive failures
Recovery On any success for that tool

SemanticCacheMiddleware

File: mw_cache.go

Deduplicates idempotent tool calls by caching results keyed on configurable semantic identity fields. Only tools with configured key fields are eligible. Entries expire after 120 seconds (TTL). Max 128 entries.

toolwrap.SemanticCacheMiddleware(map[string][]string{
    "create_recurring_task": {"name"},
})
Aspect Detail
State TTLMap[any] (bounded, TTL-based)
Per-tool ✅ key fields configured per tool
TTL 120s
Short-circuits On cache hit → returns cached result

HITLApprovalMiddleware

File: mw_hitl.go

Gates non-readonly tool calls on human approval. Extracts the optional _justification field from args, creates an approval request via the ApprovalStore, emits a TOOL_APPROVAL_REQUEST event to the UI, and blocks until resolved. Maintains a session-scoped approval cache (256 entries, FIFO eviction) to avoid re-prompting for the same tool+args. Stores user feedback in WorkingMemory.

toolwrap.HITLApprovalMiddleware(store, eventChan, threadID, runID, workingMemory)
Aspect Detail
State Approval cache (map[string]struct{})
Per-tool IsAllowed() allowlists readonly tools
Approval cache 256 entries, SHA-256 keyed
Short-circuits On rejection → error; on feedback → re-planning error

ContextEnrichMiddleware

File: mw_context.go

Injects EventChan, ThreadID, RunID, and MessageOrigin into the context.Context before tool execution. Ensures nested tools (e.g. sub-agents via create_agent) can propagate HITL values even when the runner creates a fresh Go context.

toolwrap.ContextEnrichMiddleware(eventChan, threadID, runID, origin)
Aspect Detail
State Stateless (reads from deps)
Per-tool N/A — applies to all
Position Innermost — runs just before execute()

EmitterMiddleware

File: mw_emitter.go

Emits AgentToolResponseMsg events to the TUI event channel after every tool call, including the (truncated) response and any error. Falls back to EventChanFromContext for sub-agent calls.

toolwrap.EmitterMiddleware(eventChan)
Aspect Detail
State Stateless
Per-tool N/A — emits for all tools

LoggerMiddleware

File: mw_logger.go

Logs the outcome of every tool call — Debug level for success, Error level for failure — using the context logger.

toolwrap.LoggerMiddleware()

AuditMiddleware

File: mw_audit.go

Writes tool call results to the audit trail via the audit.Auditor interface. Includes redacted args, response length, truncation status, and error. Falls back to a basic stderr JSON logger when no auditor is provided.

toolwrap.AuditMiddleware(auditor)

Opt-In Middlewares

These are enabled via MiddlewareConfig fields in the config file. All default to disabled.


TimeoutMiddleware / PerToolTimeoutMiddleware

File: mw_timeout.go · Config: timeout

Enforces a maximum execution time per tool call via context.WithTimeout. PerToolTimeoutMiddleware supports per-tool overrides with a fallback default.

// Global timeout
toolwrap.TimeoutMiddleware(30 * time.Second)

// Per-tool overrides
toolwrap.PerToolTimeoutMiddleware(
    map[string]time.Duration{"execute_code": 2 * time.Minute},
    30 * time.Second, // fallback
)

Config:

timeout:
  enabled: true
  default: 30s
  per_tool:
    execute_code: 120s
Aspect Detail
State Stateless
Per-tool ✅ via per_tool map
Short-circuits On deadline exceeded → context.DeadlineExceeded

RateLimitMiddleware

File: mw_ratelimit.go · Config: rate_limit
Backed by: golang.org/x/time/rate

Token-bucket rate limiter with global and per-tool limits. When the bucket is exhausted, the call is rejected immediately (non-blocking). Per-tool limiters are lazily created on first use.

toolwrap.RateLimitMiddleware(toolwrap.RateLimitConfig{
    GlobalRatePerMinute:  60,
    PerToolRatePerMinute: map[string]float64{"web_search": 10},
})

Config:

rate_limit:
  enabled: true
  global_rate_per_minute: 60
  per_tool_rate_per_minute:
    web_search: 10
Aspect Detail
State *rate.Limiter per tool (lazily created)
Per-tool per_tool_rate_per_minute map
Algorithm Token bucket (golang.org/x/time/rate)
Short-circuits On limit exceeded → error

CircuitBreakerMiddleware

File: mw_circuitbreaker.go · Config: circuit_breaker
Backed by: sony/gobreaker/v2

Full three-state circuit breaker (closed → open → half-open → closed) with per-tool breaker instances. Each tool gets its own TwoStepCircuitBreaker. When the failure threshold is reached, the circuit opens and all calls fail fast. After the cooldown (OpenDuration), a probe call is allowed. If it succeeds, the circuit closes.

toolwrap.CircuitBreakerMiddleware(toolwrap.CircuitBreakerConfig{
    FailureThreshold: 5,
    OpenDuration:     30 * time.Second,
    HalfOpenMaxCalls: 1,
})

Config:

circuit_breaker:
  enabled: true
  failure_threshold: 5
  open_duration: 30s
  half_open_max_calls: 1
Aspect Detail
State *gobreaker.TwoStepCircuitBreaker per tool
Per-tool ✅ independent breaker per tool name
States Closed → Open → Half-Open → Closed
Short-circuits When open → error; when half-open and max probes reached → error

ConcurrencyMiddleware

File: mw_concurrency.go · Config: concurrency
Backed by: golang.org/x/sync/semaphore

Limits the number of concurrent tool executions using weighted semaphores. Supports both a global cap and per-tool overrides (bulkhead pattern). When the limit is reached, the call blocks until a slot frees up or the context is cancelled.

toolwrap.ConcurrencyMiddleware(toolwrap.ConcurrencyConfig{
    GlobalLimit:   10,
    PerToolLimits: map[string]int{"web_search": 3},
})

Config:

concurrency:
  enabled: true
  global_limit: 10
  per_tool_limits:
    web_search: 3
Aspect Detail
State *semaphore.Weighted per tool + global
Per-tool per_tool_limits map
Behaviour Blocks (not rejects) until slot available

RetryMiddleware

File: mw_retry.go · Config: retry
Backed by: cenkalti/backoff/v4

Automatically retries failed tool calls with exponential backoff and jitter. The Retryable predicate decides whether an error is transient. Non-retryable errors propagate immediately via backoff.Permanent. Context cancellation stops retries.

toolwrap.RetryMiddleware(toolwrap.RetryConfig{
    MaxAttempts:    3,
    InitialBackoff: 500 * time.Millisecond,
    MaxBackoff:     10 * time.Second,
    Retryable:      func(err error) bool { return !errors.Is(err, ErrFatal) },
})

Config:

retry:
  enabled: true
  max_attempts: 3
  initial_backoff: 500ms
  max_backoff: 10s
Aspect Detail
State Stateless per-call
Per-tool N/A (global config)
Algorithm Exponential backoff + jitter (cenkalti/backoff/v4)
Retryable Configurable predicate (nil = all errors retryable)

MetricsMiddleware

File: mw_metrics.go · Config: metrics

Records OpenTelemetry metrics for every tool call:

Metric Type Description
{prefix}.call.count Counter Total tool calls
{prefix}.call.duration_ms Histogram Call latency in ms
{prefix}.call.errors Counter Failed calls

All metrics carry a tool.name attribute for per-tool breakdown.

cfg.Metrics.MetricsMiddleware() // prefix from config

Config:

metrics:
  enabled: true
  prefix: "myapp.tools"

TracingMiddleware

File: mw_tracing.go · Config: tracing

Creates an OpenTelemetry span for every tool call. Records tool name, argument size, errors, and injects span context for correlation with nested/sub-agent calls.

toolwrap.TracingMiddleware()

Config:

tracing:
  enabled: true
Aspect Detail
Span name tool.<tool_name>
Attributes tool.name, tool.args.size
Error handling span.RecordError() + codes.Error

InputValidationMiddleware

File: mw_validate.go · Config: validation

Validates tool call arguments against the tool's declared InputSchema before execution. Checks JSON validity and required field presence. Intentionally lightweight (no full JSON Schema validation) to keep the dependency footprint small.

toolwrap.InputValidationMiddleware(func(name string) *tool.Declaration {
    return registry.Get(name)
})
Aspect Detail
Checks Valid JSON + required fields from schema
Per-tool ✅ uses each tool's declared schema
Short-circuits On validation failure → error with field name

OutputSanitizationMiddleware

File: mw_sanitize.go · Config: sanitize

Scrubs sensitive data from tool outputs using case-insensitive pattern matching. Patterns are configured per tool — tools not in the map pass through unmodified.

toolwrap.OutputSanitizationMiddleware(
    map[string][]string{
        "read_file": {"API_KEY", "password", "secret"},
    },
    "[REDACTED]",
)

Config:

sanitize:
  enabled: true
  replacement: "[REDACTED]"
  per_tool:
    read_file: ["API_KEY", "password", "secret"]
    execute_code: ["token"]
Aspect Detail
Matching Case-insensitive substring
Per-tool per_tool map (tool → patterns)
Position Post-execution (sanitizes output before it reaches the LLM)

ContextModeMiddleware

File: mw_contextmode.go · Config: context_mode

Compresses oversized tool responses using local BM25-like chunk scoring — no LLM call required. Chunks text on paragraph boundaries, scores each chunk against query terms extracted from the tool's input arguments, and returns only the top-K most relevant chunks in reading order. Sits before AutoSummarizeMiddleware as a cheap first-pass reducer: if context mode shrinks a response below the summarise threshold, the LLM summariser is never called.

toolwrap.ContextModeMiddleware(toolwrap.ContextModeConfig{
    Threshold: 20000,
    MaxChunks: 10,
    ChunkSize: 800,
})

Config:

context_mode:
  enabled: true       # opt-in; set true to activate
  threshold: 20000     # compress responses above this char count
  max_chunks: 10       # return at most this many top-scored chunks
  chunk_size: 800      # target chars per chunk
Aspect Detail
Algorithm BM25-lite (TF-IDF with saturation + length normalisation)
State Stateless
Per-tool N/A — applies to all tools
Fallback Boundary selection (first + last chunks) when no query terms available
Position Post-execution, before AutoSummarizeMiddleware

Writing Custom Middleware

Implement the Middleware interface or use MiddlewareFunc:

// As an interface
type myMiddleware struct{ /* deps */ }

func (m *myMiddleware) Wrap(next toolwrap.Handler) toolwrap.Handler {
    return func(ctx context.Context, tc *toolwrap.ToolCallContext) (any, error) {
        // before
        result, err := next(ctx, tc)
        // after
        return result, err
    }
}

// As a function
func MyMiddleware() toolwrap.MiddlewareFunc {
    return func(next toolwrap.Handler) toolwrap.Handler {
        return func(ctx context.Context, tc *toolwrap.ToolCallContext) (any, error) {
            // your logic
            return next(ctx, tc)
        }
    }
}

Compose into any chain:

chain := toolwrap.CompositeMiddleware{
    MyMiddleware(),
    toolwrap.PanicRecoveryMiddleware(),
    toolwrap.TimeoutMiddleware(30 * time.Second),
}

Files

File Contents
middleware.go Core types: Handler, Middleware, MiddlewareFunc, CompositeMiddleware, Chain
config.go MiddlewareConfig — central config aggregating all sub-configs
wrapper.go Wrapper, NewWrapper, MiddlewareDeps, DefaultMiddlewares
service.go Service, NewService, WrapRequest — production entry point
context.go WithOriginalQuestion / OriginalQuestionFrom context helpers
mw_loop.go PanicRecoveryMiddleware, LoopDetectionMiddleware, FailureLimitMiddleware
mw_cache.go SemanticCacheMiddleware
mw_hitl.go HITLApprovalMiddleware
mw_context.go ContextEnrichMiddleware
mw_emitter.go EmitterMiddleware
mw_logger.go LoggerMiddleware
mw_audit.go AuditMiddleware
mw_timeout.go TimeoutMiddleware, PerToolTimeoutMiddleware
mw_ratelimit.go RateLimitMiddleware
mw_circuitbreaker.go CircuitBreakerMiddleware
mw_concurrency.go ConcurrencyMiddleware
mw_retry.go RetryMiddleware
mw_metrics.go MetricsMiddleware
mw_tracing.go TracingMiddleware
mw_validate.go InputValidationMiddleware
mw_sanitize.go OutputSanitizationMiddleware
mw_contextmode.go ContextModeMiddleware — local BM25 chunk compression
mw_summarize.go AutoSummarizeMiddleware — LLM-backed summarisation

Documentation

Overview

Package toolwrap provides composable middleware for tool execution.

Each Middleware wraps a Handler, returning a new Handler with added behaviour (audit logging, HITL approval, caching, loop detection, etc.). The Service assembles the default middleware chain via DefaultMiddlewares

Package toolwrap – context-mode middleware for local tool-output compression.

Package toolwrap – PII rehydration of tool-call arguments before execution.

Package toolwrap – auto-summarization middleware for large tool results.

Package toolwrap provides composable middleware for tool execution.

Each Middleware wraps a Handler, returning a new Handler with added behaviour (audit logging, HITL approval, caching, loop detection, etc.). The Service assembles the default middleware chain via DefaultMiddlewares and callers can customise or extend the chain.

The Service holds session-stable dependencies (Auditor, ApprovalStore) so callers only need to supply per-request fields (WorkingMemory, ThreadID, RunID) via WrapRequest.

Index

Constants

View Source
const AuditEventHITLDecision audit.EventType = "hitl_decision"

AuditEventHITLDecision is the audit event type for HITL approval/rejection.

Variables

This section is empty.

Functions

func IsRetrievalTool added in v0.1.6

func IsRetrievalTool(name string) bool

IsRetrievalTool reports whether the given tool name is classified as a retrieval-only tool (memory_search, graph_query, etc.).

func OriginalQuestionFrom

func OriginalQuestionFrom(ctx context.Context) string

OriginalQuestionFrom extracts the original question from the context.

func TruncateForAudit

func TruncateForAudit(s string, maxLen int) string

TruncateForAudit truncates a string to maxLen runes for audit log metadata.

func WithCancelCause added in v0.1.6

func WithCancelCause(ctx context.Context, fn context.CancelCauseFunc) context.Context

WithCancelCause stores a context.CancelCauseFunc in the context so that middleware (loop detection) can terminate the sub-agent run. Only sub-agents set this; the parent agent's context has no cancel function, preserving backward compatibility.

func WithOriginalQuestion

func WithOriginalQuestion(ctx context.Context, question string) context.Context

WithOriginalQuestion stashes the user's original question in the context so that requireApproval can persist it alongside the approval row. Without this, recovered approvals after a restart would have no way to replay the original request through the chat handler.

Types

type ApproveList

type ApproveList struct {
	// contains filtered or unexported fields
}

ApproveList is an in-memory, thread-safe list of tool approvals that bypass the HITL gate for a limited duration. Blind entries are stored in a TTL+LRU cache so expired entries are evicted on access and size is bounded. Filter entries are pruned opportunistically when adding new entries.

func NewApproveList

func NewApproveList() *ApproveList

NewApproveList creates an empty in-memory approve list.

func (*ApproveList) AddBlind

func (l *ApproveList) AddBlind(toolName string, duration time.Duration)

AddBlind adds a tool to the approve list for the given duration. Any call to that tool within the duration is auto-approved regardless of args.

func (*ApproveList) AddWithArgsFilter

func (l *ApproveList) AddWithArgsFilter(toolName string, substrings []string, duration time.Duration)

AddWithArgsFilter adds a tool to the approve list when args contain any of the given substrings, for the given duration. If substrings is empty, it behaves like AddBlind for that tool.

func (*ApproveList) IsApproved

func (l *ApproveList) IsApproved(toolName string, args string) bool

IsApproved returns true if the tool is on the approve list and not expired. For blind entries, any args match. For filter entries, args must contain at least one of the stored substrings. Blind entries use a TTL+LRU cache so expired entries are evicted on access.

type CircuitBreakerConfig

type CircuitBreakerConfig struct {
	// Enabled activates the circuit breaker middleware. Defaults to false.
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
	// FailureThreshold is the number of consecutive failures before the
	// circuit opens. Defaults to 5 if zero.
	FailureThreshold int `yaml:"failure_threshold,omitempty" toml:"failure_threshold,omitempty,omitzero"`
	// OpenDuration is how long the circuit stays open before transitioning
	// to half-open for a probe. Defaults to 30s if zero.
	OpenDuration time.Duration `yaml:"open_duration,omitempty" toml:"open_duration,omitempty"`
	// HalfOpenMaxCalls is the number of test calls allowed in half-open
	// state before deciding to close or re-open. Defaults to 1 if zero.
	HalfOpenMaxCalls int `yaml:"half_open_max_calls,omitempty" toml:"half_open_max_calls,omitempty,omitzero"`
}

CircuitBreakerConfig configures the CircuitBreakerMiddleware.

type CircuitBreakerMW

type CircuitBreakerMW struct {
	// contains filtered or unexported fields
}

CircuitBreakerMW implements the standard three-state circuit breaker (closed → open → half-open → closed) using sony/gobreaker.

The TwoStepCircuitBreaker separates the "allow" check from the "report outcome" callback, which makes it straightforward to swap the in-memory state to a remote backend (Redis, DynamoDB) — you only need to replace the inner breaker with one that reads/writes counts externally. See gobreaker.Counts for available counters.

The optional scope field prefixes breaker keys so that the same tool name in different agents gets independent circuit state. This prevents policy-denied failures in one agent from opening the circuit globally.

func CircuitBreakerMiddleware

func CircuitBreakerMiddleware(cfg CircuitBreakerConfig) *CircuitBreakerMW

CircuitBreakerMiddleware creates a per-tool circuit breaker middleware backed by sony/gobreaker. Each tool gets its own breaker instance.

func (*CircuitBreakerMW) OpenTools

func (m *CircuitBreakerMW) OpenTools() []string

OpenTools returns the names of tools whose circuit breaker is currently in the Open state. The adaptive loop uses this to remove broken tools from the LLM's tool set — preventing wasted calls where the LLM tries a broken tool only to get rejected by the middleware.

When a scope is set, only breakers matching this scope are considered and the scope prefix is stripped from the returned tool names.

func (*CircuitBreakerMW) WithScope

func (m *CircuitBreakerMW) WithScope(scope string) *CircuitBreakerMW

WithScope returns a new CircuitBreakerMW that shares the same breaker map and config but uses the given scope as a key prefix. This scopes circuit state per-agent so that failures (e.g. policy denials) in one agent don't open the circuit for other agents using the same tool.

If scope is empty, 'this' is returned unchanged. Without per-agent scoping, a policy that denies tool X in agent A would trip the circuit globally, preventing agent B from using tool X even though agent B has no such policy.

func (*CircuitBreakerMW) Wrap

func (m *CircuitBreakerMW) Wrap(next Handler) Handler

type CompositeMiddleware

type CompositeMiddleware []Middleware

func (CompositeMiddleware) Wrap

func (c CompositeMiddleware) Wrap(next Handler) Handler

type ConcurrencyConfig

type ConcurrencyConfig struct {
	// Enabled activates the concurrency middleware. Defaults to false.
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
	// GlobalLimit is the maximum number of concurrent tool calls across
	// all tools. Zero means no global limit.
	GlobalLimit int `yaml:"global_limit,omitempty" toml:"global_limit,omitempty,omitzero"`
	// PerToolLimits maps tool names to per-tool concurrency caps.
	// Tools not in the map are only subject to the global limit.
	PerToolLimits map[string]int `yaml:"per_tool_limits,omitempty" toml:"per_tool_limits,omitempty"`
}

ConcurrencyConfig configures the ConcurrencyMiddleware.

type ContextModeConfig

type ContextModeConfig struct {
	// Enabled activates the context-mode middleware. Default: false (disabled).
	// Set to true to opt in.
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
	// Threshold is the character count above which a response is compressed.
	// When 0, defaultContextModeThreshold (20 000) is used.
	Threshold int `yaml:"threshold,omitempty" toml:"threshold,omitempty"`
	// MaxChunks is the maximum number of scored chunks to return.
	// When 0, defaultMaxChunks (10) is used.
	MaxChunks int `yaml:"max_chunks,omitempty" toml:"max_chunks,omitempty"`
	// ChunkSize is the target character count per chunk.
	// When 0, defaultChunkSize (800) is used.
	ChunkSize int `yaml:"chunk_size,omitempty" toml:"chunk_size,omitempty"`
	// MinTermLen is the minimum character length for a query term to be
	// considered meaningful. When 0, the package default (3) is used.
	// Lowering this value retains shorter tokens such as 2-character IDs.
	MinTermLen int `yaml:"min_term_len,omitempty" toml:"min_term_len,omitempty"`
	// PerTool maps tool names to per-tool overrides. Non-zero fields in the
	// override replace the global defaults for that tool. This lets operators
	// tune compression aggressiveness per tool — e.g. relax thresholds for
	// run_shell output.
	PerTool map[string]ContextModeToolOverride `yaml:"per_tool,omitempty" toml:"per_tool,omitempty"`
}

ContextModeConfig controls the context-mode middleware.

type ContextModeToolOverride added in v0.1.6

type ContextModeToolOverride struct {
	Threshold  int `yaml:"threshold,omitempty" toml:"threshold,omitempty"`
	MaxChunks  int `yaml:"max_chunks,omitempty" toml:"max_chunks,omitempty"`
	ChunkSize  int `yaml:"chunk_size,omitempty" toml:"chunk_size,omitempty"`
	MinTermLen int `yaml:"min_term_len,omitempty" toml:"min_term_len,omitempty"`
}

ContextModeToolOverride holds per-tool overrides for the context-mode middleware. Only non-zero fields take effect; zero values fall back to the global defaults.

type DirectCaller

type DirectCaller struct{}

DirectCaller is the default ToolCaller that invokes tools in-process via the tool.CallableTool interface. This is the legacy execution path and is used when no alternative caller is configured.

func (DirectCaller) CallTool

func (DirectCaller) CallTool(_ context.Context, _ string, _ []byte) (any, error)

CallTool implements ToolCaller by casting to tool.CallableTool. This is never used directly — it exists as documentation. The Wrapper.execute() method uses the same logic inline for backward compatibility.

type HITLOption

type HITLOption func(*hitlApprovalMiddleware)

HITLOption configures the behaviour of the HITL approval middleware.

func WithApproveListOption

func WithApproveListOption(list *ApproveList) HITLOption

WithApproveListOption injects an in-memory approve list so the middleware can auto-approve when IsApproved returns true. Used by Service and by tests.

func WithHITLAuditor added in v0.1.7

func WithHITLAuditor(a audit.Auditor) HITLOption

WithHITLAuditor injects an auditor so HITL approval/rejection decisions are written to the durable audit trail (not just the AG-UI event bus).

func WithNonBlockingHITL

func WithNonBlockingHITL() HITLOption

WithNonBlockingHITL configures the middleware to return an interrupt.Error instead of blocking in hitl.ApprovalStore.WaitForResolution.

Use this when tool calls run inside an executor that models human interaction as an interrupt/resume cycle (e.g. Temporal workflows). The returned interrupt.Error carries the approval ID and the hitl.ApprovalRequest as its Payload.

Default: blocking (the current in-process behaviour).

func WithSharedApprovalCache

func WithSharedApprovalCache(cache *approvalCache) HITLOption

WithSharedApprovalCache injects a session-scoped approval cache shared across all sub-agents. When a tool+args combination is approved in one sub-agent, it is auto-approved in subsequent sub-agents within the same session, avoiding redundant HITL prompts.

type Handler

type Handler func(ctx context.Context, tc *ToolCallContext) (any, error)

Handler is a function that processes a tool call and returns its output. The terminal handler executes the real tool; middlewares wrap this with cross-cutting concerns.

func Chain

func Chain(terminal Handler, middlewares ...Middleware) Handler

Chain composes a slice of middlewares into a single Handler by wrapping the terminal handler from outermost (index 0) to innermost (last index). If middlewares is empty, the terminal handler is returned unchanged.

type MetricsConfig

type MetricsConfig struct {
	Enabled bool   `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
	Prefix  string `yaml:"prefix,omitempty" toml:"prefix,omitempty"`
}

MetricsConfig controls the MetricsMiddleware.

func (MetricsConfig) MetricsMiddleware

func (cfg MetricsConfig) MetricsMiddleware() MiddlewareFunc

MetricsMiddleware returns a Middleware that records OpenTelemetry metrics for every tool call: a call counter, a latency histogram, and an error counter. Without this middleware, operators have no quantitative signal for tool usage patterns, p99 latencies, or failure rates.

type Middleware

type Middleware interface {
	Wrap(next Handler) Handler
}

Middleware wraps a Handler, returning a new Handler with added behaviour. Middlewares are composed in order: the first middleware in the slice is the outermost wrapper and executes first.

The open-source default chain (from DefaultMiddlewares) is:

LoopDetection → FailureLimit → SemanticCache → FileCache →
HITLApproval → ContextEnrich → Audit → Execute

func AuditMiddleware

func AuditMiddleware(auditor audit.Auditor) Middleware

AuditMiddleware returns a Middleware that writes tool call results to the audit trail for compliance and debugging. Without this middleware, tool calls would produce no durable trace.

func AutoSummarizeMiddleware

func AutoSummarizeMiddleware(summarize SummarizeFunc, threshold int) Middleware

AutoSummarizeMiddleware returns a Middleware that detects tool responses exceeding the configured character threshold and summarises them via the provided SummarizeFunc. If summarize is nil the middleware is a no-op. Identical content is served from a short-lived cache to avoid redundant LLM calls when multiple sub-agent steps produce the same tool output.

func ConcurrencyMiddleware

func ConcurrencyMiddleware(cfg ConcurrencyConfig) Middleware

ConcurrencyMiddleware returns a Middleware that limits concurrent tool executions using golang.org/x/sync/semaphore.

func ContextModeMiddleware

func ContextModeMiddleware(cfg ContextModeConfig) Middleware

ContextModeMiddleware returns a Middleware that detects tool responses exceeding the configured character threshold and compresses them by chunking the text and returning only the top-K most relevant chunks, scored against the tool's input arguments using BM25-lite.

This middleware is complementary to AutoSummarizeMiddleware: if context mode reduces a 200K response to 15K (below the summarise threshold), the LLM summariser is never called — saving both time and API cost. Without this middleware, every large tool response would require an LLM call for compression.

func FailureLimitMiddleware

func FailureLimitMiddleware() Middleware

FailureLimitMiddleware returns a Middleware that blocks a tool after maxConsecutiveToolFailures consecutive errors (of any kind). Unlike CircuitBreakerMiddleware, this has no recovery timer — the tool stays blocked until a success. Use CircuitBreakerMiddleware for automatic recovery after a cooldown.

func HITLApprovalMiddleware

func HITLApprovalMiddleware(
	store hitl.ApprovalStore,
	wm *rtmemory.WorkingMemory,
	opts ...HITLOption,
) Middleware

HITLApprovalMiddleware creates a new HITL approval middleware. Approval request events are emitted via the agui event bus (keyed by MessageOrigin in context), so no explicit event channel is needed.

When called without WithSharedApprovalCache, a per-middleware cache is used (suitable for tests). The Service always passes a shared cache so that approvals carry across sub-agents within the same session.

func LoopDetectionMiddleware

func LoopDetectionMiddleware() Middleware

LoopDetectionMiddleware returns a Middleware that blocks a tool after maxConsecutiveRepeatCalls identical (same name + args) consecutive calls, and cancels the sub-agent after maxConsecutiveEmptyResults consecutive empty results from retrieval tools. This prevents infinite agent loops where the LLM keeps issuing the same call or rephrasing queries against an empty store.

func PanicRecoveryMiddleware

func PanicRecoveryMiddleware() Middleware

PanicRecoveryMiddleware returns a Middleware that recovers from panics in downstream handlers to prevent server crashes.

func RateLimitMiddleware

func RateLimitMiddleware(cfg RateLimitConfig) Middleware

RateLimitMiddleware returns a Middleware that throttles tool calls using golang.org/x/time/rate token-bucket limiters. It supports both a global rate limit and per-tool overrides (bulkhead pattern).

func SemanticCacheMiddleware

func SemanticCacheMiddleware(keyFields map[string][]string) Middleware

SemanticCacheMiddleware creates a new semantic dedup middleware. keyFields maps tool names to the JSON argument fields that form the semantic identity of a call. Only tools in this map are eligible for caching; all others pass through.

Example:

SemanticCacheMiddleware(map[string][]string{
    "create_recurring_task": {"name"},
})

type MiddlewareConfig

type MiddlewareConfig struct {
	ContextModeConfig ContextModeConfig        `yaml:"context_mode,omitempty" toml:"context_mode,omitempty"`
	Timeout           TimeoutConfig            `yaml:"timeout,omitempty" toml:"timeout,omitempty"`
	RateLimit         RateLimitConfig          `yaml:"rate_limit,omitempty" toml:"rate_limit,omitempty"`
	Retry             RetryConfig              `yaml:"retry,omitempty" toml:"retry,omitempty"`
	CircuitBreaker    CircuitBreakerConfig     `yaml:"circuit_breaker,omitempty" toml:"circuit_breaker,omitempty"`
	Concurrency       ConcurrencyConfig        `yaml:"concurrency,omitempty" toml:"concurrency,omitempty"`
	Metrics           MetricsConfig            `yaml:"metrics,omitempty" toml:"metrics,omitempty"`
	Tracing           TracingConfig            `yaml:"tracing,omitempty" toml:"tracing,omitempty"`
	Validation        ValidationConfig         `yaml:"validation,omitempty" toml:"validation,omitempty"`
	Sanitize          SanitizeMiddlewareConfig `yaml:"sanitize,omitempty" toml:"sanitize,omitempty"`
}

MiddlewareConfig is the central configuration for all toolwrap middlewares. It is embedded in GenieConfig and populated from the config file (YAML/TOML). Each middleware has its own sub-struct (defined alongside its middleware in the respective mw_*.go file) with an Enabled or Disabled flag. Where applicable, per-tool overrides are supported via map[string] fields on the individual configs.

func DefaultMiddlewareConfig

func DefaultMiddlewareConfig() MiddlewareConfig

DefaultMiddlewareConfig returns sensible defaults. Circuit breaker is enabled by default to prevent agents from burning LLM calls retrying tools that are consistently failing (e.g. DuckDuckGo rate-limited). All other optional middlewares start disabled.

type MiddlewareDeps

type MiddlewareDeps struct {
	Auditor audit.Auditor
	// SemanticKeyFields maps tool names to the JSON argument fields that
	// form the semantic identity of a call for deduplication.
	SemanticKeyFields map[string][]string
	// WorkingMemory is used for HITL feedback storage.
	WorkingMemory *rtmemory.WorkingMemory
	// Summarize is an optional function that condenses large tool results.
	// When non-nil, tool responses exceeding the threshold are automatically
	// summarized before being returned to the LLM. Typically backed by
	// agentutils.Summarizer.
	Summarize SummarizeFunc
	// SummarizeThreshold is the character count above which a tool result
	// triggers auto-summarization. When 0, defaultSummarizeThreshold is used.
	SummarizeThreshold int
	// Config holds opt-in middleware settings (metrics, tracing, retry, etc).
	// Zero value disables all optional middlewares.
	Config MiddlewareConfig
	// CircuitBreaker is an optional shared circuit breaker singleton.
	// When set, it is reused across all Wrap() calls so that tool
	// failures in one sub-agent trip the circuit for ALL agents.
	CircuitBreaker *CircuitBreakerMW
}

MiddlewareDeps carries the dependencies needed by the default middleware chain. These are supplied per-request via WrapRequest and per-service via the Service constructor.

func (MiddlewareDeps) DefaultMiddlewares

func (deps MiddlewareDeps) DefaultMiddlewares(
	others ...Middleware,
) Middleware

DefaultMiddlewares returns the standard middleware chain. The chain is ordered from outermost (runs first) to innermost (runs just before the terminal handler):

PanicRecovery → PIIRehydrate → [Tracing] → [Metrics] → Emitter → Logger → Audit →
[Timeout] → [RateLimit] → [CircuitBreaker] → [Concurrency] → [Retry] →
SemanticCache → LoopDetection → FailureLimit → [Validation] →
[Sanitize] → HITLApproval → ContextEnrich

Bracketed items are opt-in and only included when their Enabled flag is true in MiddlewareConfig.

type MiddlewareFunc

type MiddlewareFunc func(next Handler) Handler

func InputValidationMiddleware

func InputValidationMiddleware(toolDeclarations func(name string) *tool.Declaration) MiddlewareFunc

InputValidationMiddleware returns a Middleware that validates tool call arguments against the tool's declared InputSchema before execution. It checks that:

  • The arguments are valid JSON
  • Required top-level fields (from the schema) are present

Without this middleware, malformed arguments pass through to the tool implementation, leading to cryptic runtime errors instead of clear validation messages that help the LLM self-correct.

The validation is intentionally lightweight (required-field check) rather than a full JSON Schema validator to keep the dependency footprint small.

func LoggerMiddleware

func LoggerMiddleware() MiddlewareFunc

LoggerMiddleware returns a Middleware that logs the outcome of every tool call — debug for success, error for failure. It wraps the downstream handler, measures the response, and emits a structured log line. Without this middleware, tool call outcomes would be invisible in application logs.

func OutputSanitizationMiddleware

func OutputSanitizationMiddleware(perTool map[string][]string, replacement string) MiddlewareFunc

OutputSanitizationMiddleware returns a Middleware that scrubs sensitive data from tool outputs before they propagate back up the chain and into the LLM context window. Patterns are configured per tool via the perTool map (tool name → list of case-insensitive substrings to redact). Tools not in the map pass through unmodified. Without this middleware, secrets returned by tools (e.g. environment variables, config files) would be stored in the conversation history and potentially leaked.

func PIIRehydrateMiddleware

func PIIRehydrateMiddleware() MiddlewareFunc

PIIRehydrateMiddleware returns a Middleware that rehydrates [HIDDEN:hash] placeholders in tool-call arguments back to original values before the tool runs. The LLM sees redacted content (e.g. [HIDDEN:53233f] instead of an email) and may echo that in tool arguments; without rehydration, tools like email_send would receive invalid values and fail (e.g. "recipient address <[HIDDEN:53233f]> is not a valid RFC 5321 address"). The replacer is stored in context by the model BeforeModel callback (pii.WithReplacer). When present, this middleware replaces placeholders in tc.Args so the tool receives real addresses, tokens, etc.

func PerToolTimeoutMiddleware

func PerToolTimeoutMiddleware(perTool map[string]time.Duration, fallback time.Duration) MiddlewareFunc

PerToolTimeoutMiddleware returns a Middleware that enforces per-tool timeout overrides. Tools not present in the map use the fallback duration. This allows expensive tools (e.g. code execution) to have a longer deadline than fast tools (e.g. read_file).

func RetryMiddleware

func RetryMiddleware(cfg RetryConfig) MiddlewareFunc

RetryMiddleware returns a Middleware that automatically retries failed tool calls with exponential backoff and jitter, powered by cenkalti/backoff/v4. Only errors for which cfg.Retryable returns true are retried; non-retryable errors propagate immediately via backoff.Permanent. Without this middleware, transient failures (network timeouts, 429 responses) require the LLM to manually re-issue the call, wasting a turn and tokens.

func TimeoutMiddleware

func TimeoutMiddleware(d time.Duration) MiddlewareFunc

TimeoutMiddleware returns a Middleware that enforces a maximum execution time per tool call. If the tool does not complete within the deadline, the context is cancelled and the call returns context.DeadlineExceeded. Without this middleware, a misbehaving tool (e.g. a shell command or external API call) could hang indefinitely, blocking the agent loop.

func TracingMiddleware

func TracingMiddleware() MiddlewareFunc

TracingMiddleware returns a Middleware that creates an OpenTelemetry span for every tool call. The span records the tool name, argument size, and any error that occurs. It also injects the span context into the downstream context so nested tool calls (e.g. sub-agents) are correlated as children. Without this middleware, tool calls are invisible in distributed traces, making it impossible to diagnose latency or failure chains.

func (MiddlewareFunc) Wrap

func (f MiddlewareFunc) Wrap(next Handler) Handler

type RateLimitConfig

type RateLimitConfig struct {
	// Enabled activates the rate limit middleware. Defaults to false.
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
	// GlobalRatePerMinute is the maximum number of tool calls per minute
	// across all tools. Zero means no global limit.
	GlobalRatePerMinute float64 `yaml:"global_rate_per_minute,omitempty" toml:"global_rate_per_minute,omitempty,omitzero"`
	// PerToolRatePerMinute maps tool names to per-tool rate limits.
	// Tools not in the map use the global limit. Zero entries are ignored.
	PerToolRatePerMinute map[string]float64 `yaml:"per_tool_rate_per_minute,omitempty" toml:"per_tool_rate_per_minute,omitempty"`
}

RateLimitConfig specifies rate limits for the RateLimitMiddleware.

type RetryConfig

type RetryConfig struct {
	// Enabled activates the retry middleware. Defaults to false.
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
	// MaxAttempts is the total number of attempts (including the first call).
	// Defaults to 3 if zero.
	MaxAttempts int `yaml:"max_attempts,omitempty" toml:"max_attempts,omitempty,omitzero"`
	// InitialBackoff is the delay before the first retry. Subsequent retries
	// double this value (exponential backoff). Defaults to 500ms if zero.
	InitialBackoff time.Duration `yaml:"initial_backoff,omitempty" toml:"initial_backoff,omitempty"`
	// MaxBackoff caps the backoff duration. Defaults to 10s if zero.
	MaxBackoff time.Duration `yaml:"max_backoff,omitempty" toml:"max_backoff,omitempty"`
	// Retryable decides whether an error is transient. When nil, all errors
	// are considered retryable. Return false to stop retrying immediately.
	Retryable func(err error) bool `yaml:"-" toml:"-"`
}

RetryConfig configures the RetryMiddleware behaviour.

type SanitizeMiddlewareConfig

type SanitizeMiddlewareConfig struct {
	// Enabled activates the output sanitization middleware.
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
	// Replacement is the string that replaces each redacted occurrence.
	// Defaults to "[REDACTED]".
	Replacement string `yaml:"replacement,omitempty" toml:"replacement,omitempty"`
	// PerTool maps tool names to per-tool redaction patterns (case-insensitive).
	// Tools not in the map pass through unmodified.
	PerTool map[string][]string `yaml:"per_tool,omitempty" toml:"per_tool,omitempty"`
}

SanitizeMiddlewareConfig controls the OutputSanitizationMiddleware.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service holds the session-stable dependencies for tool wrapping. Create one at startup and reuse it for every request.

func NewService

func NewService(
	auditor audit.Auditor,
	approvalStore hitl.ApprovalStore,
	fn SummarizeFunc,
	opts ...ServiceOption,
) *Service

func (*Service) CircuitBreaker

func (s *Service) CircuitBreaker() *CircuitBreakerMW

CircuitBreaker returns the shared circuit breaker instance, or nil if circuit breaking is disabled. Callers can use OpenTools() to query which tools are currently tripped.

func (*Service) Wrap

func (s *Service) Wrap(tools []tool.Tool, req WrapRequest) []tool.Tool

Wrap wraps each tool with the middleware chain. Each Wrapper is constructed with an eagerly-built middleware chain via DefaultMiddlewares.

type ServiceOption

type ServiceOption func(*Service)

ServiceOption configures optional behaviour on a toolwrap Service.

func WithApprovalCacheTTL

func WithApprovalCacheTTL(ttl time.Duration) ServiceOption

WithApprovalCacheTTL sets the time-to-live for the shared HITL approval cache. After this duration a previously approved tool+args combination requires fresh human approval. Default is 10 minutes.

func WithApproveList

func WithApproveList(list *ApproveList) ServiceOption

WithApproveList injects the in-memory approve list so the HITL middleware can auto-approve tools that the user added via "approve for X mins" in the UI.

func WithExtraMiddleware

func WithExtraMiddleware(mw ...Middleware) ServiceOption

WithExtraMiddleware appends caller-supplied middleware to the default chain. These run after the built-in middlewares (audit, cache, HITL, etc.) and just before the terminal tool-execution handler.

Use this to inject domain-specific middleware (e.g. policy enforcement) from the host application without modifying the genie core.

func WithMiddlewareConfig

func WithMiddlewareConfig(cfg MiddlewareConfig) ServiceOption

WithMiddlewareConfig overrides the default middleware configuration. Pass a per-agent config to enable rate limiting, tracing, retries, etc. on a per-agent basis. When omitted, DefaultMiddlewareConfig() is used.

type SummarizeConfig

type SummarizeConfig struct {
	// Enabled activates the summarization middleware. Default: false (opt-in).
	Enabled bool
	// Threshold is the character count above which a result is summarized.
	// When 0, defaultSummarizeThreshold (100 000) is used.
	Threshold int
}

SummarizeConfig controls the auto-summarization behaviour for large tool results.

type SummarizeFunc

type SummarizeFunc func(ctx context.Context, content string) (string, error)

SummarizeFunc is called with the tool response content and returns a condensed version. The concrete implementation is typically backed by agentutils.Summarizer, wired at the Service layer to avoid importing agentutils (and creating a circular dependency) from this package.

type TimeoutConfig

type TimeoutConfig struct {
	// Enabled activates the timeout middleware. Defaults to false.
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
	// Default is the fallback timeout for all tools.
	Default time.Duration `yaml:"default,omitempty" toml:"default,omitempty"`
	// PerTool maps tool names to per-tool timeout overrides.
	PerTool map[string]time.Duration `yaml:"per_tool,omitempty" toml:"per_tool,omitempty"`
}

TimeoutConfig controls the TimeoutMiddleware.

type ToolCallContext

type ToolCallContext struct {
	// ToolName is the declared name of the tool being called.
	ToolName string
	// OriginalArgs is the raw JSON arguments before any middleware processing.
	OriginalArgs []byte
	// Args is the JSON arguments after middleware processing (e.g. _justification stripped).
	Args []byte
	// Justification is extracted from the _justification field by the HITL middleware.
	Justification string

	// ResumeValue is set by the executor when resuming a previously-interrupted
	// tool call. The concrete type depends on the InterruptKind:
	//   - InterruptClarify:  string (the user's answer)
	//   - InterruptApproval: *hitl.ApprovalRequest (with resolved status)
	//
	// When nil, this is a fresh (non-resumed) call.
	ResumeValue any
}

ToolCallContext carries per-call state through the middleware chain. Middlewares read and write fields to communicate with each other (e.g. JustificationExtract sets Justification, HITLApproval reads it).

type ToolCaller

type ToolCaller interface {
	// CallTool executes the named tool with the given JSON arguments.
	// Implementations must be safe for concurrent use.
	CallTool(ctx context.Context, toolName string, args []byte) (any, error)
}

ToolCaller is the pluggable contract for invoking a tool.

The default implementation casts to tool.CallableTool and calls Call() directly. Alternative implementations (e.g. toolexec) may route through Temporal activities, gRPC, or any other execution backend.

This interface lives in toolwrap — not in toolexec — so the middleware package has zero knowledge of Temporal, keeping the dependency graph clean.

type TracingConfig

type TracingConfig struct {
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
}

TracingConfig controls the TracingMiddleware.

type ValidationConfig

type ValidationConfig struct {
	Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
}

ValidationConfig controls the InputValidationMiddleware.

type WrapRequest

type WrapRequest struct {
	WorkingMemory *rtmemory.WorkingMemory
	// AgentName, when set, scopes the circuit breaker to this agent.
	// Policy-denied failures in agent A will only open the circuit for
	// agent A, not for agent B using the same tool without the policy.
	// When empty, the global (unscoped) circuit breaker is used.
	AgentName string
}

WrapRequest contains the per-request fields needed when wrapping tools.

type Wrapper

type Wrapper struct {
	tool.Tool
	// contains filtered or unexported fields
}

Wrapper wraps a tool with a composable middleware chain. After decoupling, the Wrapper is a thin shell: just the underlying tool and a pre-built middleware chain. All mutable state lives inside the middleware closures.

When a ToolCaller is set, the terminal handler delegates to it instead of calling tool.CallableTool.Call() directly. This allows plugging in alternative execution strategies (e.g. Temporal activities) without changing any middleware.

func NewWrapper

func NewWrapper(t tool.Tool, deps MiddlewareDeps) *Wrapper

NewWrapper creates a Wrapper with an eagerly-built middleware chain. This is the primary constructor for tests and callers that don't go through Service.Wrap.

func (*Wrapper) Call

func (w *Wrapper) Call(ctx context.Context, jsonArgs []byte) (any, error)

Call executes the tool through the configured middleware chain.

Directories

Path Synopsis
Code generated by counterfeiter.
Code generated by counterfeiter.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL