tools

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package tools: grep is a pure-Go recursive search over the workspace — no dependency on an external ripgrep/grep binary being installed, in keeping with Kram's "never crash / always works" goal. It costs some performance on very large trees versus a real ripgrep, which is an accepted v0 trade-off.

Package tools: LSP tools give the model real semantic navigation (diagnostics, go-to-definition, find-references) on top of the grep/glob/read_file tools, which find text but don't understand structure. Backed by internal/lsp, which lazily starts one language server per language the first time it's needed and reuses that connection afterward.

Package tools is the daemon's tool registry: the concrete capabilities (file I/O, search, shell) the agent loop can call, each scoped to a session's workspace directory so a tool call can never read or write outside the project it was invoked for.

Index

Constants

View Source
const ToolOrderRest = "<unlisted-tools>"

ToolOrderRest is the reserved marker in a configured tool order naming where every tool NOT explicitly listed gets inserted, alphabetically — the same position and ordering the generated Tools overview already uses when no order is configured at all. A configured order must contain this marker exactly once; there is no implicit "everything else goes last" behavior, so a deployment can't accidentally bury unlisted tools without saying so.

Variables

This section is empty.

Functions

func IsReadOnly added in v0.6.0

func IsReadOnly(name string) bool

IsReadOnly reports whether name is on the read-only allowlist — the gate internal/daemon/agent uses to run contiguous stretches of one tool batch concurrently. Unknown names (MCP tools included) are never read-only.

func OrderToolNames added in v0.3.0

func OrderToolNames(visible []string, order []string) []string

OrderToolNames arranges visible (already alphabetically sorted by the caller — see Registry.VisibleTools) according to order: names listed explicitly appear in that order, and every other visible name is inserted, still alphabetical, at the ToolOrderRest position. order == nil returns visible unchanged — today's plain alphabetical behavior, byte-for-byte. A listed name that isn't currently visible (disabled, or hidden by permission policy for this assembly) is simply absent from the result, the same as it would be from an unordered overview — this function only arranges what VisibleTools() already decided to show, never adds to it.

func StartedBackgroundProcessID added in v0.2.8

func StartedBackgroundProcessID(result string) string

StartedBackgroundProcessID extracts the ID from run_background's own stable success result. Keeping this parser next to the producer lets the agent add click metadata without teaching the TUI to scrape human-facing text.

func UnknownToolOrderNames added in v0.3.0

func UnknownToolOrderNames(order []string, known map[string]bool) []string

UnknownToolOrderNames returns every name in order (other than the rest marker) that isn't present in known — the "typo in config" case ValidateToolOrder alone can't catch, since it has no registry to check against. Called once at Registry construction against the full registered-tool universe (every tool that exists, not just the ones currently visible — a name valid at startup but disabled or denied for one particular assembly must not become a startup error just because this workspace's policy currently hides it).

func ValidateToolOrder added in v0.3.0

func ValidateToolOrder(order []string) error

ValidateToolOrder checks a configured order's shape — no duplicate entries, exactly one ToolOrderRest marker — without needing a live Registry. It deliberately does NOT check that listed names correspond to real registered tools: only the registry that will actually render with this order knows its full tool universe, so that check happens separately, once, when a Registry using this order is constructed (see Registry's own tool-order validation) — failing there instead of here is what makes an unregistered name fail loudly instead of just vanishing from the overview.

func WithApprover

func WithApprover(ctx context.Context, a Approver) context.Context

WithApprover attaches the approver for the current turn to ctx — called by agent.runLoop before executing tools, mirroring WithAsker.

func WithAsker

func WithAsker(ctx context.Context, a Asker) context.Context

WithAsker attaches the asker for the current turn to ctx — called by agent.runLoop before executing tools, mirroring WithDepth.

func WithDepth

func WithDepth(ctx context.Context, depth int) context.Context

WithDepth attaches the current subagent nesting depth (0 = the top-level conversation) to ctx. delegate_task reads it back to enforce maxSpawnDepth without threading a depth parameter through every tool's Execute signature — only delegate_task cares about it.

func WithRunModel added in v0.5.0

func WithRunModel(ctx context.Context, model string) context.Context

WithRunModel attaches the combo the current run routes to.

Types

type ApprovalDecision

type ApprovalDecision string

ApprovalDecision is the user's answer to a policy-gated tool call.

const (
	ApprovalOnce   ApprovalDecision = "once"
	ApprovalAlways ApprovalDecision = "always"
	ApprovalDeny   ApprovalDecision = "deny"
)

type Approver

type Approver interface {
	// diff is a unified diff previewing an edit_file/write_file change so
	// the user can review it before approving; "" for tools with no
	// reviewable change (see diffForToolCall).
	Approve(ctx context.Context, toolName, subject, diff string) (ApprovalDecision, error)
}

Approver asks the user to sign off on a tool call the permission policy marked Ask — implemented by agent.Service, injected per-turn via context, same pattern ask.go's Asker uses. Deliberately a separate interface from Asker: ask_question means "I don't have enough information to proceed"; an approval means "I know exactly what I want to do, but policy requires sign-off first." Conflating the two would make a policy-driven pause look to the model (and the user) like the agent being unsure, which it isn't.

type Asker

type Asker interface {
	Ask(ctx context.Context, question string, options []string) (string, error)
}

Asker pauses the current turn to get a real answer from the user — implemented by agent.Service, injected per-call via context (the same pattern depth.go uses) rather than through Registry, since unlike delegation this needs the *current turn's* live event channel and session ID, not just a fixed dependency wired in once at startup.

type BackgroundProcessInfo added in v0.2.8

type BackgroundProcessInfo struct {
	ID            string     `json:"id"`
	Command       string     `json:"command"`
	PID           int        `json:"pid"`
	Running       bool       `json:"running"`
	ExitCode      int        `json:"exit_code"`
	ExitError     string     `json:"exit_error,omitempty"`
	StartedAt     time.Time  `json:"started_at"`
	EndedAt       *time.Time `json:"ended_at,omitempty"`
	OutputBytes   int64      `json:"output_bytes"`
	RetainedBytes int        `json:"retained_bytes"`
	Truncated     bool       `json:"truncated"`
}

BackgroundProcessInfo is the read-only, structured view of one process owned by this daemon. It intentionally contains no os.Process or command handle: callers can observe lifecycle and output without acquiring a second way to mutate process state outside the permission-gated tools.

type BackgroundProcessOutput added in v0.2.8

type BackgroundProcessOutput struct {
	ID        string `json:"id"`
	Output    string `json:"output"`
	Cursor    int64  `json:"cursor"`
	Reset     bool   `json:"reset"`
	Running   bool   `json:"running"`
	ExitCode  int    `json:"exit_code"`
	ExitError string `json:"exit_error,omitempty"`
	Truncated bool   `json:"truncated"`
}

BackgroundProcessOutput is one cursor-based read of captured stdout and stderr. Cursor is an absolute byte position in this process's output stream. Reset tells a client to replace, rather than append to, its local copy.

type Delegator

type Delegator interface {
	RunTask(ctx context.Context, goal, taskContext, model string, depth int) (string, error)
}

Delegator runs one subtask to completion in a fresh, isolated child session and returns its final answer. Implemented by agent.Service; declared here (not imported from the agent package) because agent already depends on Registry for its own tool calls — agent importing tools importing agent would be a cycle. Registry.SetDelegator wires the concrete implementation in after both are constructed.

type MetadataProvider added in v0.2.2

type MetadataProvider interface {
	ToolMetadata() ToolMetadata
}

MetadataProvider is implemented by a Tool that wants a hand-curated prompt summary instead of the automatic Description()-derived one.

type Registry

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

Registry holds every tool available to the agent loop for one workspace.

func NewRegistry

func NewRegistry(workspace string, st *store.Store, disabled map[string]bool) *Registry

NewRegistry builds the default tool set scoped to workspace — every file and shell tool refuses to operate outside this directory. st, if non-nil, backs the memory_write/memory_search tools (cross-session memory, scoped to this workspace plus store.GlobalScope) and session_search (deterministic full-text search over real conversation history, a distinct concern from curated memory — see internal/daemon/store/search.go); passing nil omits those tools entirely rather than registering ones that would always fail. disabled names every tool (or skill — they share one namespace) turned off via the CLI's tools/skills screen; nil or empty means everything's on. Taking a plain map here rather than importing internal/toolsettings keeps this package from depending on the settings-storage format — daemon.go loads the store and passes its Disabled() map in.

func (*Registry) AllTools

func (r *Registry) AllTools() []ToolInfo

AllTools returns every registered tool (including disabled ones), name order, for the settings screen — Definitions() deliberately can't be reused here since it already filters disabled tools out.

func (*Registry) BackgroundProcessOutput added in v0.2.8

func (r *Registry) BackgroundProcessOutput(id string, cursor *int64) (BackgroundProcessOutput, bool)

BackgroundProcessOutput returns an incremental output snapshot for id.

func (*Registry) BackgroundProcesses added in v0.2.8

func (r *Registry) BackgroundProcesses() []BackgroundProcessInfo

BackgroundProcesses exposes read-only process metadata to the daemon's local control surface. Mutation remains confined to process_kill and daemon shutdown, preserving the permission boundary around destructive actions.

func (*Registry) Definitions

func (r *Registry) Definitions() []openai.Tool

Definitions returns every visible tool's definition (see VisibleTools) in the gateway's wire format, ready to attach to a ChatCompletionRequest.

func (*Registry) Execute

func (r *Registry) Execute(ctx context.Context, name string, args json.RawMessage) (result string, err error)

Execute runs a named tool, or returns an error result (not a Go error) if the name is unknown, disabled, or blocked by permission policy — the agent loop feeds this text straight back to the model as the tool's result either way, so the model can see and recover from a bad or unavailable tool name itself. The disabled check here is defense in depth: Definitions() already keeps a disabled tool out of what's offered, but a model can still try to call a tool name it saw in an earlier turn before it was disabled.

Every tool call — built-in, custom (manifest), and MCP-backed alike — funnels through this one method, which is what makes them all subject to the same permission policy without each tool implementation needing to know policy exists.

func (*Registry) RegisterMCP

func (r *Registry) RegisterMCP(m *mcp.Manager)

RegisterMCP adds every tool from every connected MCP server to the registry, plus four generic tools covering resources and prompts (mcp_resource_list/read, mcp_prompt_list/get) — those aren't per-server-per-item like tools are (a server can expose an unbounded, changing set of resources), so they're cross-cutting tools that take a server name as an argument rather than one registered tool per item. Called after NewRegistry because connecting to servers is I/O that can block or fail, and the registry has to exist (and the daemon has to be able to start) either way.

func (*Registry) ReplaceDisabled added in v0.2.5

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

ReplaceDisabled updates the effective tool/skill profile without restarting the daemon. The CLI persists the same desired set first, then sends it here; subsequent model calls and executions immediately observe the new profile.

func (*Registry) SetDelegator

func (r *Registry) SetDelegator(d Delegator)

SetDelegator wires the concrete subagent runner into the registry's delegate_task tool, after both the registry and the agent.Service that implements Delegator exist — daemon.go calls this once during startup. Until it's called, delegate_task exists (the model can see it in tool definitions) but reports itself unavailable rather than being hidden, which would otherwise require rebuilding the registry after the agent service is constructed.

func (*Registry) Skills

func (r *Registry) Skills() []Skill

Skills lists every discovered skill (project + global), disabled state included — same purpose as AllTools but for skills, which aren't registered as one fixed Tool each the way built-ins are.

func (*Registry) Snapshots added in v0.6.0

func (r *Registry) Snapshots() *snapshot.Store

Snapshots exposes the workspace snapshot store to callers above the tool layer — the agent's automatic pre-mutation checkpoint and the daemon's rewind endpoint. Deliberately direct store access, not routed through tool execution: an automatic checkpoint must neither trigger a permission ask nor be skipped because the user disabled the snapshot *tools* for the model — it's a harness feature, not a model action.

func (*Registry) StopBackgroundProcesses

func (r *Registry) StopBackgroundProcesses()

StopBackgroundProcesses kills every process started by run_background — called on daemon shutdown so a background dev server doesn't outlive the daemon that started it as an orphan.

func (*Registry) StopLSPServers

func (r *Registry) StopLSPServers()

StopLSPServers shuts down every language server this registry's LSP tools started, if any — called on daemon shutdown for the same reason as StopBackgroundProcesses: a language server is a subprocess Kram started, so it's Kram's job to make sure it doesn't outlive the daemon as an orphan. A workspace that never called an lsp_* tool never started any server, so this is a no-op in the common case.

func (*Registry) ToolMetadata added in v0.2.2

func (r *Registry) ToolMetadata(name string) ToolMetadata

ToolMetadata looks up name's hand-curated metadata, or derives a fallback from its Description() (first sentence only, to stay short — several Description()s run 3-5 sentences, appropriate for the wire schema but too long for a one-line prompt overview) if it doesn't implement MetadataProvider, isn't registered, or has an empty Summary. Never returns a value with an empty Summary for a registered tool — every tool gets *some* usable line.

func (*Registry) VisibleTools added in v0.2.3

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

VisibleTools returns every tool the model can actually be offered right now, name order — the single source of truth Definitions() (the wire schema) and internal/daemon/agent's compileToolsOverview (the prompt's generated Tools section) both derive from, so a tool can never be announced in the prompt without also being callable, or the reverse. Before this existed, compileToolsOverview built its list from AllTools instead, which only excludes disabled tools — a tool the permission policy denies unconditionally (e.g. a Strict preset's "delete_file: deny *") stayed disabled=false and so got announced in the prompt with no matching function in the wire schema. Filters the same two things Definitions() always has: a disabled tool, and one the permission policy denies unconditionally (see permission.Evaluator.FullyDenied) — a capability the model can never successfully use shouldn't cost tokens in the schema or occupy a line in the overview. A tool denied only for *some* patterns stays visible, since it's still sometimes usable and the policy is enforced at call time either way.

type Skill

type Skill struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Path        string `json:"path"`     // SKILL.md path, for error messages / the toggle UI
	Scope       string `json:"scope"`    // "project" or "global"
	Disabled    bool   `json:"disabled"` // set by Registry.Skills(), not discoverSkills itself
	Body        string `json:"-"`        // everything after the frontmatter
}

Skill is one discovered skill: a folder containing a SKILL.md with a small frontmatter block (name, description) followed by the actual instructions as markdown. This is the same shape opencode's Agent Skills, Hermes Agent's agentskills.io-based skills, and OpenClaude's DiscoverSkillsTool all converge on — a lightweight open convention, not something Kram invented.

type TodoItem

type TodoItem struct {
	Content string `json:"content"`
	Status  string `json:"status"` // "pending", "in_progress", "completed"
}

TodoItem is one entry in the project's task list.

type Tool

type Tool interface {
	Name() string
	Description() string
	// Schema is the JSON Schema (as a raw object) describing the tool's
	// arguments, passed straight through to the gateway/provider.
	Schema() json.RawMessage
	// Execute runs the tool and returns its result as text — every tool's
	// output is text, even for structured data (JSON-encoded as a string),
	// since that's what every provider's tool-result message expects.
	Execute(ctx context.Context, args json.RawMessage) (string, error)
}

Tool is one callable capability the agent loop can offer the model.

type ToolInfo

type ToolInfo struct {
	Name        string
	Description string
	Disabled    bool
}

ToolInfo is one entry in the registry's full listing (AllTools) — every tool that exists, regardless of enabled state, for the CLI's tools/skills toggle screen.

type ToolMetadata added in v0.2.2

type ToolMetadata struct {
	Summary string
	// PreferOver names another tool this one should be reached for
	// instead of, when relevant — e.g. run_background over bash for a
	// dev server. Empty when there's no such competing default.
	PreferOver string
}

ToolMetadata is a short, prompt-facing summary of a tool — distinct from and shorter than its Description() (which stays in full in the wire schema, for once the model has already selected the tool as a candidate and needs the complete picture). A tool with no hand-curated ToolMetadata still gets one via Registry.ToolMetadata's fallback — that's the point: a tool can't vanish from a generated overview just because nobody wrote a Summary for it yet, which is exactly the bug that motivated this (21 of 38 registered tools were never mentioned in the hand-maintained prompt prose — see DECISIONS.md).

Jump to

Keyboard shortcuts

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