A mini code agent with Docker Agent - Part 2: `filesystem` + `todo`
In the previous blog post, the agent only had shell. It could already read and write files (cat, echo >, sed...), but in a fragile and dangerous way: shell strings to escape, no limit on the paths touched, output to parse by hand.
A code agent deserves better. Today, this second blog post adds two built-in tools:
**filesystem**: typed and scoped file operations (read, write, list, search, edit);**todo**: a task list the agent keeps itself so it doesn't get lost in a multi-step job.
The filesystem toolset
toolsets:
- type: filesystem
allow_list:
- "."
Where shell exposes a generic command, filesystem exposes several dedicated tools to the model — typically read_file, write_file, list_directory, search, edit_file. Each has a clear schema (path, content...), so the model produces structured arguments instead of shell lines to quote.
In the loop, this gives clean calls, easy to read and to check:
Calling write_file(path: "notes.md", content: "# Mes notes")
write_file response → "File written successfully: notes.md (11 bytes)"
Calling read_file(path: "notes.md")
read_file response → "# Mes notes"
Scoping access: allow_list / deny_list
By default, the toolset is unrestricted: relative paths start from the working directory, but nothing stops the agent from reading/writing an absolute path or going up with ... Handy locally, dangerous as soon as an agent acts on someone else's behalf.
So we bound the access:
| Setting | Effect |
|---|---|
allow_list | limits operations to a fixed set of roots |
deny_list | carves out forbidden subtrees, even under an allowed root |
Recognized tokens: "." (working directory), "~" (home), "~/foo", "${env.VAR}", an absolute path (as-is), a relative path (joined to the working directory). Symbolic links are resolved before the check: impossible to escape an allowed root through a symlink.
Here, allow_list: ["."] locks the agent inside its working directory.
filesystemscopes where the agent can go. In the next blog post we'll see how thepermissionsblock scopes which actions (which tool calls) are allowed — two complementary safeguards.
✋ A very, very, very important note: the safest way to use an agent without risk is to "contain" it in a restricted VM, a container with limited rights and network access, or a sandbox built specifically for this like
**[sbx](https://docs.docker.com/ai/sandboxes/)**. If you use a VM or a container, "in theory" you limit access to the host filesystem, but you'll have to deal with network access, container security, and also the visibility of your secrets (environment variables, config files...); don't forget that the agent "sees everything" and can exfiltrate data through the network or the logs. When I'm back from holidays, I'll write an article about managing secrets withsbx.
The todo toolset
toolsets:
- type: todo
There is nothing to configure. The tool gives the agent a task scratchpad: it adds the steps it plans to follow, then checks them off as it goes.
Why it's especially useful for a small model: the context window is narrow, and on a long task the model easily "forgets" where it is. An explicit task list, re-read at every turn, acts as a structured working memory: it keeps the course ("I still have to write the tests"), avoids omissions and repetitions.
We explicitly instruct it to use it:
instruction: |
For any multi-step task, keep a list up to date with the `todo` tool:
add the steps and mark them done as you go.
✋ Note: keep in mind that we're working with a very small model, so a task list that's too long or too detailed can overflow the context window. So keep the list short and concise.
shell vs filesystem: which one, when?
shell | filesystem | |
|---|---|---|
| Surface | huge (the whole system) | dedicated file operations |
| Security | arbitrary commands | bounded paths (allow_list/deny_list) |
| Output | text to parse | structured results |
| Ideal for | exploring, running tools (git, go test) | reading/writing/editing files cleanly |
The two are complementary: many code agents declare shell and filesystem. We split them here for teaching purposes; nothing stops you from combining the toolsets.
Our complete agent.yaml for this lesson:
agents:
root:
model: mellum
description: A code agent that reads, writes and edits files, and tracks its work.
instruction: |
You are a code agent. Use the `filesystem` tools to read, create and edit
files instead of printing code into the conversation. For any multi-step
task, keep a list up to date with the `todo` tool: add the steps and mark
them done as you go.
toolsets:
- type: filesystem
allow_list:
- "."
- type: todo
models:
mellum:
provider: dmr
model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
base_url: http://host.docker.internal:12434/engines/v1
# base_url: http://localhost:12434/engines/v1
📝 You'll find all the configuration files over here: 02-filesystem-todo
Running it
We move into the lesson folder; the agent reads and writes in this folder (allow_list: ["."]):
cd 02-filesystem-todo
# TUI
docker-agent run localhost.agent.yaml
# Or TUI with sbx (sandbox):
sbx run docker-agent --kit . -- run agent.yaml


You can also run the agent in a single command (without the TUI):
docker agent run --exec --yolo agent.yaml \
"Create a notes.md file with the title '# Mes notes' then re-read it."
A complete example in the TUI
The best way to see the todo at work is to launch the TUI and hand it a task that clearly has several steps. Open the TUI:
cd 02-filesystem-todo
docker-agent run localhost.agent.yaml
Then paste this prompt:
Create a small Node.js module `calc/` with its tests. Keep your todo up to date.
1. Create the folder `calc/`.
2. Write `calc/operations.js` with two exported functions: `add(a, b)` and `divide(a, b)`.
`divide` must throw an `Error` when `b === 0`. Comment each function.
3. Write `calc/operations.test.js` with tests using Node's built-in runner
(`node:test` and `node:assert`): one nominal case per function, plus a test
asserting that `divide(1, 0)` throws.
4. Write `calc/README.md` explaining the module and how to run the tests (`node --test`).
5. Re-read `calc/operations.js` and `calc/operations.test.js` to check they are consistent.
When you are done, give me a recap of your todo.
Here the todo keeps the course on a classic chain code → tests → doc → review:

👋 Heads-up: if you want the agent to actually run the code, you'll need to add the shell tool to the toolsets section:
toolsets:
- type: filesystem
allow_list:
- "."
- type: todo


Key takeaways
filesystemreplacesshellhacks with typed and safe file operations.allow_list/deny_listlock the agent inside a perimeter — essential outside the dev machine.todogives the model an explicit working memory, precious when the context is small.- You combine toolsets: an agent can have
shell,filesystemandtodoall at once.
See you very soon for the next part: the next blog post will add think and memory, so the agent can think before acting and remember what it did.
Written by

No comments yet. Be the first to comment!