fstools

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package fstools implements the correctness- and security-critical filesystem tool bodies of the mecatl kit — Read, Edit, Write, Grep, Glob, and an OPTIONAL Bash — as tool.Tool values executing against an injected tool.Workspace (and, for Bash, an injected tool.CommandRunner). It travels WITH the importable engine module so an external consumer of engine/agent gets these tools — and their enforced invariants — by import, not by re-deriving them:

  • Edit's three invariants: read-before-edit (+ unchanged-since), exact match, and uniqueness-unless-replace_all.
  • Write's read-before-overwrite (Edit invariant #1 for existing files).
  • Read/Grep/Glob output caps with clear truncation markers.
  • Bash's partial-output-preserving timeout/cancel handling and the trailer that always survives the output cap.

These bodies depend only on engine/session + engine/tool (+ stdlib). They never touch the real OS: Read/Edit/Write/Grep/Glob go through the tool.Workspace seam, and Bash goes through the injected tool.CommandRunner — so a consumer picks the FileSystem/Workspace and shell backend. The reference in-memory Workspace is engine/adapter/memfs; the honest no-op is engine/adapter/nofs.

Opt-in / opt-out

The catalog is composed, not fixed. A consumer may:

  • take everything: register All() (Read/Edit/Write/Grep/Glob) via Register, then add NewBashTool() only when a shell is configured;
  • take a subset: register only the tool values it wants;
  • swap a tool by name: register its own Tool under the same Spec().Name in place of one of these;
  • ignore the package entirely and supply its own tools.

Bash is deliberately NOT in All(): it needs a tool.CommandRunner and command execution is optional. A deployment with no shell simply never constructs one.

Recoverable vs harness errors

Each tool parses its session.ToolCall.Args (JSON), runs against the seam, and returns a session.ToolResult. Recoverable, model-addressable failures (a missing argument, a failed Edit invariant, a non-existent file) are returned as an *error* ToolResult via session.NewToolError so the model can read and recover from them; the Go error return is reserved for harness-level faults the model cannot act on.

Every tool carries a documentation-quality ToolSpec.Description (the model's onboarding manual, gauntlet #10) and a correct ReadOnly() value, which drives the agent loop's read-parallel / mutate-serial dispatch (gauntlet #4).

Output cap

MaxOutputBytes (25,000 bytes) mirrors internal/adapter/toolkit.MaxOutputBytes EXACTLY — keep those two byte-identical. It is redefined locally (not imported) because engine/ is its own module and toolkit lives under the host repo's internal/adapter tree, which the engine module must not import. The related domain bound engine/session.MaxToolResultTextBytes (25 KiB = 25,600 bytes) is a slightly LARGER upper bound on any tool-result text block that these tool caps sit under — deliberately not byte-identical.

Index

Constants

View Source
const BashToolName = tool.BashToolName

BashToolName aliases tool.BashToolName, the single authority for the name the Bash tool registers under (the permission evaluator special-cases the literal, so the constant lives in the port package where every implementation — this adapter's AND engine/agent's background-capable BashTool — can import it). Callers probe the catalog for bash enablement by referencing the constant rather than a local literal that could drift on a rename (see internal/adapter/server.Service.capabilities).

View Source
const MaxOutputBytes = 25_000

MaxOutputBytes caps the byte length of a single tool's textual result. It is the fstools output cap; tools append a truncation marker (see truncate) when they trim to it. It mirrors internal/adapter/toolkit.MaxOutputBytes EXACTLY (both 25,000 bytes); the domain block bound engine/session.MaxToolResultTextBytes (25 KiB = 25,600 bytes) is a larger upper bound these caps sit under.

View Source
const TruncationMarker = "\n... [output truncated: exceeded 25000 bytes]"

TruncationMarker is the suffix truncate appends when it trims a body to the byte cap. It is exported so a caller that must reserve room for content AFTER a truncated body (Bash's timeout/cancel trailer, which must survive the cap) can account for the marker's length without hard-coding the literal.

Variables

This section is empty.

Functions

func All

func All() []tool.Tool

All returns the always-available filesystem tools as a fresh slice, in the canonical catalog order. Bash is NOT included: it requires a tool.CommandRunner and is optional — add it separately via NewBashTool when a runner is configured.

func NewBashTool

func NewBashTool() tool.Tool

NewBashTool constructs the Bash tool. The runner is NOT captured here — it is read off the tool.Environment at Execute time (issue #462). The composition root registers the returned tool ONLY when a runner is available for the namespace; without one, the catalog has no Bash and the agent runs shell-less.

func Register

func Register(cat *tool.Catalog) error

Register adds the always-available filesystem tools (everything in All(), i.e. NOT Bash) to cat. It returns the first registration error (e.g. a name collision) encountered, or nil on success. To enable command execution, additionally register NewBashTool(), e.g. cat.MustRegister(fstools.NewBashTool()).

Types

type BashTool

type BashTool struct{}

BashTool runs a shell command via the CommandRunner bound to the tool.Environment it executes against (issue #462). It is statically classified as non-read-only: deciding whether a specific command is read-only is governance's job, not this tool's.

The runner is read off the Environment at Execute time, NOT captured at construction: a bound runner is part of the per-namespace Environment (main session, or a forked child whose runner is bound to the child namespace), so the command's cwd always matches the workspace the tool executes against, never a stale shared parent base. A namespace with no shell (env.CommandRunner == nil) surfaces ErrNoShell honestly rather than aborting. The composition root decides whether to REGISTER a Bash tool at all based on runner availability; a shell-less catalog simply omits Bash.

Residual: this fixes the runner's working DIRECTORY, not Bash's trust model. Unlike path-scoped Edit/Write (confined by os.Root), Bash can still escape its cwd via absolute paths or `cd` — that is inherent to running a shell, the same as in the main session. The fix removes the ACCIDENTAL shared-base mutation (a fork branch's relative-path Bash landing in the parent base), which is what ParallelTool.ReadOnly() / the read-only-share / mutating-fork isolation needs.

func (BashTool) Execute

Execute runs the command, honoring an optional timeout, and returns combined output with the exit code. The runner is read off env; a shell-less namespace (nil runner) surfaces ErrNoShell.

func (BashTool) ReadOnly

func (BashTool) ReadOnly() bool

ReadOnly reports that Bash is statically treated as mutating.

func (BashTool) Spec

func (BashTool) Spec() tool.ToolSpec

Spec returns the model-facing specification of the Bash tool.

type EditTool

type EditTool struct{}

EditTool replaces an exact substring in a file, enforcing the three Edit invariants (read-before-edit, exact-match, uniqueness) against the Workspace read-ledger. It mutates state, so ReadOnly is false.

func (EditTool) Execute

Execute enforces the three Edit invariants and writes the modified file via a conditional replace (ADR 0208).

func (EditTool) ReadOnly

func (EditTool) ReadOnly() bool

ReadOnly reports that Edit mutates state.

func (EditTool) Spec

func (EditTool) Spec() tool.ToolSpec

Spec returns the model-facing specification of the Edit tool.

type GlobTool

type GlobTool struct{}

GlobTool lists files matching a glob pattern. It does not mutate state, so ReadOnly is true.

func (GlobTool) Execute

Execute runs the glob and returns capped, sorted paths.

func (GlobTool) ReadOnly

func (GlobTool) ReadOnly() bool

ReadOnly reports that Glob does not mutate state.

func (GlobTool) Spec

func (GlobTool) Spec() tool.ToolSpec

Spec returns the model-facing specification of the Glob tool.

type GrepTool

type GrepTool struct{}

GrepTool searches file contents for a regular expression. It does not mutate state, so ReadOnly is true.

func (GrepTool) Execute

Execute runs the search and returns capped, formatted matches.

func (GrepTool) ReadOnly

func (GrepTool) ReadOnly() bool

ReadOnly reports that Grep does not mutate state.

func (GrepTool) Spec

func (GrepTool) Spec() tool.ToolSpec

Spec returns the model-facing specification of the Grep tool.

type ReadTool

type ReadTool struct{}

ReadTool reads a file and returns its contents with 1-based line-number prefixes, recording the read in the Workspace ledger so Edit's read-before-edit invariant can later be satisfied.

func (ReadTool) Execute

Execute reads the file, returns it with line-number prefixes, and records the read in the Workspace ledger.

func (ReadTool) ReadOnly

func (ReadTool) ReadOnly() bool

ReadOnly reports that Read does not mutate state.

func (ReadTool) Spec

func (ReadTool) Spec() tool.ToolSpec

Spec returns the model-facing specification of the Read tool.

type WriteTool

type WriteTool struct{}

WriteTool writes a file. Creating a new file needs no prior read; overwriting an existing file requires a read-before-overwrite, mirroring Edit invariant #1. It mutates state, so ReadOnly is false.

func (WriteTool) Execute

Execute writes the file, enforcing read-before-overwrite on existing paths via the version protocol (ADR 0208): a NEW file uses create-only; an EXISTING file requires a recorded version, re-reads the current version, and finishes with a conditional replace against that current version. No unconditional operation is used by the agent-facing Write tool.

func (WriteTool) ReadOnly

func (WriteTool) ReadOnly() bool

ReadOnly reports that Write mutates state.

func (WriteTool) Spec

func (WriteTool) Spec() tool.ToolSpec

Spec returns the model-facing specification of the Write tool.

Jump to

Keyboard shortcuts

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