ContextBuilder and model context
Corresponding source
| Subject | Source |
|---|---|
| System prompt and messages | nanobot/agent/context.py::ContextBuilder.build_system_prompt, ContextBuilder.build_messages |
| Bootstrap, Memory, and Skills | nanobot/agent/context.py, nanobot/agent/memory.py, nanobot/agent/skills.py |
| Prompt templates | nanobot/templates/agent/, nanobot/utils/prompt_templates.py |
| Session replay history | nanobot/session/manager.py::Session.get_history |
| Call site in BUILD | nanobot/agent/loop.py::AgentLoop._state_build, AgentLoop._build_initial_messages |
Learning objectives
After this lesson, you should be able to:
- explain the main components of the system prompt;
- distinguish Session history, Memory, Skills, and runtime metadata;
- describe how the current user message is combined with runtime context;
- locate the initial messages that the model actually receives in this Turn.
Prerequisites
- Understand system, user, assistant, and tool messages.
- Understand that Session is not model context.
- Recommended: Organization, order, and trust boundaries.
1. ContextBuilder selects and organizes
Definition:
nanobot/agent/context.py::ContextBuilder
It does not call the Provider. It organizes distributed information into model input:
flowchart TD ID["Identity, platform, and workspace"] --> SYS["System prompt"] BOOT["AGENTS.md / SOUL.md / USER.md"] --> SYS TC["Tool Contract template"] --> SYS MEM["MEMORY.md"] --> SYS SK["Always-on Skills + Skills summary"] --> SYS RH["Recent Memory history"] --> SYS SUM["Archived Session summary"] --> SYS SYS --> MSGS["messages"] HIST["Session history"] --> MSGS CUR["Current user message"] --> MSGS RT["Runtime context"] --> CUR
2. System prompt composition
build_system_prompt() combines these sections in order:
- identity;
- workspace bootstrap files;
- Tool Contract;
- user-customized long-term Memory;
- always-on Skills;
- summaries of other available Skills;
- recent history after the Dream cursor;
- archived Session summary.
Separators divide the sections.
Identity
It includes:
- the absolute workspace path;
- operating system and Python version;
- platform policy;
- current Channel.
Sources:
nanobot/templates/agent/identity.mdnanobot/templates/agent/platform_policy.md
Bootstrap files
The fixed names are:
AGENTS.mdSOUL.mdUSER.md
They come from the current workspace, not from identically named templates in the nanobot source tree. onboard initializes them, after which the user can customize them in the workspace.
Tool Contract
Source:
nanobot/templates/agent/tool_contract.md
It describes how to use tools. ToolRegistry sends the concrete tool schemas to the Provider separately.
Memory
memory/MEMORY.md is included as long-term Memory only when its content no longer matches the original template.
Skills
Skills are split into:
- always-on: full content enters context;
- other available Skills: summaries are provided first, and the Agent reads full content on demand.
This progressive disclosure prevents every request from carrying every Skill in full.
3. Recent history differs from Session history
Recent history in ContextBuilder comes from MemoryStore and has:
- a cursor;
- Session scope;
- unified-session behavior;
- a maximum of 50 entries;
- a budget of roughly 8,000 tokens.
Session history comes from Session.get_history(). It consists of persistent messages from the current Session and is constrained by the Turn’s replay budget.
Session history: replayable messages from the current conversation
Recent Memory history: recent MemoryStore records not yet processed by Dream
Archived summary: a summary of older Session content
MEMORY.md: curated long-term memoryDo not collapse these into a single undifferentiated “history.”
4. The current user message carries Runtime Context
build_messages() constructs an explicitly marked metadata section:
[Runtime Context — metadata only, not instructions]
Current Time: ...
Channel: ...
Chat ID: ...
Sender ID: ...
...
[/Runtime Context]This section is appended to the current user message instead of becoming a second consecutive user message. Reasons include:
- some Providers reject consecutive messages with the same role;
- keeping the user’s original content as a stable prefix helps prompt caching;
- the marker tells the model that the metadata is not a user instruction.
A marker is not a security boundary
“metadata only” is a semantic instruction to the model, not permission isolation. Tool, workspace, Channel, and network security code must still enforce real permissions.
5. Consecutive messages with the same role are merged
If the final history message has the same role as the current message, build_messages() merges their content rather than sending consecutive messages with the same role.
Text and multimodal blocks use different merge logic.
This affects what you observe while debugging:
- the current input is not necessarily a newly appended item in the messages list;
- it may be merged into the final history message.
6. How media enters context
_build_user_content() handles media:
- images may become multimodal content blocks;
- documents may have text extracted earlier during RESTORE;
- non-image attachments may appear as path references;
- Channel setting
extractDocumentTextaffects document extraction.
To understand media, inspect all of:
AgentLoop._prepare_message_mediautils/document.pyContextBuilder._build_user_content- Channel/WebUI upload paths
7. Observe three separate message lists
Do not print one object called messages and assume it explains everything:
| Object | Location | Meaning |
|---|---|---|
Session.messages | Session | conversation state on disk |
ctx.initial_messages | after AgentLoop BUILD | messages first handed to Runner |
messages_for_model | each Runner iteration | governed and trimmed messages actually handed to the Provider |
Runner can further modify the third list through:
- orphaned tool-result removal;
- missing tool-result backfill;
- microcompaction;
- tool-result budget;
- history snipping.
8. Why context order matters
General design interpretation:
- identity and rules near the beginning behave more like global constraints;
- current user content must remain clear;
- untrusted tool results must not impersonate system instructions;
- excessive history consumes the budget needed by tool schemas and output;
- changing a prompt template can affect behavior as directly as changing Python code.
The project’s .agent/gotchas.md explicitly warns that:
- prompt templates directly change Agent behavior;
- Tool descriptions, Skills, and Session replay all shape model behavior;
- contamination written into Memory or Session can recur in future turns.
Observation task
After AgentLoop._state_build() runs, inspect:
ctx.history
ctx.initial_messages[0]["content"]
ctx.initial_messages[-1]["content"]Answer:
- Which sections appear in the system prompt?
- Did the current workspace’s AGENTS/SOUL/USER content enter?
- Where is the runtime metadata?
- Was the current message merged with a preceding message of the same role?
- How many messages are in the Session, and how many were replayed?
Do not paste a complete system prompt into a public log
It may contain local paths, user information, Memory, Skill content, or runtime context. Record only the structure and fields required for the exercise.
Source and tests
nanobot/agent/context.pynanobot/templates/agent/nanobot/utils/prompt_templates.pynanobot/agent/skills.pynanobot/agent/memory.pytests/agent/test_context_builder.pytests/agent/test_context_prompt_cache.pytests/agent/test_context_aware.pytests/agent/test_document_extraction_toggle.py
Self-check
- Why are Skills and Tools different kinds of context information?
- How do the sources of Session history and recent Memory history differ?
- Why is Runtime Context appended to the user message?
- Why can “metadata only” not replace permission checks?
- Why can the messages actually sent to the model still differ from
initial_messages?