Skip to content

12. Real Read-Only Tools

This chapter explains why the project removes toy capability and adds real read-only project exploration tools. A coding agent needs to work against real repositories, not a custom demo function.

After reading this chapter, you should understand:

  • what ls, find, grep, and read each provide
  • why dedicated read-only tools come before shell
  • how path policy affects file access
  • why output limits and debug details are separate

Background

The toy tool proved tool mechanics, but it could not make the agent useful.

A coding agent needs to explore the local project. The first real tool surface became read-only file exploration.

Tool Set

Current active built-ins:

text
ls      list directory entries
find    find files by pattern
grep    search file contents with ripgrep
read    read UTF-8 text with line pagination

The implementation now lives in:

text
lib/agent-builtins.ts

The original committed version used lib/agent-workspace-tools.ts; that file was later removed when the tool contract boundary was renamed and generalized.

Why Not Shell First

Shell is powerful, but it raises hard problems:

  • process sandboxing
  • command approval
  • destructive commands
  • output streaming and truncation
  • long-running sessions
  • cancellation of process trees

The read-only tools gave the agent real usefulness while postponing those risks.

Path Policy

Current tools use:

text
current_project

The path policy code lives in:

text
lib/agent-path-policy.ts

Relative paths resolve from the project root. Paths outside the allowed root become model-visible tool errors.

Output Limits

Tools do not dump unbounded output into history.

Examples:

  • read supports offset and limit
  • grep has match limits and byte limits
  • find and ls return deterministic bounded lists
  • notices tell the model how to continue

This is important because tool output enters model-visible history.

Model-Facing vs Debug Details

The model sees compact text.

The debug UI can see structured details.

That split is essential: the model should not have to parse internal JSON, but humans need enough structure to inspect what happened.

Tests

tests/agent-builtins.test.ts covers:

  • file content and line metadata
  • path escape rejection
  • pagination notices
  • strict-mode null optional arguments
  • real rg execution
  • deterministic find and ls
  • timeout and abort behavior through the runtime

Git Evidence

Relevant commit:

text
0b1c88f Add workspace read tools

A later evolution renamed the layer from workspace tools to built-in read-only tools and removed the toy tool: the name no longer implies "workspace only", and access scope is decided entirely by path policy.

Common Misunderstandings

Misunderstanding 1: Shell Makes Dedicated Tools Unnecessary

Shell is flexible, but harder to control for permissions, truncation, output format, and safety. Dedicated read-only tools provide stable, structured, lower-risk exploration first.

Misunderstanding 2: Read/Grep Naturally Mean Workspace-Only

Tool names should not define access scope. Access scope belongs to path policy, such as current project, allowed roots, or danger full access.

Misunderstanding 3: Tool Output Should Always Be Complete

Model context is limited. Tools should return useful bounded text, and when truncated, provide actionable continuation hints.

Chapter Summary

This chapter moves the agent from toy tools to real file exploration: dedicated read-only tools provide controlled capability, path policy manages access, and output formatting serves both model readability and debug inspection.

Chapter Checkpoint

Every behavior of the four read-only tools is pinned by tests, and none of them needs a key:

bash
npx tsx --test tests/agent-builtins.test.ts

Measured tail output:

text
✔ grep returns structured matches from ripgrep (31.4035ms)
✔ find returns matching project file paths (9.157917ms)
✔ ls returns directory entries with deterministic order (0.746417ms)
✔ tool runtime converts timeout into model-visible text output (12.34925ms)
ℹ tests 22
ℹ pass 22
ℹ fail 0

The 22 cases cover every claim in this chapter: grep really invokes the local ripgrep binary (no mock), path escapes are rejected at the path-policy layer and returned as model-visible errors, read truncation carries a pagination notice, and timeout and abort become model-visible text instead of runtime crashes.