shuttle

package
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 12, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Copyright 2026 Teradata

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Count

func Count() int

Count returns the number of registered tools in the global registry.

func List

func List() []string

List returns all registered tool names from the global registry.

func Register

func Register(tool Tool)

Register registers a tool in the global registry.

func RegisterCustomHook added in v1.4.0

func RegisterCustomHook(name string, ctor func(b HookBinding) (Hook, error))

RegisterCustomHook records a compiled-in custom hook constructor under name, the entrypoint a deployment calls from its init/startup to contribute a clearance/DLP hook. It takes a Go function value, not a script, path, or plugin file — nothing is interpreted at runtime and no sandbox is used. A later registration under the same name replaces the earlier constructor.

func Unregister

func Unregister(name string)

Unregister removes a tool from the global registry.

func ValidateHooksConfig added in v1.4.0

func ValidateHooksConfig(cfg HooksConfig) error

ValidateHooksConfig reports the first structurally invalid binding in a tools.hooks config: an unknown kind, an empty scope, an uncompilable matcher/read_pattern, or a kind missing a required field. It shares validateBinding with BuildChainFromConfig, so a config that validates clean here builds without a structural error, and it wraps each fault with the offending binding's index and kind in BuildChainFromConfig's style. It is deps-free — callable at save time without a live ChainDeps — so a custom binding's name-registry membership is left to build (an unregistered name fails closed there).

func ValidateHooksConfigWithRegistry added in v1.4.0

func ValidateHooksConfigWithRegistry(cfg HooksConfig, reg CustomHookRegistry) error

ValidateHooksConfigWithRegistry is ValidateHooksConfig plus custom-name resolution: when a registry is supplied, a custom binding whose Name has no registered constructor is a validation error — so a host's save door can reject an unknown custom hook at save time (C-010) instead of the operator discovering a deny-all agent at the next session build. Pass ProcessCustomHookRegistry() to validate against the process registry.

Types

type AdmissionRequest added in v1.4.0

type AdmissionRequest struct {
	Ctx       context.Context
	ToolName  string
	Params    map[string]interface{} // the tool's own params (MCP nested/carried op lives here)
	UserID    string                 // caller identity resolved from Ctx
	SessionID string                 // session identity resolved from Ctx
	State     ApprovedSetAccessor    // may be nil until an approved-set store is wired
}

AdmissionRequest is the immutable view of a tool call handed to every hook. State is the approved-set accessor and may be nil until one is wired.

type AdmissionResult added in v1.4.0

type AdmissionResult struct {
	Decision      Decision
	AuditDecision string
}

AdmissionResult is the outcome of running the chain for one tool call. AuditDecision is set when a matched hook is an AuditHook and its AuditDecisionFor returns a non-empty string; it is carried to the persist path.

func (AdmissionResult) PersistedDecision added in v1.4.0

func (r AdmissionResult) PersistedDecision() string

PersistedDecision is the value the executor stamps into the tool-execution record: a matched audit hook's decision when one fired, and otherwise "deny" for a denied call — a denial is intrinsically classifiable and the analytics view keys policy_denied_count on this column, so it must ride every deny, audited or not. A non-denied, non-audited call stamps nothing.

type ApprovedSetAccessor added in v1.4.0

type ApprovedSetAccessor interface {
	Record(ctx context.Context, stateKey string, ids []CallIdentity) error
	Contains(ctx context.Context, stateKey string, id CallIdentity) (bool, error)
	ForgetSession(sessionID string)
}

ApprovedSetAccessor records and queries prior approvals keyed by a caller state key. ForgetSession is the reclamation half of the contract: a host that retires a session drops its approvals through it (the agent's DeleteSession does), so the set's growth is bounded by LIVE sessions, not by every session the process ever served.

func NewApprovedSet added in v1.4.0

func NewApprovedSet() ApprovedSetAccessor

NewApprovedSet builds an ApprovedSetAccessor. The composition layer binds it to the executor so a gated-allowlist reads it as AdmissionRequest.State.

type AskResolver added in v1.4.0

type AskResolver interface {
	Resolve(req AdmissionRequest, d Decision) Decision
}

AskResolver turns an Ask verdict into a terminal Allow/Deny. A nil resolver means Ask fails closed to Deny.

func NewHITLAskResolver added in v1.4.0

func NewHITLAskResolver(store HumanRequestStore, timeout, poll time.Duration, notifier Notifier) AskResolver

NewHITLAskResolver builds the Ask resolver wired into ChainDeps.Ask. timeout bounds the turn-blocking wait (default 300s when non-positive); poll is the store poll interval (default 1s, mirroring ContactHumanConfig). notifier is fired once the pending request is stored so the hold surfaces on the progress stream; a nil notifier disables the emit (the hold is unaffected).

type AuditHook added in v1.4.0

type AuditHook interface {
	Hook
	AuditDecisionFor(final Decision) string
}

AuditHook is a Hook that additionally reports how a final decision should be recorded. The chain checks matched hooks for this interface and carries the non-empty result on AdmissionResult.AuditDecision. A "" result is not recorded.

type BuiltinToolProvider added in v1.1.0

type BuiltinToolProvider interface {
	GetTool(name string) Tool
}

BuiltinToolProvider is an interface for getting builtin tools. This avoids import cycles with pkg/shuttle/builtin.

type CallIdentity added in v1.4.0

type CallIdentity string

CallIdentity is the shared call-equality key for approved-set membership. Canonicalize is the normalization that derives it.

func Canonicalize added in v1.4.0

func Canonicalize(toolName string, params map[string]interface{}, stmtParam string) CallIdentity

Canonicalize derives the call-equality key an approved-set membership check compares on. Normalization is conservative and fail-closed — it prefers a false-deny over a false-allow and never widens the equivalence class: it takes the SQL statement param named by stmtParam, trims it, collapses internal whitespace runs to a single space, and strips a trailing ';'. No keyword or case folding, no semantic rewrite. The write side that records approvals and this read side normalize through this one function, so a rendered statement matches its re-emitted whitespace-different form while a textually-outside statement does not. toolName selects the normalization; only the SQL statement normalization exists today.

type Chain added in v1.4.0

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

Chain evaluates admission hooks for every tool call and combines their verdicts into a single decision. It also fans out post-tool observation to its PostToolHooks after a tool body runs.

func BuildChainFromConfig added in v1.4.0

func BuildChainFromConfig(cfg HooksConfig, deps ChainDeps) (*Chain, error)

BuildChainFromConfig assembles the admission chain from a tools.hooks config. The name-level permission hook is placed first when a checker is supplied, then one hook per binding in config order. Each gated-allowlist binding also contributes a recorder post-tool hook — the sole writer of its approved set — so the render tool's completions populate the set the gated read side checks. A malformed binding — unknown kind, empty scope, bad matcher/read pattern, a gated-allowlist missing state_key/source_tool, or a custom name with no registered hook — is an error, so serve aborts rather than run silently ungoverned (fail-closed).

func NewChain added in v1.4.0

func NewChain(hooks []Hook, postHooks []PostToolHook, askResolver AskResolver) *Chain

NewChain builds a chain from ordered admission hooks, post-tool hooks, and an optional Ask resolver. Hook order is the caller's responsibility (the name-level permission hook is placed first by the builder); combination is order-independent.

func (*Chain) Admit added in v1.4.0

func (c *Chain) Admit(req AdmissionRequest) AdmissionResult

Admit evaluates every hook whose Matches reports true, combines their verdicts as Deny > Ask > Allow (most restrictive wins), and resolves an Ask outcome via the AskResolver — with no resolver, Ask fails closed to Deny. A hook that panics is treated as a matched Deny (fail-closed). With no matching hook the decision is NoDecision, which the executor treats as today's pass-through. For each matched AuditHook, AuditDecisionFor(final) sets AdmissionResult.AuditDecision.

func (*Chain) Observe added in v1.4.0

func (c *Chain) Observe(req AdmissionRequest, result *Result)

Observe fans a completed tool call out to every post-tool hook. A nil chain or nil hook is a no-op; a panicking hook is contained so observation cannot break execution that already completed.

type ChainDeps added in v1.4.0

type ChainDeps struct {
	Perm        *PermissionChecker
	ApprovedSet ApprovedSetAccessor
	Ask         AskResolver
	Custom      CustomHookRegistry
}

ChainDeps carries the collaborators BuildChainFromConfig needs to turn bindings into live hooks: the name-level permission checker (folded in as the first hook), the approved-set accessor a gated-allowlist reads, the resolver an Ask verdict defers to, and the registry a custom binding resolves against. The pending-emit collaborator is NOT injected here — it is a constructor argument of NewHITLAskResolver, the one way in.

type ContactHumanConfig

type ContactHumanConfig struct {
	Store        HumanRequestStore
	Notifier     Notifier
	Timeout      time.Duration        // Default timeout for requests (default: 5 minutes)
	PollInterval time.Duration        // How often to check for responses (default: 1 second)
	Tracer       observability.Tracer // Tracer for observability (default: NoOpTracer)
	Logger       *zap.Logger          // Logger for structured logging (default: NoOp logger)
}

ContactHumanConfig configures the ContactHumanTool.

type ContactHumanTool

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

ContactHumanTool provides human-in-the-loop capabilities for agents. Implements 12-Factor Agent Compliance (Factor 7: Human Oversight & Approval).

Use cases: - Approval workflows (e.g., "Should I delete this data?") - High-stakes decisions (e.g., "Confirm this financial transaction") - Ambiguity resolution (e.g., "Which interpretation is correct?") - Quality gates (e.g., "Review this generated code before deployment")

func NewContactHumanTool

func NewContactHumanTool(config ContactHumanConfig) *ContactHumanTool

NewContactHumanTool creates a new human-in-the-loop tool.

func (*ContactHumanTool) Backend

func (t *ContactHumanTool) Backend() string

func (*ContactHumanTool) Description

func (t *ContactHumanTool) Description() string

Description returns the tool description. Deprecated: Description loaded from PromptRegistry (prompts/tools/human.yaml). This fallback is used only when prompts are not configured.

func (*ContactHumanTool) Execute

func (t *ContactHumanTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

func (*ContactHumanTool) InputSchema

func (t *ContactHumanTool) InputSchema() *JSONSchema

func (*ContactHumanTool) Name

func (t *ContactHumanTool) Name() string

type CustomHookRegistry added in v1.4.0

type CustomHookRegistry interface {
	// contains filtered or unexported methods
}

CustomHookRegistry resolves a custom binding's Name to the constructor that builds its hook. The constructor receives the full binding, so a custom hook is placed in the chain with the binding's scope and matcher like any library policy. A name with no registered constructor is a build error.

func ProcessCustomHookRegistry added in v1.4.0

func ProcessCustomHookRegistry() CustomHookRegistry

ProcessCustomHookRegistry returns the process-wide custom hook registry to pass as ChainDeps.Custom at serve, so a `kind:"custom"` binding resolves against the constructors RegisterCustomHook compiled in.

type Decision added in v1.4.0

type Decision struct {
	Kind   DecisionKind
	Reason string
}

Decision is a hook's verdict plus a human-readable reason carried on Deny/Ask.

type DecisionKind added in v1.4.0

type DecisionKind int

DecisionKind is the verdict a hook (or the combined chain) reaches for a tool call. Ordering by restrictiveness is Deny > Ask > Allow; NoDecision is the sentinel returned when no hook matched a request and is never produced by a hook's Evaluate.

const (
	// Allow lets the tool body run.
	Allow DecisionKind = iota
	// Deny blocks the tool body; the executor returns a permission_denied Result.
	Deny
	// Ask defers to an AskResolver; with no resolver wired it fails closed to Deny.
	Ask
	// NoDecision means no hook matched. The executor treats it as a pass-through:
	// the tool runs exactly as if no chain were attached.
	NoDecision
)

type Error

type Error struct {
	// Code is a machine-readable error code
	Code string

	// Message is a human-readable error message
	Message string

	// Details provides additional error context
	Details map[string]interface{}

	// Retryable indicates if the operation can be retried
	Retryable bool

	// Suggestion provides a suggestion for fixing the error
	Suggestion string
}

Error represents a tool execution error with structured information.

type Executor

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

Executor executes tools with tracking and error handling.

func NewExecutor

func NewExecutor(registry *Registry) *Executor

NewExecutor creates a new tool executor.

func (*Executor) ApprovedSet added in v1.4.0

func (e *Executor) ApprovedSet() ApprovedSetAccessor

ApprovedSet returns the wired approved-set accessor (nil when none). The set is per-accessor state, so introspection must go through the same instance the executor threads to hooks.

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, toolName string, params map[string]interface{}) (result *Result, err error)

Execute executes a tool by name with the given parameters.

func (*Executor) ExecuteWithTool

func (e *Executor) ExecuteWithTool(ctx context.Context, tool Tool, params map[string]interface{}) (result *Result, err error)

ExecuteWithTool executes a specific tool instance (not from registry).

func (*Executor) ListAvailableTools

func (e *Executor) ListAvailableTools() []Tool

ListAvailableTools returns all tools available in the executor's registry.

func (*Executor) ListToolsByBackend

func (e *Executor) ListToolsByBackend(backend string) []Tool

ListToolsByBackend returns all tools for a specific backend.

func (*Executor) SetAdmissionChain added in v1.4.0

func (e *Executor) SetAdmissionChain(chain *Chain)

SetAdmissionChain configures the admission hook chain consulted before every tool body runs. A nil chain leaves execution as a pure pass-through.

func (*Executor) SetApprovedSet added in v1.4.0

func (e *Executor) SetApprovedSet(accessor ApprovedSetAccessor)

SetApprovedSet configures the approved-set accessor threaded to admission hooks as AdmissionRequest.State. A nil accessor leaves a gated-allowlist with no store to read, so it fails closed.

func (*Executor) SetBuiltinToolProvider added in v1.1.0

func (e *Executor) SetBuiltinToolProvider(provider BuiltinToolProvider)

SetBuiltinToolProvider configures the builtin tool provider for dynamic builtin tool registration.

func (*Executor) SetIdentityResolver added in v1.4.0

func (e *Executor) SetIdentityResolver(resolver func(context.Context) string)

SetIdentityResolver configures how AdmissionRequest.UserID is read from the call context. pkg/shuttle cannot import the storage layer that owns the user-id context key without an import cycle, so the composition layer injects the resolver here. A nil resolver yields an empty UserID.

func (*Executor) SetMCPManager

func (e *Executor) SetMCPManager(manager MCPManager)

SetMCPManager configures the MCP manager for dynamic MCP tool registration.

func (*Executor) SetPermissionChecker

func (e *Executor) SetPermissionChecker(checker *PermissionChecker)

SetPermissionChecker configures permission checking for tool execution.

func (*Executor) SetSharedMemory

func (e *Executor) SetSharedMemory(sharedMemory *storage.SharedMemoryStore, threshold int64)

SetSharedMemory configures shared memory for large result handling.

func (*Executor) SetToolRegistry

func (e *Executor) SetToolRegistry(registry ToolRegistry)

SetToolRegistry configures the tool registry for dynamic tool discovery. When a tool is not found in the local registry, the executor will check the tool registry and dynamically register MCP tools if found.

func (*Executor) SharedMemoryThreshold added in v1.4.0

func (e *Executor) SharedMemoryThreshold() int64

SharedMemoryThreshold reports the byte threshold at or above which a result is stored by reference. Exposed so callers can assert both offload sites agree.

func (*Executor) Stats added in v1.1.0

func (e *Executor) Stats() ExecutorStats

Stats returns metrics about executor operations. Includes large parameter optimization statistics.

type ExecutorStats added in v1.1.0

type ExecutorStats struct {
	LargeParamStores      int64 // Count of parameters stored in shared memory
	LargeParamDerefs      int64 // Count of parameters dereferenced
	LargeParamBytesStored int64 // Total bytes stored for parameters
	LargeParamDerefErrors int64 // Count of dereference failures
}

ExecutorStats holds metrics about executor operations.

type Hook added in v1.4.0

type Hook interface {
	Matches(req AdmissionRequest) bool
	Evaluate(req AdmissionRequest) Decision
}

Hook is a single admission policy: it decides whether it applies to a request (Matches) and, when it does, returns its verdict (Evaluate).

type HookBinding added in v1.4.0

type HookBinding struct {
	// Kind selects the policy: "gated-allowlist" | "denylist" | "audit" | "ask" | "custom".
	Kind string `mapstructure:"kind" json:"kind"`
	// Scope is the tool selector: an exact tool name or a "<prefix>*" pattern,
	// with the same match semantics as tools.permissions.
	Scope string `mapstructure:"scope" json:"scope"`
	// Matcher selects calls by their params, including a dispatch tool's
	// carried/nested op addressed by path. An absent matcher selects every call
	// to the scoped tools (a scope-only binding).
	Matcher MatcherSpec `mapstructure:"matcher" json:"matcher"`
	// StateKey names the approved-set partition a gated-allowlist reads.
	StateKey string `mapstructure:"state_key" json:"state_key"`
	// ReadPattern classifies a read-only call (gated-allowlist): anchored to
	// each statement's head, and every statement of the payload must match.
	ReadPattern string `mapstructure:"read_pattern" json:"read_pattern"`
	// StmtParam names the param holding the statement whose identity is checked
	// (gated-allowlist, required); shared with the render/record side's
	// canonicalization.
	StmtParam string `mapstructure:"stmt_param" json:"stmt_param"`
	// Pattern is not a valid field: a denylist selects calls with Matcher.
	// Declared only so a config that sets it fails validation loudly instead of
	// being dropped by the decoder.
	Pattern string `mapstructure:"pattern" json:"pattern"`
	// SourceTool names the render/record tool a gated-allowlist trusts as the
	// source of approved-set entries.
	SourceTool string `mapstructure:"source_tool" json:"source_tool"`
	// ResultPath locates the rendered statements within a render Result.
	ResultPath string `mapstructure:"result_path" json:"result_path"`
	// Name is the registry key of a custom hook.
	Name string `mapstructure:"name" json:"name"`
}

HookBinding declares one library policy purely from config: which policy (Kind), the tools it governs (Scope), the params it selects on (Matcher), and the policy-specific parameters. A binding is turned into a live Hook by BuildChainFromConfig; a malformed binding fails serve startup (fail-closed).

func (*HookBinding) UnmarshalJSON added in v1.4.0

func (b *HookBinding) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a binding with key normalization: any case/underscore variant of a known field lands on its canonical field, unknown keys fail.

type HooksConfig added in v1.4.0

type HooksConfig struct {
	Bindings []HookBinding `mapstructure:"hooks" json:"bindings"`
}

HooksConfig is the `tools.hooks` block Viper unmarshals: an ordered list of library-policy bindings. It lives in package shuttle (not cmd/looms) so a library builder can construct an admission chain without importing main. The json tags define the wire shape an out-of-process host (e.g. an agent profile's hooks_config) transports. The documented JSON key set is the snake_case one on the tags; the historical Go-field-name casing keeps decoding through the normalizing UnmarshalJSON below, which folds any case/underscore variant of a known key onto its canonical spelling before a strict decode — so "StateKey" and "state_key" both land, and a key that is no spelling of any field still fails loudly.

func ParseHooksConfig added in v1.4.0

func ParseHooksConfig(raw []byte) (HooksConfig, error)

ParseHooksConfig decodes a JSON hooks config and refuses the silent-empty trap: a document whose keys miss the schema fails loudly (unknown fields are rejected), so a mis-keyed config errors at the save door instead of building a chain that governs nothing. A genuinely empty document (empty input, "{}", "null", or an explicitly empty bindings list) parses to an empty config.

func (*HooksConfig) UnmarshalJSON added in v1.4.0

func (c *HooksConfig) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the config with key normalization at every level, so even a decode NOT routed through ParseHooksConfig is strict: a top-level key that is no spelling of the list key fails loudly instead of yielding a zero-binding config that validates clean and governs nothing.

type HumanRequest

type HumanRequest struct {
	ID          string                 `json:"id"`
	AgentID     string                 `json:"agent_id"`
	SessionID   string                 `json:"session_id"`
	Question    string                 `json:"question"`
	Context     map[string]interface{} `json:"context"`
	RequestType string                 `json:"request_type"` // "approval", "decision", "input", "review"
	Priority    string                 `json:"priority"`     // "low", "normal", "high", "critical"
	Kind        string                 `json:"kind"`         // "approval" (hook-held) | "question" (contact_human); absent → question
	Summary     string                 `json:"summary"`      // display digest: tool+args (approval) | question text (question)
	Timeout     time.Duration          `json:"timeout"`
	CreatedAt   time.Time              `json:"created_at"`
	ExpiresAt   time.Time              `json:"expires_at"`

	// Params carries the held call's full parameter map for an approval; a
	// question has no held call and leaves it empty. Stamped at origin, never
	// re-derived downstream.
	Params map[string]interface{} `json:"params"`
	// ParamsTruncated reports that the paramsMaxBytes bound cut whole pairs out
	// of Params.
	ParamsTruncated bool `json:"params_truncated"`

	// Response fields (populated when human responds)
	Status       string                 `json:"status"` // "pending", "approved", "rejected", "timeout", "responded"
	Response     string                 `json:"response"`
	ResponseData map[string]interface{} `json:"response_data"`
	RespondedAt  *time.Time             `json:"responded_at"`
	RespondedBy  string                 `json:"responded_by"`
}

HumanRequest represents a request for human input.

type HumanRequestStore

type HumanRequestStore interface {
	// Store saves a new human request
	Store(ctx context.Context, req *HumanRequest) error

	// Get retrieves a human request by ID. Absence contract: the postgres
	// store returns (nil, nil) for a missing row so callers can distinguish
	// absence from a store failure; the sqlite and in-memory stores return an
	// error. Callers must treat BOTH a nil request and an error as
	// possibly-absent (the ask waiter does), never dereference unchecked.
	Get(ctx context.Context, id string) (*HumanRequest, error)

	// Update updates an existing human request
	Update(ctx context.Context, req *HumanRequest) error

	// List returns all pending requests (for human review interface)
	ListPending(ctx context.Context) ([]*HumanRequest, error)

	// ListBySession returns all requests for a session
	ListBySession(ctx context.Context, sessionID string) ([]*HumanRequest, error)

	// RespondToRequest resolves a pending, non-expired request exactly once.
	// The expiry guard is the store's own — no caller payload can lift it. On
	// an already-decided or expired request it is a no-op returning nil (the
	// caller reads current state via Get). Errors only on a missing row / store failure.
	RespondToRequest(ctx context.Context, requestID, status, response, respondedBy string, responseData map[string]interface{}) error

	// ExpireRequest terminally closes a pending request as "timeout" on behalf
	// of the harness — the waiter's give-up path, a canceled turn's abandon
	// write, an expiry sweep. It is the ONLY path that may close a row past its
	// expiry; a row already resolved is left untouched (closing is not
	// resolving). A missing row is a no-op. respondedBy records the closing
	// actor (e.g. "system:expiry", "system:cancel").
	ExpireRequest(ctx context.Context, requestID, respondedBy string) error

	// Close releases any resources held by the store.
	Close() error
}

HumanRequestStore manages storage and retrieval of human requests.

type InMemoryHumanRequestStore

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

InMemoryHumanRequestStore provides an in-memory implementation of HumanRequestStore. Suitable for testing and single-instance deployments.

func NewInMemoryHumanRequestStore

func NewInMemoryHumanRequestStore() *InMemoryHumanRequestStore

NewInMemoryHumanRequestStore creates a new in-memory store.

func (*InMemoryHumanRequestStore) Close added in v1.2.0

func (s *InMemoryHumanRequestStore) Close() error

Close is a no-op; in-memory store has no resources to release.

func (*InMemoryHumanRequestStore) ExpireRequest added in v1.4.0

func (s *InMemoryHumanRequestStore) ExpireRequest(ctx context.Context, requestID, respondedBy string) error

ExpireRequest terminally closes a pending request as "timeout" regardless of its expiry — the harness's close for abandoned or swept rows. A resolved row is left untouched (closing is not resolving); a missing row is a no-op.

func (*InMemoryHumanRequestStore) Get

func (*InMemoryHumanRequestStore) ListBySession

func (s *InMemoryHumanRequestStore) ListBySession(ctx context.Context, sessionID string) ([]*HumanRequest, error)

func (*InMemoryHumanRequestStore) ListPending

func (s *InMemoryHumanRequestStore) ListPending(ctx context.Context) ([]*HumanRequest, error)

func (*InMemoryHumanRequestStore) RespondToRequest

func (s *InMemoryHumanRequestStore) RespondToRequest(ctx context.Context, requestID, status, response, respondedBy string, responseData map[string]interface{}) error

RespondToRequest resolves a pending, non-expired request exactly once. On an already-decided or expired request it is a no-op returning nil, so the caller reads current state via Get. Errors only on a missing request. The expiry guard is the store's own: no status value in the caller's payload can lift it — terminal closes past expiry go through ExpireRequest.

func (*InMemoryHumanRequestStore) Store

func (*InMemoryHumanRequestStore) Update

type InstrumentedExecutor

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

InstrumentedExecutor wraps an Executor with observability instrumentation. It captures detailed traces and metrics for every tool execution, including: - Tool name and parameters - Execution duration and success/failure - Result data and errors - Backend information

This wrapper is transparent and can wrap any Executor.

func NewInstrumentedExecutor

func NewInstrumentedExecutor(executor *Executor, tracer observability.Tracer) *InstrumentedExecutor

NewInstrumentedExecutor creates a new instrumented tool executor.

func (*InstrumentedExecutor) Execute

func (e *InstrumentedExecutor) Execute(ctx context.Context, toolName string, params map[string]interface{}) (*Result, error)

Execute executes a tool by name with observability instrumentation.

func (*InstrumentedExecutor) ExecuteWithTool

func (e *InstrumentedExecutor) ExecuteWithTool(ctx context.Context, tool Tool, params map[string]interface{}) (*Result, error)

ExecuteWithTool executes a specific tool instance with observability.

func (*InstrumentedExecutor) ListAvailableTools

func (e *InstrumentedExecutor) ListAvailableTools() []Tool

ListAvailableTools delegates to the underlying executor.

func (*InstrumentedExecutor) ListToolsByBackend

func (e *InstrumentedExecutor) ListToolsByBackend(backend string) []Tool

ListToolsByBackend delegates to the underlying executor.

type JSONNotifier

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

JSONNotifier sends notifications as JSON to a configured endpoint (webhook).

func NewJSONNotifier

func NewJSONNotifier(webhookURL string) *JSONNotifier

NewJSONNotifier creates a new JSON webhook notifier.

func (*JSONNotifier) Notify

func (n *JSONNotifier) Notify(ctx context.Context, req *HumanRequest) error

type JSONSchema

type JSONSchema struct {
	Type        string                 `json:"type"`
	Description string                 `json:"description,omitempty"`
	Properties  map[string]*JSONSchema `json:"properties,omitempty"`
	Required    []string               `json:"required,omitempty"`
	Items       *JSONSchema            `json:"items,omitempty"`
	Enum        []interface{}          `json:"enum,omitempty"`
	Default     interface{}            `json:"default,omitempty"`
	Format      string                 `json:"format,omitempty"`
	Pattern     string                 `json:"pattern,omitempty"`
	Minimum     *float64               `json:"minimum,omitempty"`
	Maximum     *float64               `json:"maximum,omitempty"`
	MinLength   *int                   `json:"minLength,omitempty"`
	MaxLength   *int                   `json:"maxLength,omitempty"`
	// Composite schema keywords (JSON Schema §10.2.1)
	// Required for MCP tools that use nullable types like anyOf: [{type: "string"}, {type: "null"}]
	AnyOf []*JSONSchema `json:"anyOf,omitempty"`
	OneOf []*JSONSchema `json:"oneOf,omitempty"`
	AllOf []*JSONSchema `json:"allOf,omitempty"`
	Not   *JSONSchema   `json:"not,omitempty"`
}

JSONSchema represents a JSON Schema for tool parameters. This follows the JSON Schema spec for type definitions.

func FromJSON

func FromJSON(data []byte) (*JSONSchema, error)

FromJSON creates a JSONSchema from JSON bytes.

func NewArraySchema

func NewArraySchema(description string, items *JSONSchema) *JSONSchema

NewArraySchema creates a new array schema.

func NewBooleanSchema

func NewBooleanSchema(description string) *JSONSchema

NewBooleanSchema creates a new boolean schema.

func NewNumberSchema

func NewNumberSchema(description string) *JSONSchema

NewNumberSchema creates a new number schema.

func NewObjectSchema

func NewObjectSchema(description string, properties map[string]*JSONSchema, required []string) *JSONSchema

NewObjectSchema creates a new object schema with the given properties.

func NewStringSchema

func NewStringSchema(description string) *JSONSchema

NewStringSchema creates a new string schema.

func NormalizeSchema

func NormalizeSchema(schema *JSONSchema) *JSONSchema

NormalizeSchema ensures a JSON Schema complies with JSON Schema draft 2020-12. This is critical for Bedrock Claude models which strictly validate schemas.

Common issues fixed: - Object types with nil properties -> empty map {} - Missing type fields -> inferred from structure - Nested objects with nil properties -> recursively normalized

func (*JSONSchema) MarshalJSON

func (s *JSONSchema) MarshalJSON() ([]byte, error)

MarshalJSON implements custom JSON marshaling to ensure Bedrock compliance. Object types must have "properties": {} (not omitted) per JSON Schema 2020-12.

func (*JSONSchema) ToJSON

func (s *JSONSchema) ToJSON() ([]byte, error)

ToJSON converts the schema to JSON bytes.

func (*JSONSchema) WithDefault

func (s *JSONSchema) WithDefault(value interface{}) *JSONSchema

WithDefault adds a default value to the schema.

func (*JSONSchema) WithEnum

func (s *JSONSchema) WithEnum(values ...interface{}) *JSONSchema

WithEnum adds enum values to the schema.

func (*JSONSchema) WithFormat

func (s *JSONSchema) WithFormat(format string) *JSONSchema

WithFormat adds a format constraint to the schema.

func (*JSONSchema) WithLength

func (s *JSONSchema) WithLength(minLen, maxLen *int) *JSONSchema

WithLength adds length constraints to the schema.

func (*JSONSchema) WithPattern

func (s *JSONSchema) WithPattern(pattern string) *JSONSchema

WithPattern adds a pattern constraint to the schema.

func (*JSONSchema) WithRange

func (s *JSONSchema) WithRange(min, max *float64) *JSONSchema

WithRange adds min/max constraints to the schema.

type MCPManager

type MCPManager interface {
	GetClient(serverName string) (interface{}, error)
}

MCPManager is an interface for getting MCP clients. This avoids import cycles with pkg/mcp/manager.

type Matcher added in v1.4.0

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

Matcher is a compiled param selector: a resolved param path plus a comparison op over Value. A Matcher with no path matches every call (an unfiltered binding governs every call to its scoped tools).

func (Matcher) MatchesParams added in v1.4.0

func (m Matcher) MatchesParams(params map[string]interface{}) bool

MatchesParams reports whether the call's params satisfy the matcher. A match-all matcher always matches. Otherwise the path is resolved through nested maps to a value that is compared, as a string, under the op; an unresolved path does not match.

type MatcherSpec added in v1.4.0

type MatcherSpec struct {
	ParamPath string `mapstructure:"param_path" json:"param_path"`
	Op        string `mapstructure:"op" json:"op"`
	Value     string `mapstructure:"value" json:"value"`
}

MatcherSpec is the raw matcher a binding carries (the HookBinding.Matcher field). ParamPath addresses the param to test and may traverse nested maps (a dispatch tool's carried op) using dot-separated segments. Op is the comparison against Value: "equals", "regex", or "contains".

func (MatcherSpec) Compile added in v1.4.0

func (s MatcherSpec) Compile() (Matcher, error)

Compile turns a MatcherSpec into a Matcher, validating at serve. An empty spec compiles to a match-all Matcher. A non-empty spec requires a ParamPath and a known Op; a "regex" op compiles Value and a bad pattern is an error (fail-closed, L-Cfg-1).

func (*MatcherSpec) UnmarshalJSON added in v1.4.0

func (m *MatcherSpec) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a matcher spec with the same key normalization as HookBinding.

type MockTool

type MockTool struct {
	MockName        string
	MockDescription string
	MockSchema      *JSONSchema
	MockBackend     string
	MockExecute     func(ctx context.Context, params map[string]interface{}) (*Result, error)
	ExecuteCount    int
	LastParams      map[string]interface{}
	// contains filtered or unexported fields
}

MockTool is a mock implementation of the Tool interface for testing. It allows tests to control all tool behavior and verify interactions. Thread-safe for concurrent testing.

func (*MockTool) Backend

func (m *MockTool) Backend() string

Backend returns the mock backend type.

func (*MockTool) Description

func (m *MockTool) Description() string

Description returns the mock tool description.

func (*MockTool) Execute

func (m *MockTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

Execute runs the mock execution function.

func (*MockTool) InputSchema

func (m *MockTool) InputSchema() *JSONSchema

InputSchema returns the mock input schema.

func (*MockTool) Name

func (m *MockTool) Name() string

Name returns the mock tool name.

type NoOpNotifier

type NoOpNotifier struct{}

NoOpNotifier is a no-op implementation of Notifier for testing.

func (*NoOpNotifier) Notify

func (n *NoOpNotifier) Notify(ctx context.Context, req *HumanRequest) error

type Notifier

type Notifier interface {
	// Notify sends a notification about a human request
	Notify(ctx context.Context, req *HumanRequest) error
}

Notifier sends notifications to humans when their input is requested.

type PermissionChecker

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

PermissionChecker checks if a tool can be executed based on configuration. Entries in the allow/deny lists match a tool name either exactly or, when the entry ends with "*", by prefix. This lets a whole MCP server's tools be trusted with a single pattern (e.g. "opendata:*" matches "opendata:query"), rather than enumerating every tool name. A bare "*" matches everything.

func NewPermissionChecker

func NewPermissionChecker(config PermissionConfig) *PermissionChecker

NewPermissionChecker creates a new permission checker.

func (*PermissionChecker) Advertisable added in v1.4.0

func (pc *PermissionChecker) Advertisable(toolName string) bool

Advertisable reports whether a tool should be offered to the LLM at all.

A tool the checker would unconditionally refuse — one that is hard-disabled, or one that requires approval while no approval mechanism is wired up — is hidden from the model. Otherwise the model only discovers it can't use the tool by calling it and eating a denial: that wastes a turn and records an intentional policy decision as a tool failure in the analytics. Hiding such tools costs no capability (they could never have run) and keeps the model's tool surface honest.

This mirrors CheckPermission's decision tree but never blocks and has no side effects, so it is safe to call once per tool per turn. It is the single place to revisit when an interactive approval callback is implemented (at which point approval-required tools become advertisable again).

func (*PermissionChecker) CheckPermission

func (pc *PermissionChecker) CheckPermission(ctx context.Context, toolName string, params map[string]interface{}) error

CheckPermission checks if a tool can be executed. Returns nil if allowed, error if denied.

func (*PermissionChecker) IsToolAllowed

func (pc *PermissionChecker) IsToolAllowed(toolName string) bool

IsToolAllowed returns true if a tool is explicitly allowed (whitelist), matching exact names and "<prefix>*" patterns.

func (*PermissionChecker) IsToolDisabled

func (pc *PermissionChecker) IsToolDisabled(toolName string) bool

IsToolDisabled returns true if a tool is explicitly disabled (blacklist), matching exact names and "<prefix>*" patterns.

func (*PermissionChecker) IsYOLOMode

func (pc *PermissionChecker) IsYOLOMode() bool

IsYOLOMode returns true if YOLO mode is enabled.

func (*PermissionChecker) RequiresApproval

func (pc *PermissionChecker) RequiresApproval() bool

RequiresApproval returns true if user approval is required for tools.

type PermissionConfig

type PermissionConfig struct {
	RequireApproval bool
	YOLO            bool
	AllowedTools    []string
	DisabledTools   []string
	DefaultAction   string // "allow" or "deny"
	TimeoutSeconds  int
}

PermissionConfig holds permission configuration.

type PostToolHook added in v1.4.0

type PostToolHook interface {
	Observe(req AdmissionRequest, result *Result)
}

PostToolHook observes a tool call after the body has run. It never gates execution; it exists for recording and audit side effects.

type PromptAwareTool

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

PromptAwareTool wraps a tool and loads descriptions from PromptRegistry. Falls back to tool's native Description() if prompt not found. This enables externalized tool descriptions while maintaining backward compatibility.

func (*PromptAwareTool) Backend

func (p *PromptAwareTool) Backend() string

Backend returns the backend type this tool requires (delegates to wrapped tool).

func (*PromptAwareTool) Description

func (p *PromptAwareTool) Description() string

Description loads the tool description from PromptRegistry. Falls back to the wrapped tool's native Description() if: - PromptRegistry lookup fails - Prompt is not found - Prompt is empty

This ensures tools always have a valid description, even if prompts are not configured.

func (*PromptAwareTool) Execute

func (p *PromptAwareTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

Execute runs the tool with given parameters (delegates to wrapped tool).

func (*PromptAwareTool) InputSchema

func (p *PromptAwareTool) InputSchema() *JSONSchema

InputSchema returns the JSON Schema for tool parameters (delegates to wrapped tool).

func (*PromptAwareTool) Name

func (p *PromptAwareTool) Name() string

Name returns the tool's unique identifier (delegates to wrapped tool).

type Registry

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

Registry manages tool registration and lookup.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new tool registry.

func (*Registry) Count

func (r *Registry) Count() int

Count returns the number of registered tools.

func (*Registry) Get

func (r *Registry) Get(name string) (Tool, bool)

Get retrieves a tool by name.

func (*Registry) IsRegistered added in v1.1.0

func (r *Registry) IsRegistered(name string) bool

IsRegistered checks if a tool is already registered. This is useful for progressive tool disclosure (registering tools only when needed).

func (*Registry) List

func (r *Registry) List() []string

List returns all registered tool names.

func (*Registry) ListByBackend

func (r *Registry) ListByBackend(backend string) []Tool

ListByBackend returns all tools for a specific backend. Pass empty string to get backend-agnostic tools.

func (*Registry) ListTools

func (r *Registry) ListTools() []Tool

ListTools returns all registered tools.

func (*Registry) Register

func (r *Registry) Register(tool Tool)

Register registers a tool with the registry. If a tool with the same name already exists, it will be replaced.

func (*Registry) Unregister

func (r *Registry) Unregister(name string)

Unregister removes a tool from the registry.

type Result

type Result struct {
	// Success indicates if the tool executed successfully
	Success bool

	// Data contains the result data (format varies by tool)
	// For small results, data is stored here directly
	// For large results, use DataReference instead
	Data interface{}

	// Error contains error information if execution failed
	Error *Error

	// Metadata contains tool-specific metadata
	Metadata map[string]interface{}

	// ExecutionTime in milliseconds
	ExecutionTimeMs int64

	// CacheHit indicates if this result came from cache
	CacheHit bool

	// DataReference points to large result data in shared memory
	// When set, Data field should contain only a brief summary
	DataReference *loomv1.DataReference
}

Result represents the outcome of tool execution.

type SQLiteConfig

type SQLiteConfig struct {
	Path   string               // Database file path (default: ":memory:")
	Tracer observability.Tracer // Tracer for observability (default: NoOpTracer)
}

SQLiteConfig configures the SQLite store.

type SQLiteHumanRequestStore

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

SQLiteHumanRequestStore provides persistent SQLite storage for human requests. Suitable for production deployments with request history and audit trail.

func NewSQLiteHumanRequestStore

func NewSQLiteHumanRequestStore(config SQLiteConfig) (*SQLiteHumanRequestStore, error)

NewSQLiteHumanRequestStore creates a new SQLite-backed human request store.

func (*SQLiteHumanRequestStore) Close

func (s *SQLiteHumanRequestStore) Close() error

Close closes the database connection.

func (*SQLiteHumanRequestStore) ExpireRequest added in v1.4.0

func (s *SQLiteHumanRequestStore) ExpireRequest(ctx context.Context, requestID, respondedBy string) error

ExpireRequest terminally closes a pending row as "timeout" regardless of its expiry — the harness's close for abandoned or swept rows. A resolved row matches zero rows and is left untouched (closing is not resolving); a missing row is a no-op.

func (*SQLiteHumanRequestStore) Get

Get retrieves a human request by ID.

func (*SQLiteHumanRequestStore) ListBySession

func (s *SQLiteHumanRequestStore) ListBySession(ctx context.Context, sessionID string) ([]*HumanRequest, error)

ListBySession returns all requests for a session.

func (*SQLiteHumanRequestStore) ListPending

func (s *SQLiteHumanRequestStore) ListPending(ctx context.Context) ([]*HumanRequest, error)

ListPending returns all pending requests.

func (*SQLiteHumanRequestStore) RespondToRequest

func (s *SQLiteHumanRequestStore) RespondToRequest(ctx context.Context, requestID, status, response, respondedBy string, responseData map[string]interface{}) error

RespondToRequest resolves a pending, non-expired request exactly once. On an already-decided or expired request it is a no-op returning nil, so the caller reads current state via Get. Errors only on a missing request.

func (*SQLiteHumanRequestStore) Store

Store saves a new human request to the database.

func (*SQLiteHumanRequestStore) Update

Update updates an existing human request.

type Tool

type Tool interface {
	// Name returns the tool's unique identifier
	Name() string

	// Description returns a human-readable description for LLM context
	Description() string

	// InputSchema returns the JSON Schema for tool parameters
	InputSchema() *JSONSchema

	// Execute runs the tool with given parameters
	Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

	// Backend returns the backend type this tool requires (e.g., "teradata", "postgres", "api")
	// Empty string means the tool is backend-agnostic
	Backend() string
}

Tool defines the interface for executable tools (shuttles) in the agent framework. Tools are the primary mechanism for agents to interact with backends and perform domain-specific operations. Each tool encapsulates a single capability.

Why "shuttle"? Tools "shuttle" data and execution between the LLM and the backend, like a shuttle in weaving carries thread back and forth across the loom.

func Get

func Get(name string) (Tool, bool)

Get retrieves a tool from the global registry.

func ListByBackend

func ListByBackend(backend string) []Tool

ListByBackend returns all tools for a specific backend from the global registry.

func ListTools

func ListTools() []Tool

ListTools returns all registered tools from the global registry.

func MustGet

func MustGet(name string) Tool

MustGet retrieves a tool from the global registry and panics if not found. This is useful for testing and initialization code.

func NewPromptAwareTool

func NewPromptAwareTool(tool Tool, registry prompts.PromptRegistry, promptKey string) Tool

NewPromptAwareTool wraps a tool with PromptRegistry-based descriptions. If registry is nil, returns the original tool unchanged (no wrapping overhead).

Example:

registry := prompts.NewFileRegistry("./prompts")
tool := builtin.NewHTTPClientTool()
wrapped := shuttle.NewPromptAwareTool(tool, registry, "tools.http_request")
description := wrapped.Description() // Loads from prompts/tools/http_request.yaml

type ToolRegistry

type ToolRegistry interface {
	Search(ctx context.Context, req *loomv1.SearchToolsRequest) (*loomv1.SearchToolsResponse, error)
}

ToolRegistry is an interface for dynamic tool discovery. This avoids import cycles with pkg/tools/registry.

type ToolScope added in v1.4.0

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

ToolScope is the set of tools a binding governs, derived from the binding's Scope selector. It reuses the exact/prefix semantics of tools.permissions: a bare tool name matches exactly, a trailing "*" matches by prefix (e.g. "opendata:*" governs every "opendata:" tool), and a bare "*" governs all.

func NewToolScope added in v1.4.0

func NewToolScope(scope string) ToolScope

NewToolScope compiles a single Scope selector into a ToolScope.

func (ToolScope) MatchesTool added in v1.4.0

func (s ToolScope) MatchesTool(name string) bool

MatchesTool reports whether the named tool is governed by this scope.

Directories

Path Synopsis
Package metadata provides rich, self-describing tool metadata loading.
Package metadata provides rich, self-describing tool metadata loading.

Jump to

Keyboard shortcuts

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