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, andreadeach 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:
ls list directory entries
find find files by pattern
grep search file contents with ripgrep
read read UTF-8 text with line paginationThe implementation now lives in:
lib/agent-builtins.tsThe 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:
current_projectThe path policy code lives in:
lib/agent-path-policy.tsRelative 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:
readsupportsoffsetandlimitgrephas match limits and byte limitsfindandlsreturn 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
nulloptional arguments - real
rgexecution - deterministic
findandls - timeout and abort behavior through the runtime
Git Evidence
Relevant commit:
0b1c88f Add workspace read toolsA 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:
npx tsx --test tests/agent-builtins.test.tsMeasured tail output:
✔ 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 0The 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.