toolset

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package toolset is the built-in agent_toolset_20260401: the six tools the platform executes for the model — bash, read, write, edit, glob, grep — run inside the session's sandbox.

Two halves. Tools turns an agent's toolset entry into the definitions the model is handed (name, description, input schema); Runner.Run executes one call of a named tool against a sandbox. Nothing here talks to the event log or the work queue: what a tool call means for the session is the executor's, and this package only knows how to run one.

The reference implementation of these six is anthropic-sdk-go's tools/agenttoolset, which runs them on the host and therefore has to confine the file tools to a workdir and warn that bash cannot be confined at all. Here the container IS the confinement, and bash runs in it like everything else, so the file tools resolve relative paths against the workdir and otherwise let a path be a path: a model that wants /etc can read it with bash regardless, and a lexical check that bash ignores is theatre, not a boundary.

Divergences from that reference, all deliberate:

  • No workdir confinement (above). Absolute paths and absolute glob patterns are accepted.
  • grep shells out to GNU grep inside the sandbox (PCRE where the image's grep has it, POSIX ERE otherwise) rather than preferring ripgrep and falling back to a Go walker. One implementation, one behaviour, and no dependence on what the image happens to ship beyond the /bin/bash the sandbox already requires.
  • The tools carry no state between calls except bash's, which is the shell package's snapshot; there is no per-runner session object to close.
  • write and edit preserve the permission bits of an existing regular file they replace, where the reference writes a fixed 0644 (its atomicWriteFile chmods the temporary file to that constant before renaming). Where nothing is carried over — a new file, a symlink, a docker sandbox whose user cannot chmod the temporary file (#209) — 0644 is what lands here too. The Claude Code harness preserves them, and the workflow that decided it is ordinary — `chmod +x` a script in bash, edit it, run it (#204). The rename that makes the write atomic is the sandbox backends'; both carry the mode over (internal/sandbox/filefault.go).

Index

Constants

View Source
const (
	// MaxOutputBytes caps what a tool call returns to the model. The sandbox
	// caps a command's output an order of magnitude higher (that cap is a
	// memory guard on the executor); this one is the model's context budget,
	// and it is the tool result that goes on the event log forever.
	MaxOutputBytes = 100 << 10

	// DefaultTimeout bounds a tool call the model did not time itself, and
	// MaxTimeout bounds the one it did. A model-chosen timeout is a lease the
	// executor has to keep alive, so it cannot be unbounded.
	DefaultTimeout = 2 * time.Minute
	MaxTimeout     = 10 * time.Minute
)
View Source
const DefaultAgentToolsetPolicy = domain.PolicyAlwaysAllow

DefaultAgentToolsetPolicy is the permission policy a built-in tool resolves to when its agent_toolset entry sets none. The plan states the reference resolves the agent toolset to always_allow; the wire types carry no resolved default to corroborate that, so this is the plan's value, not a recorded one — flip this one constant once a real managed-agents endpoint can be recorded.

View Source
const MetricToolDuration = "tool.execution.duration"

MetricToolDuration is deliberately not one of OTel's gen_ai.* metrics. Those describe a client's call to a GenAI provider and require gen_ai.provider.name; running bash in a container is not that, and inventing a provider value to satisfy the convention would make the metric lie about what it measured. So the name is the platform's own, following OTel's naming rules (dotted, lowercase, unit in the Unit field rather than the name), while the attributes reuse the semconv keys that genuinely apply — gen_ai.tool.name is the same tool the model named, and error.type is the standard failure dimension. It is exported so the telemetry contract test can assert this name reaches an OTLP collector.

Variables

This section is empty.

Functions

func CapOutput added in v0.2.0

func CapOutput(s string) string

CapOutput trims content to MaxOutputBytes, marking the truncation. Exported for the same reason RecordRun is: the executor's web driver produces tool results outside this Runner and must honor the SAME log budget — one cap, one meaning, whatever process ran the tool.

func IsWebTool added in v0.2.0

func IsWebTool(name string) bool

IsWebTool reports whether name is a built-in tool that executes in the executor's process rather than the sandbox. The executor's sandbox scan, the BYOC worker's, and the queue-kind decisions all consult this one predicate, so the split cannot drift between them.

func Policies

Policies resolves the permission policy of every built-in tool an agent_toolset_20260401 entry enables, keyed by tool name. It mirrors Tools' enable resolution, so disabled tools are absent; the brain reads it to stamp evaluated_permission on each tool_use and to decide whether a turn's calls suspend for human confirmation.

func RecordRun added in v0.2.0

func RecordRun(ctx context.Context, name string, d time.Duration, res Result, err error)

RecordRun records one tool call's duration. It resolves the meter per call rather than caching an instrument at package scope: a tool call costs a sandbox round trip, which dwarfs this, and a cached instrument would pin whichever MeterProvider happened to be installed first — leaving the metric silently wired to a dead provider in any process that configures telemetry after the first call, and untestable besides.

Exported because the executor's web driver runs the web tools outside this package's Runner and must record through the SAME instrument — one metric name, one meaning, whatever process ran the tool.

func SanitizeText added in v0.2.0

func SanitizeText(s string) string

SanitizeText strips NUL bytes from tool output. Postgres's jsonb cannot store \u0000 inside a string value, so a NUL anywhere in a result — one byte of /dev/zero on stdout is enough — would fault the event append, and a faulted work item reclaim-loops, re-running the same command into the same failure. Sanitized before CapOutput so the log budget is spent on bytes that survive. Exported for the executor's web driver, which produces results outside this Runner (the same reason CapOutput is exported).

func Tools

func Tools(raw json.RawMessage) ([]json.RawMessage, error)

Tools returns the model-facing definitions of the built-in tools an agent_toolset_20260401 entry enables, in the wire's order.

func Validate

func Validate(raw json.RawMessage) error

Validate checks that an agent_toolset_20260401 entry resolves — its enable flags and the permission policies of its enabled tools are well-formed. It is the create-time counterpart to Tools/Policies: an entry that fails here would otherwise be stored on the agent and wedge every turn when the brain resolves it, so the API validates at agent creation to make a malformed toolset a 400 instead.

Types

type Result

type Result struct {
	Content string
	// SearchResults, when non-nil, is the structured content of a web_search
	// answer: the tool_result carries these search_result blocks instead of a
	// text block (an empty non-nil slice is an empty content array — a search
	// with no hits). Only the executor's web driver sets it; nil keeps today's
	// text shape byte-identical.
	SearchResults []domain.SearchResultBlock
	IsError       bool
}

Result is one tool call as the model sees it. IsError marks a tool-level failure — a missing file, a bad regex, a nonzero exit — which the model reads and can recover from. A backend fault (the sandbox is gone, the daemon is unreachable) is never a Result: it comes back from Run as an error, and what happens to the tool call then is the executor's decision, not the model's.

type Runner

type Runner struct {
	Sandbox sandbox.Sandbox
	// Session scopes the bash shell's state in the container.
	Session domain.ID
	// Workdir is where relative tool paths resolve. Empty means the sandbox's
	// own default, which is where its Exec already runs.
	Workdir string
}

Runner executes built-in tool calls inside one session's sandbox.

func (Runner) Run

func (r Runner) Run(ctx context.Context, id domain.ID, name string, input json.RawMessage) (res Result, err error)

Run executes the named built-in tool. id names this call — the tool-use event's id — and scopes the bash shell's per-call files.

Every tool call the platform runs arrives here, from the cloud executor and the BYOC worker alike, so this is the one place the tool-execution metric can be recorded once and mean the same thing at both deployment points.

Jump to

Keyboard shortcuts

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