tools

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// DefaultOutputArchiveLimit is the per-call archive budget. It is deliberately
	// larger than a typical MaxOutput so the archive is worth reading, and small
	// enough that a runaway command cannot fill the workspace from one call.
	DefaultOutputArchiveLimit = 1 << 20 // 1 MiB
	// DefaultOutputArchiveDir sits beside the compaction eviction sink's dir so
	// everything the model is expected to read back lives under one root.
	DefaultOutputArchiveDir = ".agent/tool-output"
)
View Source
const SubagentToolName = "spawn_subagent"

SubagentToolName is the registry name of the spawn_subagent tool. Exported so a distribution can scope it out per turn (e.g. WithExcludedTools on an ephemeral one-shot, which must not spawn durable child threads).

View Source
const WorkingDirectoryMetaKey = "working_directory"

WorkingDirectoryMetaKey carries a client-selected per-turn working directory. The local workspace still enforces its read/write roots; this value only changes how otherwise-relative tool paths are resolved.

Variables

View Source
var (
	ErrInvalidCommandPolicy = errors.New("tools: invalid command policy")
	ErrCommandPolicyFixture = errors.New("tools: command policy fixture failed")
)
View Source
var (
	ErrInvalidTool     = errors.New("tools: invalid tool")
	ErrToolExists      = errors.New("tools: already registered")
	ErrUnknownTool     = errors.New("tools: unknown tool")
	ErrInvalidArgument = errors.New("tools: invalid argument")
)
View Source
var ErrToolRequiresApproval = fmt.Errorf("tools: approval required")

Functions

func ApplySafeError

func ApplySafeError(event *ToolEvent, err error)

func ArgumentsHash

func ArgumentsHash(args []byte) string

func DurationMillis

func DurationMillis(elapsed time.Duration) int64

func MessageIDFrom

func MessageIDFrom(ctx context.Context) (string, bool)

MessageIDFrom returns the assistant message id carried by WithMessageID.

func MessageWorkingDirectory

func MessageWorkingDirectory(message protocol.ChatMessage) (string, bool)

MessageWorkingDirectory returns an absolute client-selected cwd when present.

func RegisterArtifact

func RegisterArtifact(reg *Registry, cfg ArtifactConfig) error

RegisterArtifact registers present_artifact: the agent names workspace files it produced (e.g. a chart it saved) and the harness surfaces them to the user as native image/file content parts on the assistant message — bypassing markdown image rendering entirely. Small images inline as a data_uri; larger images and non-image files upload via the ArtifactUploader and ride as a vfs_ref the frontend presigns and fetches.

func RegisterInjectImage

func RegisterInjectImage(reg *Registry, cfg InjectImageConfig) error

RegisterInjectImage registers inject_image: the agent names a local image it produced (a chart it saved, a rendered slide/PDF page) and the harness injects it as a user-role image message into the agent's OWN context so it can visually inspect its own work before finishing. The image snapshot rides as a base64 data_uri (reusing the provider passthrough — no provider change); when the image enters context the harness escalates the rest of the turn to a vision-capable model.

func RegisterLocal

func RegisterLocal(reg *Registry, cfg LocalConfig) error

func RegisterMCPListResourceTemplatesTool

func RegisterMCPListResourceTemplatesTool(reg *Registry, cfg MCPResourceConfig) error

func RegisterMCPListResourcesTool

func RegisterMCPListResourcesTool(reg *Registry, cfg MCPResourceConfig) error

func RegisterMCPReadResourceTool

func RegisterMCPReadResourceTool(reg *Registry, cfg MCPResourceConfig) error

func RegisterMCPTool

func RegisterMCPTool(reg *Registry, cfg MCPToolConfig) error

func RegisterMemory

func RegisterMemory(reg *Registry, recaller MemoryRecaller) error

RegisterMemory registers the memory_search and memory_get tools backed by the recaller, with descriptors so they are exposed via the provider tool-API.

func RegisterRemote

func RegisterRemote(reg *Registry, names []string, cfg RemoteConfig) error

func RegisterSubagent

func RegisterSubagent(reg *Registry, runner subagent.Runner, maxDepth int) error

RegisterSubagent registers the spawn_subagent tool, which delegates a self-contained sub-task to a fresh, bounded sub-agent. maxDepth caps nesting (a call at or beyond the limit returns an error result rather than recursing). The runner may be a late-bound *subagent.Ref so the tool can be surfaced to the model before the concrete runner is constructed.

func RegisterSubagentWithConfig

func RegisterSubagentWithConfig(reg *Registry, cfg SubagentConfig) error

func ResolveWorkingPath

func ResolveWorkingPath(ctx context.Context, path string) string

ResolveWorkingPath resolves a tool path from the active per-turn cwd. With no active cwd it preserves the original argument byte-for-byte.

func SafeToolError

func SafeToolError(err error) (string, string)

func StampWorkingDirectory

func StampWorkingDirectory(message *protocol.ChatMessage, cwd string)

StampWorkingDirectory records cwd on an inbound message. Empty and relative values are ignored so an untrusted message cannot silently reinterpret paths.

func ViewMutatingTool added in v0.0.2

func ViewMutatingTool(name string) bool

ViewMutatingTool reports tools that can modify the selected execution view. Shell and Python are conservatively mutating because their payloads are general programs and cannot be proven read-only from the tool name alone.

func WithCommandDelegate

func WithCommandDelegate(ctx context.Context, d CommandDelegate) context.Context

WithCommandDelegate carries a CommandDelegate on ctx. nil is a no-op.

func WithFileDelegate

func WithFileDelegate(ctx context.Context, d FileDelegate) context.Context

WithFileDelegate carries a FileDelegate on ctx. nil is a no-op.

func WithMemoryAuthority

func WithMemoryAuthority(ctx context.Context, auth MemoryAuthority) context.Context

WithMemoryAuthority returns a context carrying the per-turn MemoryAuthority. The runner sets this before a history load so an authority-aware store can apply the user's OBO grant; stores that don't need it simply ignore it.

func WithMessageID

func WithMessageID(ctx context.Context, id string) context.Context

WithMessageID carries the enclosing assistant message id on ctx so the Request builders (session + dynamic invocation) can stamp Request.MessageID. Mirrors how MemoryAuthority flows on ctx. Empty id is a no-op.

func WithModelPreference

func WithModelPreference(ctx context.Context, fn ModelPreferenceFunc) context.Context

WithModelPreference returns a context carrying the per-turn model-preference fn.

func WithPartEmitter

func WithPartEmitter(ctx context.Context, e PartEmitter) context.Context

WithPartEmitter returns a context carrying the per-turn PartEmitter.

func WithSkillRealizer

func WithSkillRealizer(ctx context.Context, fn SkillRealizerFunc) context.Context

WithSkillRealizer returns a context carrying the per-turn skill realizer.

func WithToolDelegate added in v0.0.2

func WithToolDelegate(ctx context.Context, d ToolDelegate) context.Context

WithToolDelegate carries a full-invocation delegate on ctx. nil is a no-op.

func WithViewWriteAccess added in v0.0.2

func WithViewWriteAccess(ctx context.Context, access workspacepkg.ViewWriteAccess) context.Context

WithViewWriteAccess applies a local execution ceiling even when standalone mode has no explicit distributed view binding.

func WithWorkingDirectory

func WithWorkingDirectory(ctx context.Context, cwd string) context.Context

WithWorkingDirectory carries the active cwd across the whole tool loop, including synchronous subagents spawned from that turn.

func WithWorldStateSink

func WithWorldStateSink(ctx context.Context, s WorldStateSink) context.Context

WithWorldStateSink returns a context carrying the per-turn WorldStateSink.

func WithoutToolDelegate added in v0.0.2

func WithoutToolDelegate(ctx context.Context) context.Context

WithoutToolDelegate marks a host-local execution boundary. A remote/worker delegate calls a local registry to execute the selected tool; that nested registry must not rediscover the outer delegate and recurse into itself.

func WorkingDirectoryFrom

func WorkingDirectoryFrom(ctx context.Context) (string, bool)

WorkingDirectoryFrom returns the active per-turn cwd.

Types

type ArtifactConfig

type ArtifactConfig struct {
	// Workspace reads the artifact bytes the agent names. Required.
	Workspace *localtools.Workspace
	// Uploader stores large images + non-image files and returns a vfs_ref. nil →
	// inline images only; non-image files error.
	Uploader ArtifactUploader
	// InlineMaxBytes is the image inline-vs-upload threshold (<=0 → 128 KiB).
	InlineMaxBytes int
	// MaxBytes hard-caps a single artifact read (<=0 → 32 MiB).
	MaxBytes int
	// SessionDir, when set, returns the per-(thread,context) working directory a
	// code session writes relative files into (e.g. sahara's
	// /sahara/sessions/<key>). A RELATIVE artifact path is resolved against it
	// first — so a file a kernel saved with a bare name (plt.savefig('p.png')) is
	// found — falling back to the workspace root. nil (or "" result) → paths
	// resolve against the workspace root only (the prior behavior). The harness
	// stays scheme-agnostic; the distribution owns the path derivation.
	SessionDir func(threadID, context string) string
}

ArtifactConfig configures the present_artifact tool.

type ArtifactUploader

type ArtifactUploader interface {
	Upload(ctx context.Context, addr protocol.MessageAddress, name, mimeType string, data []byte) (vfsRef string, err error)
}

ArtifactUploader uploads agent-produced artifact bytes to a durable, frontend-fetchable store and returns an opaque reference (a data-connectors vfs_ref) the frontend resolves to a presigned download URL. The harness core ships no uploader; the distribution wires one (sahara: data-connectors). A nil uploader limits present_artifact to inlining images (non-image files error).

type AuditRecord

type AuditRecord struct {
	CallID        string       `json:"call_id"`
	ToolName      string       `json:"tool_name"`
	Decision      DecisionCode `json:"decision"`
	Reason        string       `json:"reason"`
	AuditCode     string       `json:"audit_code"`
	ArgumentsHash string       `json:"arguments_hash"`
}

func NewAuditRecord

func NewAuditRecord(req Request, decision Decision) AuditRecord

type AuditSink

type AuditSink interface {
	Record(ctx context.Context, record AuditRecord) error
}

type CommandDecider

type CommandDecider interface {
	DecideCommand(req CommandRequest) CommandDecision
}

type CommandDecision

type CommandDecision struct {
	Decision
	MatchedRuleID    string
	MatchedPrefix    []string
	MatchedRules     []CommandRuleMatch
	ReadOnly         bool
	HostExecutable   string
	ResolvedProgram  string
	WorkspaceRoot    string
	WorkingDirectory string
	EnvKeys          []string
}

type CommandDelegate

type CommandDelegate interface {
	RunCommand(ctx context.Context, spec localtools.CommandSpec) (localtools.CommandResult, error)
}

CommandDelegate overrides RunCommand for a turn: when one is on ctx (WithCommandDelegate), the shell/python handlers run the command through it (e.g. the ACP terminal/* client capability) instead of cfg.Workspace. The command policy is still enforced first, on the handler side.

func CommandDelegateFrom

func CommandDelegateFrom(ctx context.Context) CommandDelegate

CommandDelegateFrom returns the CommandDelegate carried by WithCommandDelegate, or nil.

type CommandPattern

type CommandPattern struct {
	Tokens []CommandToken
}

type CommandPolicy

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

func NewCommandPolicy

func NewCommandPolicy(cfg CommandPolicyConfig) (*CommandPolicy, error)

func (*CommandPolicy) DecideCommand

func (p *CommandPolicy) DecideCommand(req CommandRequest) CommandDecision

type CommandPolicyConfig

type CommandPolicyConfig struct {
	Rules                  []CommandRule
	HostExecutables        []HostExecutable
	ResolveHostExecutables bool
	DefaultDecision        DecisionCode
}

type CommandRequest

type CommandRequest struct {
	Argv          []string
	WorkspaceRoot string
	CWD           string
	Env           []string
}

type CommandRule

type CommandRule struct {
	ID             string
	Decision       DecisionCode
	Reason         string
	AuditCode      string
	Pattern        CommandPattern
	HostExecutable string
	ReadOnly       bool
	Match          [][]string
	NotMatch       [][]string
	EnvAllowlist   []string
}

type CommandRuleMatch

type CommandRuleMatch struct {
	RuleID          string       `json:"rule_id"`
	MatchedPrefix   []string     `json:"matched_prefix"`
	Decision        DecisionCode `json:"decision"`
	Reason          string       `json:"reason"`
	AuditCode       string       `json:"audit_code"`
	ReadOnly        bool         `json:"read_only"`
	HostExecutable  string       `json:"host_executable,omitempty"`
	ResolvedProgram string       `json:"resolved_program,omitempty"`
}

type CommandToken

type CommandToken struct {
	Literal      string
	Alternatives []string
}

func AnyToken

func AnyToken(values ...string) CommandToken

func LiteralToken

func LiteralToken(value string) CommandToken

type ConcurrencyClass added in v0.0.2

type ConcurrencyClass int

ConcurrencyClass declares whether invocations of a tool may overlap other calls from the same assistant message. This is execution metadata only: it is intentionally not sent to the model or inferred from a tool name.

The zero value is conservative. A call is eligible for concurrent execution only when every call in the assistant's batch is explicitly ParallelSafe.

const (
	// ConcurrencyUnspecified keeps the tool sequential. This is the descriptor
	// zero value so existing and dynamically discovered tools fail closed.
	ConcurrencyUnspecified ConcurrencyClass = iota
	// ConcurrencySequential explicitly requires source-ordered execution.
	ConcurrencySequential
	// ConcurrencyParallelSafe promises that overlapping invocations do not
	// mutate shared state, do not use turn-mutating emitters, and are safe under
	// the turn's shared cancellation. Effects must be returned in Result.
	ConcurrencyParallelSafe
)

type Decision

type Decision struct {
	Code      DecisionCode `json:"code"`
	Reason    string       `json:"reason"`
	AuditCode string       `json:"audit_code"`
}

func (Decision) Allowed

func (d Decision) Allowed() bool

type DecisionCode

type DecisionCode string
const (
	DecisionAllow            DecisionCode = "allow"
	DecisionRequiresApproval DecisionCode = "requires_approval"
	DecisionDeny             DecisionCode = "deny"
)

type Descriptor

type Descriptor struct {
	Name        string
	Description string
	Parameters  json.RawMessage // JSON-schema object; empty => {"type":"object"}
	// CatalogKind is the portable descriptor kind for dynamically published
	// tools. Empty defaults to "remote" in the ToolProvider compatibility
	// adapter.
	CatalogKind string
	// CatalogMeta is provider-owned metadata that is admitted as part of the
	// immutable catalog entry and returned to that same provider at invocation.
	// It is never exposed as model arguments. Transport adapters use it to carry
	// an upstream exact reference through a wrapper catalog without trusting
	// caller-supplied routing fields.
	CatalogMeta map[string]json.RawMessage
	// Effect is the highest admitted portable side-effect class. Empty defaults
	// conservatively to execute; providers should set it when they can classify
	// the tool more precisely.
	Effect spec.ToolEffect
	// Trust is the authorization hint for the tool. Zero value (TrustDefault)
	// leaves authorization to the runtime policy; a provider may stamp a stronger
	// hint (a later stage — no local descriptor sets it today).
	Trust TrustLevel
	// Concurrency is the tool's neutral runtime concurrency contract. It is not
	// part of the model-facing function schema. Unspecified is sequential.
	Concurrency ConcurrencyClass
}

Descriptor is the model-facing metadata for a tool: its name, a one-line description, and a JSON-schema object for its arguments. It is what the provider tool-API needs to expose the tool to the model.

type DynamicPolicy

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

DynamicPolicy augments a base policy with runtime grants from the approval flow: in-memory grants (session and hydrated "always") plus an optional GrantStore for durable persistence. Grants are keyed by (workspace, tool). Safe for concurrent use.

func NewDynamicPolicy

func NewDynamicPolicy(base Policy, store GrantStore) *DynamicPolicy

func (*DynamicPolicy) Decide

func (p *DynamicPolicy) Decide(req Request) Decision

func (*DynamicPolicy) GrantAlways

func (p *DynamicPolicy) GrantAlways(ctx context.Context, workspaceID, tool string) error

GrantAlways persists a durable per-workspace grant (and covers the session). Degrades to a session grant when no store is configured.

func (*DynamicPolicy) GrantSession

func (p *DynamicPolicy) GrantSession(workspaceID, tool string)

GrantSession records an in-memory grant for the rest of this session/process.

func (*DynamicPolicy) Hydrate

func (p *DynamicPolicy) Hydrate(ctx context.Context, workspaceID string) error

Hydrate loads a workspace's durable "always" grants into memory (called once per workspace before serving tools). No-op without a store.

type EffectLookup added in v0.0.2

type EffectLookup func(tool string) (spec.ToolEffect, bool)

EffectLookup resolves a tool name to its declared portable effect. The bool is false for a tool that declared none — an unclassified tool must never be treated as read-only by omission.

type FileAuditSink

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

func NewFileAuditSink

func NewFileAuditSink(path string) (*FileAuditSink, error)

func (*FileAuditSink) Record

func (s *FileAuditSink) Record(ctx context.Context, record AuditRecord) (err error)

type FileChange

type FileChange struct {
	Path string `json:"path"`
	Kind string `json:"kind"`
}

type FileDelegate

type FileDelegate interface {
	ReadFile(ctx context.Context, path string, maxBytes int64) (string, error)
	WriteFile(ctx context.Context, path, content string) error
	EditFile(ctx context.Context, path, oldText, newText string) error
}

FileDelegate overrides the workspace file ops for a turn: when one is on ctx (WithFileDelegate), the read_file/write_file/edit_file handlers route through it instead of cfg.Workspace. The ACP channel wires one that proxies to the editor's fs/* client capability so edits land in the user's open buffers. No ACP import here — the seam is ctx-carried, so tools stays transport-agnostic. ReadFile may be called concurrently when an assistant emits a batch composed entirely of explicitly parallel-safe read tools; implementations must correlate concurrent requests or serialize internally.

func FileDelegateFrom

func FileDelegateFrom(ctx context.Context) FileDelegate

FileDelegateFrom returns the FileDelegate carried by WithFileDelegate, or nil.

type GrantStore

type GrantStore interface {
	// ListGranted returns the tools durably authorized for a workspace.
	ListGranted(ctx context.Context, workspaceID string) ([]string, error)
	// Grant durably authorizes tool for workspaceID.
	Grant(ctx context.Context, workspaceID, tool string) error
	// IsGranted reports whether tool is durably authorized for workspaceID.
	// Used by the approval slow-path to short-circuit a prompt when a prior
	// "always" grant already exists in durable storage.
	IsGranted(ctx context.Context, workspaceID, tool string) (bool, error)
}

GrantStore persists "always"-scoped tool authorizations per workspace. The approval flow writes to it on an "always" grant; DynamicPolicy hydrates from it. Implemented by the distribution (e.g. a MemoryLayer-backed store). Phase-3 surface; DynamicPolicy works without one (session-only).

type Handler

type Handler interface {
	Invoke(ctx context.Context, req Request) (Result, error)
}

type HandlerFunc

type HandlerFunc func(ctx context.Context, req Request) (Result, error)

func (HandlerFunc) Invoke

func (f HandlerFunc) Invoke(ctx context.Context, req Request) (Result, error)

type HostExecutable

type HostExecutable struct {
	Name  string
	Paths []string
}

type InjectImageConfig

type InjectImageConfig struct {
	// Workspace reads the image bytes the agent names. Required.
	Workspace *localtools.Workspace
	// SessionDir, when set, returns the per-(thread,context) working directory a
	// code session writes relative files into (sahara's /sahara/sessions/<key>).
	// A RELATIVE image path resolves against it first, falling back to the
	// workspace root. nil → workspace-root only. Shares the artifact tool's
	// derivation; the distribution owns it.
	SessionDir func(threadID, context string) string
	// MaxBytes hard-caps a single injected image read (<=0 → 5 MiB).
	MaxBytes int
	// MaxImageEdge, when > 0, downscales an injected image so its longest edge is
	// <= MaxImageEdge before it's inlined as a data_uri — bounding the vision-token
	// cost of a high-resolution screenshot / PDF-page render. Only the model-bound
	// copy is scaled; the on-disk artifact is untouched. <=0 → no downscale.
	MaxImageEdge int
}

InjectImageConfig configures the inject_image tool. It mirrors the artifact tool's path-resolution seam (Workspace + SessionDir) so a bare relative path resolves to the code session's working dir (where the python tool saves).

type LocalConfig

type LocalConfig struct {
	Workspace     *localtools.Workspace
	Python        string
	Exa           *localtools.ExaClient
	Timeout       time.Duration
	MaxOutput     int
	CommandPolicy CommandDecider
	// OutputArchiveLimit bounds the bytes one truncated command retains in the
	// recovery archive. 0 uses DefaultOutputArchiveLimit; negative disables
	// archiving (the truncation marker is still emitted — a silent cut is the
	// bug, the archive is the remedy).
	OutputArchiveLimit int
	// OutputArchiveDir is the workspace-relative directory archives are written
	// to. Empty uses DefaultOutputArchiveDir.
	OutputArchiveDir string
}

type MCPCaller

type MCPCaller interface {
	CallTool(ctx context.Context, server string, tool string, args json.RawMessage) (mcp.CallToolResult, error)
}

type MCPProvider

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

MCPProvider exposes configured MCP servers' tools as a dynamic, per-turn tool provider (structurally a turn ToolProvider: ID/Tools/Invoke). It replaces the static per-tool RegisterMCPTool model while keeping identical tool names, schemas, and result shapes so the swap is behavior-identical. Trust is left at the descriptor zero value (TrustDefault): gating is the pipeline's job.

func NewMCPProvider

func NewMCPProvider(cfg MCPProviderConfig) *MCPProvider

NewMCPProvider builds an MCPProvider from cfg.

func (*MCPProvider) ID

func (p *MCPProvider) ID() string

ID identifies the provider.

func (*MCPProvider) Invoke

func (p *MCPProvider) Invoke(ctx context.Context, req Request) (Result, error)

Invoke resolves the registry name back to (server, tool) — via the route map built by Tools(), falling back to mcpParseRegistryName — calls CallTool, and converts the result identically to RegisterMCPTool's handler.

func (*MCPProvider) Tools

Tools lists every configured server's tools and converts each mcp.Tool to a Descriptor using the mcpRegistryName scheme. A per-server ListTools error is logged and that server skipped (best-effort, mirroring the manager's own tolerance); other servers still contribute. It also refreshes the internal registry-name -> (server, tool) route map used by Invoke.

type MCPProviderConfig

type MCPProviderConfig struct {
	Caller  MCPCaller
	Servers []string
}

MCPProviderConfig configures an MCPProvider. Caller is the same MCPCaller used by RegisterMCPTool (a *mcp.Manager also satisfies the internal lister used for discovery). Servers is the explicit set of MCP server names to expose.

type MCPResourceConfig

type MCPResourceConfig struct {
	RegistryName string
	ServerName   string
	Client       mcp.ResourceClient
}

type MCPToolConfig

type MCPToolConfig struct {
	RegistryName string
	ServerName   string
	ToolName     string
	Caller       MCPCaller
}

type MemoryAuthority

type MemoryAuthority struct {
	GrantID     string
	SubjectType string
	SubjectID   string
}

MemoryAuthority is the per-turn OBO grant for MemoryLayer calls. A zero value means "use the client's default authority" (the configured fallback grant).

func MemoryAuthorityFrom

func MemoryAuthorityFrom(ctx context.Context) (MemoryAuthority, bool)

MemoryAuthorityFrom returns the MemoryAuthority carried on ctx, and whether one was present. Absence yields the zero value (no OBO → client/default behavior).

type MemoryHit

type MemoryHit struct {
	ID      string  `json:"id"`
	Content string  `json:"content"`
	Score   float64 `json:"score"`

	// Detail fields — populated by memory_get (fetch-by-id) to give the model
	// full context + provenance; memory_search leaves them empty so scan results
	// stay lean (omitempty drops them). Document provenance (source filename,
	// document id, page) lives in Metadata/Tags for ingested memories.
	Type           string         `json:"type,omitempty"`
	Subtype        string         `json:"subtype,omitempty"`
	Tags           []string       `json:"tags,omitempty"`
	Metadata       map[string]any `json:"metadata,omitempty"`
	Abstract       string         `json:"abstract,omitempty"`
	SourceMemoryID string         `json:"source_memory_id,omitempty"`
	CreatedAt      string         `json:"created_at,omitempty"`
}

MemoryHit is a single recalled memory.

type MemoryRecaller

type MemoryRecaller interface {
	Recall(ctx context.Context, auth MemoryAuthority, workspace, query string, limit int) ([]MemoryHit, error)
	GetMemory(ctx context.Context, auth MemoryAuthority, id string) (MemoryHit, error)
}

MemoryRecaller is the read surface of MemoryLayer the harness exposes to the model as on-demand tools (semantic recall + fetch-by-id). Implemented by the MemoryLayer SDK adapter in internal/memory. Workspace + OBO authority are passed per call (the turn's chat workspace and grant) rather than baked into the client.

type ModelPreferenceFunc

type ModelPreferenceFunc func(name string) bool

ModelPreferenceFunc requests that the thread switch to model `name` for subsequent turns. Best-effort: the runner applies it only if `name` is a registered/available model and returns whether it did. It pins the thread's model exactly like the /model command — effective from the NEXT turn, since the current turn's model is fixed at turn start. An empty name, no model registry, or an unknown model is a silent no-op.

Like WorldStateSink and PartEmitter, this is a request-scoped capability the runner installs on ctx rather than threading a model-switch dependency through every tool signature. load_skill uses it to honor a skill's metadata.scitrera.preferred_model.

func ModelPreferenceFrom

func ModelPreferenceFrom(ctx context.Context) (ModelPreferenceFunc, bool)

ModelPreferenceFrom returns the model-preference fn carried on ctx, and whether one was present. Absence is a no-op (e.g. a transport with no model registry).

type PartEmitter

type PartEmitter interface {
	// UpsertPart streams part: appended the first time its id is seen, or merged
	// in place (part_updated) on subsequent calls with the same id. A part with
	// no id is appended each call. The latest version of each id-bearing part is
	// folded into the finalized assistant message.
	UpsertPart(ctx context.Context, part protocol.ContentPart) error
}

PartEmitter lets a tool surface a content part on the current assistant message's stream (e.g. a todo checklist the model writes via todo_write). The runner provides one per turn via the context; it streams the part and folds it into the finalized message so it persists. Like MemoryAuthority, it is a request-scoped capability carried on ctx rather than threaded through every tool signature.

func PartEmitterFrom

func PartEmitterFrom(ctx context.Context) (PartEmitter, bool)

PartEmitterFrom returns the PartEmitter carried on ctx, and whether one was present. Tools that want to surface a part check this; absence means the part is simply not streamed (e.g. a non-streaming CLI path).

type PatchFileDelegate added in v0.0.2

type PatchFileDelegate interface {
	ApplyPatch(ctx context.Context, operations []localtools.PatchOperation) ([]localtools.PatchChange, error)
}

PatchFileDelegate is an optional extension for transports that can preserve apply_patch's multi-file validation and commit semantics. When a FileDelegate is present without this extension, apply_patch fails closed instead of bypassing the editor/remote filesystem and mutating the local workspace.

type Policy

type Policy interface {
	Decide(req Request) Decision
}

type PreparedInvocation added in v0.0.2

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

PreparedInvocation is a registry call whose policy, audit, and execution-view checks have already succeeded. Preparing a batch in assistant source order lets the turn runner settle every approval before any explicitly safe call is started. A prepared invocation is immutable and may be invoked once.

func (PreparedInvocation) Invoke added in v0.0.2

func (p PreparedInvocation) Invoke(ctx context.Context) (Result, error)

Invoke executes the already-admitted call body. It does not repeat policy or approval checks; the execution-view ceiling was captured by Prepare.

type ReadOnlyAutoApprove added in v0.0.2

type ReadOnlyAutoApprove struct {
	Base   Policy
	Effect EffectLookup
}

ReadOnlyAutoApprove is the read-only approval tier: it allows any tool whose declared effect is `read` and defers everything else to the base policy.

It exists because an always-ask configuration that prompts for `read_file` teaches users to approve without reading the prompt, which costs more safety than the prompt ever bought. Only an EXPLICIT read effect qualifies: a tool with no declared effect, or one whose effect the provider could not classify, falls through to the base policy unchanged.

func NewReadOnlyAutoApprove added in v0.0.2

func NewReadOnlyAutoApprove(base Policy, lookup EffectLookup) ReadOnlyAutoApprove

NewReadOnlyAutoApprove wraps base so read-effect tools resolved through lookup are auto-approved. A nil lookup disables the tier (every call defers to base), so a misconfiguration fails closed rather than approving everything.

func (ReadOnlyAutoApprove) Decide added in v0.0.2

func (p ReadOnlyAutoApprove) Decide(req Request) Decision

type Registry

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

func NewAuditedRegistry

func NewAuditedRegistry(policy Policy, audit AuditSink) *Registry

func NewRegistry

func NewRegistry() *Registry

func (*Registry) Describe

func (r *Registry) Describe(d Descriptor)

Describe attaches model-facing metadata for an already-registered tool. Unknown names are ignored so callers can describe best-effort.

func (*Registry) Descriptors

func (r *Registry) Descriptors() []Descriptor

Descriptors returns descriptors for all registered tools, sorted by name. Tools registered without an explicit descriptor get a name-only entry so the model still learns they exist.

func (*Registry) EffectOf added in v0.0.2

func (r *Registry) EffectOf(name string) (spec.ToolEffect, bool)

EffectOf returns a registered tool's declared portable effect. The bool is false for an unknown tool or one registered without a descriptor/effect, so a caller can distinguish "declared read" from "never said" — a policy that conflated the two would auto-approve every unclassified tool.

func (*Registry) Invoke

func (r *Registry) Invoke(ctx context.Context, req Request) (Result, error)

func (*Registry) Names

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

func (*Registry) Override

func (r *Registry) Override(name string, handler Handler) error

Override registers handler under name, replacing any existing handler for that name (unlike Register, which errors on a duplicate). Used to swap an already-registered tool implementation — e.g. redirecting the local "python" tool to a sandboxed execd backend. Descriptors are left untouched; re-describe separately if the schema changes.

func (*Registry) Prepare added in v0.0.2

func (r *Registry) Prepare(ctx context.Context, req Request) (PreparedInvocation, error)

Prepare resolves a tool and performs every admission check without executing its body. Authorization is deliberately separated from Invoke so a caller can preflight an entire explicitly parallel-safe batch deterministically.

func (*Registry) Register

func (r *Registry) Register(name string, handler Handler) error

func (*Registry) SetEventSink

func (r *Registry) SetEventSink(events ToolEventSink)

func (*Registry) SetExcluded

func (r *Registry) SetExcluded(names []string)

SetExcluded marks tool names that must NOT enter the registry: subsequent Register/Override/Describe calls for an excluded name become silent no-ops, so the tool is invisible to the model (absent from Descriptors) and uninvocable (Invoke returns ErrUnknownTool). Call before registering tools; additive across calls. This is the general "drop a built-in tool" seam (e.g. a distribution that provides web_search as a skill instead of the built-in Exa tool).

func (*Registry) SetPolicy

func (r *Registry) SetPolicy(policy Policy, audit AuditSink)

type RemoteConfig

type RemoteConfig struct {
	Invoker RemoteInvoker
	Policy  Policy
	Audit   AuditSink
}

type RemoteInvoker

type RemoteInvoker interface {
	InvokeTool(ctx context.Context, envelope protocol.ToolInvokeEnvelope) (Result, error)
}

type Request

type Request struct {
	CallID string
	// MessageID is the id of the assistant message that carries this tool call.
	// Tools that create cross-thread back-refs (spawn_subagent) record it as the
	// parent MESSAGE id; the tool CallID alone doesn't identify the enclosing
	// message. Empty when the caller doesn't supply it.
	MessageID string
	Name      string
	Arguments json.RawMessage
	Addr      protocol.MessageAddress
	// Authority is the turn's OBO grant, injected by the session so tools (e.g.
	// memory_search) act on behalf of the turn's user with a fresh grant.
	Authority MemoryAuthority
	// Approved marks a call the user just authorized via the approval flow, so
	// the registry bypasses the policy gate for this single invocation. (Durable
	// session/always grants are recorded separately in the policy.)
	Approved bool
	// ToolRef is the exact provider-qualified catalog entry selected for a
	// dynamically surfaced tool. It is nil for the standalone static-registry
	// path and older bare-name callers.
	ToolRef *protocol.ToolReference
	// CatalogMeta is copied from the exact catalog entry after the independent
	// invocation-time resolution succeeds. It is trusted admitted metadata, not
	// model input. Providers may use it to validate an upstream exact reference.
	CatalogMeta map[string]json.RawMessage
}

func RequestFromEnvelope

func RequestFromEnvelope(env protocol.ToolInvokeEnvelope) Request

type Result

type Result struct {
	CallID   string          `json:"call_id"`
	Name     string          `json:"name"`
	Payload  json.RawMessage `json:"payload"`
	IsError  bool            `json:"is_error"`
	Metadata ResultMetadata  `json:"metadata,omitempty"`
	// Parts holds optional extra content parts the tool loop records alongside
	// the tool_result part (e.g. a subagent reference part). Runtime-only (not a
	// wire field); nil = today's single tool_result behavior.
	Parts []protocol.ContentPart `json:"-"`
	// InjectUserParts holds content parts the tool loop records as a NEW
	// user-role message (distinct from Parts, which co-locate on the tool-result
	// message). Used by inject_image to put a locally-produced image into the
	// model's OWN context: an image must ride on a user-role message (a tool-role
	// message is text-only), so it cannot go on the tool_result. Runtime-only;
	// nil = no-op. An image part here also triggers mid-loop vision escalation.
	InjectUserParts []protocol.ContentPart `json:"-"`
}

func NewJSONResult

func NewJSONResult(callID string, name string, payload json.RawMessage) (Result, error)

func (Result) ContentPart

func (r Result) ContentPart() (protocol.ContentPart, error)

type ResultMetadata

type ResultMetadata struct {
	PayloadBytes    int          `json:"payload_bytes,omitempty"`
	ExitCode        int          `json:"exit_code,omitempty"`
	OutputBytes     int          `json:"output_bytes,omitempty"`
	OutputTruncated bool         `json:"output_truncated,omitempty"`
	PID             int          `json:"pid,omitempty"`
	FileChanges     []FileChange `json:"file_changes,omitempty"`
	// References are stable pointers returned by a tool to an authoritative
	// record. They let execution observers retain provenance without copying the
	// record payload into an operational ledger.
	References []ResultReference `json:"references,omitempty"`
}

type ResultReference added in v0.0.2

type ResultReference struct {
	System string `json:"system"`
	Kind   string `json:"kind"`
	ID     string `json:"id"`
}

type SkillRealizerFunc

type SkillRealizerFunc func(ctx context.Context, names []string) (int, error)

SkillRealizerFunc materializes the named skills' bundle files — utils.py, references/, assets/, and the shared bundle — into the distribution's skills root (e.g. the sandbox's shared /skills directory), so code running in the code-sidecar (e.g. import_path('/skills/<name>/utils.py'), and skill bodies citing that root) resolves them. load_skill calls it with the target skill plus its prerequisites. It is best-effort and idempotent: it returns the number of files realized; absence on ctx (no materialize dir / non-relay transport) is a silent no-op.

Like ModelPreference and WorldStateSink, this is a request-scoped capability the runner installs on ctx rather than threading a materialization dependency through every tool signature. The realization SOURCE (MemoryLayer bundle files, a workspace skill folder, an image-baked root) is the distribution's concern; the tool just names the skills it loaded.

func SkillRealizerFrom

func SkillRealizerFrom(ctx context.Context) (SkillRealizerFunc, bool)

SkillRealizerFrom returns the skill realizer carried on ctx, and whether one was present. Absence is a no-op (e.g. a transport with no skills materialization).

type StaticPolicy

type StaticPolicy struct {
	Allowed map[string]string
}

func (StaticPolicy) Decide

func (p StaticPolicy) Decide(req Request) Decision

type SubagentConfig

type SubagentConfig struct {
	Runner   subagent.Runner
	MaxDepth int
	Catalog  subagent.Catalog
	// AllowBackground advertises + enables the `background` spawn argument. Set it
	// only when the Runner supports detached execution (implements
	// subagent.BackgroundRunner with a Notifier wired); off → the tool is
	// synchronous-only and the schema omits `background`.
	AllowBackground bool
}

type ToolDelegate added in v0.0.2

type ToolDelegate interface {
	HandlesTool(name string) bool
	InvokeTool(ctx context.Context, req Request) (Result, error)
}

ToolDelegate redirects complete tool invocations for a turn. It is used when the tool authority is on another host (for example, an Aether-connected TUI that owns the selected checkout). Registry policy and audit run before this seam, so the remote host receives only admitted calls. InvokeTool may be called concurrently only for descriptors explicitly marked ParallelSafe; implementations must support that overlap or serialize internally.

func ToolDelegateFrom added in v0.0.2

func ToolDelegateFrom(ctx context.Context) ToolDelegate

ToolDelegateFrom returns the full-invocation delegate on ctx, or nil.

type ToolEvent

type ToolEvent struct {
	Status           ToolEventStatus         `json:"status"`
	CallID           string                  `json:"call_id"`
	ToolName         string                  `json:"tool_name"`
	Addr             protocol.MessageAddress `json:"addr"`
	ArgumentsHash    string                  `json:"arguments_hash"`
	ArgumentsBytes   int                     `json:"arguments_bytes"`
	DurationMS       int64                   `json:"duration_ms,omitempty"`
	ApprovalDecision string                  `json:"approval_decision,omitempty"`
	PolicyDecision   string                  `json:"policy_decision,omitempty"`
	Reason           string                  `json:"reason,omitempty"`
	IsError          bool                    `json:"is_error,omitempty"`
	ErrorCode        string                  `json:"error_code,omitempty"`
	ErrorMessage     string                  `json:"error,omitempty"`
	Result           ResultMetadata          `json:"result,omitempty"`
}

func NewToolEvent

func NewToolEvent(status ToolEventStatus, req Request) ToolEvent

type ToolEventSink

type ToolEventSink interface {
	// EmitToolEvent may be called concurrently for explicitly parallel-safe tool
	// bodies. Implementations must synchronize shared state or serialize events.
	EmitToolEvent(ctx context.Context, event ToolEvent) error
}

type ToolEventStatus

type ToolEventStatus string
const (
	ToolEventQueued   ToolEventStatus = "queued"
	ToolEventStarted  ToolEventStatus = "started"
	ToolEventFinished ToolEventStatus = "finished"
	ToolEventAborted  ToolEventStatus = "aborted"
)

type TrustLevel

type TrustLevel int

TrustLevel is a tool's authorization hint carried on its Descriptor: how the authorization pipeline should treat it before the runtime policy weighs in. The zero value (TrustDefault) preserves today's behavior — the local requires-approval trigger stays the runtime policy, not this hint.

const (
	// TrustDefault leaves authorization to the runtime policy (local tools) or
	// the provider's own gating (provider tools). Zero value; today's behavior.
	TrustDefault TrustLevel = iota
	// TrustRequiresApproval marks a tool that must be approved before it runs.
	TrustRequiresApproval
	// TrustPreAuthorized marks a tool authorized ahead of time (no prompt); it
	// still passes through the safety authorizer like every other tool.
	TrustPreAuthorized
	// TrustRequiresFreshApproval requires a new once-only human decision for
	// every invocation. Session and durable grants cannot bypass it. Use it for
	// mutations whose exact arguments, not merely the tool name, need review.
	TrustRequiresFreshApproval
)

type WorldStateSink

type WorldStateSink interface {
	// RecordInvokedSkill notes that the model loaded a skill this turn (its name
	// and origin). The sink stamps the current turn as the skill's LastTurn.
	RecordInvokedSkill(name, source string)
	// RecordFile notes a file the agent wrote or edited this turn (kind is
	// "write" or "edit"). Aged in WorldState.RecentFiles.
	RecordFile(path, kind string)
	// RecordTodos records the current todo board (the model rewrites the whole
	// list via todo_write). Items are merged by id, keeping the latest touch, so
	// the board survives compaction.
	RecordTodos(items []spec.TodoItem)
	// RecordSubagent notes a sub-agent the model spawned/resumed this turn: its
	// thread_id handle (id, the re-addressable key), display name, status, and
	// short summary. Aged in WorldState.ActiveSubagents so the orchestrator sees
	// its live specialists and can route follow-ups back to them. Primitives only
	// (no compaction import from this package).
	RecordSubagent(id, name, status, summary string)
}

WorldStateSink lets a tool record durable, compaction-surviving world-state contributions — e.g. load_skill recording which skill the model loaded this turn. The runner provides one per turn via ctx and folds the collected state into the turn's message meta, so it rides ExtractWorldState across compaction (unlike the tool_result body, which is a droppable message). Like PartEmitter and MemoryAuthority, it is a request-scoped capability carried on ctx rather than threaded through every tool signature.

func WorldStateSinkFrom

func WorldStateSinkFrom(ctx context.Context) (WorldStateSink, bool)

WorldStateSinkFrom returns the WorldStateSink carried on ctx, and whether one was present. Absence is a no-op (e.g. a transport with no world-state plumbing).

Jump to

Keyboard shortcuts

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