sage-tools is a Sage extension pack publishing focused tools such as edit, rg, find_files, find_references, and subagent.
Sage already provides native read and write tools, so the editing/search
members focus on exact-text editing and project discovery. The subagent member
adds a narrow blocking child-Sage scout run for orchestration use cases.
edit applies one or more exact-text replacements to a single project file.
Schema:
path(string, required): file path to edit. Relative, parent-relative, and absolute paths are allowed.edits(array, required): one or more replacements.old_text(string, required): exact text that must occur once in the original file.new_text(string, required): replacement text.
Behavior:
- Read the original file.
- Validate every edit before writing anything.
- Fail without changing the file if any
old_textis empty, missing, ambiguous, or overlaps another edit. - Apply all replacements to the original content.
- Write through a same-directory temporary file and atomic rename.
- Return a unified diff for the file.
This tool targets one file per call. For multiple files, call edit once per
file so each file has its own atomic write boundary. Paths are intentionally not
constrained to the project root; use exact old_text context to avoid editing
the wrong file.
rg searches the active project with ripgrep and returns compact, limited match
rows. It is the raw ripgrep escape hatch for unsupported patterns.
Schema:
pattern(string, required): search pattern (regex by default, literal whenfixed_stringsis true).paths(arrayofstring, optional): project-relative files or directories to search. Defaults to the project root.include_globs/exclude_globs(arrayofstring, optional): additional ripgrep glob patterns.fixed_strings(boolean, optional): treat pattern as literal text.case_sensitive(boolean, optional): when false, pass--ignore-case.context_lines(integer, optional): context lines before/after each match. Capped at 3.max_results(integer, optional): maximum rows to return. Capped at 500.max_output_bytes(integer, optional): maximum total bytes of row summaries. Capped at 65536.timeout_ms(integer, optional): ripgrep timeout. Capped at 10000.rg_executable(string, optional): executable override for testing or custom PATH installs; path separators and traversal are rejected.
Behavior:
- The search is project-root confined. Path arguments must be project-relative;
absolute paths and
..traversal are rejected. - Ripgrep's own ignore rules (
.gitignore,.ignore,.rgignore) apply automatically. There are no sage-tools-level default excludes. Agents should passexclude_globs/include_globsfor project-specific noise control over directories likenode_modules,buildartifacts, or generated files. - Result limits enforce caps on total rows (500), context lines (3), output
bytes (65536), timeout (10000 ms), and per-line column width (240 chars).
When any limit is hit,
details.metareports truncation metadata. - Ripgrep is required. If
rgis not found, the tool raises an actionable error with install guidance for macOS (brew install ripgrep), Debian/Ubuntu (apt install ripgrep), and Arch Linux (pacman -S ripgrep). There is no fallback to grep or Lua-based file scanning. - Returns compact match rows (
path,line,column,summary,kind) indetails.rowsand metadata indetails.meta. When there are no matches, rows is empty and the tool succeeds.
find_references finds exact text or identifier references in the active project
without exposing arbitrary regex input.
Schema:
identifier(string, optional): exact text to find with fixed-string, word-boundary matching. Use this for simple identifiers.text(string, optional): exact literal text to find without word-boundary matching. Use this for punctuation-heavy member references likectx.pack.require.- Exactly one of
identifierortextmust be provided. paths(arrayofstring, optional): project-relative files or directories to search. Defaults to the project root.include_globs/exclude_globs(arrayofstring, optional): additional ripgrep glob patterns.case_sensitive(boolean, optional): when false, pass--ignore-case.context_lines(integer, optional): context lines before/after each match. Capped at 3.max_results(integer, optional): maximum rows to return. Capped at 500.max_output_bytes(integer, optional): maximum total bytes of row summaries. Capped at 65536.timeout_ms(integer, optional): ripgrep timeout. Capped at 10000 ms.rg_executable(string, optional): executable override for testing or custom PATH installs; path separators and traversal are rejected.
Behavior:
- Searches are project-root confined. Path arguments must be project-relative;
absolute paths and
..traversal are rejected. - The tool follows
rgexclude behavior: ripgrep's own ignore rules apply automatically, and sage-tools does not add default exclude globs. Useexclude_globsfor caller-specific noise reduction. - Both modes use ripgrep fixed-string matching and reject empty or multiline
queries.
identifieralso passes ripgrep--word-regexp. - Returns compact reference rows (
path,line,column,summary,kind) indetails.rowsplus mode/query and truncation metadata indetails.meta. - When there are no matches, rows is empty and the tool succeeds.
find_files finds likely relevant project files by filename and/or content hints without dumping broad shell output.
Schema:
filename_hints(arrayofstring, optional): case-insensitive filename or path fragments.content_hints(arrayofstring, optional): content patterns. Regex by default, literal whenfixed_stringsis true.paths(arrayofstring, optional): project-relative files or directories to search. Defaults to the project root.include_globs/exclude_globs(arrayofstring, optional): additional ripgrep glob patterns.fixed_strings(boolean, optional): treat content hints as literal text.case_sensitive(boolean, optional): when false, pass--ignore-casefor content hints. Filename hint matching is always case-insensitive.context_lines(integer, optional): context lines before/after content matches. Capped at 3.max_results(integer, optional): maximum rows to return across filename and content matches. Capped at 500.max_output_bytes(integer, optional): maximum total bytes of row summaries. Capped at 65536.timeout_ms(integer, optional): ripgrep timeout. Capped at 10000 ms.rg_executable(string, optional): executable override for testing or custom PATH installs; path separators and traversal are rejected.
Behavior:
- Searches are project-root confined. Path arguments must be project-relative; absolute paths and
..traversal are rejected. - The tool follows
rgexclude behavior: ripgrep's own ignore rules apply automatically, and sage-tools does not add default exclude globs. Useexclude_globsfor caller-specific noise reduction. - Filename hints are matched case-insensitively against paths returned by
rg --files. - Content hints are searched with ripgrep and return line/column metadata when available.
- Result limits enforce shared caps on rows, context lines, output bytes, timeout, and summary width. Truncation metadata is reported in
details.meta. - When there are no matches, rows is empty and the tool succeeds.
subagent runs one child Sage prompt synchronously and returns a compact summary
plus the final child answer. V1 defaults to a read-only scout policy: the child
is invoked with --allow-tool read, --no-extensions, prompt/system-prompt
files, --structured-output, and --no-streaming.
Schema:
task(string, required): child prompt task.label(string, optional): label used in summaries and temp-output names. Defaults toscout.system_prompt(string, optional): child system prompt. Defaults to a read-only scout role.child(object, optional): child runtime policy.sage_executable(string, optional): Sage executable path or command. Defaults tosage.provider/model(string, optional): passed as--providerand--model. Sage model refs such as<provider>/<model>should be passed asmodel = "provider/model".tools(arrayofstring, optional): repeated--allow-toolvalues. Defaults to{ "read" }. Ifextensions.modeisnone, tools must be Sage built-ins; extension tools such asrgorfind_filesrequireextensions.mode = "default"or"dirs".timeout_ms(integer, optional): child process timeout in milliseconds. Defaults to no timeout. Omitting it is usually preferable because subagent duration is difficult to estimate; set it only when the child task has a known upper bound.extensions(object, optional): extension policy.mode:none(default),default, ordirs.dirs: directories passed as repeated--extension-dirwhen mode isdirs.noneis rejected whentoolscontains non-built-in extension tools, because the child Sage run would disable the extensions that provide those tools.
debug.events(boolean, optional): include parsed structured child events indetails.events.
Behavior:
- Writes the child task and system prompt to session-owned temp-output files via
Sage's
ctx.temp_output.create(...)API. - Invokes child Sage with
sage.execute(...), prompt file inputs, and structured JSONL output enabled. - Parses structured child events for the final assistant answer, run failure, and tool counts.
- Stores raw child stdout JSONL in temp output when available and returns its
URI in
details.structured_output_uri; otherwise returnsdetails.structured_output_inline. This is the machine-readable surface for parent agents and is raw child stdout only, without stderr or wrappers. - Stores combined child stdout/stderr in temp output when available and returns
its URI in
details.full_output_uri; otherwise returns an inline fallback. This is a human/debug surface, not a machine-parsing contract. - Returns compact model-visible content containing the run outcome, tool counts, final child answer, or actionable failure diagnostics such as exit status, stderr summary, and failed child tool summaries.
Details include outcome, final_answer, failure, tool_counts,
diagnostics, child_policy, process metadata, prompt/system-prompt temp URIs,
raw structured-output reference, and full-output reference. diagnostics includes
failed_tools, stderr_summary, and process_summary when available. Parent
agents should query details.structured_output_uri for the child structured JSONL
event stream on both successful and failed child runs. V1 is intentionally
blocking and does not support background lifecycle, fanout, chains, persistent
role files, session forking, supervisor/intercom channels, or live child progress
while blocked.
The original blockers for this tool were Sage nested schemas and temp-output
APIs. They are resolved in Sage by nested map-style schemas and
ctx.temp_output.create/read; this tool does not use the removed experimental
ctx.call_tool API.
Shared Lua code lives under the pack-level lua/ directory and is loaded from
member extensions and their tests with ctx.pack.require(...). Keep reusable
runtime helpers in lua/support/; keep extension-specific tool logic inside the
member extension directories.
sage extensions install https://github.com/deevus/sage-tools.gitFrom a Sage source checkout:
zig build run -- extensions test /path/to/sage-tools/edit
zig build run -- extensions test /path/to/sage-tools/rg
zig build run -- extensions test /path/to/sage-tools/subagentWith an installed sage binary:
sage extensions test /path/to/sage-tools/edit
sage extensions test /path/to/sage-tools/rg
sage extensions test /path/to/sage-tools/subagent