Documentation
¶
Overview ¶
Package code provides workspace-scoped tools for reading, writing, patching, searching, and executing commands.
These tools are powerful and intentionally not a sandbox. Downstream agents decide which tools to expose and which guardrails, workspace roots, read boundaries, and shell policies to apply.
Package code holds the file-editing tools used by the coder profile. All path-taking tools share a Workspace value that enforces a root boundary — no tool in this package may touch paths outside that root.
Index ¶
- Constants
- Variables
- func ExhaustiveStepStatuses(f func(StepStatus))
- func GitHead(ctx context.Context, workspace string) string
- func NewMemoryPlanStore() *memoryPlanStore
- func PatchExistingPaths(text string) []string
- func PatchPaths(text string) []string
- func UntrackedFiles(ctx context.Context, workspace string) map[string]bool
- func WorktreeDiff(ctx context.Context, workspace, base string, exclude map[string]bool) string
- type ApplyPatchArgs
- type ApplyPatchTool
- type BashArgs
- type BashOption
- type BashOutputArgs
- type BashOutputResult
- type BashOutputTool
- type BashTool
- type ContextSandboxer
- type EditArgs
- type EditFileHLArgs
- type EditFileHLTool
- type EditTool
- type FileMapArgs
- type FileMapResult
- type FileMapTool
- type GlobArgs
- type GlobResult
- type GlobTool
- type GrepArgs
- type GrepHit
- type GrepResult
- type GrepTool
- type HashlineEdit
- type ListProcessesArgs
- type ListProcessesResult
- type ListProcessesTool
- type LsArgs
- type LsResult
- type LsTool
- type OutputSnapshot
- type Plan
- type PlanStep
- type PlanStore
- type ProcessID
- type ProcessInfo
- type ProcessManager
- func (m *ProcessManager) Close(ctx context.Context)
- func (m *ProcessManager) Info(id ProcessID) (ProcessInfo, error)
- func (m *ProcessManager) Kill(id ProcessID, signal syscall.Signal) (int, error)
- func (m *ProcessManager) KillAll(ctx context.Context)
- func (m *ProcessManager) List() []ProcessInfo
- func (m *ProcessManager) Output(id ProcessID, stdoutCursor, stderrCursor uint64, maxLines int) (OutputSnapshot, error)
- func (m *ProcessManager) SetOutputSink(s ProcessOutputSink)
- func (m *ProcessManager) StartProcess(command string) (ProcessID, error)
- func (m *ProcessManager) StartProcessContext(ctx context.Context, command string) (ProcessID, error)
- func (m *ProcessManager) Wait(id ProcessID) error
- type ProcessManagerOption
- func WithMaxAliveProcesses(n int) ProcessManagerOption
- func WithProcessEnv(env map[string]string) ProcessManagerOption
- func WithProcessOutputBuffer(lines int) ProcessManagerOption
- func WithProcessOutputSink(s ProcessOutputSink) ProcessManagerOption
- func WithProcessSandbox(sb Sandboxer) ProcessManagerOption
- func WithReapAfter(d time.Duration) ProcessManagerOption
- type ProcessOutputSink
- type ProcessWorkspace
- type ReadArgs
- type ReadFileHLArgs
- type ReadFileHLTool
- type ReadOption
- type ReadTool
- type RetrieveCodeArgs
- type RetrieveCodeResult
- type RetrieveCodeTool
- type Sandboxer
- type SavePlanAppendArgs
- type SavePlanAppendTool
- type SavePlanArgs
- type SavePlanTool
- type StepStatus
- func (s StepStatus) IsValid() bool
- func (s StepStatus) MarshalBinary() ([]byte, error)
- func (s StepStatus) MarshalJSON() ([]byte, error)
- func (s StepStatus) MarshalText() ([]byte, error)
- func (s StepStatus) MarshalYAML() ([]byte, error)
- func (s *StepStatus) Scan(value any) error
- func (s StepStatus) String() string
- func (s *StepStatus) UnmarshalBinary(by []byte) error
- func (s *StepStatus) UnmarshalJSON(by []byte) error
- func (s *StepStatus) UnmarshalText(by []byte) error
- func (s *StepStatus) UnmarshalYAML(by []byte) error
- func (s StepStatus) Value() (driver.Value, error)
- type StopProcessArgs
- type StopProcessTool
- type UpdatePlanArgs
- type UpdatePlanStepArg
- type UpdatePlanTool
- type Workspace
- func (w Workspace) LockPath(absPath string) func()
- func (w Workspace) MkdirParentInRoot(abs string) error
- func (w Workspace) OSRoot() *os.Root
- func (w Workspace) OpenFileInRoot(abs string, flag int, perm os.FileMode) (*os.File, error)
- func (w Workspace) ReadDirInRoot(abs string) ([]os.DirEntry, error)
- func (w Workspace) ReadDirPath(abs string) ([]os.DirEntry, error)
- func (w Workspace) ReadFileInRoot(abs string) ([]byte, error)
- func (w Workspace) ReadFilePath(abs string) ([]byte, error)
- func (w Workspace) RelToRoot(abs string) (string, error)
- func (w Workspace) RemoveInRoot(abs string) error
- func (w Workspace) Resolve(p string) (string, error)
- func (w Workspace) ResolveForRead(p string, unrestricted bool) (string, error)
- func (w Workspace) Root() string
- func (w Workspace) StatInRoot(abs string) (os.FileInfo, error)
- func (w Workspace) StatPath(abs string) (os.FileInfo, error)
- func (w Workspace) WriteFileInRoot(abs string, data []byte, perm os.FileMode) error
- type WriteAppendArgs
- type WriteAppendTool
- type WriteArgs
- type WriteResult
- type WriteTool
Constants ¶
const ( ToolNameBash tools.ToolName = "bash" ToolNameRead tools.ToolName = "read" ToolNameWrite tools.ToolName = "write" ToolNameWriteAppend tools.ToolName = "write_append" ToolNameEdit tools.ToolName = "edit" ToolNameLs tools.ToolName = "ls" ToolNameGrep tools.ToolName = "grep" ToolNameGlob tools.ToolName = "glob" ToolNameSavePlan tools.ToolName = "save_plan" ToolNameSavePlanAppend tools.ToolName = "save_plan_append" ToolNameApplyPatch tools.ToolName = "apply_patch" ToolNameUpdatePlan tools.ToolName = "update_plan" ToolNameFileMap tools.ToolName = "file_map" ToolNameRetrieveCode tools.ToolName = "retrieve_code" // Long-running process management — paired with bash background=true. ToolNameBashOutput tools.ToolName = "bash_output" ToolNameStopProcess tools.ToolName = "stop_process" ToolNameListProcesses tools.ToolName = "list_processes" )
Tool name constants for the file-editing toolset.
const PlansDir = ".zarlcode/plans"
PlansDir is the workspace-relative directory where save_plan writes markdown plan documents. Hidden under .zarlcode/ alongside the other zarlcode config (skills, agents, prompts) so the workspace root stays uncluttered. Mkdirs lazily on first save.
Variables ¶
var ErrParseStepStatus = errors.New("invalid input provided to parse to StepStatus")
var ErrProcessNotFound = errors.New("processmgr: process not found")
ErrProcessNotFound is returned by Output / Kill / Info when the process_id doesn't match any tracked process.
var ErrTooManyProcesses = errors.New("processmgr: too many live processes")
ErrTooManyProcesses is returned by StartProcess when the live count is already at the configured cap.
var StepStatuses = stepStatusesContainer{ PENDING: StepStatus{ // contains filtered or unexported fields }, INPROGRESS: StepStatus{ // contains filtered or unexported fields }, COMPLETED: StepStatus{ // contains filtered or unexported fields }, }
StepStatuses is a main entry point using the StepStatus type. It it a container for all enum values and provides a convenient way to access all enum values and perform operations, with convenience methods for common use cases.
Functions ¶
func ExhaustiveStepStatuses ¶
func ExhaustiveStepStatuses(f func(StepStatus))
ExhaustiveStepStatuses iterates over all enum values and calls the provided function for each value. This function is useful for performing operations on all valid enum values in a loop.
func GitHead ¶
GitHead returns the workspace's current HEAD commit, or "" when the workspace isn't a git repository. Capture it before a run starts so a HEAD the agent moves mid-run doesn't shift the diff baseline.
func NewMemoryPlanStore ¶
func NewMemoryPlanStore() *memoryPlanStore
NewMemoryPlanStore returns a PlanStore backed by an in-memory value. No locking — the runner serialises tool calls so concurrent SetPlan from multiple tool invocations can't happen.
func PatchExistingPaths ¶ added in v0.10.0
PatchExistingPaths returns only paths that a patch expects to already exist: Update and Delete sources. Add destinations and Move-to destinations are creations and therefore excluded.
func PatchPaths ¶
PatchPaths scans patch text for the file headers it touches and returns workspace-relative paths in declaration order. Add / Update / Delete each contribute their target path; an Update followed by "*** Move to:" contributes both the original and the destination so observability layers can diff both ends.
Returns nil on a patch that doesn't parse — callers treat that as "no observable paths" and skip snapshotting. The full parser inside Execute will surface the syntax error to the model.
Exposed for pkg/agent/diffrecorder: apply_patch can mutate multiple files in a single call, so the single-"path"-argument extractor the rest of the recordable tools share doesn't fit. The recorder calls this to enumerate the files it should snapshot before dispatching apply_patch.
func UntrackedFiles ¶
UntrackedFiles returns the set of untracked paths git reports for the workspace. Capture it before a run and pass it to WorktreeDiff as exclude so pre-existing untracked files don't count as agent changes. Empty map when the workspace isn't a git repository.
func WorktreeDiff ¶
WorktreeDiff returns the unified diff of the workspace against base ("" means HEAD): tracked changes from `git diff base`, plus a synthesized /dev/null diff for each untracked file not in exclude (plain diff omits untracked files). Best-effort by design — it returns "" when nothing changed or git is unavailable, and a failed tracked-file diff doesn't stop untracked capture.
Types ¶
type ApplyPatchArgs ¶
type ApplyPatchArgs struct {
Patch string `json:"patch" doc:"The full patch text including the \x60*** Begin Patch\x60 / \x60*** End Patch\x60 envelope."`
}
ApplyPatchArgs is the typed argument struct ApplyPatchTool.Execute decodes into via tools.DecodeArgs.
type ApplyPatchTool ¶
type ApplyPatchTool struct {
// contains filtered or unexported fields
}
ApplyPatchTool applies a Codex-style "stripped-down diff" patch across multiple files in the workspace. The patch grammar is the envelope-and-hunks format documented at openai/codex/codex-rs/apply-patch/apply_patch_tool_instructions.md — we implement it verbatim so models trained against it (GPT-5 family, Claude) emit patches the parser accepts as-is.
The tool buys two things over the existing edit/write tools:
- Multi-file changes commit atomically — every file change is staged in memory, and the commit phase only fires after every hunk has applied cleanly. A bad hunk halts the whole patch with no half-written files.
- Models that know the format produce far more reliable diffs than they do dictating search/replace strings, especially for overlapping or sequence-sensitive edits.
func NewApplyPatchTool ¶
func NewApplyPatchTool(ws Workspace) *ApplyPatchTool
NewApplyPatchTool returns the unified-diff patch tool bound to ws.
func (*ApplyPatchTool) Definition ¶
func (t *ApplyPatchTool) Definition() tools.ToolSpec
Definition advertises apply_patch with the single patch parameter (the full *** Begin Patch / *** End Patch envelope); Mutates is true because a committed patch adds, updates, deletes, or moves workspace files.
func (*ApplyPatchTool) Execute ¶
func (t *ApplyPatchTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute parses the patch, plans all file mutations, then commits them in one pass. Errors at any stage abort the whole operation.
type BashArgs ¶
type BashArgs struct {
Command string `json:"command" doc:"Shell command for bash -c (or sh -c)."`
TimeoutSeconds int `json:"timeout_seconds,omitempty" doc:"Timeout seconds; max 600. Ignored in background."`
Background bool `json:"background,omitempty" doc:"Start managed background process."`
Description string `json:"description,omitempty" doc:"Short process label."`
}
BashArgs is the typed argument struct BashTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.
type BashOption ¶
type BashOption func(*BashTool)
BashOption tunes BashTool construction. The variadic options pattern is consistent with the rest of pkg/ai/tools.
func WithEnv ¶
func WithEnv(env map[string]string) BashOption
WithEnv appends child-process environment variables to every shell command. Values override the inherited process environment for the spawned shell and its children.
func WithProcessManager ¶
func WithProcessManager(m *ProcessManager) BashOption
WithProcessManager enables managed background processes — the bash tool returns a process_id usable by bash_output / kill_bash / list_processes instead of a raw PID + log path.
func WithSandbox ¶
func WithSandbox(sb Sandboxer) BashOption
WithSandbox confines every foreground command behind sb (background commands go through the ProcessManager, which carries its own sandboxer — wire the same instance to both or they drift). Nil is a no-op so callers can pass through an unset optional.
type BashOutputArgs ¶
type BashOutputArgs struct {
ProcessID ProcessID `json:"process_id" doc:"Process id returned by bash(background=true)."`
StdoutCursor int `json:"stdout_cursor,omitempty" doc:"Last-seen stdout cursor; omit on first call to read from start."`
StderrCursor int `json:"stderr_cursor,omitempty" doc:"Last-seen stderr cursor; omit on first call to read from start."`
MaxLines int `json:"max_lines,omitempty" doc:"Cap returned lines per stream (default 1000, 0 = no cap)."`
Output tools.OutputFormat `` /* 132-byte string literal not displayed */
}
BashOutputArgs is the typed argument struct BashOutputTool.Execute decodes into via tools.DecodeArgs. Cursor and max_lines are integers, not uint64, because JSON numbers + the runner's parameter normalisation prefer int.
type BashOutputResult ¶
type BashOutputResult struct {
Snapshot OutputSnapshot
Output tools.OutputFormat
}
BashOutputResult is bash_output's structured Data: the polled snapshot plus the requested output mode. A consumer renders from Snapshot directly; the model sees String(): labelled sections or the JSON snapshot, per Output.
func (BashOutputResult) String ¶
func (r BashOutputResult) String() string
String renders the model-facing form for the requested output mode. Stdout and stderr are run through tools.RedactSecrets first — the same best-effort scrub the foreground bash path applies — so a backgrounded `printenv` (or any command that prints a token) doesn't bypass redaction on its way into the conversation history.
type BashOutputTool ¶
type BashOutputTool struct {
// contains filtered or unexported fields
}
BashOutputTool reads incremental stdout/stderr from a background process started via bash(background=true). The agent passes the cursor returned by the previous call to avoid re-reading the same content — same pattern as Claude Code's BashOutput, so models already trained on that surface know what to do.
func NewBashOutputTool ¶
func NewBashOutputTool(m *ProcessManager) *BashOutputTool
NewBashOutputTool returns the tool that reads buffered output from a background process managed by m.
func (*BashOutputTool) Definition ¶
func (*BashOutputTool) Definition() tools.ToolSpec
Definition advertises bash_output with process_id (required), stdout/stderr cursors, max_lines, and a labeled|json output enum; polling never mutates.
func (*BashOutputTool) Execute ¶
func (t *BashOutputTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute requires process_id, defaults max_lines to 1000, and reads the incremental snapshot from the process manager at the supplied cursors (negative cursors read from the start; unknown ids are NotFound). The result's String() redacts secrets line by line before the model sees the output.
type BashTool ¶
type BashTool struct {
// contains filtered or unexported fields
}
BashTool runs a shell command with cwd set to the workspace root.
procMgr (optional) routes background=true through a managed process with in-memory output capture so the agent can poll via bash_output and kill via stop_process. When nil, background mode falls back to the legacy "detach + write to log file" path. The zarlcode wires a real ProcessManager via WithProcessManager; other consumers (headless tests) can omit it and keep the simpler log-file behaviour.
func NewBashTool ¶
func NewBashTool(ws Workspace, opts ...BashOption) *BashTool
NewBashTool returns the shell tool rooted at ws. Without a process manager (WithProcessManager), background execution degrades to the legacy detach-and-log path.
func (*BashTool) Definition ¶
Definition advertises bash with command (required), timeout_seconds, background, and description parameters; the spec text documents the 1MB output cap, 300s default / 600s max timeout, background process management, and the pkill -f footgun. Mutates stays false — a shell command is not a tracked file edit and must not count as patch-producing work — but AffectsWorkspace is true: a command can write files or mutate git/env state, so cache-invalidation and plan-first gating treat it as workspace-changing via ChangesWorkspace.
func (*BashTool) Execute ¶
Execute runs the command via /bin/bash -c (falling back to /bin/sh) with cwd at the workspace root. background=true — or a command matching longRunningPatterns, which is auto-backgrounded with a notice — returns immediately with a process handle. Foreground runs cap output at bashMaxOutput (1 MB), enforce the clamped timeout by SIGKILLing the whole process group, strip ANSI, redact secrets, and append the exit code; the ProcessEffect carries timeout/truncation flags.
type ContextSandboxer ¶ added in v0.16.0
ContextSandboxer selects confinement using call-scoped policy such as a spawned task's work mode. Implementations must retain the same composition contract as Sandboxer.
type EditArgs ¶
type EditArgs struct {
Path string `json:"path" doc:"Path inside the workspace."`
OldString string `json:"old_string" doc:"Exact text to replace."`
NewString string `json:"new_string" doc:"Replacement text."`
ReplaceAll bool `json:"replace_all,omitempty" doc:"Replace every occurrence (default false)."`
}
EditArgs is the typed argument struct EditTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.
type EditFileHLArgs ¶
type EditFileHLArgs struct {
Path string `json:"path" doc:"Path inside the workspace."`
Edits []HashlineEdit `json:"edits" doc:"Atomic edits; pass an array even for one."`
}
EditFileHLArgs is the typed argument struct EditFileHLTool.Execute decodes.
type EditFileHLTool ¶
type EditFileHLTool struct {
// contains filtered or unexported fields
}
EditFileHLTool edits a workspace file through the read output's line/hash anchors. Its Definition returns ToolNameEdit — it replaces EditTool in the standard toolset without changing the name the model sees.
func NewEditFileHLTool ¶
func NewEditFileHLTool(ws Workspace) *EditFileHLTool
NewEditFileHLTool returns the hashline edit tool bound to ws.
func (*EditFileHLTool) Definition ¶
func (t *EditFileHLTool) Definition() tools.ToolSpec
Definition advertises edit as a mutating line-anchor edit tool.
func (*EditFileHLTool) Execute ¶
func (t *EditFileHLTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute verifies the requested line/hash anchors against the current file and then performs one byte-level splice at line boundaries. The old file content never has to be reproduced in the arguments; stale anchors are refused before any write occurs.
type EditTool ¶
type EditTool struct {
// contains filtered or unexported fields
}
EditTool performs an exact-string replacement in a workspace file. It is retained for legacy consumers and focused tests; the standard coderunner / zarlcode tool surface now exposes EditFileHLTool under the same `edit` name.
Without replace_all, old_string must appear exactly once — otherwise the edit is rejected to prevent accidental whole-file rewrites.
Whitespace-tolerant fallback ¶
When exact match returns zero hits (and replace_all is off), the tool re-tries on a line-normalised view: per-line trailing whitespace and the \r in CRLF are stripped from both old_string and the file before the search. If exactly one match exists in the normalised view, the corresponding byte range in the *original* file is replaced. The model's exact whitespace inside lines and around the splice point is preserved; the model is informed in the result message so it can see the fuzzy path fired.
Ambiguity is still refused: if normalisation produces multiple hits, the edit is rejected with the count so the model adds more context.
Argument size cap ¶
Both old_string and new_string are capped at maxEditArgBytes (default 64KB, tunable via CODE_EDIT_MAX_BYTES — see limits.go). The cap is generous — modern providers handle 64KB string args cleanly. Historical context: older llama.cpp builds dropped characters inside multi-KB streaming tool-call JSON, which is why this cap exists at all. The cap is tighter than write's because edit always carries two such args (old_string + new_string) and the failure mode is per-arg.
func NewEditTool ¶
NewEditTool returns the legacy exact-string edit tool bound to ws.
func (*EditTool) Definition ¶
Definition advertises the legacy exact-string edit shape: path, old_string, new_string, and replace_all. Mutates is true because a successful edit rewrites the file in place.
func (*EditTool) Execute ¶
Execute caps old_string and new_string at maxEditArgBytes (64KB default) up front, then — holding the path lock across the read-modify-write — requires a unique exact match unless replace_all is set, falling back to a single-hit whitespace-normalised match when the exact search finds nothing. Ambiguity at either stage is refused with the match count; success emits a FileModify effect.
This legacy exact-string path remains useful for narrow consumers, but the standard coding surface prefers EditFileHLTool's anchored workflow.
type FileMapArgs ¶ added in v0.2.1
type FileMapArgs struct {
// Root scopes the walk to a subtree. Empty means the workspace root.
Root string `json:"root,omitempty" doc:"Optional sub-tree to scan, relative to the workspace. Empty = workspace root."`
// Pattern selects files under Root using the same doublestar glob semantics as glob. Empty defaults to *.go.
Pattern string `` /* 127-byte string literal not displayed */
// IncludeTests includes *_test.go files. Default false.
IncludeTests bool `json:"include_tests,omitempty" doc:"Include Go test files (*_test.go). Default false."`
// MaxFiles caps the number of files parsed. Default 200.
MaxFiles int `json:"max_files,omitempty" doc:"Cap on parsed files. Default 200."`
// Output selects labelled plaintext or JSON. Empty means labelled.
Output tools.OutputFormat `json:"output,omitempty" enum:"labeled,json" doc:"Output format: \"labeled\" (default) or \"json\"."`
}
FileMapArgs configures the syntax-aware file map.
type FileMapResult ¶ added in v0.2.1
type FileMapResult struct {
Payload fileMapPayload
Output tools.OutputFormat
}
FileMapResult is file_map's structured result and model-facing renderer.
func (FileMapResult) String ¶ added in v0.2.1
func (r FileMapResult) String() string
String renders either compact labelled text or JSON.
type FileMapTool ¶ added in v0.2.1
type FileMapTool struct {
// contains filtered or unexported fields
}
FileMapTool builds a deterministic, syntax-aware outline for source files.
func NewFileMapTool ¶ added in v0.2.1
func NewFileMapTool(ws Workspace, opts ...ReadOption) *FileMapTool
NewFileMapTool returns a deterministic source-outline tool bound to ws.
func (*FileMapTool) Definition ¶ added in v0.2.1
func (t *FileMapTool) Definition() tools.ToolSpec
Definition advertises file_map as a read-only AST outline tool.
func (*FileMapTool) Execute ¶ added in v0.2.1
func (t *FileMapTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute parses matching files and returns their deterministic outlines.
type GlobArgs ¶
type GlobArgs struct {
// Pattern is the doublestar-flavoured glob. See type docs for
// semantics. Required.
Pattern string `` /* 193-byte string literal not displayed */
// Root scopes the walk to a subtree (workspace-relative). Empty
// means the whole workspace.
Root string `json:"root,omitempty" doc:"Optional sub-tree to scope the walk to (workspace-relative). Empty = whole workspace."`
// IncludeDirs adds directory entries to the result. Default
// false — most callers want files only.
IncludeDirs bool `json:"include_dirs,omitempty" doc:"Include directory entries in results. Default false (files only)."`
// MaxResults caps the returned list. Default 200; a workspace
// of 50k files would otherwise produce a multi-MB result.
MaxResults int `` /* 130-byte string literal not displayed */
// Output selects the model-facing rendering. Empty = labelled.
Output tools.OutputFormat `` /* 129-byte string literal not displayed */
}
GlobArgs is the typed argument struct shared by both glob impls. Field tags drive both JSON decoding and SchemaFor schema generation.
type GlobResult ¶
type GlobResult struct {
Entries []globEntry
Pattern string
Root string
Truncated bool
MaxResults int
Output tools.OutputFormat
}
GlobResult is glob's structured Data: the matched entries plus the call inputs needed to render. A consumer (the TUI) renders from Entries directly instead of re-parsing a string, while the model sees String(): labelled plaintext or the JSON payload, per the requested Output.
func (GlobResult) String ¶
func (r GlobResult) String() string
String renders the model-facing form for the requested output mode.
type GlobTool ¶
type GlobTool struct {
// contains filtered or unexported fields
}
GlobTool enumerates workspace paths matching a glob pattern. The output field selects the model-facing rendering (labelled plaintext, the default, or JSON); the structured entries are identical either way.
Labelled output shape:
matches: 3 pattern: *.go main.go 142B pkg/foo/foo.go 256B
JSON output shape:
{
"pattern": "*.go",
"matches": 3,
"truncated": false,
"entries": [
{"path": "main.go", "size": 142, "dir": false},
{"path": "pkg/foo/foo.go", "size": 256, "dir": false}
]
}
Pattern semantics (matched by doublestar.Match):
*.go — match files anywhere in the tree whose basename ends in .go **/*.go — same, but explicit (the more common spelling) pkg/**/*.go — go files anywhere under pkg/ pkg/agent/* — direct children of pkg/agent (single segment) **/main.go — every main.go in the tree
A bare basename pattern (no path separator) matches anywhere recursively. A pattern containing `/` is rooted against the workspace (or the `root` arg) and respects path structure literally.
func NewGlobTool ¶
func NewGlobTool(ws Workspace, opts ...ReadOption) *GlobTool
NewGlobTool returns a JSON glob tool bound to the given workspace.
func (*GlobTool) Definition ¶
Definition advertises glob with pattern (required), root, include_dirs, max_results (default 200), and a labeled|json output enum; enumeration never mutates.
type GrepArgs ¶
type GrepArgs struct {
Pattern string `json:"pattern" doc:"Regular expression."`
Path string `json:"path,omitempty" doc:"Subpath inside workspace (default = workspace root)."`
Glob string `json:"glob,omitempty" doc:"Glob filter (e.g. *.go)."`
CaseInsensitive bool `json:"case_insensitive,omitempty" doc:"Case-insensitive match."`
MaxResults int `json:"max_results,omitempty" doc:"Cap on returned matches (default 100)."`
Output tools.OutputFormat `json:"output,omitempty" enum:"labeled,json" doc:"Output format: \"labeled\" (default, hits grouped by file) or \"json\"."`
}
GrepArgs is the typed argument struct GrepTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.
type GrepHit ¶
GrepHit is one match in a grep result: the file, line number, and the matched line's text.
type GrepResult ¶
type GrepResult struct {
Hits []GrepHit
Truncated bool
Output tools.OutputFormat
Pattern string
Path string
Glob string
MaxResults int
}
GrepResult is grep's structured Data: the matches, whether the max-results cap truncated them, and the call inputs needed to render. It is the tool's re-parsing a string, while the model sees String(): labelled plaintext or a JSON object, per the requested Output.
func (GrepResult) String ¶
func (r GrepResult) String() string
String renders the model-facing form for the requested output mode.
type GrepTool ¶
type GrepTool struct {
// contains filtered or unexported fields
}
GrepTool wraps ripgrep and returns structured matches. The output field selects the model-facing rendering (labelled plaintext or JSON); the structured hits are identical either way.
func NewGrepTool ¶
func NewGrepTool(ws Workspace, opts ...ReadOption) *GrepTool
NewGrepTool returns the content-search tool bound to ws.
func (*GrepTool) Definition ¶
Definition advertises grep with pattern (required), path, glob, case_insensitive, max_results (default 100), and a labeled|json output enum; searching never mutates.
type HashlineEdit ¶ added in v0.3.0
type HashlineEdit struct {
StartLine int `json:"start_line" doc:"1-based line from read."`
StartHash string `json:"start_hash" doc:"3/4-char hash from read."`
EndLine int `json:"end_line,omitempty" doc:"Inclusive range end; omit for one line."`
EndHash string `json:"end_hash,omitempty" doc:"Hash for end_line."`
NewString string `json:"new_string,omitempty" doc:"Replacement/insertion bytes, including intended newlines."`
Mode string `` /* 135-byte string literal not displayed */
}
HashlineEdit describes one edit inside EditFileHLArgs.Edits.
type ListProcessesArgs ¶
type ListProcessesArgs struct {
Output tools.OutputFormat `json:"output,omitempty" enum:"labeled,json" doc:"Output format: \"labeled\" (default, one block per process) or \"json\"."`
}
ListProcessesArgs carries only the output-format choice — the tool otherwise takes no arguments.
type ListProcessesResult ¶
type ListProcessesResult struct {
Procs []ProcessInfo
Output tools.OutputFormat
}
ListProcessesResult is list_processes's structured Data: the process snapshot plus the requested output mode. A consumer renders from Procs directly; the model sees String(): labelled blocks or the JSON list, per Output.
func (ListProcessesResult) String ¶
func (r ListProcessesResult) String() string
String renders the model-facing form for the requested output mode.
type ListProcessesTool ¶
type ListProcessesTool struct {
// contains filtered or unexported fields
}
ListProcessesTool returns a snapshot of every tracked process — live processes plus those that exited recently enough to still be in the reap window. The agent uses this to discover process_ids it forgot, audit what's running before spawning more work, and reconcile state after a long pause.
func NewListProcessesTool ¶
func NewListProcessesTool(m *ProcessManager) *ListProcessesTool
NewListProcessesTool returns the tool that enumerates the background processes managed by m.
func (*ListProcessesTool) Definition ¶
func (*ListProcessesTool) Definition() tools.ToolSpec
Definition advertises list_processes whose only parameter is the labeled|json output enum; listing never mutates.
func (*ListProcessesTool) Execute ¶
func (t *ListProcessesTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute snapshots every tracked process from the manager — running plus recently exited — and returns a ListProcessesResult rendered per the requested output format; nothing past argument decoding can fail.
type LsArgs ¶
type LsArgs struct {
Path string `json:"path,omitempty" doc:"Directory inside workspace (default = root)."`
ShowHidden bool `json:"show_hidden,omitempty" doc:"Include dotfiles."`
Output tools.OutputFormat `` /* 138-byte string literal not displayed */
}
LsArgs is the typed argument struct LsTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.
type LsResult ¶
type LsResult struct {
Entries []lsEntry
Path string
ShowHidden bool
Output tools.OutputFormat
}
LsResult is ls's structured Data: the directory entries plus the call inputs needed to render. A consumer (the TUI) renders from Entries directly instead of re-parsing a string, while the model sees String(): labelled plaintext or a JSON array, per the requested Output.
type LsTool ¶
type LsTool struct {
// contains filtered or unexported fields
}
LsTool lists workspace directory entries. Hidden entries (starting with ".") are excluded by default. The output field selects the model-facing rendering (labelled plaintext or JSON); the structured entries are identical either way.
func NewLsTool ¶
func NewLsTool(ws Workspace, opts ...ReadOption) *LsTool
NewLsTool returns the directory-listing tool bound to ws.
func (*LsTool) Definition ¶
Definition advertises ls with optional path, show_hidden, and a labeled|json output enum; listing never mutates.
type OutputSnapshot ¶
type OutputSnapshot struct {
ID ProcessID `json:"process_id"`
Running bool `json:"running"`
ExitCode *int `json:"exit_code,omitempty"`
Stdout []string `json:"stdout"`
Stderr []string `json:"stderr"`
StdoutCursor uint64 `json:"stdout_cursor"`
StderrCursor uint64 `json:"stderr_cursor"`
StdoutDroppedSince uint64 `json:"stdout_dropped_since,omitempty"`
StderrDroppedSince uint64 `json:"stderr_dropped_since,omitempty"`
}
OutputSnapshot is the result of one bash_output call: incremental stdout/stderr since the supplied cursor plus a fresh cursor for the next poll. Dropped counters non-zero mean lines rotated out of the ring buffer between reads — the agent uses this signal to decide whether to throttle output (or accept a partial view).
type Plan ¶
type Plan struct {
Steps []PlanStep `json:"steps"`
Explanation string `json:"explanation,omitempty"`
}
Plan is the structured state mutated by update_plan and rendered in the plan dock tab. Explanation is the optional one-liner the model sends with each update (e.g. "split step 2 into two steps") — surfaced in the pane so the user can see why the plan changed.
type PlanStep ¶
type PlanStep struct {
Text string `json:"step"`
Status StepStatus `json:"status"`
}
PlanStep is one entry in the ordered plan. Text is the human-readable description; Status is the lifecycle marker. Steps have no stable identity beyond their position — update_plan replaces the whole list, so callers don't need to thread IDs.
type PlanStore ¶
type PlanStore interface {
// SetPlan replaces the current plan with p. The store is expected
// to broadcast the change to any listeners (typically the TUI)
// before returning, so the user sees the new state immediately
// after the tool call completes.
SetPlan(p Plan)
// GetPlan returns the current plan (zero-value Plan when none).
GetPlan() Plan
}
PlanStore is the dependency seam between the update_plan tool and whatever process-wide state owns the live plan. Tools are wired in at registration with a concrete *shellModel implementation; tests use an in-memory fake.
Defined consumer-side here (not in zkit/agent/runner or zarlcode/tui) because the tool is the only direct caller. Other consumers read state via the same interface from wherever they happen to live.
type ProcessID ¶ added in v0.3.1
type ProcessID string
ProcessID identifies a managed background process.
type ProcessInfo ¶
type ProcessInfo struct {
ID ProcessID `json:"process_id"`
Command string `json:"command"`
PID int `json:"pid"`
CWD string `json:"cwd"`
StartedAt time.Time `json:"started_at"`
Running bool `json:"running"`
ExitedAt time.Time `json:"exited_at"`
ExitCode int `json:"exit_code,omitempty"`
StdoutLines int `json:"stdout_lines"`
StderrLines int `json:"stderr_lines"`
}
ProcessInfo is the public snapshot of a managed process. Returned by List and embedded in tool-result payloads.
type ProcessManager ¶
type ProcessManager struct {
// contains filtered or unexported fields
}
func NewProcessManager ¶
func NewProcessManager(ws ProcessWorkspace, opts ...ProcessManagerOption) *ProcessManager
NewProcessManager constructs a manager bound to a workspace. Apply options to tune caps + reap window; defaults are tuned for the zarlcode use case (16 concurrent processes, 10k lines per stream, 60s post-exit retention).
func (*ProcessManager) Close ¶
func (m *ProcessManager) Close(ctx context.Context)
Close terminates every live process (with the same bounded escalation as KillAll) AND tears down the background reaper goroutine. Idempotent — safe to call multiple times. Use this instead of bare KillAll when the manager itself should not outlive the call (almost always the right answer on shell exit).
The reaper goroutine receives on a close channel; after Close returns its `reapDone` channel is closed too, so callers can observe full teardown by selecting on m.ReaperDone() if needed.
func (*ProcessManager) Info ¶
func (m *ProcessManager) Info(id ProcessID) (ProcessInfo, error)
Info returns a single process's snapshot. Useful for /processes detail views; the tool layer uses List().
func (*ProcessManager) Kill ¶
Kill sends signal to the process. SIGTERM (the default) gives the process 5s to exit cleanly before escalating to SIGKILL. SIGINT applies a similar two-stage escalation. SIGKILL is immediate. Returns the eventual exit code (or -1 if the process didn't exit in time).
func (*ProcessManager) KillAll ¶
func (m *ProcessManager) KillAll(ctx context.Context)
KillAll terminates every live process and waits for them to exit, bounded by the supplied context. Use on shell shutdown so orphan processes don't leak past the TUI quit.
func (*ProcessManager) List ¶
func (m *ProcessManager) List() []ProcessInfo
List returns a snapshot of all tracked processes (live + recently exited still in the reap window). Ordered newest-first so the most relevant process is at the top of the agent's view.
func (*ProcessManager) Output ¶
func (m *ProcessManager) Output(id ProcessID, stdoutCursor, stderrCursor uint64, maxLines int) (OutputSnapshot, error)
Output returns incremental stdout/stderr for the named process. stdoutCursor / stderrCursor come from a prior call's snapshot; pass 0 on the first call to read from the start. maxLines caps the return per stream (0 = no cap).
func (*ProcessManager) SetOutputSink ¶ added in v0.11.1
func (m *ProcessManager) SetOutputSink(s ProcessOutputSink)
SetOutputSink installs (or clears) the process-exit output callback after construction. The app wires this once both the manager and the sink that persists its output exist. Set before starting background processes.
func (*ProcessManager) StartProcess ¶
func (m *ProcessManager) StartProcess(command string) (ProcessID, error)
StartProcess spawns the command in the workspace root, captures stdout/stderr into ring buffers, and returns the assigned process_id. The process is detached from ctx — once started it outlives the originating tool call. The manager retains a reference until reapAfter elapses past its exit.
command runs through /bin/bash -c (falling back to /bin/sh) to match the synchronous bash tool's semantics — same env, same shell, same setsid isolation.
func (*ProcessManager) StartProcessContext ¶ added in v0.16.0
func (m *ProcessManager) StartProcessContext(ctx context.Context, command string) (ProcessID, error)
StartProcessContext is StartProcess with call-scoped sandbox policy. The context is consulted while preparing confinement but does not own the detached process lifetime; ProcessManager.Close/Kill remain the owners.
func (*ProcessManager) Wait ¶ added in v0.11.0
func (m *ProcessManager) Wait(id ProcessID) error
Wait blocks until the managed process has exited and its output pipes have drained.
type ProcessManagerOption ¶
type ProcessManagerOption func(*ProcessManager)
ProcessManagerOption tunes the manager at construction.
func WithMaxAliveProcesses ¶
func WithMaxAliveProcesses(n int) ProcessManagerOption
WithMaxAliveProcesses caps concurrent live processes. Hit it and StartProcess returns ErrTooManyProcesses without forking anything.
func WithProcessEnv ¶
func WithProcessEnv(env map[string]string) ProcessManagerOption
WithProcessEnv appends child-process environment variables to every managed background shell command. Values override the inherited process environment.
func WithProcessOutputBuffer ¶
func WithProcessOutputBuffer(lines int) ProcessManagerOption
WithProcessOutputBuffer sets the per-stream ring buffer cap (in lines). Lower values reduce memory pressure on chatty processes; higher values let the agent inspect more history.
func WithProcessOutputSink ¶ added in v0.11.1
func WithProcessOutputSink(s ProcessOutputSink) ProcessManagerOption
WithProcessOutputSink installs a callback that receives a background process's full accumulated stdout/stderr when it exits.
func WithProcessSandbox ¶
func WithProcessSandbox(sb Sandboxer) ProcessManagerOption
WithProcessSandbox confines every background process behind sb — the same instance the bash tool gets via WithSandbox, so foreground and background commands run under one policy. Nil is a no-op.
func WithReapAfter ¶
func WithReapAfter(d time.Duration) ProcessManagerOption
WithReapAfter sets how long an exited process stays in the manager for post-mortem inspection. Default 60s.
type ProcessOutputSink ¶ added in v0.11.1
ProcessOutputSink receives a background process's full accumulated output when it exits. Runs on the process's reaper goroutine; implementations should be fast and must not block indefinitely.
type ProcessWorkspace ¶
type ProcessWorkspace interface {
Root() string
}
ProcessWorkspace is the minimum surface from code.Workspace the manager needs. Defined locally so consumers can pass a fake in tests without pulling the whole workspace package.
type ReadArgs ¶
type ReadArgs struct {
Path string `json:"path" doc:"Path relative to the workspace root (or absolute, must be inside root)."`
Offset int `json:"offset,omitempty" doc:"Zero-based line offset to start reading from."`
Limit int `json:"limit,omitempty" doc:"Maximum number of lines to return (default 2000)."`
}
ReadArgs is the typed argument struct ReadTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.
type ReadFileHLArgs ¶
type ReadFileHLArgs struct {
Path string `json:"path" doc:"Path relative to the workspace root (or absolute, must be inside root)."`
Offset int `json:"offset,omitempty" doc:"Zero-based line offset to start reading from."`
Limit int `json:"limit,omitempty" doc:"Maximum number of lines to return (default 2000)."`
HashLen int `json:"hash_len,omitempty" doc:"Hash prefix length: 3 or 4 base64 SHA-256 characters (default 4)."`
}
ReadFileHLArgs is the typed argument struct ReadFileHLTool.Execute decodes.
type ReadFileHLTool ¶
type ReadFileHLTool struct {
// contains filtered or unexported fields
}
ReadFileHLTool reads a file with line-number + hash anchors for the edit tool. Its Definition returns ToolNameRead — it replaces ReadTool in the standard toolset without changing the name the model sees.
func NewReadFileHLTool ¶
func NewReadFileHLTool(ws Workspace, opts ...ReadOption) *ReadFileHLTool
NewReadFileHLTool returns the hashline read tool bound to ws.
func (*ReadFileHLTool) Definition ¶
func (t *ReadFileHLTool) Definition() tools.ToolSpec
Definition advertises read with path, offset, limit, and hash_len.
func (*ReadFileHLTool) Execute ¶
func (t *ReadFileHLTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute returns line-oriented file content as LINE:HASH|text rows. Hashes are computed over displayed line content only; LF and CRLF terminators are not included, while other whitespace is.
type ReadOption ¶ added in v0.1.3
type ReadOption func(*readPolicy)
ReadOption tunes read-side tools (read, hashline read, ls, grep, glob).
func WithUnrestrictedReads ¶ added in v0.1.3
func WithUnrestrictedReads() ReadOption
WithUnrestrictedReads allows read-side tools to access paths outside the workspace root. Mutating tools ignore this policy and remain workspace-bound.
type ReadTool ¶
type ReadTool struct {
// contains filtered or unexported fields
}
ReadTool reads a file from the workspace and returns line-numbered content.
func NewReadTool ¶
func NewReadTool(ws Workspace, opts ...ReadOption) *ReadTool
NewReadTool returns the file-reading tool bound to ws.
func (*ReadTool) Definition ¶
Definition advertises read with path (required), offset, and limit parameters; reads never mutate, so Mutates stays false.
func (*ReadTool) Execute ¶
Execute resolves the path inside the workspace (or anywhere on the host when unrestricted reads are enabled), refuses files over readMaxBytes (10 MB) before reading, rejects content with a NUL byte in the first 8 KB as binary, and returns 1-based line-numbered output starting at offset (negative offsets clamp to 0; default limit readDefaultLimit = 2000 lines, with a truncation footer when more lines remain).
type RetrieveCodeArgs ¶ added in v0.2.1
type RetrieveCodeArgs struct {
// Query is tokenized and matched against syntax chunks. Required.
Query string `json:"query" doc:"Search query. Tokens are matched deterministically against paths, symbol metadata, and chunk text."`
// Root scopes the scan to a subtree. Empty means the workspace root.
Root string `json:"root,omitempty" doc:"Optional sub-tree to scan, relative to the workspace. Empty = workspace root."`
// Pattern selects files under Root using glob semantics. Empty defaults to *.go.
Pattern string `json:"pattern,omitempty" doc:"Glob pattern for files to retrieve from. Empty defaults to *.go."`
// IncludeTests includes *_test.go files. Default false.
IncludeTests bool `json:"include_tests,omitempty" doc:"Include Go test files (*_test.go). Default false."`
// Limit caps returned chunks. Default 8.
Limit int `json:"limit,omitempty" doc:"Maximum chunks to return. Default 8."`
// MaxFiles caps the number of files parsed. Default 500.
MaxFiles int `json:"max_files,omitempty" doc:"Cap on parsed files. Default 500."`
// MaxBytesPerChunk caps rendered source text per chunk. Default 12000.
MaxBytesPerChunk int `json:"max_bytes_per_chunk,omitempty" doc:"Cap rendered source bytes per chunk. Default 12000."`
// Output selects labelled plaintext or JSON. Empty means labelled.
Output tools.OutputFormat `json:"output,omitempty" enum:"labeled,json" doc:"Output format: \"labeled\" (default) or \"json\"."`
}
RetrieveCodeArgs configures deterministic code retrieval.
type RetrieveCodeResult ¶ added in v0.2.1
type RetrieveCodeResult struct {
Payload retrieveCodePayload
Output tools.OutputFormat
}
RetrieveCodeResult is retrieve_code's structured result and renderer.
func (RetrieveCodeResult) Paths ¶ added in v0.9.0
func (r RetrieveCodeResult) Paths() []string
Paths returns the workspace paths represented by the retrieved chunks in result order, with duplicates removed. The returned slice is independent of the result and may be mutated by the caller.
func (RetrieveCodeResult) String ¶ added in v0.2.1
func (r RetrieveCodeResult) String() string
String renders labelled text by default or JSON when requested.
type RetrieveCodeTool ¶ added in v0.2.1
type RetrieveCodeTool struct {
// contains filtered or unexported fields
}
RetrieveCodeTool performs deterministic lexical retrieval over syntax chunks.
func NewRetrieveCodeTool ¶ added in v0.2.1
func NewRetrieveCodeTool(ws Workspace, opts ...ReadOption) *RetrieveCodeTool
NewRetrieveCodeTool returns a deterministic retrieval tool bound to ws.
func (*RetrieveCodeTool) Definition ¶ added in v0.2.1
func (t *RetrieveCodeTool) Definition() tools.ToolSpec
Definition advertises retrieve_code as a read-only deterministic retrieval tool.
func (*RetrieveCodeTool) Execute ¶ added in v0.2.1
func (t *RetrieveCodeTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute scans files, syntax-chunks them, ranks chunks deterministically, and returns the top matches.
type Sandboxer ¶
Sandboxer hardens a fully-prepared command just before it starts — the implementation may rewrite argv (re-exec shims), adjust SysProcAttr (namespaces), or both. Defined here, consumer-side: the bash tool and the process manager are the only spawners, and they only need this one method. The concrete implementation lives in zkit/agent/sandbox; anything satisfying the shape works (tests use in-package fakes).
A Sandboxer must compose with the spawner's own setup: it is called after Dir, Env, stdio, and SysProcAttr (Setsid) are in place, and must mutate rather than replace what's already there.
type SavePlanAppendArgs ¶
type SavePlanAppendArgs struct {
Name string `` /* 255-byte string literal not displayed */
Content string `json:"content" doc:"Bytes to append. Keep each call under the configured cap (256KB default)."`
}
SavePlanAppendArgs is the typed argument struct SavePlanAppendTool.Execute decodes into via tools.DecodeArgs.
type SavePlanAppendTool ¶
type SavePlanAppendTool struct {
// contains filtered or unexported fields
}
SavePlanAppendTool appends content to a plan file under .zarlcode/plans/<name>.md. It is the fallback path for plans larger than the SavePlanTool one-shot cap, and mirrors the WriteTool → WriteAppendTool relationship — same scaffold-then- chunk recipe, narrowed to the plans directory so the read-only invariant of plan mode is preserved.
Why a dedicated append for plans (not just write_append) ¶
write_append's path is unconstrained inside the workspace, so plan mode can't expose it without re-opening the "models can use the carve-out to mutate arbitrary files" door save_plan was designed to close. save_plan_append's path is locked the same way save_plan's is: same slug regex, same join under PlansDir, same rejection of slashes / dots / traversal.
Usage pattern when a plan exceeds [maxWriteContentBytes]:
save_plan(name, "") // empty scaffold save_plan_append(name, "<up to ~256KB chunk>") // chunk 1 save_plan_append(name, "<up to ~256KB chunk>") // chunk 2 ... etc
No state is tracked between calls — the tool is stateless and just opens the file in append mode each time. The running file size is reported back in the success message so the model can sanity-check progress without re-reading the file.
func NewSavePlanAppendTool ¶
func NewSavePlanAppendTool(ws Workspace) *SavePlanAppendTool
NewSavePlanAppendTool returns the tool that appends steps to an existing plan artifact in ws.
func (*SavePlanAppendTool) Definition ¶
func (t *SavePlanAppendTool) Definition() tools.ToolSpec
Definition advertises save_plan_append with name (required slug) and content; like save_plan it leaves Mutates unset — appends land only under the plans directory.
func (*SavePlanAppendTool) Execute ¶
func (t *SavePlanAppendTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute requires both name and content, caps each chunk at maxAppendContentBytes (256KB default), enforces the same safePlanName slug rules as save_plan, then appends to .zarlcode/plans/<name>.md under the path lock — creating the parent directory and file when the scaffold step was skipped. Success reports the running file size.
type SavePlanArgs ¶
type SavePlanArgs struct {
Name string `` /* 228-byte string literal not displayed */
Content string `json:"content" doc:"Full markdown body of the plan."`
}
SavePlanArgs is the typed argument struct SavePlanTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.
type SavePlanTool ¶
type SavePlanTool struct {
// contains filtered or unexported fields
}
SavePlanTool persists a plan-mode markdown artifact to <workspace>/.zarlcode/plans/<name>.md.
Why a dedicated tool, not the generic write ¶
Plan mode strips write/edit/bash from the model's tool surface so the read-only invariant is unbreakable. But "produce a plan" is pointless if the plan can only live in the transcript — once the session compacts or the user closes the shell, it's gone.
save_plan is the carve-out: a single, narrow, path-locked write the model can call from plan mode to drop the plan as a real markdown file. Writes outside PlansDir are rejected at the tool layer; the model cannot use save_plan as a back-door to modify arbitrary files.
The tool is also available in build mode (registered unconditionally at startup) so a build-mode agent can save a retrospective or post-mortem in the same convention.
func NewSavePlanTool ¶
func NewSavePlanTool(ws Workspace) *SavePlanTool
NewSavePlanTool returns the tool that writes a structured plan artifact into ws.
func (*SavePlanTool) Definition ¶
func (t *SavePlanTool) Definition() tools.ToolSpec
Definition advertises save_plan with an optional name slug and required content; Mutates is left unset even though it writes a file — the write is confined to the plans directory.
func (*SavePlanTool) Execute ¶
func (t *SavePlanTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute requires content and caps it at maxWriteContentBytes (shared with write, 256KB default — oversized content gets a scaffold-plus-save_plan_append recipe), defaults an empty name to a plan-YYYYMMDD-HHMM timestamp slug, enforces the safePlanName regex, and writes <name>.md under PlansDir while holding the path lock.
type StepStatus ¶
type StepStatus struct {
// contains filtered or unexported fields
}
StepStatus is a type that represents a single enum value. It combines the core information about the enum constant and it's defined fields.
func ParseStepStatus ¶
func ParseStepStatus(input any) (StepStatus, error)
ParseStepStatus parses the input value into an enum value. It returns the parsed enum value or an error if the input is invalid. It is a convenience function that can be used to parse enum values from various input types, such as strings, byte slices, or other enum types.
func (StepStatus) IsValid ¶
func (s StepStatus) IsValid() bool
IsValid checks whether the StepStatuses value is valid. A valid value is one that is defined in the original enum and not marked as invalid.
func (StepStatus) MarshalBinary ¶
func (s StepStatus) MarshalBinary() ([]byte, error)
MarshalBinary implements the encoding.BinaryMarshaler interface for StepStatus. It returns the binary representation of the enum value as a byte slice.
func (StepStatus) MarshalJSON ¶
func (s StepStatus) MarshalJSON() ([]byte, error)
MarshalJSON implements the json.Marshaler interface for StepStatus. It returns the JSON representation of the enum value as a byte slice.
func (StepStatus) MarshalText ¶
func (s StepStatus) MarshalText() ([]byte, error)
MarshalText implements the encoding.TextMarshaler interface for StepStatus. It returns the string representation of the enum value as a byte slice
func (StepStatus) MarshalYAML ¶
func (s StepStatus) MarshalYAML() ([]byte, error)
MarshalYAML implements the yaml.Marshaler interface for StepStatus. It returns the string representation of the enum value.
func (*StepStatus) Scan ¶
func (s *StepStatus) Scan(value any) error
Scan implements the database/sql.Scanner interface for StepStatus. It parses the string representation of the enum value from the database row. It returns an error if the row does not contain a valid enum value.
func (StepStatus) String ¶
func (s StepStatus) String() string
String implements the Stringer interface. It returns the canonical absolute name of the enum value.
func (*StepStatus) UnmarshalBinary ¶
func (s *StepStatus) UnmarshalBinary(by []byte) error
UnmarshalBinary implements the encoding.BinaryUnmarshaler interface for StepStatus. It parses the binary representation of the enum value from the byte slice. It returns an error if the byte slice does not contain a valid enum value.
func (*StepStatus) UnmarshalJSON ¶
func (s *StepStatus) UnmarshalJSON(by []byte) error
UnmarshalJSON implements the json.Unmarshaler interface for StepStatus. It parses the JSON representation of the enum value from the byte slice. It returns an error if the input is not a valid JSON representation.
func (*StepStatus) UnmarshalText ¶
func (s *StepStatus) UnmarshalText(by []byte) error
UnmarshalText implements the encoding.TextUnmarshaler interface for StepStatus. It parses the string representation of the enum value from the byte slice. It returns an error if the byte slice does not contain a valid enum value.
func (*StepStatus) UnmarshalYAML ¶
func (s *StepStatus) UnmarshalYAML(by []byte) error
UnmarshalYAML implements the yaml.Unmarshaler interface for Planet. It parses the byte slice representation of the enum value and returns an error if the YAML byte slice does not contain a valid enum value.
type StopProcessArgs ¶
type StopProcessArgs struct {
ProcessID ProcessID `json:"process_id" doc:"Process id returned by bash(background=true)."`
Signal string `` /* 153-byte string literal not displayed */
}
StopProcessArgs is the typed argument struct StopProcessTool.Execute decodes into via tools.DecodeArgs. Signal stays a string and is matched against TERM/KILL/INT inside Execute (the JSON Schema declares the enum; the SchemaGuardrail validates membership before dispatch).
type StopProcessTool ¶
type StopProcessTool struct {
// contains filtered or unexported fields
}
StopProcessTool kills a background process. SIGTERM by default with a 5s escalate-to-SIGKILL grace; explicit signal override available for the rare case the agent needs immediate KILL or a gentle INT.
func NewStopProcessTool ¶
func NewStopProcessTool(m *ProcessManager) *StopProcessTool
NewStopProcessTool returns the tool that terminates a background process managed by m.
func (*StopProcessTool) Definition ¶
func (*StopProcessTool) Definition() tools.ToolSpec
Definition advertises stop_process with process_id (required) and an optional TERM/KILL/INT signal enum; the spec text documents the SIGTERM-with-5s-grace default and idempotent exit-code semantics.
func (*StopProcessTool) Execute ¶
func (t *StopProcessTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute requires process_id, maps the signal string (case-insensitively) to SIGKILL or SIGINT — anything else falls back to SIGTERM — and delegates to the manager's Kill, returning {process_id, exit_code, killed_at}; unknown ids are NotFound.
type UpdatePlanArgs ¶
type UpdatePlanArgs struct {
Plan []UpdatePlanStepArg `json:"plan" doc:"Complete ordered plan; replaces the prior plan."`
Explanation string `json:"explanation,omitempty" doc:"Optional short reason for the update."`
}
UpdatePlanArgs is the typed argument struct UpdatePlanTool.Execute decodes into via tools.DecodeArgs. The `plan` array decodes directly into a typed slice — no .([]any) / .(map[string]any) dance, no per-field type assertions.
type UpdatePlanStepArg ¶
type UpdatePlanStepArg struct {
Step string `json:"step" doc:"Step description."`
Status StepStatus `json:"status" enum:"pending,in_progress,completed" doc:"Step status."`
}
UpdatePlanStepArg is one step in the typed update_plan payload. Mirrors the JSON Schema's per-item object {step, status}.
type UpdatePlanTool ¶
type UpdatePlanTool struct {
// contains filtered or unexported fields
}
UpdatePlanTool is the structured-plan tracker. It coexists with save_plan: save_plan persists the narrative markdown archive, update_plan owns the live, mutating step list rendered in the plan dock tab.
API mirrors Codex's update_plan verbatim: every call carries the FULL plan (steps + statuses). No partial-update semantics — the model resends the whole list each time. This is what GPT-5 family models are trained on, and it's easier to reason about (no step-id thread to maintain across calls).
Sequence-of-call expectations:
- PLAN mode end: call update_plan once with all steps at "pending" to seed the structured list alongside the markdown plan save_plan persisted.
- BUILD mode, starting work: call update_plan with the same list, flipping one step to "in_progress".
- BUILD mode, finishing a step: call update_plan again with that step at "completed" and (optionally) the next one "in_progress".
The prompt enforces this; the tool itself is permissive — it accepts any plan shape, since the model occasionally rearranges/extends steps mid-task and we'd rather see the rework than reject it.
func NewUpdatePlanTool ¶
func NewUpdatePlanTool(store PlanStore) *UpdatePlanTool
NewUpdatePlanTool returns a tool bound to store. Caller is responsible for the broadcast behaviour of store.SetPlan (in production the shellModel implementation pushes a tea.Msg to the TUI).
func (*UpdatePlanTool) Definition ¶
func (t *UpdatePlanTool) Definition() tools.ToolSpec
Definition advertises update_plan taking the full plan array ({step, status} items, status enum pending|in_progress|completed) plus an optional explanation; Mutates is left unset — the plan lives in the store, not in workspace files.
func (*UpdatePlanTool) Execute ¶
func (t *UpdatePlanTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute requires a non-empty plan array, trims each step, treats an omitted status as pending, and validates statuses via StepStatus.IsValid — the first empty step or unknown status fails the whole call. On success it replaces the stored plan wholesale and returns per-status counts.
type Workspace ¶
type Workspace struct {
// contains filtered or unexported fields
}
Workspace is a filesystem boundary. All paths handed to a code tool are resolved against the root; anything that would resolve outside the root (including via symlinks or "..") is refused.
Workspace values are passed by value to each tool but share their path-lock map AND the openat-style os.Root handle via pointer, so concurrent write/edit calls to the same path serialise correctly even when issued through different tool instances, and every writer benefits from the root-anchored open without re-opening the directory handle per call.
TOCTOU hardening: the per-tool boundary check via Workspace.Resolve uses lexical + EvalSymlinks-of-prefix matching, which is sufficient to reject obviously-out-of-tree paths cheaply. The actual file open then goes through the *os.Root handle so that even if the parent directory is swapped for a symlink between resolve and open, the kernel-level traversal refuses to follow it. Earlier shape did `os.WriteFile(abs, ...)` against an arbitrary absolute path — `Resolve` told you the path was safe at check time but nothing stopped a concurrent local actor from escaping the root between check and use.
func NewWorkspace ¶
NewWorkspace returns a Workspace rooted at the given absolute path. The root is canonicalized via filepath.EvalSymlinks so symlink-to-outside checks compare apples to apples, then opened as an os.Root so subsequent file operations can use openat-style traversal that refuses to escape the directory regardless of symlinks underneath.
func (Workspace) LockPath ¶
LockPath acquires the per-path mutex for the given (already-resolved) absolute path and returns the unlock function. Callers must defer the returned unlock. Used by write/edit/append-tools to serialise writers targeting the same file.
func (Workspace) MkdirParentInRoot ¶
MkdirParentInRoot creates the parent directory of abs (if it doesn't already exist) via the openat-style os.Root handle. Use before [OpenFileInRoot] when the target path is a fresh write into a directory that may not exist yet.
func (Workspace) OSRoot ¶
OSRoot returns the underlying os.Root handle. Writers in this package use it for openat-style file operations; external callers almost never need it. May be nil for zero-value Workspaces (e.g. constructed in tests without NewWorkspace); callers should fall back to plain os operations in that case.
func (Workspace) OpenFileInRoot ¶
OpenFileInRoot opens the named file relative to the workspace root. Same TOCTOU-safety argument as WriteFileInRoot — uses openat-style traversal so a symlinked parent can't escape.
func (Workspace) ReadDirInRoot ¶
ReadDirInRoot reads a directory through the workspace root handle.
func (Workspace) ReadDirPath ¶ added in v0.1.3
ReadDirPath reads a directory via the workspace root handle when it remains under the workspace, falling back to direct host directory reads for unrestricted read-side paths outside the workspace.
func (Workspace) ReadFileInRoot ¶
ReadFileInRoot reads abs through the workspace root handle. This is the read-side companion to WriteFileInRoot: callers still use Resolve for friendly errors and path locks, but the actual open happens relative to os.Root so a concurrent symlink swap cannot escape.
func (Workspace) ReadFilePath ¶ added in v0.1.3
ReadFilePath reads abs through the workspace root handle when it remains under the workspace, falling back to direct host reads for unrestricted read-side paths outside the workspace.
func (Workspace) RelToRoot ¶
RelToRoot returns the path of abs relative to the workspace root. Used by writers to convert the absolute path Resolve returned into the root-relative form os.Root expects. Returns an error if abs is outside the root — defensive check; callers normally pass a path that Resolve already validated.
func (Workspace) RemoveInRoot ¶
RemoveInRoot removes the file at the workspace-relative path via the os.Root handle. Mirrors os.Remove semantics (file-only; directories are out of scope for code tools).
func (Workspace) Resolve ¶
Resolve returns the cleaned absolute path for p, where p may be relative (joined to root) or absolute (must be inside root). Symlinks are followed and the final path is re-checked against root.
func (Workspace) ResolveForRead ¶ added in v0.1.3
ResolveForRead resolves p for a read-side tool. When unrestricted is false it enforces the workspace root exactly like Resolve. When unrestricted is true, reads may escape the workspace: absolute paths are used as-is and relative paths are cleaned after joining to the workspace root, so ../ segments may walk out.
func (Workspace) StatInRoot ¶
StatInRoot stats abs through the workspace root handle.
func (Workspace) StatPath ¶ added in v0.1.3
StatPath stats abs either through the workspace root handle (for paths still under root) or directly through the host filesystem (for unrestricted read-side paths outside the workspace).
func (Workspace) WriteFileInRoot ¶
WriteFileInRoot writes data to the workspace-relative path via the os.Root handle. Parent directories are created (also via the root handle) up to the workspace root — never beyond.
This is the TOCTOU-safe replacement for the os.WriteFile + os.MkdirAll pair the writers used to call directly. The kernel refuses to follow symlinks out of the root even if a directory in the path is swapped for a symlink between Resolve and Write.
Falls back to plain os operations for zero-value Workspaces (tests constructing tools without NewWorkspace); production paths always go through the os.Root handle.
type WriteAppendArgs ¶
type WriteAppendArgs struct {
Path string `json:"path" doc:"Path relative to workspace root."`
Content string `json:"content" doc:"Bytes to append. Keep each call under the configured cap (256KB default)."`
}
WriteAppendArgs is the typed argument struct WriteAppendTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.
type WriteAppendTool ¶
type WriteAppendTool struct {
// contains filtered or unexported fields
}
WriteAppendTool appends bytes to a file, creating it if missing. It exists as the fallback path for files larger than the `write` cap — modern llama.cpp builds + hosted providers handle the full 256KB default cleanly, but a model that needs to emit something larger (or hits a server that struggles with long streaming args) can scaffold with an empty `write` and stream chunks via `write_append`.
Historical note: older llama.cpp builds dropped characters inside multi-KB streaming tool-call JSON; the tool was originally authored to chunk at ~500 bytes for Qwen3.6. That regime is gone — the cap is now 256KB by default (tunable via CODE_APPEND_MAX_BYTES). Don't chunk smaller than necessary; the extra round-trips cost iterations.
Pattern for an oversized file (>256KB):
write(path, "") // empty scaffold write_append(path, "<up to ~256KB chunk>") // chunk 1 write_append(path, "<up to ~256KB chunk>") // chunk 2 ... etc
No state is tracked between calls — the tool is stateless and just opens the file in append mode each time. Concurrent calls to the same path race; the workspace's per-call serialisation should keep that from happening, but if you parallelise tool calls in the future, add a mutex keyed by absolute path.
func NewWriteAppendTool ¶
func NewWriteAppendTool(ws Workspace) *WriteAppendTool
NewWriteAppendTool returns the append-or-create file tool bound to ws.
func (*WriteAppendTool) Definition ¶
func (t *WriteAppendTool) Definition() tools.ToolSpec
Definition advertises write_append with required path and content; Mutates is true because each call appends bytes, creating the file and parent directories when missing.
func (*WriteAppendTool) Execute ¶
func (t *WriteAppendTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)
Execute caps each chunk at maxAppendContentBytes (256KB default), resolves the path, and — under the per-path lock — mkdirs the parent and opens O_APPEND|O_CREATE through the workspace root handle. Success reports the running file size and emits a FileAppend effect.
type WriteArgs ¶
type WriteArgs struct {
Path string `json:"path" doc:"Path relative to workspace root."`
Content string `json:"content" doc:"Full file contents to write."`
}
WriteArgs is the typed argument shape WriteTool's Execute decodes into. Field tags drive both JSON decoding and SchemaFor schema generation — doc tags supply the LLM-facing descriptions.
type WriteResult ¶ added in v0.2.1
WriteResult is write's structured success payload.
func (WriteResult) String ¶ added in v0.2.1
func (r WriteResult) String() string
String renders the model-facing success text for WriteResult.
type WriteTool ¶
type WriteTool struct {
// contains filtered or unexported fields
}
WriteTool creates a new file inside the workspace. Existing paths are rejected with a Validation error whose Reason names edit as the recovery path — this is a runtime invariant, not prompt guidance: the tool physically cannot overwrite, so models that would otherwise silently clobber an existing file are forced through the edit/read loop. Small-model benchmark runs show the refusal fires on a substantial fraction of exercises and consistently improves correctness — the model's "rewrite the whole file" instinct is rarely the right move once a file exists.
Content size cap ¶
content is capped at maxWriteContentBytes (default 256KB, tunable via CODE_WRITE_MAX_BYTES — see limits.go). Older llama.cpp builds dropped characters inside long streaming tool-call JSON; the cap is generous now (modern servers handle 256KB cleanly) but the scaffold-with-empty-write + chunked write_append fallback path stays available for whatever genuine outliers a model might produce. The cap is enforced at the tool layer because prompt compliance breaks under context pressure.
func NewWriteTool ¶
NewWriteTool returns a workspace-scoped write tool. The returned concrete type caches a typed-tool adapter so Execute stays a thin dispatch boundary.
func (*WriteTool) Definition ¶
Definition advertises write with required path and content; Mutates is true because a successful call creates a new file. The spec text routes existing-path retries to edit.
func (*WriteTool) Execute ¶
Execute rejects content over maxWriteContentBytes (256KB default) before touching the filesystem, resolves the path, then — under the per-path lock — refuses existing paths with an edit recipe and writes through the workspace's os.Root handle, emitting a FileCreate effect carrying the byte count.
Source Files
¶
- append.go
- apply_patch.go
- bash.go
- code.go
- doc.go
- edit.go
- filemap.go
- generated.go
- gitdiff.go
- glob.go
- grep.go
- hashline.go
- limits.go
- ls.go
- plan_state.go
- process_tools.go
- processmgr.go
- read.go
- readopts.go
- retrieve_code.go
- ringbuffer.go
- sandboxer.go
- save_plan.go
- save_plan_append.go
- step_status.go
- stepstatuses_enums.go
- update_plan.go
- workspace.go
- write.go