Skip to content

CH 09 · Core Subsystems and the Flow of One Message

Word count~2,730 wordsTime~14 minPrereqCH 08 plugin treeLevelConceptual

Chapter Goal

CH 08 explained that dsh is "a plugin tree"; this chapter zooms in: what each key plugin (subsystem) on the tree handles, how they coordinate, then follows one message all the way through — from when you press Enter to when it replies, what happens in between. Once you see these two things, you'll know how dsh works internally.

Core Subsystems: A Restaurant's Back Kitchen Team

Continuing the small restaurant from CH 08: the kitchen is not one person, but a team with clear divisions of labor, each handling their own work, passing things to each other. dsh's core subsystems are this team:

SubsystemStaff counterpartWhat it handles
agent-loopHead chefThe default driver: decides "what to do next" — request the model, call tools, collect results; the whole loop is driven by it
llmTakeout line operatorConnects to external models via adapters, sends messages out, takes replies back
toolsTool cabinet + gatekeeperTool registry + controlled execution pipeline; each agent uses its own (scope isolation)
system-promptPrep platterStitches the prompt fragments and tool schemas registered by various plugins into "what this plate shows the model"
sessionAccount bookAppend-only session log: what you sent, what the model replied, what tools ran — all on record
scopeWorkstation isolationMakes each agent's registration only effective in its own cell, no cross-talk

How they coordinate, look at this diagram:

How core subsystems collaborate (illustration)

A mnemonic: the head chef (agent-loop) gives orders, the platter (system-prompt) preps, the line operator (llm) calls for takeout, the tool cabinet (tools) hands out tools, and all results are recorded in the account book (session). The account book is the star of the next chapter.

A Key Unit: step and turn

Before looking at the flow, first distinguish two terms; they are the foundation for understanding the whole chain:

  • step = one model request + the tools it calls. The model "thinks + acts" once is one step.
  • turn = zero or more steps. From picking up your first input until no more work is owed.

Why might one reply have several steps? Because the model often does "think a bit → call a tool → see the result and think again → call another..." until it feels it's enough, then gives the final reply. This whole chain is one turn, and each "think + act" inside is one step. The official words: a turn opens before picking up the first input, and closes when no work is owed.

A real-life analogy: you order one meal (turn), the waiter may come and go several times (step) — first confirm if the dish is available, then serve, then ask if you want extra — until the order is fully done.

A Message's Flow: From Your Enter Key to Its Reply

Now walk a message through in full. Follow this diagram top to bottom:

One message's flow: from your Enter key to its reply (illustration)

① turn/start: the turn opens. Before you press Enter, this turn hasn't started; after you press, the turn first opens, entering "pick up input".

② Pick up input (inbox). All inputs go into a mailbox called inbox first. The message you sent will immediately wake the head chef to start working; while "injected context" (the kind you saw in CH 04) stays in the mailbox first, waiting for another real message to wake it — so "injecting data" itself won't interrupt the flow.

③ Assemble: prompt + tool schema. system-prompt stitches the fragments registered by various plugins into a plate, telling the model "who you are, what tools are available, what the tools look like".

④ agent/pre-step: decide what the model sees. This is the first "live extension point" — plugins can rewrite the content to be sent out here, or directly reject it. If rejected or rewritten to empty, the turn still closes and records as usual; the log shows "tried once but didn't run".

⑤ step/start: ledger + take history. The message you sent is written into the session log as user/message; at the same time, the model's history for this turn is projected from the log (deriveMessages()). Everything the model sees comes from the log — this is a line we'll keep coming back to.

⑥ Request the model → receive the reply. agent/request sends the request (interceptable), llm/stream streams back, and the model's reply is written to the log as assistant/message.

⑦ Need to call a tool? If the model decides "I need to act", it's tool/call → execute → result tool/result written back to the log. There are interception points before and after execution (pre/post-execute). If no tool is needed, skip to the next step.

⑧ step/end: more work? If the tool result needs the model to process it again (or new input comes in), open another step, go back to "pick up input"; until no work is owed, go agent/turn-stoppingturn/end, this turn ends, and you see the final reply.

A key distinction to keep in mind: the right side of the diagram marks two kinds of events —

  • Persistent events (turn/*, step/*, user/message, assistant/*, tool/*): written to the session log, still there after restart;
  • Live extension points (agent/pre-step, agent/request, llm/stream, tools/*): only effective while running; plugins use them to intercept and change behavior, but they don't land in the ledger themselves.

This echoes CH 08's "events are extension points": to observe or intercept running work, watch live events; to keep facts around, use persistent events.

Where the Model's Memory Comes From

One line: the session log is the context the model sees. What the model "remembers" in this turn is not something it remembers itself; it's the history the system projects from the log — so "what the model sees is what is recorded" is a hard rule in dsh: anything that reaches a model request must be reconstructable from the log, and the runtime checks this with an invariant. This rule is the axis of the whole design; we'll cover it in CH 10.

A quick cross-reference with earlier: the "Trajectory view" you saw in CH 04 — that row of ASSISTANT / TOOL timeline — under the hood is just a visualization of this session log. What the model said, what tools it called, what the results were — all rendered from the log. So when you ask "why does it remember what I said earlier", the answer is plain: it's not the model that remembers, it's the log that remembers — every time before a query, the system projects the history from the log and feeds it to the model.

What you learned in this chapter

  • [ ] State the divisions of labor of the core subsystems (agent-loop / llm / tools / system-prompt / session), and explain how they coordinate
  • [ ] Explain the difference between step and turn (step = one model request + tools; turn = zero or more steps)
  • [ ] Walk through one message's complete flow along the diagram: turn/start → pick up input → assemble → pre-step → step/start → request model → tool call → step/end → turn/end
  • [ ] Distinguish persistent events from live extension points, and state each's purpose (leaving evidence vs intercepting)
  • [ ] State the meaning of the rule "what the model sees is what is recorded"

Open Source · MIT · Community Driven