A mini code agent with Docker Agent - Part 9: a team of agents to spread the context out
And here we are at the 9th and final step of our Docker Agent series. We are going to see how to split the work between several agents, so that each one has less context to carry, and therefore more room to think.
In the previous blog post, step 08, we used toolsets, a cookbook and instructions with a single agent, and therefore inside a single context window. It works, but the window fills up fast, the model has to carry the whole context at once, and our agent slows down and struggles to stay focused.
Today, we are going to do the same work, with the same model, but spread over three specialists and a coordinator (so 4 agents). This is not "more agents to do more": it is "spreading the context load", and so each specialist gets instructions for one particular mission, with only what it needs (hence less "memory pressure" per agent, hence "more room" to think). The coordinator delegates, waits, then synthesises the answers.
Delegation with sub_agents
So we are going to use docker-agent's delegation mechanism: an agent can declare sub_agents, and the coordinator can delegate a task to a specialist through the transfer_task tool. The specialist runs in an isolated sub-session, sends its result back, and the parent keeps the lead to synthesise.
Here is a simplified example of what we are going to set up, in the agent.yaml file:
agents:
root:
model: mellum
description: Rust mentor that routes each request to the right specialist and synthesises the answer.
sub_agents:
- cookbook
- implementer
- verifier
cookbook: # the knowledge
toolsets:
- type: rag
ref: rag_knowledge
implementer: # the hands
toolsets:
- type: filesystem
allow_list:
- ${env.PWD}/workspace
- type: shell
- type: todo
verifier: # the judge
toolsets:
- type: shell
- type: filesystem
allow_list:
- ${env.PWD}/workspace
readonly: true # write_file and edit_file DISAPPEAR
- type: lsp
command: rust-analyzer
As soon as an agent declares sub_agents, docker-agent automatically adds the transfer_task tool to it. The coordinator calls it to hand a task over to a child; the child runs in an isolated sub-session, sends its result back, and the parent keeps the lead to synthesise.
Two design details drive everything else:
- Each agent's description is crucial. It is what the coordinator reads to decide who to delegate to, exactly like the description of a tool guides the choice of that same tool. That is why
cookbook's description lists the topics it covers: routing is decided there, not in the instruction. - Each specialist has different tools, the ones its role needs. That gives us better framing, a shorter prompt, and therefore less confusion for a small model.
An example of how the agents chain together
"Write a Rust program that reads a file line by line and prints how many lines it has"
root -> transfer_task(cookbook) -> the rules + the corpus files
root -> transfer_task(implementer) -> cargo init, src/main.rs, cargo check
root -> transfer_task(verifier) -> cargo check + cargo clippy, commands quoted
root -> "crate path … · verdict: No issues found."
One shared model, or different models?
Here, all the agents share the same model (mellum). This is a deliberate choice: every distinct local model loaded at the same time uses up its own RAM (so with 3 different models, for instance, their memory footprints add up). With four agents on a single model, we only load the model once.
But of course nothing forces that on you, and this is one of docker-agent's strengths: each agent can have its own model, and each model its own provider and its own endpoint.
That is the real promise of multi-agent: the most demanding role can get the strongest model, without paying for that model on every single turn.
The trade-off is still RAM for local models. In the case we are interested in here, I am RAM-constrained, so I would rather use a single shared local model.
The complete agent.yaml for this lesson
agents:
# The coordinator: no tools at all
root:
model: mellum
description: Rust mentor that routes each request to the right specialist and synthesises the answer.
instruction: |
You lead a small Rust team. You never do the work yourself.
You delegate with `transfer_task`, then combine what comes back into one answer.
For ANY request that involves writing or changing code, run these three steps in order, every time.
1. `cookbook` — ask for the idioms and rules the task needs.
2. `implementer` — pass it the request AND the rules from step 1, quoted.
3. `verifier` — pass it the crate path `implementer` reported back.
For a plain question with no code to write, step 1 alone is the answer.
What you report is:
- the crate path, what `implementer` said it wrote,
- and `verifier`'s verdict quoted word for word.
sub_agents:
- cookbook
- implementer
- verifier
# 1. The knowledge
cookbook:
model: mellum
description: Answers Rust questions from the Rust cookbook — syntax, ownership, Option and Result, error handling, iterators, collections, strings, traits, testing, and common compiler errors.
instruction: |
You answer Rust questions from the Rust cookbook, and from nothing else.
Search it before you answer, every time.
- Answer only from what the search returns. If the cookbook is silent,
say so — do not fall back on what you remember about Rust.
- Name the source files you used.
- When you are asked for the rules behind a piece of code, return them
as a short list of rules, not as prose: someone else will apply them.
toolsets:
- type: rag
ref: rag_knowledge
# 2. The hands
implementer:
model: mellum
description: Writes and edits Rust code in the workspace.
add_environment_info: true
instruction: |
Your working directory is `./workspace/`
- Always create a sub-directory in `./workspace/` with the name of the new project (default is `demo`)
- Always start a new program in the project directory with `cargo init`.
- Run the `cargo check` on what you touched, and fix what it reports until it passes.
- Finish by reporting the crate path, the files you wrote, and the last line of the check.
toolsets:
- type: shell
# 3. The judge
verifier:
model: mellum
description: Checks Rust code with cargo and rust-analyzer, and reports what is wrong. Changes nothing.
add_environment_info: true
instruction: |
You verify Rust code using rust-analyzer. You change nothing, ever: report only.
Use `cargo check --manifest-path` and `cargo clippy --manifest-path`
toolsets:
- type: shell
- type: filesystem
- type: lsp
command: rust-analyzer
file_types: [".rs"]
working_dir: "${env.PWD}/workspace"
lifecycle:
profile: resilient
startup_timeout: 10s
# The Rust book, defined once, used by only one agent: the cookbook agent
rag:
rag_knowledge:
tool:
description: >
Search the Rust cookbook:
syntax, ownership and borrowing, Option and Result, error handling,
iterators, collections, strings, structs and enums,
traits and generics, testing, concurrency,
file I/O, JSON, CLI parsing,
and the most common compiler errors with their fixes.
docs:
- ./knowledge
strategies:
- type: chunked-embeddings
embedding_model: embedder
database: ./knowledge-embeddings.db
vector_dimensions: 768
similarity_metric: cosine_similarity
threshold: 0.45
limit: 3
chunking:
size: 1200
overlap: 100
respect_word_boundaries: true
models:
mellum:
provider: dmr
model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
base_url: http://localhost:12434/engines/v1
#base_url: http://host.docker.internal:12434/engines/v1
provider_opts:
context_size: 16384
embedder:
provider: dmr
model: huggingface.co/unsloth/embeddinggemma-300m-gguf:Q8_0
base_url: http://localhost:12434/engines/v1
#base_url: http://host.docker.internal:12434/engines/v1
max_tokens: 2048
📝 You'll find all the configuration files over here: 09-multi-agents
Running it
We still have the same prerequisites: the two models to load, and the Rust toolchain for the verifier agent:
docker model pull huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
docker model pull huggingface.co/unsloth/embeddinggemma-300m-gguf:Q8_0
rustup component add rust-analyzer clippy
Then start Docker Agent from the 09-multi-agents folder in TUI mode:
cd 09-multi-agents
# TUI
docker-agent run localhost.agent.yaml
# from a container or a sandbox:
docker-agent run agent.yaml
You can also use sbx to run the whole thing in a throwaway sandbox, as described below:
The folder also comes with an sbx kit (spec.yaml), to run all of this in sbx:
sbx run docker-agent --kit . -- run agent.yaml
A prompt to try
Don't forget that we are working with a (very) small model, so the more directive the prompt, the more effective it is. Here is an example of a prompt to try:
I need a demo program in Rust using structs:
- a Dog struct with name, breed, and age fields
- a Human struct with first_name, last_name, and age fields
- a Dog belongs to a Human (owner)
- a Human can have multiple Dogs (pets)
Initialize a Human with two Dogs, and print out the Human's name and the names of their Dogs.
TODO:
- search the cookbook for the rules about structs and ownership
- have the implementer agent write the code according to those rules
- have the verifier agent check the code and report any issues
✋ Pick requests that force the work to circulate: as in the
TODO:above, first the rules, then the code, then the verification.
On startup, we can now see 3 agents in the TUI, plus the root coordinator:

Once root has taken in the prompt, it first "calls" cookbook to get the rules:

Once the rules have been collected, cookbook hands control back to root, which calls implementer to write the code:

When implementer is done generating, it hands control back to root, which then notifies verifier to check the code:

verifier runs the usual checks:

verifier returns the verdict to root, which then synthesises the answers and displays them to the user:

And there we are, we made the work "circulate" between the agents, each one having less context to carry, and therefore more room to think. And along the way we started to learn how structs work in Rust, and how to use them properly.
✋ Let's not forget that vibe-coding is not possible with small models; we use our team of agents to help us in our discovery of the Rust language.
Key takeaways
- A team of specialists often beats a generalist, especially with a small model, and above all because each window now only carries its own share.
sub_agents= delegation (transfer_task): the parent hands over, waits, synthesises; the children are isolated.- The coordinator gets no tools at all. That is what stops it doing the work itself in the window we want to keep free.
- Each agent's
descriptiondrives the routing to the right agent: it is the most important text in the file. - A single shared local model avoids memory pressure; but one model (and one endpoint) per agent lets you give each role the most suitable model (for the use cases where you have more memory, or if you use remote endpoints).
Written by

No comments yet. Be the first to comment!