Documentation
¶
Overview ¶
Package sandbox provides isolation backends and the tools that run inside them, implementing agentcore.Sandbox. It is an edge package: it shells out to the host's container runtime, so it lives outside the agentcore leaf and is injected by the host (agentruntime), never imported by the core.
Index ¶
- Constants
- func BrowserUseLimits() agentcore.SandboxLimits
- func ComputerUseLimits() agentcore.SandboxLimits
- func DefaultWorkspaceBase() (string, error)
- func NewGuardedClient(timeout time.Duration) *http.Client
- func ResolvePinnedWorkspace(path string) (string, error)
- func ValidateEvalConfig(raw string) error
- func ValidateLSPConfig(raw string) error
- type BrowserTool
- type DockerSandbox
- func (s *DockerSandbox) Available(ctx context.Context) bool
- func (s *DockerSandbox) CloseSession(id string) error
- func (s *DockerSandbox) Exec(ctx context.Context, req agentcore.SandboxExec) (agentcore.SandboxResult, error)
- func (s *DockerSandbox) Image() string
- func (s *DockerSandbox) Start(ctx context.Context, req agentcore.SandboxExec) (agentcore.SandboxProcess, error)
- func (s *DockerSandbox) StopEgress()
- type EditFileTool
- type EditLinesTool
- type EvalConfig
- type EvalJavaScriptConfig
- type EvalPythonConfig
- type EvalSessionRegistry
- type EvalTool
- type GlobTool
- type GrepTool
- type HTTPOption
- type HTTPTool
- type HostSandbox
- type InjectionGuard
- type LSPConfig
- type LSPServerConfig
- type LSPSessionRegistry
- type LSPTool
- type Option
- type ReadFileTool
- type SearchProvider
- type SearchResult
- type ShellTool
- type WebFetchOption
- type WebFetchTool
- type WebSearchTool
- type Workspace
- type WorkspaceScope
- type WriteFileTool
Constants ¶
const ( ToolReadFile = "read_file" ToolWriteFile = "write_file" )
const ( ToolGrep = "grep" ToolGlob = "glob" )
const ToolBrowserUse = "browser_use"
ToolBrowserUse is the higher-privilege, Claude-Code-level browser surface. It runs `agent-browser` CLI commands inside a persistent, network-enabled Chrome sandbox so the agent can drive a real browser across calls — open a page, snapshot its (LLM-optimized) accessibility tree, click by ref, type, and screenshot — with state surviving between invocations in one conversation.
const ToolComputerUse = "computer_use"
ToolComputerUse is the higher-privilege, Claude-Code-level shell. Unlike run_shell (locked: ephemeral, read-only, no network), it runs in a persistent session container with network egress and a writable filesystem, so the agent can install tooling (pip/apt/npm) and have it — plus any files it writes — survive across calls: write code, run it, install a PDF/DOCX/XLSX parser, then produce a PDF/PPTX/HTML artifact. It is a deliberate, policy-granted capability distinct from run_shell so a project opts into it explicitly.
const ToolEditFile = "edit_file"
const (
ToolEditLines = "edit_lines"
)
const (
ToolEval = "eval"
)
const ToolHTTPRequest = "http_request"
ToolHTTPRequest is the stable tool name. A consumer's Policy must permit it before the model is shown the tool.
It is the legitimate-egress counterpart to the container tools in this package: where run_shell runs untrusted code with --network none, this makes *controlled* outbound calls to an operator-approved host allowlist. Where the call is made from depends on the substrate it was built with — see HTTPTool.
const (
ToolLSP = "lsp"
)
const ToolRunShell = "run_shell"
ToolRunShell is the stable tool name the model calls to run a shell command. A consumer's Policy must permit this name before the model is shown the tool.
const ToolWebFetch = "web_fetch"
ToolWebFetch is the stable name of the open web-fetch tool. Like http_request it must be permitted by policy before the model sees it.
const ToolWebSearch = "web_search"
ToolWebSearch is the stable name of the web-search tool. Like web_fetch it must be permitted by policy before the model sees it.
Variables ¶
This section is empty.
Functions ¶
func BrowserUseLimits ¶
func BrowserUseLimits() agentcore.SandboxLimits
BrowserUseLimits is the envelope for the persistent browser sandbox: network on (to load pages and download the browser binary on first use), writable filesystem (Chrome's profile/cache + cloakbrowser's ~/.cloakbrowser binary), and generous memory/time so a page render finishes. Still hard-isolated by the backend (no host env, all caps dropped, no-new-privileges, resource caps).
func ComputerUseLimits ¶
func ComputerUseLimits() agentcore.SandboxLimits
ComputerUseLimits is the envelope for the persistent computer_use shell: network on (to install tooling), writable filesystem (so package managers can write), and generous time/memory so a build, a LibreOffice conversion, or a document render can finish. It stays hard-isolated by the backend (no host env, all caps dropped, no-new-privileges, resource caps).
func DefaultWorkspaceBase ¶
DefaultWorkspaceBase is ~/.agentray/workspaces — where agent files live when the operator has not said otherwise.
It has a default at all because requiring configuration to get a workspace is how a capability quietly ships turned off: with no root configured the file tools have nowhere to work, so they are withheld, and the agent a user just granted read_file to cannot read a file. A default that works out of the box is the difference between "recommended" and "required".
func NewGuardedClient ¶
NewGuardedClient returns an http.Client that shares this package's SSRF backstop — every dial resolves the host and refuses the connection if any resolved IP is loopback/private/link-local (the cloud-metadata endpoint) — but WITHOUT a host allowlist. It is for outbound delivery to user-configured destinations (alert webhooks, Slack incoming-webhooks) where the host set is open-ended but the private-network SSRF guard must still hold. Redirects are not followed, closing the "3xx bounces to a blocked host" gap. Zero uses the default timeout; a negative timeout disables deadlines but keeps cancellation.
func ResolvePinnedWorkspace ¶
ResolvePinnedWorkspace turns a user-chosen folder into the absolute path a workspace can be rooted at, or explains why it cannot be one.
It is exported so the setting can be validated where it is *saved* rather than only where it is used. A typo'd folder should be a red message under the field the user is looking at, not a tool error inside a run three days later that reads like the agent broke.
The only path refused outright is the filesystem root. Every other folder is the user's call — that is what pinning means — but "/" is never a considered choice, and an agent with write tools rooted there is a mistake with no undo.
func ValidateEvalConfig ¶
func ValidateLSPConfig ¶
Types ¶
type BrowserTool ¶
type BrowserTool struct {
// contains filtered or unexported fields
}
BrowserTool drives a browser via the agent-browser CLI on the substrate it was built with. With an injected Sandbox it reuses a persistent, browser-scoped session container so the agent-browser daemon (and the page it controls) survive across calls — the property that makes multi-step browsing possible. The daemon self-reaps on idle (AGENT_BROWSER_IDLE_TIMEOUT_MS, set in the image) so no zombie browser pins resources between conversations. With no sandbox the same CLI is invoked on the host machine, where the daemon is already a long-lived host process and persists without a container.
func NewBrowserTool ¶
func NewBrowserTool(sb agentcore.Sandbox, workspace *Workspace, limits agentcore.SandboxLimits, image string) *BrowserTool
NewBrowserTool builds the browser_use tool over sb, with the agent workspace mounted (so screenshots/exports persist on the host) and a Chrome-capable image. sb is optional: nil drives an `agent-browser` installed on the host machine instead, where the daemon persists across calls without a session container and the image is irrelevant. limits is the isolation envelope; pass BrowserUseLimits() for the network+writable browser profile.
func (*BrowserTool) Name ¶
func (t *BrowserTool) Name() string
func (*BrowserTool) Schema ¶
func (t *BrowserTool) Schema() agentcore.ToolSchema
type DockerSandbox ¶
type DockerSandbox struct {
// contains filtered or unexported fields
}
DockerSandbox runs each command in an ephemeral, hardened container via the host `docker` CLI. It holds no daemon connection and keeps no state between calls: every Exec is `docker run --rm` with a fresh container, so a compromised execution cannot persist or reach a sibling.
func NewDockerSandbox ¶
func NewDockerSandbox(opts ...Option) *DockerSandbox
NewDockerSandbox builds a DockerSandbox with hardened defaults.
func (*DockerSandbox) Available ¶
func (s *DockerSandbox) Available(ctx context.Context) bool
Available reports whether the docker runtime is reachable.
func (*DockerSandbox) CloseSession ¶
func (s *DockerSandbox) CloseSession(id string) error
CloseSession reaps the persistent container for id. It is idempotent and satisfies agentcore.SessionSandbox so the runtime can tear a session down when a conversation ends.
func (*DockerSandbox) Exec ¶
func (s *DockerSandbox) Exec(ctx context.Context, req agentcore.SandboxExec) (agentcore.SandboxResult, error)
Exec runs req to completion in a fresh hardened container and returns the captured output. A non-zero exit code is returned as a SandboxResult (not an error); error is reserved for the backend itself failing.
func (*DockerSandbox) Image ¶
func (s *DockerSandbox) Image() string
Image returns the configured sandbox image (used by tests / diagnostics).
func (*DockerSandbox) Start ¶
func (s *DockerSandbox) Start(ctx context.Context, req agentcore.SandboxExec) (agentcore.SandboxProcess, error)
Start launches an interactive command in the same fresh hardened container Exec uses, exposing protocol pipes until Wait. The container is force-removed after Wait or Kill because terminating the docker CLI alone is not sufficient to reap a detached child on every daemon/platform combination.
func (*DockerSandbox) StopEgress ¶
func (s *DockerSandbox) StopEgress()
StopEgress tears down every cached egress proxy. Called on sandbox shutdown.
type EditFileTool ¶
type EditFileTool struct {
// contains filtered or unexported fields
}
EditFileTool performs surgical in-place edits: it replaces an exact substring in a workspace file rather than rewriting the whole thing (Claude Code's Edit / pi's edit). This keeps large files cheap to change and makes intent reviewable — the model states the precise text it is swapping. It shares Workspace and the same substrate seam as read_file/write_file, so the read-modify-write happens wherever those happen — on the host under the symlink/escape guards by default, inside the sandbox when one is provided.
func NewEditFileTool ¶
func NewEditFileTool(sb agentcore.Sandbox, workspace *Workspace) *EditFileTool
NewEditFileTool builds edit_file over the given sandbox. sb is optional: nil edits the file on the host filesystem under the Workspace guards (the default), non-nil reads and rewrites it from inside the sandbox.
func (*EditFileTool) Name ¶
func (t *EditFileTool) Name() string
func (*EditFileTool) Schema ¶
func (t *EditFileTool) Schema() agentcore.ToolSchema
type EditLinesTool ¶
type EditLinesTool struct {
// contains filtered or unexported fields
}
EditLinesTool applies several line-addressed changes to one immutable file snapshot. Every coordinate refers to the original snapshot, so an agent can change distant regions in one call without recalculating line numbers after each hunk. The whole-file hash remains the authority: line numbers make calls compact, while the hash prevents them from landing on a different revision.
func NewEditLinesTool ¶
func NewEditLinesTool(sb agentcore.Sandbox, workspace *Workspace) *EditLinesTool
NewEditLinesTool builds edit_lines over the same host-or-sandbox filesystem seam as read_file, write_file, and edit_file.
func (*EditLinesTool) Name ¶
func (t *EditLinesTool) Name() string
func (*EditLinesTool) Schema ¶
func (t *EditLinesTool) Schema() agentcore.ToolSchema
type EvalConfig ¶
type EvalConfig struct {
Python EvalPythonConfig `json:"python,omitempty"`
JavaScript EvalJavaScriptConfig `json:"javascript,omitempty"`
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
MaxOutputBytes int `json:"max_output_bytes,omitempty"`
MaxBridgeCalls int `json:"max_bridge_calls,omitempty"`
}
EvalConfig selects the operator-provisioned Python and JavaScript runtimes used by the persistent eval tool. The zero-value JSON object is useful on a laptop (python3 and node on PATH); a server normally supplies language images or the combined AgentRay eval image.
func ParseEvalConfig ¶
func ParseEvalConfig(raw string) (EvalConfig, error)
type EvalJavaScriptConfig ¶
type EvalPythonConfig ¶
type EvalSessionRegistry ¶
type EvalSessionRegistry struct {
// contains filtered or unexported fields
}
EvalSessionRegistry retains language runtimes across the short-lived tool instances built for consecutive turns of one conversation. Capacity is a soft limit: active sessions are never killed to make room for another call.
func NewEvalSessionRegistry ¶
func NewEvalSessionRegistry(capacity int, idle time.Duration) *EvalSessionRegistry
func (*EvalSessionRegistry) Close ¶
func (r *EvalSessionRegistry) Close()
Close releases every retained process. It is primarily useful to embedding hosts and tests; normal sessions also have idle and hard lifetime bounds.
type EvalTool ¶
type EvalTool struct {
// contains filtered or unexported fields
}
EvalTool executes one Python or JavaScript cell in a retained, conversation-and-language-scoped subprocess. The registry—not this per-run tool instance—owns the process, so state survives runtime rebuilding while Python and JavaScript can never share globals accidentally.
func NewEvalTool ¶
func NewEvalTool(sb agentcore.Sandbox, workspace *Workspace, registry *EvalSessionRegistry, namespace string, config EvalConfig) (*EvalTool, error)
func (*EvalTool) RunRich ¶
RunRich preserves MIME displays for the agent loop. Run remains the compatibility path for direct callers and returns the identical text.
func (*EvalTool) Schema ¶
func (t *EvalTool) Schema() agentcore.ToolSchema
type GlobTool ¶
type GlobTool struct {
// contains filtered or unexported fields
}
GlobTool lists workspace files whose relative path matches a glob pattern (Claude Code's Glob), supporting * ? and ** segments. Results are sorted for stable output and capped for token safety.
func NewGlobTool ¶
NewGlobTool builds glob over the given sandbox. sb is optional: nil walks the host filesystem under the Workspace guards (the default), non-nil lists the tree inside the sandbox in a single exec.
func (*GlobTool) Schema ¶
func (t *GlobTool) Schema() agentcore.ToolSchema
type GrepTool ¶
type GrepTool struct {
// contains filtered or unexported fields
}
GrepTool searches file contents in the workspace by regular expression (Claude Code's Grep / pi's grep). The matching is always pure Go regexp in this process — only the file I/O moves: a guarded host walk by default, or a batched listing + read inside the sandbox when one is provided. Keeping RE2 above the substrate seam is what makes both paths return the same lines in the same order. Returns file:line:match lines, capped for token safety.
func NewGrepTool ¶
NewGrepTool builds grep over the given sandbox. sb is optional: nil walks and reads the host filesystem under the Workspace guards (the default), non-nil lists and reads inside the sandbox — batched into one exec per call, never one per file.
func (*GrepTool) Schema ¶
func (t *GrepTool) Schema() agentcore.ToolSchema
type HTTPOption ¶
type HTTPOption func(*HTTPTool)
HTTPOption configures an HTTPTool. It is distinct from Option, which configures the DockerSandbox backend.
func WithHTTPAllowHosts ¶
func WithHTTPAllowHosts(hosts []string) HTTPOption
WithHTTPAllowHosts sets the exact-match host allowlist (case-insensitive, port stripped). A request to any other host is refused.
func WithHTTPAllowPlain ¶
func WithHTTPAllowPlain(allow bool) HTTPOption
WithHTTPAllowPlain permits plain http:// URLs (default: https only).
func WithHTTPTimeout ¶
func WithHTTPTimeout(d time.Duration) HTTPOption
WithHTTPTimeout overrides the per-request timeout. Zero disables it while preserving caller cancellation; negative values leave the default intact.
func WithHTTPWorkspace ¶
func WithHTTPWorkspace(ws *Workspace) HTTPOption
WithHTTPWorkspace gives the tool the run's workspace, enabling save_as. Without it the tool still works — it just cannot hand a body to the file and shell tools, which is the difference between fetching data and being able to use it.
type HTTPTool ¶
type HTTPTool struct {
// contains filtered or unexported fields
}
HTTPTool makes guarded outbound HTTP requests. It is the worked consumer of the credential vault (governance F7): the place a {{cred:NAME}} secret actually gets used.
The tool is deliberately dumb about credentials: the agentcore loop resolves {{cred:NAME}} placeholders in the argument JSON at the trust boundary before Run is ever called, so an Authorization header the model wrote as "Bearer {{cred:API_KEY}}" arrives here already resolved to the real value — the model never saw the literal, and this tool never needs the vault.
Where the request is made from depends on the substrate. With no sandbox (the default) it goes out from the host process over net/http. With a sandbox it goes out from inside the container, so a compromised request cannot reach anything the container's network envelope does not permit. The resolved secret then has to cross that boundary, and it does so on the container's stdin as a curl config — never in argv (readable via `ps` by every process in the container) and never in an environment variable. See httpsandbox.go.
What it is careful about either way is SSRF. An agent that can make arbitrary outbound requests can reach cloud metadata (169.254.169.254), internal services, and localhost. Defenses, default-deny:
- scheme must be https (http is opt-in)
- the URL host must be in the configured allowlist
- a guarded dialer re-checks the resolved IP at connect time and refuses loopback / private / link-local / unspecified addresses, which also closes the DNS-rebinding TOCTOU gap (allowlisted name re-pointed at a blocked IP). On the sandbox path the same check runs in the egress proxy the container's traffic is confined to, so the backstop survives the move.
- redirects are not followed (a 3xx is surfaced to the model as-is)
Safe for concurrent use: the allowlist is read-only after construction and http.Client is concurrency-safe.
func NewHTTPRequestTool ¶
func NewHTTPRequestTool(sb agentcore.Sandbox, opts ...HTTPOption) *HTTPTool
NewHTTPRequestTool builds the http_request tool. sb is optional: nil makes the request from this host process (the default), non-nil makes it from inside the sandbox with egress confined to the same allowlist. The guarded dialer is installed either way — for the host path directly on the client, for the sandbox path in the egress proxy — so every request is IP-checked at connect.
func (*HTTPTool) AllowHosts ¶
AllowHosts returns the configured allowlist (for startup logging / tests).
func (*HTTPTool) Run ¶
Run executes the guarded request. args has already had {{cred:NAME}} resolved by the loop, so any secret in Headers is the real value here.
func (*HTTPTool) Schema ¶
func (t *HTTPTool) Schema() agentcore.ToolSchema
type HostSandbox ¶
type HostSandbox struct{}
HostSandbox runs commands directly on the host machine. It is the substrate every tool in this package falls back to when no agentcore.Sandbox is injected, which makes the sandbox parameter optional in both directions: nil means "run here", non-nil means "run in there", and the tools above keep a single code path either way.
It is NOT isolation. It is the closest honest host analogue of the agentcore.Sandbox contract, and it keeps exactly the parts of that contract a plain process can keep:
- Env is the only environment the child sees. os.Environ() is never inherited, so a prompt-injected `env` or `cat /proc/self/environ` cannot read the server's DB creds or API keys — the one guarantee worth the most and the one a bare exec.CommandContext would have thrown away.
- Workdir is resolved through Mounts, so a command runs in the agent workspace rather than the server's working directory.
- TimeoutSeconds is enforced with a hard kill of the whole process group, so a command that backgrounded work does not outlive its own timeout.
What it cannot keep, and what a caller must weigh before choosing it:
- The filesystem is the host's. WritableFS is not enforceable; a command can read anything the server process can read and write anywhere it can write.
- The network is the host's. Network and NetworkAllow are not enforceable — there is no container to route through the egress proxy.
- MemoryMB / CPUs / PidsLimit are not enforceable; only the timeout is.
- Image is meaningless and ignored.
- Session is ignored: the host filesystem already persists across calls, so a persistent tool needs no session container to keep its state.
So: HostSandbox is the right substrate for an embedded or local consumer of this package (a CLI on a developer's machine, a single-tenant deployment where the agent is already trusted with the box). It is the wrong substrate for a hosted, multi-tenant deployment running model-authored commands, which is why internal/runtime keeps the shell/computer/browser tools gated on a real sandbox being wired.
func NewHostSandbox ¶
func NewHostSandbox() *HostSandbox
NewHostSandbox returns the host execution substrate. The zero value is equally usable; the constructor exists so call sites read symmetrically with NewDockerSandbox.
func (*HostSandbox) Exec ¶
func (h *HostSandbox) Exec(ctx context.Context, req agentcore.SandboxExec) (agentcore.SandboxResult, error)
Exec runs one command on the host and captures its output. A non-zero exit is a SandboxResult, not an error — error is reserved for the host failing to run the command at all (binary missing, workdir unusable), matching the contract DockerSandbox implements.
func (*HostSandbox) Start ¶
func (h *HostSandbox) Start(ctx context.Context, req agentcore.SandboxExec) (agentcore.SandboxProcess, error)
Start launches an interactive host process under the same environment, workspace, timeout, and process-group rules as Exec. It is intentionally an optional capability on agentcore.Sandbox; tools that do not need a live protocol keep using the smaller buffered contract.
type InjectionGuard ¶
type InjectionGuard struct {
// contains filtered or unexported fields
}
InjectionGuard is a runtime prompt-injection / exfiltration filter installed as a BeforeToolCall hook. Where AGT's PromptDefense statically grades a system prompt *before* deployment, this scans the actual tool *arguments* the model emits *at* runtime — the live vector for indirect injection, where attacker text smuggled through retrieved data steers the agent into exfiltrating secrets or overriding its instructions.
It is deterministic regex (zero LLM cost), default-deny on match, and feeds the block reason back to the model so the run continues safely rather than failing silently. It is a cheap second layer behind the Sandbox, not a replacement: the sandbox stops a malicious command from reaching anything; the guard stops many such commands from ever being issued.
func NewInjectionGuard ¶
func NewInjectionGuard() *InjectionGuard
NewInjectionGuard builds a guard over the built-in vectors.
func (*InjectionGuard) Hook ¶
func (g *InjectionGuard) Hook() agentcore.BeforeToolCall
Hook returns the BeforeToolCall hook. It blocks any call whose arguments trip a vector; the reason is returned to the model so it can correct course.
func (*InjectionGuard) Match ¶
func (g *InjectionGuard) Match(s string) bool
Match reports whether s trips any injection vector (exposed for tests).
type LSPConfig ¶
type LSPConfig struct {
Servers []LSPServerConfig `json:"servers"`
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
}
LSPConfig is operator-authored configuration for the read-only language intelligence tool. Binaries are deliberately provisioned out of band: the agent may use a configured server, but never install or download one.
func ParseLSPConfig ¶
type LSPServerConfig ¶
type LSPServerConfig struct {
Name string `json:"name"`
Command string `json:"command"`
Args []string `json:"args,omitempty"`
Extensions []string `json:"extensions"`
LanguageID string `json:"language_id,omitempty"`
DiagnosticsOnly bool `json:"diagnostics_only,omitempty"`
Image string `json:"image,omitempty"`
InitializationOptions map[string]any `json:"initialization_options,omitempty"`
Settings map[string]any `json:"settings,omitempty"`
}
type LSPSessionRegistry ¶
type LSPSessionRegistry struct {
// contains filtered or unexported fields
}
LSPSessionRegistry retains initialized language servers across the short-lived tool instances built for consecutive turns. It is deliberately process-local: another server replica starts a clean client, so correctness never depends on sticky routing or a shared daemon.
Capacity is a soft limit. An in-flight client is never killed to make room.
func NewLSPSessionRegistry ¶
func NewLSPSessionRegistry(capacity int, idle time.Duration) *LSPSessionRegistry
func (*LSPSessionRegistry) Close ¶
func (r *LSPSessionRegistry) Close()
Close releases every retained server. Embedding hosts should call it during shutdown; idle and hard-lifetime bounds also make forgotten cleanup finite.
type LSPTool ¶
type LSPTool struct {
// contains filtered or unexported fields
}
LSPTool provides bounded read-only language intelligence. A process-local registry retains initialized servers by conversation and complete server identity; every action still synchronizes fresh workspace bytes.
func NewLSPTool ¶
func NewLSPToolWithRegistry ¶
func (*LSPTool) Schema ¶
func (t *LSPTool) Schema() agentcore.ToolSchema
type Option ¶
type Option func(*DockerSandbox)
Option configures a DockerSandbox.
func WithComputerUseImage ¶
WithComputerUseImage sets the image used for persistent computer-use sessions (a richer toolchain image with python/pandoc/office libraries). Empty leaves computer-use sessions on the default image. One-shot run_shell is unaffected.
func WithDockerBinary ¶
WithDockerBinary overrides the docker CLI path (default "docker").
type ReadFileTool ¶
type ReadFileTool struct {
// contains filtered or unexported fields
}
ReadFileTool reads a workspace file. sb decides where the read happens, not what it produces: the windowing, line numbering and truncation notice below run identically over either substrate.
func NewReadFileTool ¶
func NewReadFileTool(sb agentcore.Sandbox, workspace *Workspace) *ReadFileTool
NewReadFileTool builds read_file over the given sandbox. sb is optional: nil reads the file directly from the host filesystem under the Workspace guards (the default), non-nil reads it from inside the sandbox with the workspace bind-mounted.
func (*ReadFileTool) Name ¶
func (t *ReadFileTool) Name() string
func (*ReadFileTool) Parallel ¶
func (t *ReadFileTool) Parallel() bool
func (*ReadFileTool) Schema ¶
func (t *ReadFileTool) Schema() agentcore.ToolSchema
type SearchProvider ¶
type SearchProvider interface {
// Search runs one query and returns ranked results, best first, at most
// maxResults of them.
Search(ctx context.Context, query string, maxResults int) ([]SearchResult, error)
}
SearchProvider is the pluggable backend behind web_search. The provider is configuration, not code: the registry picks an implementation from the agent's tool config and hands it here, so adding a provider never touches the tool. Implementations must make their own egress safe — the built-in ones dial through NewGuardedClient, which re-checks every resolved IP.
func NewDuckDuckGoSearch ¶
func NewDuckDuckGoSearch(client *http.Client) SearchProvider
NewDuckDuckGoSearch builds the DuckDuckGo provider. client is optional: nil installs the shared SSRF-guarded client (resolved-IP re-check, no redirects followed), which is the only client a production build should ever use.
type SearchResult ¶
SearchResult is one ranked hit: title, URL, and a short snippet.
type ShellTool ¶
type ShellTool struct {
// contains filtered or unexported fields
}
ShellTool lets the agent run a shell command on whichever substrate it was built with. With an injected Sandbox the command runs inside it and sees nothing of the host: no filesystem, no environment, no network unless granted. With no sandbox it falls back to HostSandbox and runs on the host — still with only the declared env visible (a prompt-injected `cat /proc/self/environ` cannot read the server's DB creds or API keys) and still under the timeout, but with the host's filesystem and network. Which substrate is appropriate is the caller's decision; see HostSandbox for what is and is not enforceable there.
func NewComputerUseTool ¶
NewComputerUseTool builds the persistent computer_use shell over sb, with the agent workspace mounted (required, so artifacts persist on the host) and the ComputerUseLimits envelope. sb is optional: nil runs the commands directly on the host machine, where "persistent" needs no session container because the host filesystem already outlives every call. With a sandbox it reuses one session container per conversation, so installs and written files survive across calls — the Claude-Code-level "write code, install a tool, run it, produce a document" loop. networkAllow, when non-empty, confines the session's egress to the listed hosts (and their subdomains) via the sandbox's filtering proxy (#5b). Empty keeps the current open-network behavior. It has no effect on the host substrate, which cannot filter egress.
func NewShellTool ¶
NewShellTool builds a run_shell tool over the given sandbox. sb is optional: nil runs the command directly on the host machine via HostSandbox (the default substrate for an embedded consumer), non-nil runs it inside the sandbox. limits is the per-call isolation envelope (the zero value is fail-closed: no network, read-only fs, default resource caps); on the host substrate only its timeout is enforceable. When ws is non-nil the agent workspace is bind-mounted read-write at shellWorkdir and becomes the command's working directory, so shell commands see the same files as the file tools; when nil the shell runs in an ephemeral, empty scratch dir.
func (*ShellTool) Run ¶
Run is sequential-only (no ParallelTool): a shell command may mutate the session workdir, so concurrent runs are not opted into.
func (*ShellTool) Schema ¶
func (t *ShellTool) Schema() agentcore.ToolSchema
type WebFetchOption ¶
type WebFetchOption func(*WebFetchTool)
func WithWebFetchTimeout ¶
func WithWebFetchTimeout(timeout time.Duration) WebFetchOption
WithWebFetchTimeout sets the host fetch deadline; zero leaves cancellation to the caller.
type WebFetchTool ¶
type WebFetchTool struct {
// contains filtered or unexported fields
}
WebFetchTool fetches an arbitrary public URL and returns its readable text (Claude Code's WebFetch). It is the open-egress counterpart to http_request: where http_request is host-allowlisted for talking to specific APIs, web_fetch is meant for reading the open web, so it has no host allowlist. SSRF is still closed off at the IP layer — the same guarded dialer as http_request re-checks every resolved address (including each redirect hop) and refuses loopback / private / link-local / metadata, so "no allowlist" does not mean "can reach internal services". HTML is reduced to text to keep results small.
With a sandbox injected the fetch is made from inside the container instead. Because the tool has no host allowlist of its own, the container's egress is pinned per call to the requested URL's host (and its subdomains) — an empty egress allowlist would hand the container an open network with no IP guard, which is exactly the SSRF surface this tool exists to close. The visible consequence is that a redirect off that host is refused by the egress proxy rather than followed.
func NewWebFetchTool ¶
func NewWebFetchTool(sb agentcore.Sandbox, ws *Workspace, options ...WebFetchOption) *WebFetchTool
NewWebFetchTool builds the web_fetch tool with the SSRF-guarded dialer installed. sb is optional: nil fetches from this host process (the default), non-nil fetches from inside the sandbox. It follows a bounded number of redirects because every hop is re-validated at connect time — by the dialer on the host path, by the egress proxy on the sandbox path — unlike http_request which cannot (its allowlist can't re-check a redirected host).
ws is optional and enables save_as: with a workspace the fetched document can be written where read_file, grep, and run_shell can reach it, which is what a page too long to read in one context needs.
func (*WebFetchTool) Name ¶
func (t *WebFetchTool) Name() string
func (*WebFetchTool) Parallel ¶
func (t *WebFetchTool) Parallel() bool
func (*WebFetchTool) Schema ¶
func (t *WebFetchTool) Schema() agentcore.ToolSchema
type WebSearchTool ¶
type WebSearchTool struct {
// contains filtered or unexported fields
}
WebSearchTool answers a query with ranked web results (title/url/snippet). It is the discovery counterpart to web_fetch: search finds the pages, web_fetch reads one. There is no host allowlist — the result set is whatever the provider returns — but the provider's own egress rides the same guarded dialer as every other host-side outbound tool, so loopback / private / link-local / metadata addresses are refused at connect time.
Host substrate only, for the same reason as http_request: the default sandbox image has no HTTP client to run the request through.
func NewWebSearchTool ¶
func NewWebSearchTool(p SearchProvider, maxResults int) *WebSearchTool
NewWebSearchTool builds the web_search tool over the given provider. maxResults is the per-call default and ceiling source: 0 uses the package default, anything above the package maximum is clamped.
func (*WebSearchTool) Name ¶
func (t *WebSearchTool) Name() string
func (*WebSearchTool) Parallel ¶
func (t *WebSearchTool) Parallel() bool
func (*WebSearchTool) Schema ¶
func (t *WebSearchTool) Schema() agentcore.ToolSchema
type Workspace ¶
type Workspace struct {
// contains filtered or unexported fields
}
Workspace guards file tools to one host directory. It accepts only relative paths, cleans them, follows the root symlink once, and rejects traversal before any filesystem operation happens.
func NewWorkspace ¶
func WorkspaceFor ¶
func WorkspaceFor(base string, scope WorkspaceScope) (*Workspace, error)
WorkspaceFor returns the workspace for one conversation, creating it if needed. An empty base resolves to DefaultWorkspaceBase, and a pinned folder wins over both.
Every id is reduced to a safe single path component before it is joined, so a hostile or merely careless ConversationID ("../../etc") cannot place the workspace outside the base. The containment is then re-checked on the joined path rather than trusted from the sanitizer, because the cost of being wrong here is an agent with a writable root anywhere on the host.
type WorkspaceScope ¶
type WorkspaceScope struct {
WorkspaceID string
ProjectID string
AgentID string
ConversationID string
// Pinned is a folder the user chose, and it replaces the derived path
// entirely rather than nesting under it.
//
// The derived layout is the right default because it keeps conversations from
// colliding and needs no decision from anyone. But an agent is often pointed
// at work that already exists — a repository, a directory of documents — and
// the answer to "which folder?" is then a folder the user already has, not
// one AgentRay invented. Pinning is that answer: the same directory across
// every conversation with this agent, which is also what makes the agent's
// output somewhere the user can find it.
//
// It must be an absolute path (a leading ~ is expanded). Unlike the derived
// segments it is NOT sanitized into a single component — the whole point is
// that it names a real place — so it is only ever set from an authenticated
// operator's configuration, never from anything the model can write.
Pinned string
// PinDisabled refuses Pinned and falls back to the derived layout.
//
// "The user's call" is only the right default when the user setting the path
// is the person who owns the host. On a hosted, multi-tenant deployment they
// are not: workspace owner/admin is reachable by self-serve signup, so an
// unconfined pin would hand a tenant the host filesystem — read via the file
// tools, and read-write via the bind mount the sandboxed tools take on
// Workspace.Root(). It is re-checked here rather than only where the value is
// saved, because a row written before this existed is still in the database.
PinDisabled bool
}
WorkspaceScope identifies the one conversation a workspace belongs to. Every field is optional; a missing one becomes unnamedSegment, so a partially identified run still gets a usable, stable directory rather than an error.
type WriteFileTool ¶
type WriteFileTool struct {
// contains filtered or unexported fields
}
func NewWriteFileTool ¶
func NewWriteFileTool(sb agentcore.Sandbox, workspace *Workspace) *WriteFileTool
NewWriteFileTool builds write_file over the given sandbox. sb is optional: nil writes directly to the host filesystem under the Workspace guards (the default), non-nil writes from inside the sandbox with the workspace bind-mounted — where the content rides stdin, never argv or env.
func (*WriteFileTool) Name ¶
func (t *WriteFileTool) Name() string
func (*WriteFileTool) Schema ¶
func (t *WriteFileTool) Schema() agentcore.ToolSchema
Source Files
¶
- browser_tool.go
- docker.go
- edit_match.go
- edit_tool.go
- egress.go
- eval_config.go
- eval_javascript_runner.go
- eval_protocol.go
- eval_registry.go
- eval_runner.go
- eval_tool.go
- file_hash.go
- file_tool.go
- host.go
- host_unix.go
- http_tool.go
- httpguard.go
- httpguard_client.go
- httpsandbox.go
- injection.go
- line_edit_tool.go
- lsp_config.go
- lsp_protocol.go
- lsp_registry.go
- lsp_tool.go
- process.go
- save_response.go
- search_ignore.go
- search_tool.go
- shell_tool.go
- webfetch_tool.go
- websearch_tool.go
- workspace.go
- workspacefs.go
- workspacepath.go