Tool server
Let an AI agent read and change the files of a workspace, run commands in it and build container images.
Last updated
On this page
Description and operation
General
Every workspace runs a tool server. An agent calls its tools with the tool call of its session, and each call takes workspace, the id of the workspace. The id starts with ws-. The agent never sends HTTP itself. The Claude Code plugin, the MCP server and the harness forward each call. For your own program, see the Tool server API.
| Group | Agent tools |
|---|---|
| Files | read, write, edit, patch, glob, grep |
| Commands | exec, process_list, process_output, process_write, process_kill |
| Watches | watch, watch_poll, watch_stop |
| Container images | container_build, container_push |
The tool server also has process_wait and process_close_stdin. The agent tools do not include them: read a process with process_output. The arguments of every tool are in the Tool server API; the schemas of the tools are the authority.
Paths are relative to the working directory, /home/kl/workspace. A path outside it gets an error. Every tool takes an optional tree; the default main is the workspace itself. A workspace that has no activity stops. A running tool call, an open process stream and an inbound connection to a port other than 7788 count as activity. A detached process does not.
Commands and processes
exec without detach is a job. The tool waits and answers exit_code, stdout and stderr. The default limit is 120000 ms (timeout_ms) and the maximum is 600000 ms. At the limit the answer has "timed_out": true and exit_code -1, and the server kills the command and its children. head, tail and quiet keep the answer short.
exec with "detach": true is a process. The tool answers {id} at once. Use the id with process_output, process_write, process_kill and process_list. A process keeps the last 4 MiB of each stream, and the list holds it for 10 minutes after it exits. At most 32 processes run in one workspace.
process_output answers next and next_err. Send them back as since and since_err to read only new output. A stop of the workspace does not lose a process: the server starts it again with the same id, at most 3 times.
Confinement
Each exec runs in a bwrap sandbox. The command sees the home of its tree, read and write. It sees /nix and a few files of /etc read-only, its own /tmp, and the network of the workspace. It does not see /etc/kloudlite, so the agent cannot read the workspace token. For that reason kl is not on the path of exec; run kl from a shell of the workspace. If bwrap cannot start, the command runs without the sandbox.
container_build and container_push run outside the sandbox, so they have the credential that exec lacks. The agent chooses only what to build. The build context and the Dockerfile must be inside the tree. container_build waits up to 600000 ms and keeps the last 40 lines of each stream. The tag and registry rules are those of kl container.
A terminal is not available: exec with "pty": true gets 400. Use SSH.
Tasks
Run a command
Reason for the task: Run a command in a workspace and read its result.
Job set-up
- Access: you.
- Agent tool:
exec - Conditions: the workspace is
ready. The tool starts an idle-stopped workspace first. - References: Tool server API.
Procedure
Run the command. Add
headortailto keep a long answer short.{"tool": "exec", "arguments": {"workspace": "ws-1a2b3c4d5e6f7a8b", "cmd": "uname -a"}}
Close-out
- Make sure that
exit_codeis0andstdoutshows the system name.
Run a long command
Reason for the task: Start a command that runs longer than the limit of a job, and read its output while it runs.
Job set-up
- Access: you.
- Agent tool:
exec - Conditions: the workspace is
ready. - References: Tool server API,
kloudlite workspaces follow.
Procedure
Start the command as a process.
{"tool": "exec", "arguments": {"workspace": "ws-1a2b3c4d5e6f7a8b", "cmd": "for i in 1 2 3; do echo $i; sleep 2; done", "detach": true}}Read the output with the
idfrom step 1. Repeat withsinceset to thenextof the last answer.{"tool": "process_output", "arguments": {"workspace": "ws-1a2b3c4d5e6f7a8b", "id": "p1"}}Stop the process if you no longer need it.
{"tool": "process_kill", "arguments": {"workspace": "ws-1a2b3c4d5e6f7a8b", "id": "p1"}}
Close-out
- Show the processes with
process_list. Make sure that the process showsexit_code0, or is gone after a kill.
Change files
Reason for the task: Read code and change it in one step, so that no half edit stays.
Job set-up
- Access: you.
- Agent tool:
edit - Conditions: the workspace is
ready. - References: Tool server API.
Procedure
Read the files.
pathstakes up to 50 files.{"tool": "read", "arguments": {"workspace": "ws-1a2b3c4d5e6f7a8b", "paths": ["src/main.rs", "Cargo.toml"]}}Change the files. All edits succeed or none applies;
filestakes up to 50 files, each with itsedits.{"tool": "edit", "arguments": {"workspace": "ws-1a2b3c4d5e6f7a8b", "files": [{"path": "src/main.rs", "edits": [{"old": "let n = 1;", "new": "let n = 2;"}]}]}}
Close-out
- Read the file again, or run
grep. Make sure that the new text is present.
Troubleshooting
| Symptom | Possible cause | Corrective action |
|---|---|---|
409 with workspace dev is stopped; start it to run tools | The workspace is not ready. | Start the workspace. |
409 with workspace dev is between pods; try again | The workspace restarts and has no address yet. | Wait a short time and call again. |
409 with workspace dev is in team acme; open a session in that team | The call names a workspace of another team. | Use a workspace of the team of the session. |
403 with EACCES ...: outside your working directory, or 400 with paths are relative to your working directory | The path is outside /home/kl/workspace. | Use a path inside /home/kl/workspace. |
404 with unknown tool NAME | The workspace does not serve this tool. | Use a name from the table above. |
500 with no process ID | The process does not exist in this workspace, or it ended long ago. | Run process_list and use an id from it. |
500 with 32 processes already; kill one first | The workspace holds 32 processes. | Stop one with process_kill. |
timed_out is true in the answer of exec | The job ran longer than its time limit. | Raise timeout_ms, up to 600000, or use detach. |