Trace a Tool-free Turn

Exercise objective

Without modifying source code, trace one CLI message through final Session persistence. You should be able to explain the order of all eight TurnState enum values and why DONE has no state handler.

Prerequisites

  • Complete one real DeepSeek request.
  • Read the chapters on the AgentLoop state machine and AgentRunner.
  • Be able to launch the current source checkout as python -m nanobot from your IDE.

Source to read

Read only these locations, in order:

  1. nanobot/cli/commands.py::agent
  2. nanobot/agent/loop.py::AgentLoop.from_config
  3. nanobot/agent/loop.py::process_direct
  4. nanobot/agent/loop.py::_process_message
  5. nanobot/agent/loop.py::_state_restore through _state_respond
  6. nanobot/agent/runner.py::run
  7. nanobot/session/manager.py::save

Predict before running

Answer these questions first:

  • Does the CLI’s one-shot mode start MessageBus.consume_inbound()?
  • Where is the Session lock acquired?
  • How many Runner iterations occur when there is no Tool Call?
  • Does SAVE occur before or after RESPOND?
  • What is the default Session key?

Procedure

1. Set breakpoints

Suggested breakpoints:

  • before AgentLoop.process_direct() calls _process_message;
  • before _process_message() calls a state handler;
  • in AgentRunner._request_model();
  • in _state_save();
  • in SessionManager.save().

2. Run with a fixed Session

Assume you set this variable in the earlier chapter:

$nanobotLab = Join-Path $env:TEMP "nanobot-learning"

From example/nanobot/code-EN, start the debugger with the equivalent of:

python -m nanobot agent `
  --config "$nanobotLab\config.json" `
  --workspace "$nanobotLab\workspace" `
  --session "learning:no-tool" `
  --message "Do not use any tools. Reply with TURN_OK only."

If the model still attempts a Tool Call, do not change the code. Try a more deterministic message and record that a real model’s choice is not a hard guarantee.

3. Record four field groups at every state

Field groupWhat to observe
ctx.stateThe current state
ctx.session.keyWhether it is learning:no-tool
ctx.history / ctx.initial_messagesWhen Session history becomes model input for this Turn
ctx.all_messages / ctx.final_contentWhat the persistence boundary receives after the Runner returns
ctx.stop_reason / ctx.outboundWhen the run result becomes an OutboundMessage

4. Inspect the trace

Before the Turn finishes, inspect ctx.trace:

  • state order;
  • duration_ms;
  • event;
  • error.

The normal path contains seven handler records. DONE is the loop’s exit condition, so no _state_done() executes.

5. Inspect the persistence file

Do not guess its filename. List it first:

Get-ChildItem -LiteralPath "$nanobotLab\workspace\sessions" -File |
  Sort-Object LastWriteTime -Descending |
  Select-Object -First 3 Name, LastWriteTime

Open the newest JSONL file and confirm that:

  • the first line is metadata;
  • later lines contain at least one user and one assistant message;
  • it contains no credentials;
  • the Session key is correct.

Expected observations

The main path should be:

RESTORE → COMPACT → COMMAND → BUILD → RUN → SAVE → RESPOND → DONE

You should also observe:

  • CLI one-shot mode calls process_direct() directly and does not depend on AgentLoop’s bus-consumer loop;
  • process_direct() still reuses _session_locks;
  • BUILD produces the complete messages sent to the Provider;
  • a one-iteration text response does not enter Tool execution;
  • SAVE persists the Turn before RESPOND constructs the response;
  • the finally block in process_direct() resets Runtime status to idle.

Completion criteria

  • Draw the actual call chain with at least seven functions.
  • Save an observation table of states and key fields.
  • Explain the shared core and distinct entry points of the CLI direct path and Gateway bus path.
  • Locate this Turn’s user and assistant records in Session JSONL.

Verification

Send another message to the same Session:

python -m nanobot agent `
  --config "$nanobotLab\config.json" `
  --workspace "$nanobotLab\workspace" `
  --session "learning:no-tool" `
  --message "What marker did the previous message ask you to return? Reply with the marker only."

If the response uses the previous Turn, Session replay is working. Then prove from the BUILD messages how that history entered model context.

Review questions

  1. Why does the direct path still need a Session lock?
  2. Can RUN duration in the trace be treated as exactly equal to Provider latency?
  3. Do ctx.messages and session.messages serve the same purpose?
  4. If the process crashes before SAVE, which data might exist only in memory or a checkpoint?

Next: Trace a Tool Call and Session