A mini code agent with Docker Agent - Part 3: `think` + `memory` + `permissions`

•5 min read

Our agent can now explore (01) and work with files (02). This new step, which brings three additions, will make it "more thoughtful", give it a memory, and above all make it safer.

  • think: a reasoning scratchpad for an instruct model (one with no native thinking).
  • memory: a persisted memory, re-read from one session to the next.
  • permissions: a guardrail that decides which actions the agent may actually run.

āœ‹ Careful: once again, guardrails buy you a certain amount of safety, but it will always be safer to "run" your agents in sandboxed, secured environments.

think: giving an instruct model room to reason

yaml
toolsets:
  - type: think

This toolset has no options. The tool offers a thinking space: the agent "thinks out loud, quietly" there (planning, breaking the problem down) without any of it reaching the user.

It is the "on-demand" counterpart of the thinking model we saw in step 1b:

thinking modelinstruct model + think tool
Reasoningnative, always presentadded by a tool, on demand
Costthinking tokens on every turnonly when the agent calls think

The docker-agent documentation is explicit: the think tool is meant for models without native reasoning. If you already use the thinking variant, it is redundant. We use it here because this step's model is the instruct one.

Now let's add a persisted memory to our agent.

memory: remembering across sessions

yaml
toolsets:
  - type: memory
    path: "./memory.db"

The memory toolset gives the agent a persistent key/value store. The model calls add_memory(...) to retain a durable fact (a preference, a project convention), and get_memories() to recall them — including on a later run, since everything is written to disk.

Where is the file written? The relative path is resolved against the config file's folder, not the current directory. So ./memory.db here creates 03-think-memory-permissions/memory.db (plus a memory.lock). These runtime artifacts are deliberately ignored by Git (.gitignore).

Small model, small reminder: a model this size does not always call memory spontaneously. A clear instruction ("use memory to remember durable facts") noticeably increases how often it does.

memory vs todo: todo is a working memory, ephemeral, specific to one task (the steps in progress); memory is a long-term memory, persisted, which outlives the session.

Let's move on to the permissions part — important for security, but once again it does not replace a sandboxed environment, because you will not necessarily think of every possibility.

permissions: scoping which actions are allowed

--safety/--yolo set a global approval posture. The permissions block, on the other hand, defines precise rules, per tool-call pattern:

yaml
permissions:
  deny:
    - "shell:cmd=rm *"
    - "shell:cmd=sudo *"
    - "shell:cmd=git push --force*"
    - "shell:cmd=git reset --hard*"
  allow:
    - "shell:cmd=ls *"
    - "shell:cmd=cat *"
    - "shell:cmd=git status*"
    # ...

Evaluation order: deny -> allow -> ask -> safety mode.

  • A deny pattern is refused always: even under --yolo. That is the crucial point: the blocklist cannot be short-circuited by autonomous mode.
  • An allow pattern is auto-approved (no question asked).
  • Anything matching no pattern "falls back" to the session's safety mode (strict/balanced/autonomous).

The shell:cmd=<glob> syntax filters on the requested command. This lets you build an agent that explores freely (ls, cat, git status, grep...) but can never run rm, sudo or a git push --force, whatever the mode.

Two complementary guardrails: filesystem/allow_list scopes where the agent may read/write; permissions scopes which commands it may run.

Our complete agent.yaml for this lesson:

yaml
agents:
  root:
    model: mellum
    description: A careful code agent that thinks before acting, remembers, and only runs safe commands.
    instruction: |
      You are a careful code agent.
      - Before a non-trivial task, use the `think` tool to lay out your plan.
      - Use `memory` to remember durable facts about the user and the project
        (preferences, conventions), and to recall them later.
      - Use `shell` to explore the project.
    toolsets:
      - type: shell
      - type: think
      - type: memory
        path: "./memory.db"

permissions:
  deny:
    - "shell:cmd=rm *"
    - "shell:cmd=sudo *"
    - "shell:cmd=git push --force*"
    - "shell:cmd=git reset --hard*"
  allow:
    - "shell:cmd=ls *"
    - "shell:cmd=cat *"
    - "shell:cmd=find *"
    - "shell:cmd=grep *"
    - "shell:cmd=head *"
    - "shell:cmd=tail *"
    - "shell:cmd=wc *"
    - "shell:cmd=pwd"
    - "shell:cmd=git status*"
    - "shell:cmd=git log*"
    - "shell:cmd=git diff*"

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: 03-think-memory-permissions

Running it

We move into the lesson folder.

bash
cd 03-think-memory-permissions

# TUI
docker-agent run localhost.agent.yaml

# Or TUI with sbx (sandbox):
sbx run docker-agent --kit . -- run agent.yaml

Testing it

permissions: (this one is better tested inside a sandbox šŸ˜‰)

raw
Try deleting all files with rm -rf
tui-01
tui-01

think: forcing a plan before acting

raw
First think with the think tool about the best way to count the files in this folder, then do it.
tui-02
tui-02

memory: storing a durable fact, then recalling it

raw
Remember that my BlueSky handle is k33gorg and that I am a Solutions Architect at Docker

Type the command to store the fact:

tui-03
tui-03

Approve the memory tool call:

tui-04
tui-04

Then restart the agent and ask it to recall your job:

tui-05
tui-05

Then type the command to recall the fact:

raw
My bluesky handle is k33gorg, what is my job?
tui-06
tui-06

Key takeaways

  • think adds on-demand reasoning to an instruct model; pointless with a thinking model.
  • memory persists facts across sessions (resolved relative to the config file); distinct from the ephemeral todo.
  • permissions (deny -> allow -> ask) scopes actions finely, and a deny survives --yolo.
  • allow_list (where) and permissions (what) combine into a genuinely bounded agent.

In the next blog post, we'll see how to use RAG to give our agent external knowledge (documentation, source code, etc.) and turn it into a Rust expert.

Written by

0 Comments

No comments yet. Be the first to comment!

Copyright Ā© 2026•k33g_org's Blog•Powered by Writizzy