tools

package
v2.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

Documentation

Overview

Package tools provides ADK-side helpers for wiring tools into the agent. Today it holds only the GateToolset wrapper that bridges permissions.Gate to ADK's tool.Toolset interface; consumer projects add their own tool implementations on top.

Package tools provides ADK-side helpers and built-in tool implementations for the agent loop:

  • GateToolset: bridges permissions.Gate to ADK Toolsets (used by mcp + skills)
  • Built-in tool suite: file I/O, shell, todo tracking, output truncation

All built-in tools share a common shape via google.golang.org/adk/tool/functiontool and the helpers in this package: output truncation (Truncate) and gate consultation (via permissions.Gate). Tool authors define a typed Args + Result pair and a handler closing over any dependencies; builtins.go assembles them into the slice consumed by agent.WithTools.

Index

Constants

This section is empty.

Variables

View Source
var ErrSchedulerDefer = errors.New("scheduler: defer to next process")

ErrSchedulerDefer is the sentinel a Scheduler returns to exit the autonomous loop with StopReasonDeferred — handing the wake-time off to whatever orchestrator restarts the process or invokes ResumeAutonomous.

Functions

func BuiltinToolNames

func BuiltinToolNames() []string

BuiltinToolNames returns a fresh copy of the canonical built-in tool names. Order matches the field order in BuiltinTools so callers can iterate deterministically.

func ContextWithWake

func ContextWithWake(ctx context.Context, wake <-chan struct{}) context.Context

ContextWithWake attaches a wake channel to ctx. SleepScheduler selects on it alongside its sleep timer and ctx.Done. Nil channels are safe (select on nil blocks forever, so the case never fires).

Exported so the autonomous driver (in package agent) can plumb the agent's wake channel through to SleepScheduler without scheduler callers learning about the agent type.

func GateToolset

func GateToolset(ts adktool.Toolset, gate *permissions.Gate, namespace string) adktool.Toolset

GateToolset wraps ts so every tool inside it goes through the permission gate before running. namespace is the policy bucket used for allow/deny matching ("mcp", "skill", etc.); a nil gate returns ts unchanged.

Reuses the existing permission UX: in `ask` mode the same prompt surface is used; in `allow` mode the same allowlist patterns apply (now with `mcp:<tool>` / `skill:<tool>` keys); in `yolo` mode the call proceeds. The bash denylist does NOT apply to non-bash tools.

func LatestActivePlan

func LatestActivePlan(agentsDir string) string

LatestActivePlan returns the path of the highest-sequence non- revoked plan in <agentsDir>/plans/, or empty string if none. Used by /replan to find what to archive.

func NewAskUserTool

func NewAskUserTool(opts AskUserOptions) (tool.Tool, error)

NewAskUserTool wraps a Prompter as an ADK tool the agent can call during a turn. The tool's handler blocks until the prompter responds, then returns the answer as the tool result — so the agent's reasoning continues in the same turn (no end-of-turn break).

Use the in-turn pattern when the wait is short (interactive CLI, quick approval). For long waits where the model should release the turn (Scion-style: emit a status, end the turn, driver loop reads the next stdin message and starts a fresh agent.Run), use a status-emitting tool instead and let your driver loop handle the hand-off.

Returns an error only if opts.Prompter is nil.

func NewFetchURLTool

func NewFetchURLTool(gate *permissions.Gate, cfg *config.Config) tool.Tool

NewFetchURLTool returns the fetch_url built-in. Only meaningful when cfg.URLScope.Allow is non-empty — with no allowlist, every fetch will be denied. The caller (builtins.go) should skip registering this tool when no allowlist is configured rather than register-then-refuse, but the tool itself is safe either way.

func NewJSONQueryTool

func NewJSONQueryTool(gate *permissions.Gate, cfg *config.Config) tool.Tool

NewJSONQueryTool returns the json_query tool. Uses gojq (pure Go, no CGO — safe for distroless) to evaluate a jq expression against JSON loaded from either a file path or an inline json string. Designed primarily to slice the large structured outputs that remote MCP servers (kubectl get pods -o json, etc.) return, so the model gets just the field it asked for rather than burning prompt-cache space on a hundreds-of-KB JSON blob.

Returns an error result (not a Go error) for malformed JSON or bad jq expressions so the model can adapt rather than aborting the turn. Real errors (gate denials, file I/O) propagate as Go errors.

func NewLifecycleTool

func NewLifecycleTool(opts LifecycleOptions) (tool.Tool, error)

NewLifecycleTool wraps a LifecycleHandler as an ADK tool the agent can call to signal state ("thinking", "blocked", "done", custom). The consumer's handler decides where the events go: stdout, a status file, an orchestrator's event log, a websocket, etc.

Returns an error only if opts.Handler is nil or AllowedStates contains an empty entry.

func NewRetrieveRawTool

func NewRetrieveRawTool(opts RetrieveRawOptions) (tool.Tool, error)

NewRetrieveRawTool exposes digest.Store.Get as an ADK tool the agent can call during a turn. The tool returns the raw payload verbatim; the caller / model is responsible for deciding what to slice or extract before feeding it to the next step.

Design intent: aggressive digesting is only safe when the model has an escape hatch to fetch back the raw payload. This tool is that hatch. Register it alongside any consumer wiring digest.Process with a Store.

Error handling: the tool NEVER returns a Go error from its handler — unknown call_ids and store failures surface as a tool-response with Raw carrying a "(error: ...)" prefix. That keeps the model in the loop (it can retry, ask the user, or pick a different call_id) rather than aborting the whole turn.

Returns an error only if opts.Store is nil at construction time. Consumers who don't have a Store should not call this — see the Store field docstring on RetrieveRawOptions.

func NewScheduleTool

func NewScheduleTool(opts ScheduleOptions) (tool.Tool, <-chan ScheduleEvent, error)

NewScheduleTool returns the schedule_next_turn tool plus a channel the autonomous driver consumes after each turn. The tool emits one event per successful call; rejected calls (validation failures, cap violations) return a tool-result error to the model and emit nothing on the channel.

The channel is buffered (default 1); a non-blocking send is used so a single misbehaving turn that calls the tool multiple times won't deadlock the goroutine. The autonomous driver drains the channel non-blockingly after each turn and consults the configured Scheduler only if an event was received.

Returns an error only when opts is invalid (currently: never; all options have sensible zero-value defaults).

func NewSciontoolStatusTool

func NewSciontoolStatusTool() (tool.Tool, error)

NewSciontoolStatusTool returns an ADK tool the agent can call to signal sticky lifecycle transitions to Scion. Wired into the built-in registry by tools.Build when `sciontool` is on PATH; can also be constructed directly by out-of-registry consumers.

func PackTool

func PackTool(req *model.LLMRequest, t Packable) error

PackTool registers t with req so ADK can dispatch tool calls back to it (via req.Tools[name]) and so the model sees its declaration (via req.Config.Tools[*].FunctionDeclarations).

This is a re-implementation of google.golang.org/adk/internal/toolinternal/toolutils.PackTool — that package is internal to ADK and we can't import it. The algorithm is identical: register the tool in req.Tools, then either append the declaration onto an existing genai.Tool that already carries FunctionDeclarations, or create a new one.

Wrappers that forward methods to an inner tool (e.g. an MCP namespace prefixer or a permission-gate wrapper) MUST call this with themselves rather than delegating to inner.ProcessRequest. Packing the wrapper ensures (a) the model sees the wrapper's renamed Declaration and (b) ADK's call-back dispatch hits the wrapper's Run, preserving namespacing and gating.

func RecordPlan

func RecordPlan(gate *permissions.Gate, agentsDir string) (tool.Tool, error)

RecordPlan returns the built-in record_plan tool. Calling it with a non-empty plan writes the plan to `<agentsDir>/plans/plan-<seq>.md` and flips the gate's `planRecorded` flag, which unblocks mutating tool calls when RequirePlanArtifact is set.

The tool is ALWAYS allowed regardless of gate mode or planRecorded state — it's the escape valve from plan-first gating. It does not call the gate; it writes directly to agentsDir/plans/ via atomic-rename.

Per docs/plan-first-design.md Q2 ("any non-empty string"), no schema validation beyond non-empty-after-trim. Plan quality is the operator's judgment, enforced via /replan when needed.

func RevokeLatestPlan

func RevokeLatestPlan(gate *permissions.Gate, agentsDir string) (string, error)

RevokeLatestPlan renames `<agentsDir>/plans/plan-<seq>.md` to `plan-<seq>-revoked.md` (preserving the audit trail) and clears the gate's planRecorded flag. Called by /replan.

Returns the path of the file that was archived (empty if no active plan existed). An empty path with no error means there was nothing to revoke; the gate flag is still cleared in case it was set out of band.

func Truncate

func Truncate(s string, maxBytes, maxLines int) string

Truncate caps s at the lower of maxBytes / maxLines. When truncation occurs, a marker line is appended so the model knows it received an abridged output and can ask for more (e.g. via offset/limit on a re-read or a narrower grep).

maxBytes <= 0 means "no byte limit"; same for maxLines.

Types

type AskUserOptions

type AskUserOptions struct {
	// Prompter is the consumer's delivery mechanism. Required.
	Prompter Prompter

	// Name overrides the tool's function name. Defaults to "ask_user"
	// when empty.
	Name string

	// Description overrides the tool's description (the prose the
	// model sees in its function-decl list). Empty falls back to a
	// sensible default.
	Description string
}

AskUserOptions configures NewAskUserTool.

type BuiltinTools

type BuiltinTools struct {
	Bash          bool // /bin/sh -c with timeout + denylist + gate
	ReadFile      bool // Read a file with offset/limit
	ReadManyFiles bool // Read a batch of files (paths + pattern) in one call
	WriteFile     bool // Atomic write/create
	EditFile      bool // Single-occurrence string replacement
	DeleteFile    bool // Remove a regular file (refuses directories)
	Stat          bool // Metadata (size / mtime / mode / is_dir) for a single path
	ListDir       bool // Sorted directory listing
	Glob          bool // Walk + filepath.Match by basename
	Grep          bool // Walk + RE2 regex per line
	JSONQuery     bool // jq expression over JSON loaded from file or inline string
	FetchURL      bool // HTTP GET against url_scope.allow; URL-allowlist enforced
	Todo          bool // In-process plan tracker
	RecordPlan    bool // Plan-first artifact + gate-flag flip (record_plan)
	// SciontoolStatus is enabled in the Default struct but Build only
	// registers it when `sciontool` is on PATH — inside a Scion
	// container. Outside Scion the tool would be inert (subprocess
	// no-op) and pointlessly visible in the model's schema.
	SciontoolStatus bool
}

BuiltinTools toggles core-agent's built-in tool suite. Each enabled flag becomes one entry in the returned Registry's Tools slice.

All defaults are on — every consumer that's writing an agent will almost certainly want read/write/edit/list/bash. The Todo store is always created; the toggle just controls whether the model can drive it via the `todo` tool.

To turn one off:

reg, _ := tools.Build(cfg, gate, tools.BuiltinTools{
    Bash: false,        // disable shell
    ReadFile: true,
    WriteFile: true,
    EditFile: true,
    ListDir: true,
    Todo: true,
})

Or use Default() and override fields directly or via Disable:

b := tools.Default()
b.Disable("bash")               // by canonical name; errors on typos
b.WriteFile = false             // or set the field directly
reg, _ := tools.Build(cfg, gate, b)

func Default

func Default() BuiltinTools

Default returns a BuiltinTools with every tool enabled. This is the recommended starting set for any agent that needs to act on its workspace.

func (*BuiltinTools) Disable

func (b *BuiltinTools) Disable(name string) error

Disable turns off the named tool. Returns an error for unknown names so typos in --disable-tools or .agents/config.json fail loudly at startup rather than silently leaving the tool on. Calling Disable twice with the same name is a no-op.

type LifecycleEvent

type LifecycleEvent struct {
	State  string
	Detail string
	Time   time.Time
}

LifecycleEvent is the payload delivered to a LifecycleHandler each time the model calls a tool built by NewLifecycleTool. State is the value the model passed (e.g. "thinking", "blocked", "done"); Detail is the optional human-readable context. Time is the moment the handler received the call.

type LifecycleHandler

type LifecycleHandler func(ctx context.Context, ev LifecycleEvent) error

LifecycleHandler receives each lifecycle emit. Returning a non-nil error surfaces the error string back to the model as the tool's response — useful for "this state isn't valid right now" feedback. A nil return acks the call with a generic "ok" so the model can keep working.

type LifecycleOptions

type LifecycleOptions struct {
	// Handler is invoked once per emit. Required.
	Handler LifecycleHandler

	// Name overrides the tool's function name. Defaults to
	// "set_status" when empty.
	Name string

	// Description overrides the tool's description (the prose the
	// model sees in its function-decl list). Empty falls back to a
	// sensible default.
	Description string

	// AllowedStates, when non-empty, restricts the State values the
	// tool will accept. A call with a State outside the set is
	// rejected with an error result returned to the model — the
	// handler is not invoked for the rejected call.
	AllowedStates []string
}

LifecycleOptions configures NewLifecycleTool.

type Packable

type Packable interface {
	Name() string
	Declaration() *genai.FunctionDeclaration
}

Packable is the minimum surface PackTool needs from a tool. Both the ADK-internal toolinternal.RequestProcessor contract and our own wrapper types satisfy it.

type Prompter

type Prompter interface {
	Prompt(ctx context.Context, question string) (string, error)
}

Prompter delivers a question from the agent to a human (or any external authority) and returns their answer. Implementations decide the channel — stdin, websocket, queue, file watch, etc.

Returning a non-nil error signals "no user available" or transport failure; the agent sees the error string as the tool's response and can adapt (typically by proceeding with its best assumption or reporting the problem) instead of blocking forever.

Prompt should respect ctx cancellation so SIGTERM, deadlines, and agent-driven aborts unblock the tool cleanly.

func RefusePrompter

func RefusePrompter(reason string) Prompter

RefusePrompter returns a Prompter that always errors with reason. Use in non-interactive runs (batch jobs, CI, daemon deployments without a wired-up channel) so the model gets a clear "no user available" signal as the ask_user tool result and adapts, rather than blocking forever on a missing reader.

Recommended reason text guides the model toward useful behavior:

tools.RefusePrompter("running unattended; proceed with reasonable defaults and explain in your final response")

func StaticPrompter

func StaticPrompter(answer string) Prompter

StaticPrompter returns a Prompter that always returns answer with no delay. Test fixture; production code should not use this.

func StdinPrompter

func StdinPrompter(in io.Reader, out io.Writer) Prompter

StdinPrompter reads the user's answer from in (typically os.Stdin). The question is written to out (typically os.Stderr) so it doesn't pollute stdout. Each call reads one newline-terminated line and returns it with surrounding whitespace trimmed.

Suitable for interactive CLI use and for stdin-driven adapters (e.g. Scion's tmux-fed message loop) where one line of input maps to one user answer.

Cancellation note: the underlying os.Stdin read isn't ctx-aware on every platform, so a hung read may not unblock until a newline arrives. For long-blocking deployments, wrap with a goroutine + channel pattern in your own Prompter.

type PrompterFunc

type PrompterFunc func(ctx context.Context, question string) (string, error)

PrompterFunc adapts a function to the Prompter interface for one-off uses.

func (PrompterFunc) Prompt

func (f PrompterFunc) Prompt(ctx context.Context, q string) (string, error)

Prompt implements Prompter.

type Registry

type Registry struct {
	Tools []tool.Tool
	Todo  *TodoStore
}

Registry is the assembled built-in tool set returned by Build.

Tools is the slice you pass to agent.WithTools(...). Todo is the underlying store, exposed so hosts can render plan progress (e.g. for a /todo slash command in a TUI).

func Build

func Build(cfg *config.Config, gate *permissions.Gate, agentsDir string, b BuiltinTools) (*Registry, error)

Build constructs the registry. cfg supplies output-truncation caps; gate gates every tool call. agentsDir is the resolved .agents/ directory (may be empty when none was found) and is required only by tools that persist artifacts to it (today: record_plan). Both cfg and gate are required.

We deliberately do NOT set ADK's functiontool.Config.RequireConfirmation even when the gate is in "ask" mode. core-agent's gate handles approval itself by calling its Prompter from inside each tool handler — going through ADK's HITL flow would be a second approval round-trip on top of ours.

type RetrieveRawOptions

type RetrieveRawOptions struct {
	// Store is the CCR backing populated by digest.Process. Required.
	// When the operator hasn't wired a store (e.g. --session-db is
	// off and no explicit FilesystemStore was configured), consumers
	// should skip registering the tool rather than passing nil here
	// — the tool would refuse every call and only confuse the model.
	Store digest.Store

	// Name overrides the tool's function name. Defaults to
	// "retrieve_raw" when empty.
	Name string

	// Description overrides the tool's description (the prose the
	// model sees in its function-decl list). Empty falls back to a
	// sensible default that explains when NOT to use it.
	Description string
}

RetrieveRawOptions configures NewRetrieveRawTool.

type ScheduleEvent

type ScheduleEvent struct {
	// WakeAt is the absolute time the next turn should run, resolved
	// from either an absolute time or a relative duration in the tool
	// args before this event is emitted.
	WakeAt time.Time

	// NextPrompt is the continuation message the next turn should
	// receive. Empty string means the driver should fall back to its
	// configured continuation prompt (typically "continue").
	NextPrompt string

	// Detail is the freeform one-liner the model passed for telemetry
	// / operator visibility (e.g. "polling cluster-A on 10m cadence").
	// Surfaces on the chat-style streaming UI and in the eventlog.
	Detail string

	// Time is the moment the tool handler received the call.
	Time time.Time
}

ScheduleEvent is what the schedule_next_turn tool emits when the model calls it. The autonomous driver consumes one event per turn from the channel returned by NewScheduleTool and feeds it to the configured Scheduler.

type ScheduleOptions

type ScheduleOptions struct {
	// Name overrides the tool's function name. Defaults to
	// "schedule_next_turn" when empty.
	Name string

	// Description overrides the model-facing tool description. Defaults
	// to a baseline that includes the report_done distinction, the
	// cadence ladder (30s fast-changing state, 5-15m steady-state,
	// 1h+ slow-changing infra), good-vs-bad next_prompt examples, and
	// the state-persistence reminder.
	Description string

	// MaxDefer caps how far in the future a single call may schedule.
	// Zero means no cap. A call with wake_in_sec or wake_at past the
	// cap returns a tool-result error to the model so it can adapt;
	// the channel sees no event for that rejected call.
	MaxDefer time.Duration

	// BufferSize sets the schedule channel buffer. Default 1 — exactly
	// one schedule emission is consumed per turn by the autonomous
	// driver, so anything past 1 is overflow that would indicate a
	// bug. Exposed for tests that want to inspect multiple emissions
	// without a driver in the loop.
	BufferSize int
}

ScheduleOptions configures NewScheduleTool.

type Scheduler

type Scheduler interface {
	// BeforeNextTurn is consulted by RunAutonomous after a turn that
	// emitted a schedule intent, after the per-turn checkpoint is
	// written. Return nil to let the loop continue at ev.WakeAt with
	// prompt=ev.NextPrompt. Return ErrSchedulerDefer to exit the loop
	// with StopReasonDeferred. Any other error aborts the run.
	BeforeNextTurn(ctx context.Context, ev ScheduleEvent) error
}

Scheduler decides what RunAutonomous should do between turns when the prior turn emitted a schedule intent via the schedule_next_turn tool. Loops whose model never emits a schedule intent never consult the scheduler — zero overhead by default.

Bundled implementations:

  • SleepScheduler — long-lived daemon: sleeps the goroutine until WakeAt, then returns nil so the loop continues.
  • ExitOnDeferScheduler — orchestrator-managed (k8s CronJob, AX, external queue): returns ErrSchedulerDefer so the loop exits cleanly with StopReasonDeferred + RunResult.NextWakeAt populated. A subsequent ResumeAutonomous picks up at the persisted wake-time.

Consumers ship their own Scheduler for distributed shapes (NATS queue, custom orchestrator). The schedulerFunc adapter is exported for one-off function-shaped implementations.

func ExitOnDeferScheduler

func ExitOnDeferScheduler() Scheduler

ExitOnDeferScheduler returns a Scheduler that exits the autonomous loop on every schedule emission via ErrSchedulerDefer. The driver reports StopReasonDeferred with RunResult.NextWakeAt populated from the schedule event; whatever orchestrator wraps the process (k8s CronJob, supervisord, AX) restarts it at or after that wake-time and a fresh ResumeAutonomous call picks up where this run left off (the deferred checkpoint persists the wake-time to the eventlog).

Use when external scheduling infrastructure already exists and the operator prefers short-lived processes over a long-lived daemon. For most monitoring shapes SleepScheduler is the cleaner default; see docs/scheduled-monitoring-design.md.

func SleepScheduler

func SleepScheduler() Scheduler

SleepScheduler returns a Scheduler that sleeps the calling goroutine until ev.WakeAt, respecting context cancellation. Returns nil on wake; returns ctx.Err() when the context is cancelled mid-sleep.

Also returns nil immediately when an external wake signal fires on the channel attached via ContextWithWake — this is how a child alert arrival or an operator Inject pierces an active sleep so the agent reacts without waiting out the full scheduled interval.

Use when the agent runs as a long-lived daemon that should retain in-memory state between scan cycles (warm prompt cache, supervisor subagent tree, etc.). For deployments where the process exits between cycles, use ExitOnDeferScheduler instead.

A WakeAt already in the past returns immediately.

type SchedulerFunc

type SchedulerFunc func(ctx context.Context, ev ScheduleEvent) error

SchedulerFunc adapts a plain function to the Scheduler interface for callers that don't want to declare a type just to wire one behavior.

func (SchedulerFunc) BeforeNextTurn

func (f SchedulerFunc) BeforeNextTurn(ctx context.Context, ev ScheduleEvent) error

BeforeNextTurn implements Scheduler.

type TodoItem

type TodoItem struct {
	ID     int    `json:"id"`
	Status string `json:"status"` // "pending" | "in_progress" | "completed"
	Text   string `json:"text"`
}

TodoItem is one entry in the agent's plan.

type TodoStore

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

TodoStore is the in-process backing for the agent's todo list. Ephemeral (one store per core-agent process); persistence across sessions can be added later if a consumer needs it.

func NewTodoStore

func NewTodoStore() *TodoStore

NewTodoStore returns a fresh, empty store.

func (*TodoStore) Items

func (s *TodoStore) Items() []TodoItem

Items returns a defensive copy of the current todo list. Useful for hosts that want to render plan progress (e.g. a /todo slash command).

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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