Documentation
¶
Overview ¶
Package tools implements the read-only investigation toolset the Sentra agent advertises to the LLM. The orchestrator (see internal/agent) stitches the LLM's tool-use blocks back into this package via Runner.Run; the LLM never sees a file's contents — only the metadata each tool returns.
Safety rail (per docs/plans/2026-05-02-sentra-design.md → "Safety rails"): NONE of the tools in this package read a file's contents, not even indirectly. They operate on snapshot manifests, blob listings, and the precomputed Findings slice. If a future tool needs content, it goes in a NEW package gated behind explicit user opt-in — keeping this package on a strict no-content diet is the architectural invariant that lets Sentra promise "the model never sees your data."
The tool surface intentionally tracks the design's enumeration:
- list_snapshots(limit, since) → []SnapshotSummary
- snapshot_stats(id) → {id, files, bytes, new_bytes, file_type_histogram}
- diff_snapshots(a, b) → {added, removed, changed}
- inspect_finding(id) → heuristics.Finding (with Details)
Each tool returns a JSON-encoded string so the orchestrator can thread results back as ToolResult messages without re-marshalling.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type RepoRunner ¶
type RepoRunner struct {
// Repo is the open repository the snapshot tools dispatch against.
// May be nil only for tools that don't touch repo state (currently
// only inspect_finding); tests that exercise the snapshot tools
// must supply a non-nil Repo.
Repo *repo.Repo
// Heuristics is retained for potential future re-runs from inside
// the agent loop (e.g. re-scanning after the LLM proposes a new
// .sentraignore entry). Today's tools don't use it.
Heuristics *heuristics.Registry
// Findings is the precomputed finding set the orchestrator passed
// in when it built this runner. Looked up by ID by inspect_finding.
Findings []heuristics.Finding
}
RepoRunner is the production Runner: it dispatches tool calls against a real *repo.Repo, plus an in-memory Findings slice populated by the most recent heuristic run.
Findings is captured by the orchestrator BEFORE the LLM loop starts and remains constant for the duration of a single Scan — that way `inspect_finding(id)` returns the same record the LLM saw in the initial summary. Mutating Findings mid-run is undefined behavior; the orchestrator owns the slice's lifetime.
func (*RepoRunner) All ¶
func (r *RepoRunner) All() []Tool
All returns a defensive copy of the static tool set so callers can't mutate the package-level slice. Order matches staticTools.
func (*RepoRunner) Run ¶
Run dispatches a tool call by name. Unknown names return a clear error rather than silently no-op'ing — the orchestrator can surface that error back to the model so it can correct its tool name.
Each tool's input/output shape is documented at the dispatch site below. Output is always a JSON-encoded string so the caller can thread it directly into a ToolResult without re-marshalling.
type Runner ¶
type Runner interface {
Schema(name string) (Tool, bool)
All() []Tool
Run(ctx context.Context, name string, input map[string]any) (string, error)
}
Runner is the bridge between the LLM's tool_use blocks and the orchestrator. The orchestrator looks up Schema(name) when building the tool advertisement for the model, calls All() to enumerate everything available, and dispatches Run(ctx, name, input) when the model asks for a tool to be invoked. Returns a JSON-encoded string so the caller can thread it directly into a ToolResult.Content without further marshalling.
Production has exactly one implementation (*RepoRunner). The interface is the orchestrator's actual contract — typed at the call site so future fake runners (for testing tool-error paths, tool-call latency, or specific tool outputs) can drop in without edits to the orchestrator.