Tools and the gate
tools::Tools is the tools a turn can call, held against one workspace.
| Tool | What it does | The limit |
|---|---|---|
read_file |
Reads from a byte offset, or from a 1-based line | 32 KiB, and the next offset or line when there is more |
grep |
Searches the workspace and returns path:line:text |
200 matches |
list_dir |
One level of names, a link named as a link | 500 names |
search_replace |
Replaces an exact string, then takes the write path | 256 KiB |
write_file |
Replaces the file, through a temporary file beside it | 256 KiB |
run |
Runs an argv in the workspace, with no shell | 120 s by default, 600 s at most, 64 KiB back per stream |
Every path is resolved first: relative to the workspace, then through every
symlink. A path the caller meant to be inside the workspace has to land there,
so a symlink pointing out of it is refused rather than followed, and a .. that
walks out is the same answer. An absolute path somewhere else is a deliberate
reach, and the card names that absolute path.
What stops at the gate
| Call | Card |
|---|---|
| Read, list, or grep inside the workspace | None, and nothing in the log but the model’s own tool call |
| Write or search_replace | A permission card with the diff, and the turn waits |
| Read or list outside the workspace | A permission card naming the absolute path |
| Every command | A permission card with the argv, and a non-default timeout |
The answers are allow_once, allow_session, and deny. A deny is the tool
result: the workspace is as it was and the command never started. An
allow_session puts one exact write path, one exact outside read path, or one
exact argv on meta.json, so that call goes through next time without asking,
and a different path, file, or command still asks. One session’s allows are that
session’s; another session’s log has never heard of them.
Writes are checked against a digest
A write stores a digest of the bytes that were on disk when the card went up. If
the file changes before the answer comes back, that answer was about a file that
is no longer there, so the write drops it, asks again, and the new card carries
the diff the file has now. The allow_session that came with the spent answer is
spent with it.
Diffs
A diff is kept whole up to 64 KiB, which is everything a person reads. A larger change keeps its first 400 lines on the card, and the path and the byte size still say how big it is. The log keeps every byte of what was proposed, so the evidence is not lost to a short card.
The turn loop
turn::Runner runs one ask as a tool loop against the chat client. The core tools include read_file, grep, list_dir, search_replace, write_file,
run, ask, finish, and use_skill.
- A read, a search, or a list inside the workspace runs immediately.
- A write, a command, or an outside read waits on the gate, and the session reads
waitinguntil the answer. askappends aquestionevent and blocks until the answer.finishappends aresultevent and ends the turn, after Stop hooks pass.
One task per running turn. An ask sent during a live turn is queued. An open question or permission
blocks new messages. A message to another session starts immediately. cancel stops the selected turn
at the next tool boundary: a running command gets SIGTERM and then SIGKILL two
seconds later.
