Skip to content

Tools

Alfred exposes a typed, capability-declared tool system. Every built-in tool is registered through the buildTool() factory and surfaced to the model via the tool registry (src/tools/index.ts). The query engine consults the capability flags to decide parallelism, permissions, and security fencing.


Tool Contract

Source: src/tools/types.ts

Every tool implements the Tool<In, Out> interface:

ts
interface Tool<In extends z.ZodTypeAny = z.ZodTypeAny, Out = unknown> {
  readonly name: string;
  readonly description: string;
  readonly inputSchema: In;                          // Zod schema — validated before call()

  call(input: z.output<In>, ctx: ToolContext): Promise<ToolResult<Out>>;

  isEnabled(): boolean;
  isReadOnly(input: z.output<In>): boolean;          // true → eligible for parallel execution
  isConcurrencySafe(input: z.output<In>): boolean;   // true → safe to run alongside other tools
  checkPermissions(input, ctx: ToolPermissionContext): Promise<PermissionResult>;

  describeCall(input: z.output<In>): string;         // short label shown in UI, e.g. "bash(ls -la)"
  render(result: ToolResult<Out>): readonly ContentBlock[];
}

ToolResult

ts
interface ToolResult<T = unknown> {
  readonly content: T;
  readonly isError?: boolean;
  /** Marks content from untrusted sources — triggers fence() in the engine (ADR 0003). */
  readonly untrusted?: boolean;
}

Setting untrusted: true causes the query engine to wrap the serialised content in a taint fence before it enters the model's prompt. See Security for the full taint + fence explanation.

ToolContext

ts
interface ToolContext {
  readonly workingDir: string;
  readonly signal: AbortSignal;
  /** Path → last-read snapshot; enables read-before-write + mtime freshness checks. */
  readonly readFileState: Map<string, { content: string; mtimeMs: number }>;
  readonly permissions: ToolPermissionContext;
}

readFileState is a session-scoped map populated by file_read and updated by file_write/file_edit. It is the mechanism that enforces the read-before-write and mtime freshness invariants.

buildTool() factory

buildTool(spec) fills conservative defaults so tool authors only declare what differs:

FlagDefaultMeaning
isEnabled()trueTool appears in the registry
isReadOnly()falseTreated as mutating; runs serially
isConcurrencySafe()falseNot run in parallel with other tools
checkPermissions()allow()Always permitted
describeCall()tool.nameFalls back to bare name
render()text blockSerialises content as text

The engine parallelises a tool call only when both isReadOnly() and isConcurrencySafe() return true for the parsed input.


Tool Registry

Source: src/tools/index.ts

getAllTools() returns the built-in set filtered by isEnabled(). findTool(tools, name) looks up a tool by name. Built-ins in registration order:

file_read, file_write, file_edit, bash, glob, grep,
memory_search, memory_upsert, memory_forget, web_fetch, load_skill

Built-in Tools Reference

file_read

Source: src/tools/fileRead.ts | Read-only, concurrency-safe

Read a UTF-8 file and return its contents with line numbers. Records {content, mtimeMs} in readFileState so subsequent file_edit or file_write calls can enforce the read-before-write and freshness invariants.

Input schema:

FieldTypeDescription
pathstringAbsolute or workspace-relative file path
offsetnumber (optional)1-based line to start from
limitnumber (optional)Maximum lines to return (default 2000)

Behavior: Output lines are formatted as {lineNum}\t{lineText}. When the file is larger than offset + limit lines, a truncation note is appended. Returns isError: true if the file does not exist. Path is resolved inside the workspace via resolveInside().


file_write

Source: src/tools/fileWrite.ts | Mutating, serial

Create a new file or overwrite an existing one. Updates readFileState after writing so the session snapshot stays fresh. Requires approval (ask) in default permission mode; auto-allows in acceptEdits mode.

Input schema:

FieldTypeDescription
pathstringFile path to write
contentstringFull file contents

Behavior: Writes atomically via Bun.write. Returns a success message including whether the file was created or overwritten and the byte count. Denies writes outside the workspace root.


file_edit

Source: src/tools/fileEdit.ts | Mutating, serial

Replace a unique snippet in a file. This tool enforces four invariants before any write reaches disk:

1. Read-before-write

The file must have been read this session (readFileState must contain an entry for the resolved path). If not, the tool returns an error instructing the model to read first.

2. Mtime freshness

The on-disk mtimeMs is compared to the snapshot recorded by file_read. If they differ, an external change has occurred and the edit is rejected with a message to re-read the file.

3. Fuzzy match ladder (seekSequence)

Source: src/tools/lib/seekSequence.ts

old_string is located by content, not by line number, using a four-rung ladder of increasingly forgiving comparators. The engine stops at the first rung that finds exactly one match:

StrategyNormalisation applied
exactNo normalisation — plain indexOf
rstripTrailing whitespace stripped from each line
trimLeading and trailing whitespace stripped from each line
collapseTrim + internal whitespace collapsed to single space

If all four rungs fail to find a match, the edit is rejected. If any rung finds more than one match and replace_all is not set, the edit is rejected with an ambiguity error — the model must provide more surrounding context.

The success message reports which strategy was used when it was not exact, e.g. Edited src/foo.ts — 1 replacement (matched via trim).

4. Post-edit syntax check

Source: src/tools/lib/syntaxCheck.ts

After constructing the edited file content in memory, checkSyntax(path, next) is called before any write. If the result would be syntactically broken, the edit is rejected and the file is left unchanged. See Code Intelligence for the full syntax check description.

Input schema:

FieldTypeDescription
pathstringFile to edit
old_stringstringExisting text to replace
new_stringstringReplacement text
replace_allboolean (optional)Replace every exact occurrence

bash

Source: src/tools/bash.ts | Conditionally read-only, serial

Execute a shell command and return combined stdout + stderr and the exit code.

Input schema:

FieldTypeDescription
commandstringShell command
timeoutnumber (optional)Timeout in ms (default 120,000)
cwdstring (optional)Working directory within the workspace

Read-only detection: isReadOnly() returns true when every chained segment (split on &&, \|\|, ;, \|, &) uses only known-safe commands. The read-only allowlist includes: ls, cat, head, tail, wc, echo, pwd, find, grep, rg, tree, stat, diff, sort, uniq, cut, awk, sed, and others. For git, only the subcommands status, log, diff, branch, show, remote, rev-parse are considered read-only.

Kill-list: checkPermissions() returns deny() for commands matching any of the following patterns, regardless of permission mode (even bypass):

  • rm with -r or -f flags targeting /, ~, $HOME, or /*
  • mkfs
  • dd writing to /dev/
  • Fork bombs (:() { : | :& };)
  • shutdown, reboot, halt, poweroff
  • Redirects to /dev/sd*, /dev/nvme*, /dev/disk*
  • chmod -R 0777 /

Security note

String matching is a UX convenience, not a security boundary. The real boundary is the OS sandbox (ALFRED_SANDBOX=1), which wraps commands in sandbox-exec on macOS and denies network access plus writes outside the workspace. Until the sandbox is enabled, bash asks for approval by default.

Output: Stdout and stderr are combined. Output is truncated to 30,000 characters. Non-zero exit codes produce isError: true with the exit code prepended.

Abort: The process is killed with SIGTERM when the AbortSignal fires.


glob

Source: src/tools/glob.ts | Read-only, concurrency-safe

List files matching a glob pattern using Bun's native Bun.Glob.

Input schema:

FieldTypeDescription
patternstringGlob pattern, e.g. src/**/*.ts
pathstring (optional)Directory to search from (default: workspace root)

Behavior: Results are workspace-relative, sorted alphabetically, capped at 500 entries. Automatically skips node_modules/, .git/, and dist/. Hidden files (dot: false) are excluded.


grep

Source: src/tools/grep.ts | Read-only, concurrency-safe

Search file contents with a regular expression. Pure JavaScript implementation — no ripgrep dependency, fully hermetic in tests.

Input schema:

FieldTypeDescription
patternstringRegular expression
pathstring (optional)Directory or file to search (default: workspace root)
globstring (optional)Only search files matching this glob (default **/*)
ignoreCaseboolean (optional)Case-insensitive match

Behavior: Returns matches as file:line:text lines (line text truncated to 300 chars), capped at 200 matches. Files larger than 1 MB are skipped. Binary files (containing a NUL byte) are skipped. Automatically skips node_modules/, .git/, and dist/.


web_fetch

Source: src/tools/webFetch.ts | Not read-only, serial

Fetch a URL over HTTP/HTTPS. All three lethal-trifecta mitigations apply — see Security and the dedicated web_fetch section there. Key points:

  • Default-deny egress; host must be in ALFRED_EGRESS_ALLOW.
  • untrusted: true is always set on a successful response — the engine fences the content.
  • redact() is applied to the body before return.
  • Body capped at maxBytes characters (default 100,000).
  • checkPermissions returns ask() for any host on the allow-list — the user sees every outbound fetch even in acceptEdits mode.

Input schema:

FieldTypeDescription
urlstringURL to fetch (http or https only)
maxBytesnumber (optional)Body character cap (default 100,000)

Source: src/tools/memoryTool.ts | Read-only, concurrency-safe

Full-text search over the workspace-local memory store (<workingDir>/.alfred/memory). Returns matching facts with type, slug, content, and timestamps.

Input schema:

FieldTypeDescription
querystringFull-text search query
knumber (optional)Max results (1–50, default 10)

memory_upsert

Source: src/tools/memoryTool.ts | Mutating, serial

Insert or update a memory fact. Upserting an existing slug overwrites the previous value (update-don't-duplicate policy).

Input schema:

FieldTypeDescription
slugstringLowercase alphanumeric + hyphens identifier
type"user" | "feedback" | "project" | "reference"Fact category
contentstringFact body (markdown OK)
scopestring (optional)Workspace-relative file/dir this fact applies to
ttlstring (optional)ISO-8601 expiry date, e.g. 2026-12-31

memory_forget

Source: src/tools/memoryTool.ts | Mutating, serial

Permanently delete a memory fact by slug. No-op if the slug does not exist.

Input schema:

FieldTypeDescription
slugstringSlug of the fact to delete

load_skill

Source: src/skills/skillTool.ts | Read-only, concurrency-safe

Level-2 on-demand skill body loader. The model holds only the Level-1 index (name + description) in context. When it needs the full procedural instructions for a skill it calls load_skill with the skill name. The tool reads and returns the SKILL.md body from <workingDir>/.alfred/skills/<name>/. This progressive-disclosure design avoids pre-loading skill bodies the model may never need.

Input schema:

FieldTypeDescription
namestringSkill name (must match a subdirectory in the skills dir)

Returns isError: true with a helpful message if the skill name is not found.

MIT Licensed.