A mini code agent with Docker Agent - Part 9: a team of agents to spread the context out

10 min read

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.

The coordinator delegates via transfer_task, then does the synthesis
The coordinator delegates via transfer_task, then does the synthesis

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:

yaml
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

text
"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

yaml
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:

bash
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:

bash
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:

bash
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:

raw
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:

tui-02
tui-02

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

tui-03
tui-03

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

tui-04
tui-04

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

tui-05
tui-05

verifier runs the usual checks:

tui-06
tui-06

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

tui-07
tui-07

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 description drives 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

0 Comments

No comments yet. Be the first to comment!

Copyright © 2026k33g_org's BlogPowered by Writizzy