Source reading and debugging minimum

Learning objectives

After this lesson, you should be able to:

  • trace source from an entry point instead of guessing from class names;
  • use rg to find definitions, callers, configuration, and tests;
  • run the pytest test closest to a problem;
  • choose meaningful breakpoints and variables to inspect;
  • distinguish proven facts, reasonable inferences, and unknowns.

Prerequisites

  • Use PowerShell.
  • Run Python.
  • Read a basic traceback.

1. Start with a traceable question

Do not begin by trying to “read the entire agent/ directory.” Choose one concrete question, such as:

  • How does one CLI message enter AgentLoop?
  • Where is the session key determined?
  • Where is a Tool Call executed?
  • Where does API streaming close?
  • Why can one session not be written concurrently?

The more specific the question, the smaller the reading scope and the more reliable the resulting call chain.

2. Begin at the entry point and its callers

Use this order:

Entry point
  → Which core objects does it create?
  → Which public method does it call?
  → How does that method enter a state machine or loop?
  → Where is state saved?
  → Who receives the output?

For example, to trace one CLI message:

  1. find the agent command in nanobot/cli/commands.py;
  2. locate the process_direct call;
  3. enter AgentLoop.process_direct();
  4. enter _process_message();
  5. inspect the TurnState transitions;
  6. enter _run_agent_loop();
  7. inspect AgentRunner.run();
  8. return to _state_save() and _state_respond().

3. Search with rg

Run these commands from example/nanobot/code-EN:

rg -n "class AgentLoop|def process_direct" nanobot
rg -n "process_direct\\(" nanobot tests
rg -n "session_key" nanobot/session nanobot/agent tests
rg -n "should_execute_tools" nanobot tests

A useful search order is:

  1. definition;
  2. all callers;
  3. configuration fields;
  4. related tests;
  5. error or log text.

Search the log text

When runtime output contains a clear log or error, search for a short, stable fragment of that text. It is often faster than guessing the function name.

4. Tests are executable implementation notes

A test can prove that a boundary is deliberately protected by the project. For example:

QuestionTest to read first
How AgentLoop connects to Runnertests/agent/test_loop_runner_integration.py
How Tool Calls executetests/agent/test_runner_tool_execution.py
How Runner handles errorstests/agent/test_runner_errors.py
Whether Session saving is atomictests/agent/test_session_atomic.py
Runtime Event ordertests/bus/test_runtime_events.py
Channel plugin discoverytests/channels/test_channel_plugins.py
Subagent lifecycletests/agent/test_subagent_lifecycle.py

Run one test file:

Set-Location "example\nanobot\code-EN"
pytest tests/agent/test_loop_runner_integration.py -q

Run one test function:

pytest tests/agent/test_runner_tool_execution.py::test_name -q

First verify the real function name with rg -n "^def test_|^async def test_"; do not invent it from the lesson.

5. Put breakpoints at state boundaries

For a normal Turn, inspect these locations first:

LocationObserve
AgentLoop.process_direct or _dispatchentry point, session key, metadata
_process_messageinitial TurnContext state
_state_restoreSession, checkpoint, media
_state_buildhistory, initial messages, tool context
AgentRunner._run_coreiteration, response, tool calls
_execute_toolstool selection, arguments, concurrency strategy
_state_savenew messages, stop reason, latency
_state_respondOutboundMessage

Do not step into a Provider SDK or WebUI component first. Identify the core state boundary where behavior diverges from expectation.

6. Observe three message sets

When debugging context, distinguish at least:

  1. Session.messages: the persistent conversation;
  2. ctx.initial_messages: the initial input to AgentRunner;
  3. messages_for_model: the governed and trimmed messages actually sent to the model.

Many “the model forgot” or “the tool result disappeared” problems originate in the differences among these three views.

7. Record evidence, not only conclusions

Use a note structure like this:

### Observation
 
- Entry: `nanobot/cli/commands.py::agent`
- Call: `AgentLoop.process_direct`
- State: `BUILD`
- Variable: `ctx.initial_messages`
- Test: `tests/agent/test_loop_runner_integration.py`
 
### Conclusion
 
Source fact: ...
 
### Not yet proven
 
- Whether every Channel supplies the same metadata
- Whether external plugins guarantee the same lifecycle

8. Do not modify the source snapshot to test a guess

During the core learning path, prefer:

  • breakpoints;
  • existing logs;
  • temporary interactive inspection;
  • existing tests;
  • minimal read-only scripts.

In the Source Practice extension tasks, make explicit, local, verifiable changes only inside learning-EN/examples or an isolated copy. Keep the code-EN source snapshot read-only throughout.

Windows notes

This workspace uses PowerShell. When reading and running nanobot:

  • reason about paths with pathlib.Path; do not assume /;
  • nanobot’s ExecTool uses cmd /c on Windows;
  • the CLI handles UTF-8 stdout/stderr;
  • MCP stdio commands include Windows path normalization;
  • do not apply Linux bubblewrap behavior to Windows.

Related notes:

  • example/nanobot/code-EN/.agent/gotchas.md
  • example/nanobot/code-EN/.agent/security.md

Self-check task

Without modifying code:

  1. find every production caller of AgentLoop.process_direct;
  2. find the definition of InboundMessage.session_key;
  3. find where ToolLoader scans external entry points;
  4. find where Session atomically replaces its target file;
  5. locate at least one matching test.

Completing this task gives you the minimum source-reading ability needed to begin tracing nanobot.

Next step

Return to the learning roadmap and continue to Runtime and Source Map.