ContextBuilder and model context

Corresponding source

SubjectSource
System prompt and messagesnanobot/agent/context.py::ContextBuilder.build_system_prompt, ContextBuilder.build_messages
Bootstrap, Memory, and Skillsnanobot/agent/context.py, nanobot/agent/memory.py, nanobot/agent/skills.py
Prompt templatesnanobot/templates/agent/, nanobot/utils/prompt_templates.py
Session replay historynanobot/session/manager.py::Session.get_history
Call site in BUILDnanobot/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

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:

  1. identity;
  2. workspace bootstrap files;
  3. Tool Contract;
  4. user-customized long-term Memory;
  5. always-on Skills;
  6. summaries of other available Skills;
  7. recent history after the Dream cursor;
  8. 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.md
  • nanobot/templates/agent/platform_policy.md

Bootstrap files

The fixed names are:

  • AGENTS.md
  • SOUL.md
  • USER.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 memory

Do 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 extractDocumentText affects document extraction.

To understand media, inspect all of:

  • AgentLoop._prepare_message_media
  • utils/document.py
  • ContextBuilder._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:

ObjectLocationMeaning
Session.messagesSessionconversation state on disk
ctx.initial_messagesafter AgentLoop BUILDmessages first handed to Runner
messages_for_modeleach Runner iterationgoverned 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:

  1. Which sections appear in the system prompt?
  2. Did the current workspace’s AGENTS/SOUL/USER content enter?
  3. Where is the runtime metadata?
  4. Was the current message merged with a preceding message of the same role?
  5. 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.py
  • nanobot/templates/agent/
  • nanobot/utils/prompt_templates.py
  • nanobot/agent/skills.py
  • nanobot/agent/memory.py
  • tests/agent/test_context_builder.py
  • tests/agent/test_context_prompt_cache.py
  • tests/agent/test_context_aware.py
  • tests/agent/test_document_extraction_toggle.py

Self-check

  1. Why are Skills and Tools different kinds of context information?
  2. How do the sources of Session history and recent Memory history differ?
  3. Why is Runtime Context appended to the user message?
  4. Why can “metadata only” not replace permission checks?
  5. Why can the messages actually sent to the model still differ from initial_messages?

Next step

Read AgentRunner’s model-tool loop.