tool

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

Package tool owns the fail-closed capability surface the model calls. Every tool implements one interface, and every method that could parallelize badly or mutate state defaults to the conservative answer, so a tool that says nothing is treated as unsafe (doc 04 section 2).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func HashBytes

func HashBytes(b []byte) string

HashBytes is the content hash the map keys freshness on, shared by every tool that arms or checks the gate.

func ResolveMutationPath

func ResolveMutationPath(path string) string

ResolveMutationPath resolves a write target the way the write tool does: symlinks resolved through the nearest existing ancestor, so a symlink pointing into a protected area cannot hide the area from the safety floor.

func UnifiedDiff

func UnifiedDiff(path, oldText, newText string) string

UnifiedDiff renders a line-based unified diff between two versions of one file. The permission renderer (doc 05) and the edit display both lean on it, so it lives here rather than in a UI package.

Types

type AntID

type AntID string

AntID identifies the calling ant, for scoping side effects and attributing tokens in the ledger.

type Attachment

type Attachment struct {
	MediaType string
	Data      []byte
}

Attachment is a non-text block a tool hands to the model. Unused until M7 gives read image passthrough; the field exists now so M7 is a capability flip, not a rewrite (doc 04 section 4.1).

type BackgroundCloser

type BackgroundCloser interface {
	CloseBackground() error
}

BackgroundCloser is implemented by a tool that owns detached processes it must reap when the session ends. The runner resolves it off the registry so it never has to name sh directly.

type Base

type Base struct{}

Base carries the fail-closed defaults. Embed it and override only the methods whose answer is not the conservative one. A tool that embeds Base and overrides nothing is a serial, unsafe, non-destructive tool with a default size cap and no permission opinion, which is exactly the shape a half-finished tool must have (doc 04 section 2.4).

func (Base) IsConcurrencySafe

func (Base) IsConcurrencySafe(json.RawMessage) bool

func (Base) IsDestructive

func (Base) IsDestructive(json.RawMessage) bool

func (Base) IsReadOnly

func (Base) IsReadOnly(json.RawMessage) bool

func (Base) MatchPrefix

func (Base) MatchPrefix(json.RawMessage) PrefixMatcher

func (Base) MaxResultSize

func (Base) MaxResultSize() int

type Diagnostic

type Diagnostic = lsp.Diagnostic

Diagnostic re-exports the language-server finding type so the typed tool displays carry diagnostics without the UI having to import the lsp package: the import-graph guard lets the UI reach the core only through event and tool (doc 02 section 1). It is an alias, so a value flows between tool and lsp with no conversion.

type DiskSpill

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

DiskSpill writes spilled results into one directory, one numbered file per spill. The loop wires it to the nest's session state dir and calls Sweep when the session ends.

func NewDiskSpill

func NewDiskSpill(dir string) *DiskSpill

NewDiskSpill builds a store over dir, creating it on first Put.

func (*DiskSpill) Put

func (s *DiskSpill) Put(content string) (SpillRef, error)

Put writes the content and returns where it lives.

func (*DiskSpill) Sweep

func (s *DiskSpill) Sweep() error

Sweep removes every spilled file, for session end.

type EditDisplay

type EditDisplay struct {
	Path        string
	Diff        string
	Diagnostics []Diagnostic
}

EditDisplay is the typed data the UI renders for an edit: a real unified diff, syntax-highlighted by the TUI, plus any diagnostics the language server reported for the edited file, shown under the diff. Never sent to the model.

type FetchDisplay

type FetchDisplay struct {
	URL         string
	ContentType string
	Bytes       int
}

FetchDisplay is the typed data the UI renders for a fetch: the URL is the consequence a permission ask shows (doc 01 section 5.3).

type FileEntry

type FileEntry struct {
	Hash  string    // content hash at last read or write
	Mtime time.Time // filesystem mtime at last read or write
	Lines int       // line count, for the unchanged-read stub
}

FileEntry is what the map remembers about one file.

type FileState

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

FileState is the per-session read-before-write map. read populates it, edit and write consult and refresh it. It is the session-level invariant that no mutation touches an unseen file (D8, doc 04 section 10). Safe for the loop's concurrent access.

func NewFileState

func NewFileState() *FileState

NewFileState builds an empty map.

func (*FileState) Apply

func (f *FileState) Apply(e *FileStateEffect)

Apply folds a tool's FileStateEffect into the map. The loop calls it after every successful call that returned one.

func (*FileState) Clear

func (f *FileState) Clear()

Clear empties the map. Compaction calls it before rebuilding the map for the restored working set (D8, doc 03 section 11): the ant only remembers reading what it actually re-read.

func (*FileState) Entry

func (f *FileState) Entry(path string) (FileEntry, bool)

Entry returns the recorded entry for a path.

func (*FileState) Fresh

func (f *FileState) Fresh(path, onDiskHash string) bool

Fresh reports whether the recorded hash still matches the content on disk. A missing entry means never-read; a mismatch means changed-since-read. The hash comparison is the whole check, no mtime, because cloud sync and antivirus bump mtimes without changing content (doc 04 section 10.2).

func (*FileState) Hash

func (f *FileState) Hash(path string) string

Hash returns the recorded content hash for a path, empty if never read or written.

func (*FileState) Set

func (f *FileState) Set(path, hash string, mtime time.Time, lines int)

Set records a read or write: the path, the content hash, the mtime, and the line count.

type FileStateEffect

type FileStateEffect struct {
	Path  string    // absolute, symlinks resolved
	Hash  string    // content hash after the operation
	Mtime time.Time // filesystem mtime after the operation
	Lines int       // line count, for the unchanged-read stub
}

FileStateEffect records what a successful read or write did to a file, so the loop can fold it into the session file-state map (doc 04 section 10). This is the arming step for the edit gate (D8).

type FindDisplay

type FindDisplay struct {
	Files  []FindFile
	Total  int  // matches before the cap
	Capped bool // whether the limit truncated the set
}

FindDisplay is the typed match structure the TUI renders as a collapsible tree. It never goes to the model (doc 04 section 11.2).

type FindFile

type FindFile struct {
	Path    string
	Mtime   time.Time
	Matches []FindMatch // empty for a glob-only search
}

FindFile is one file's slice of the match set.

type FindMatch

type FindMatch struct {
	Line int
	Text string
}

FindMatch is one matching line.

type Journal

type Journal interface {
	Append(e event.Event) event.Event
}

Journal is the slice of the append-only log a tool may write to. The colony's journal satisfies it.

type Pattern

type Pattern struct {
	Tool    string // the tool name part, "sh" in sh(git:*)
	Content string // the argument part, "git:*" in sh(git:*); empty is tool-wide
	Source  string // the pattern verbatim, journaled as written
}

Pattern is one permission rule pattern, kept as the user wrote it. The rule language proper lands with the pipeline (doc 05); tools only need enough shape here to answer MatchPrefix.

func (Pattern) CoversContent

func (p Pattern) CoversContent(content string) bool

CoversContent reports whether this pattern's content part covers one content string (a path for write and edit, a URL for fetch), using the same three forms: exact, prefix with a trailing :*, wildcard. The prefix form is a plain string prefix here, so write(src/:*) covers anything under src/. In wildcards, * stops at a path separator and ** crosses them.

func (Pattern) CoversSubcommand

func (p Pattern) CoversSubcommand(sub string) bool

CoversSubcommand reports whether this pattern's content part covers one normalized subcommand. Empty content is tool-wide and covers everything; "git commit:*" covers "git commit" and anything after it at a word boundary, so "git commit -m x" matches and "git commitx" does not; content with an inline * is a wildcard matched against the whole subcommand; content with neither must match exactly.

type PermissionResult

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

PermissionResult is a tool's contribution to the permission pipeline. The pipeline (doc 05, slice 7) weighs it together with rules and mode; a tool cannot allow past a safety check from here (D15): an allow sets a pre-approval flag and still faces the safety floor.

func AllowResult

func AllowResult() PermissionResult

AllowResult means the tool pre-approves this call. The pipeline still runs the safety floor before the approval lands.

func AskResult

func AskResult(message string) PermissionResult

AskResult means the tool wants a human to decide this call.

func DenyResult

func DenyResult(message string) PermissionResult

DenyResult means the tool refuses this call; the message tells the model why.

func Passthrough

func Passthrough() PermissionResult

Passthrough means the tool has no opinion and the general rule and mode machinery decides. It is the Base default and the answer for every tool that has nothing early to say.

func (PermissionResult) IsAllow

func (p PermissionResult) IsAllow() bool

IsAllow reports a tool pre-approval.

func (PermissionResult) IsAsk

func (p PermissionResult) IsAsk() bool

IsAsk reports a tool escalation to a human.

func (PermissionResult) IsDeny

func (p PermissionResult) IsDeny() bool

IsDeny reports a tool refusal.

func (PermissionResult) IsPassthrough

func (p PermissionResult) IsPassthrough() bool

IsPassthrough reports whether the tool deferred entirely.

func (PermissionResult) Message

func (p PermissionResult) Message() string

Message is the model-facing sentence for deny and ask.

type PrefixMatcher

type PrefixMatcher interface {
	Matches(p Pattern) bool
}

PrefixMatcher tests a permission pattern against one invocation. Only the tool knows how to parse its own arguments into the units a rule matches, which is why this lives on the tool.

type ProgressFunc

type ProgressFunc func(chunk string)

ProgressFunc receives incremental output while a tool runs.

type Registry

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

Registry resolves a tool by the name the model calls. The six core tools are registered at startup; skills and MCP tools attach later (doc 13) through the same Register call, so nothing about the registry knows they are special. It is deliberately dumb: it maps names to tools, rejects duplicates, filters to an ant's allowlist, and does nothing else (doc 04 section 13.1).

func NewRegistry

func NewRegistry() *Registry

NewRegistry builds an empty registry.

func (*Registry) DeferredTools

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

DeferredTools lists the deferred tools not yet loaded, sorted by name, so the ant can announce them by name and the search step can match a query against them.

func (*Registry) ForAllowlist

func (r *Registry) ForAllowlist(allowed []string) *Registry

ForAllowlist returns the subset a worker ant may call, filtered by its card's Commands allowlist (doc 04 section 12.1). A tool absent from the allowlist is not in the returned registry, so the model never sees it in the tool list and cannot call it.

func (*Registry) Load

func (r *Registry) Load(names ...string) []Tool

Load marks deferred tools loaded and returns the ones that were newly loaded, so the search step can echo their schemas to the model. A name that is not a deferred tool is ignored, so a bad query loads nothing.

func (*Registry) Names

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

Names lists the registered tools, sorted, for prompts and errors.

func (*Registry) Register

func (r *Registry) Register(t Tool) error

Register adds a tool, refusing a duplicate name.

func (*Registry) RegisterDeferred

func (r *Registry) RegisterDeferred(t Tool) error

RegisterDeferred adds a tool whose schema is withheld until the model loads it. The tool is announced by name only and becomes callable after Load, so a server with fifty tools the ant never touches costs a line of names and nothing more (doc 13, D20).

func (*Registry) Resolve

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

Resolve returns the tool the model named.

func (*Registry) SchemaNames

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

SchemaNames lists the tools whose schema the model may see this turn: every non-deferred tool plus any deferred tool already loaded. A deferred tool not yet loaded is absent, so its schema never rides the request.

type Result

type Result struct {
	// Model is the text the model will see, before size capping and
	// spillover.
	Model string

	// Display carries typed data for the UI renderer (a diff, a file
	// list, an exit code). It is never sent to the model. Nil for tools
	// with no rich view.
	Display any

	// Attachments are non-text blocks for the model, images for M7
	// read. Empty for everything else in v1.
	Attachments []Attachment

	// StateEffect describes any file-state map change (a read or a
	// write) so the loop can update the session invariant. Nil when the
	// tool touched no file.
	StateEffect *FileStateEffect
}

Result is what Call returns before serialization. It separates the model payload from any structured data the UI wants for rich rendering.

type Sandbox

type Sandbox interface {
	// Wrap returns a command configured to run under the sandbox, or the
	// command unchanged when the sandbox is disabled for this invocation.
	Wrap(ctx context.Context, cmd *exec.Cmd, spec SandboxSpec) (*exec.Cmd, error)
}

Sandbox wraps a command so it runs under OS-level isolation. The concrete seatbelt, namespace, or container implementation lives in doc 14; sh only knows this seam.

type SandboxSpec

type SandboxSpec struct {
	Cwd          string   // the one directory writes are allowed under, by default
	AllowNet     bool     // whether the command may reach the network
	AllowWrite   []string // extra writable paths beyond cwd
	DisableForce bool     // an explicit escape hatch, gated by permission (doc 05)
}

SandboxSpec is the policy one invocation runs under.

type Schema

type Schema struct {
	Name        string          // matches Tool.Name
	Description string          // one line, budgeted; the model reads this
	Params      json.RawMessage // JSON Schema for the argument object
}

Schema is the model-facing description of a tool.

type ShDisplay

type ShDisplay struct {
	Command     string
	Description string
	Output      string
	ExitCode    int
	Killed      bool
	Background  string // the shell id when run in the background
}

ShDisplay is the typed data the UI renders for a shell result: the command, its output for the scrollable pane, and how it ended.

type ShSub

type ShSub struct {
	Raw  string
	Norm string
}

ShSub is one subcommand of a compound command line. Raw keeps the text as written, redirections included, because the safety floor cares about a > target the rule matcher strips. Norm is the wrapper-stripped, env-stripped, redirection-stripped form the rule language matches.

func ShSplit

func ShSplit(command string) []ShSub

ShSplit splits a command line into its subcommands, each carrying its raw and normalized form. An empty or unparseable command returns nil, and the callers treat nil as "matches nothing".

func (ShSub) MutationTargets

func (s ShSub) MutationTargets(cwd string) []string

MutationTargets returns the paths this subcommand could write, resolved against cwd, computed conservatively: redirection targets always count, and for a subcommand that is not known read-only every non-flag argument counts as a candidate. A harmless word resolves to a harmless path; the safety floor only acts on the candidates that land inside a protected area (doc 05 section 7).

func (ShSub) ReadOnly

func (s ShSub) ReadOnly() bool

ReadOnly reports whether this one subcommand is known to only observe. A redirection makes any subcommand a writer, so the raw form is checked for one before the normalized form is classified.

type SkillDeps

type SkillDeps struct {
	// Lookup resolves a discovered skill by name; the bool is false for an
	// unknown name.
	Lookup func(name string) (*skill.Skill, bool)
	// Names lists the discovered names, for near-match suggestions on a miss.
	Names func() []string
	// Matcher builds the allowed-tools gate for one skill, or nil when the
	// skill declares no allowed-tools. It carries the doc 05 normalization.
	Matcher func(s *skill.Skill) skill.Matcher
	// Inline runs one gated command through the session's sh path. Nil
	// disables inline execution entirely.
	Inline skill.InlineRunner
	// Grant adds a skill's allowed-tools to the session as always-allow rules,
	// tagged with the skill name for the doc 05 audit trail.
	Grant func(name string, rules []string)
	// Trusted is false for an untrusted-content session (D19): no skill runs
	// inline shell regardless of its scope.
	Trusted bool
}

SkillDeps wires the skill tool to the discovered set and to the host's permission-gated seams. The runner builds it per session, because the matcher and the inline runner both close over that session's permission pipeline (doc 13 section 2.5).

type SkillDisplay

type SkillDisplay struct {
	Name    string
	Already bool
	Notes   []string
}

SkillDisplay is the typed data the UI renders for an invocation: the name, whether it was a re-invocation, and any deferral or gate notes. Never sent to the model.

type SpillRef

type SpillRef struct {
	Path string // absolute path the model can read() or find over
}

SpillRef points at a spilled result on disk.

func ApplyResultBudget

func ApplyResultBudget(r *Result, t Tool, tc *ToolContext) (string, SpillRef, error)

ApplyResultBudget caps the model-facing text and spills the remainder. The loop runs it after Call returns, before the result becomes a tool_result. The preview is head-heavy on purpose: the first three quarters of the budget is the head, the last quarter the tail, because the top of a command's output is usually what matters (doc 04 section 3.2). The returned ref is the zero value when nothing spilled; the loop surfaces it on the tool end event.

type SpillStore

type SpillStore interface {
	Put(content string) (SpillRef, error)
}

SpillStore persists oversized tool results and returns a reference the model can read back. Files live under the project nest and are swept on session end (doc 04 section 3.2).

type Tool

type Tool interface {
	// Name is the stable identifier the model calls and permission rules
	// match. It is lower_snake for core tools: read, write, edit, sh,
	// find, fetch.
	Name() string

	// Schema returns the JSON Schema for the argument object, plus the
	// one-line description the model reads. The loop renders this into
	// the tool list.
	Schema() Schema

	// ValidateInput checks the decoded arguments and returns a
	// model-facing reason when they are invalid. It runs before
	// permissions and shows no UI. A non-nil error here becomes a
	// tool_result the model can read and correct.
	ValidateInput(ctx context.Context, args json.RawMessage, tc *ToolContext) error

	// CheckPermissions is the tool's hook into the doc 05 pipeline. It
	// returns a PermissionResult the pipeline weighs together with rules
	// and mode. The default is Passthrough, which means "defer entirely
	// to the general system".
	CheckPermissions(ctx context.Context, args json.RawMessage, tc *ToolContext) PermissionResult

	// Call runs the tool. It streams progress through onProgress and
	// returns a Result. Call must respect ctx cancellation promptly.
	Call(ctx context.Context, args json.RawMessage, tc *ToolContext, onProgress ProgressFunc) (*Result, error)

	// IsReadOnly reports whether this invocation only observes state.
	// Default false. A read-only invocation may run in a plan-mode
	// session.
	IsReadOnly(args json.RawMessage) bool

	// IsConcurrencySafe reports whether this invocation may run in
	// parallel with other concurrency-safe tools in the same batch.
	// Default false. The loop (doc 03) partitions on this.
	IsConcurrencySafe(args json.RawMessage) bool

	// IsDestructive reports whether this invocation is irreversible (rm,
	// force push, DROP). Default false. The permission renderer (doc 05)
	// leans on this to escalate the consequence it shows.
	IsDestructive(args json.RawMessage) bool

	// MatchPrefix returns a matcher the permission pipeline (doc 05)
	// uses to test a rule pattern like sh(git commit:*) against this
	// specific invocation.
	MatchPrefix(args json.RawMessage) PrefixMatcher

	// MaxResultSize is the byte ceiling on the model-facing result
	// before it spills to disk. Zero means "no spill" (read uses this).
	MaxResultSize() int
}

Tool is one typed, fail-closed capability the model can invoke by name.

func NewEdit

func NewEdit() Tool

NewEdit builds the edit tool.

func NewFetch

func NewFetch() Tool

NewFetch builds the fetch tool with the SSRF guard on.

func NewFind

func NewFind() Tool

NewFind builds the find tool.

func NewRead

func NewRead() Tool

NewRead builds the read tool.

func NewSh

func NewSh() Tool

NewSh builds the sh tool with its own background-shell registry, one per session because the registry lives as long as the tool value.

func NewSkill

func NewSkill(deps SkillDeps) Tool

NewSkill builds the skill tool over the discovered set and the session's gated seams.

func NewToolSearch

func NewToolSearch(reg *Registry) Tool

NewToolSearch builds the search-and-load tool over a registry. Register it only when the registry actually has deferred tools, so a session with no MCP servers never shows the model a tool it cannot use.

func NewWrite

func NewWrite() Tool

NewWrite builds the write tool. It shares edit's serialization through the loop's non-concurrency-safe partition; the lock here covers a same-batch race the scheduler cannot see.

type ToolContext

type ToolContext struct {
	// Cwd is the working directory sh inherits and paths resolve
	// against. It persists across sh calls within a session; shell state
	// does not.
	Cwd string

	// Files is the read-before-write file-state map (doc 04 section 10).
	// read updates it, edit and write consult and refresh it.
	Files *FileState

	// Ant identifies the calling ant so the tool can scope side effects
	// and the ledger can attribute tokens.
	Ant AntID

	// Namespace is the calling ant's memory namespace (doc 07), the scope
	// the memory tools read and write. The loop sets it per turn from the
	// card; empty means no memory is bound and the memory tools refuse.
	Namespace string

	// Sandbox is the seam sh runs commands through (doc 14). Nil in a
	// trusted local run means "no sandbox"; non-nil wraps the command.
	Sandbox Sandbox

	// Spill is where oversized results are written (doc 04 section 3).
	Spill SpillStore

	// Journal records tool events for the append-only log (doc 01).
	Journal Journal

	// LSP is the language-server seam edit, write, and read use to fold
	// compiler diagnostics into their results and to warm the server (doc
	// 04 sections 2, 3, 6). Nil means no language server: every tool
	// degrades to no diagnostics, never a failed edit.
	LSP lsp.LSPClient

	// Now is injected so tests and replay sets are deterministic.
	Now func() time.Time
}

ToolContext is the session-scoped surface a tool is allowed to touch. The loop builds it per turn; a tool never reaches past it into globals. Inject a temp dir Cwd, a fresh FileState, and a fake clock, and a tool is fully exercisable without a real session (doc 04 section 2.3).

type WriteDisplay

type WriteDisplay struct {
	Path        string
	Content     string
	Created     bool
	Diagnostics []Diagnostic
}

WriteDisplay is the typed data the UI renders for a write: the whole new content, whether the file is new, and any diagnostics the language server reported for the written file. Never sent to the model.

Jump to

Keyboard shortcuts

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