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 ¶
- Variables
- func BuiltinToolNames() []string
- func ContextWithWake(ctx context.Context, wake <-chan struct{}) context.Context
- func GateToolset(ts adktool.Toolset, gate *permissions.Gate, namespace string) adktool.Toolset
- func NewAskUserTool(opts AskUserOptions) (tool.Tool, error)
- func NewFetchURLTool(gate *permissions.Gate, cfg *config.Config) tool.Tool
- func NewJSONQueryTool(gate *permissions.Gate, cfg *config.Config) tool.Tool
- func NewLifecycleTool(opts LifecycleOptions) (tool.Tool, error)
- func NewScheduleTool(opts ScheduleOptions) (tool.Tool, <-chan ScheduleEvent, error)
- func PackTool(req *model.LLMRequest, t Packable) error
- func Truncate(s string, maxBytes, maxLines int) string
- type AskUserOptions
- type BuiltinTools
- type LifecycleEvent
- type LifecycleHandler
- type LifecycleOptions
- type Packable
- type Prompter
- type PrompterFunc
- type Registry
- type ScheduleEvent
- type ScheduleOptions
- type Scheduler
- type SchedulerFunc
- type TodoItem
- type TodoStore
Constants ¶
This section is empty.
Variables ¶
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 ¶ added in v1.6.0
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 ¶
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 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 ¶ added in v1.8.0
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 ¶ added in v1.7.0
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 NewScheduleTool ¶ added in v1.6.0
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 PackTool ¶ added in v1.5.0
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 Truncate ¶
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
}
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 ¶
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 ¶ added in v1.5.0
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 ¶
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 ¶
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 ¶
StaticPrompter returns a Prompter that always returns answer with no delay. Test fixture; production code should not use this.
func StdinPrompter ¶
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 ¶
PrompterFunc adapts a function to the Prompter interface for one-off uses.
type Registry ¶
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, b BuiltinTools) (*Registry, error)
Build constructs the registry. cfg supplies output-truncation caps; gate gates every tool call. Both 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 ScheduleEvent ¶ added in v1.6.0
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 ¶ added in v1.6.0
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 ¶ added in v1.6.0
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 ¶ added in v1.6.0
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 ¶ added in v1.6.0
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 ¶ added in v1.6.0
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 ¶ added in v1.6.0
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.