What is the dsh agent loop? How one turn runs

From input claim to turn/end: the life cycle of a dsh conversation, the events plugins can intercept, and the guards that keep the loop honest.

Last updated: 2026-10-03

Whether you use dsh in the terminal, the web UI, or the desktop app, the same engine drives every conversation: the agent loop. It takes your message, asks the model what to do next, runs the tools the model picks, and repeats until the turn has nothing left to deliver. If you have read about modes, plugins, or subagents and still wondered what actually happens between your input and the answer, this page is the missing layer underneath.

Everything here follows the official architecture documentation and public discussion threads — sources are linked inline and at the bottom. Where the docs use a specific event name, we use the same name; nothing here is reverse-engineered guesswork. Version-specific behavior always defers to the official changelog.

TL;DR

  • ▸A step is one model request plus the tools it calls; a turn is zero or more steps that opens on input and closes once nothing is owed.
  • ▸The loop is itself a plugin (core/agent-loop is the default driver) — every stage is an event other plugins can intercept.
  • ▸Waterfall events (agent/pre-step, agent/request, llm/stream, tools/*) require listeners to call next(); agent/turn-stopping can stop a turn.
  • ▸Subagents and Agent Teams sit above the loop, not inside it; runaway loops are a plugin-design problem with established guard patterns.

What the agent loop is — and is not

DeepSeek Harness is built on Cordis, a plugin framework where services, typed events, and reversible effects are contributed to one shared context. The official docs state the consequence plainly: every part of the product is a plugin — the model adapter, the tool registry, the session log, and the agent loop itself. core/agent-loop is the default driver implementing the generic Agent interface, and it is replaceable from configuration.

Two definitions do all the work on this page, both from the official turn-flow documentation:

Turn

Zero or more steps. A turn opens before its first input is claimed and closes once nothing is owed.

Step

One model request plus the tools it calls. Three tool round-trips inside one conversation are three steps inside one turn.

Agent loop

The driver that repeats the step cycle: claim input, assemble the prompt, call the model, run tools, decide whether another step is owed.

The life of one turn, stage by stage

The official docs publish the exact event sequence of a turn. The diagram below walks it in order — the mono chips are the real identifiers from the docs, the same names you would listen for in a plugin.

  1. 1

    The turn opens

    durable
    turn/start

    turn/start fires and the loop claims the next-step input plus one queued message from the inbox. Injected context waits for a waking message before it takes effect.

  2. 2

    Prompt and tools are assembled

    Prompt sections and tool schemas are assembled, and runtime context is projected. The model's history comes from the session log, not from a free-floating in-memory array.

  3. 3

    The input gate

    agent/pre-step

    agent/pre-step listeners may rewrite or reject the claimed messages. This is the first waterfall: every listener must call next() to let the chain continue.

  4. ↳

    Rejected input closes the turn

    turn/end

    A rejected message, or a first enter rewritten to empty, closes a durable turn with no step at all.

  5. 4

    One step: the model request

    step/start · llm/stream

    After step/start, agent/request resolves the actual route, the request is derived and frozen from the log, and the reply streams back through llm/stream.

  6. 5

    The tool pipeline

    durable
    tools/pre-execute → execute → post-execute

    Each tool call runs the guarded chain tool/call → tools/pre-execute → tools/execute → tools/post-execute → tool/result. Failed steps record missing tool results.

  7. 6

    Another step?

    step/end

    After step/end the loop checks: if tools owe another request, or new input arrived, it claims the next step and the cycle repeats.

  8. 7

    The stop check

    agent/turn-stopping

    agent/turn-stopping is a serial event with no next(). A listener here can stop the turn — the official mechanism for intercepting a turn.

  9. 8

    The turn closes

    durable
    turn/end

    turn/end fires once nothing is owed. turn/*, step/*, the message events, and tool/* are durable session events — they survive a reload; the rest are live extension points.

Two details are worth remembering: retries inside a step do not repeat prompt assembly or agent/pre-step, and "model-visible means logged" is a hard rule — every model request must be reconstructable from the session log.

Where plugins hook into the loop

The docs never say "hook" — the official word is events, and most of them are waterfalls: listeners receive a context and must call next() to delegate, rewriting the payload first if they need to. Four families matter most, and agent.inject() is the official door for adding model-visible context from outside the loop:

Gate the input — agent/pre-step

Rewrite or reject what the loop just claimed. This event decides the accepted input for the step.

Intercept the request — agent/request, llm/stream

Watch or reshape the model call before it streams; cancelling during this phase commits neither the system prompt nor the user messages.

Wrap every tool — tools/pre-execute, tools/execute, tools/post-execute

The guarded execution pipeline around each tool call — the standard place for approval, logging, and policy plugins.

Stop the turn — agent/turn-stopping

Serial, no next(). Returning a stop decision here ends the turn — the event loop-guard style plugins rely on.

plugin development guide.

Agent loop vs subagents vs Agent Teams

Searches like "dsh agent swarm" mix three different layers. Anchor each layer to the loop and they stop blurring together:

The agent loop

One agent's engine. The turn, the steps, and the events on this page describe a single loop driving a single session.

Subagents

A second layer of agent instances your agent spawns. The docs define subagent providers behind one interface — "from a fresh child agent to a delegated turn in another product" — and each subagent runs its own loop.

Agent Teams (experimental)

An official opt-in coordination seam: a durable roster, task board, and mailbox layered over continuable subagents. Coordination above individual loops.

"Swarm"

A community shorthand for many-agent setups, not an official dsh feature name. Depending on whether coordination is ad hoc or structured, map it to subagents or Agent Teams.

subagents guide.

When the loop misbehaves: loop guards

The loop is designed to end: a turn closes once nothing is owed, and agent/turn-stopping exists precisely so a listener can halt one. Runaway behavior therefore almost always comes from what feeds the loop — usually a plugin that keeps injecting new input.

The best-documented case is official discussion #4819. The community plugin dsh-agent-loop auto-continues when the model answers with reasoning only and no visible text — it injects a notice message to push the loop forward. Those injected messages were written without a message id, so on cold restore the session log failed validation ("session event lacks an identified message") and the whole session became unopenable. The fixes discussed there are the canonical guard set: route synthetic messages through the normal createUserMessage path, and validate event shape at append time — not only at restore.

  • ▸Cap the cycles — a round limit per turn is the first invariant of every loop-guard recipe.
  • ▸Detect no progress — if two consecutive steps change nothing, stop instead of continuing.
  • ▸Trip on the same error twice — a circuit breaker beats an expensive retry storm.
  • ▸Keep an explicit exit gate — the loop should always have a human-visible way out.

context compaction guide.

Frequently asked questions

Short answers about the dsh agent loop — the details are in the sections above.

What is the dsh agent loop in one sentence?

It is the default driver (core/agent-loop) that turns your input into model requests and tool calls, repeating step after step until the turn has nothing left to deliver.

What is the difference between a turn and a step?

A step is one model request plus the tools it calls. A turn is zero or more steps: it opens when input is claimed and closes once nothing is owed. A single turn can contain many steps.

How do plugins hook into the agent loop?

Through events. agent/pre-step gates the input, agent/request and llm/stream sit on the model call, the three tools/* events wrap every tool execution, and agent/turn-stopping can stop the turn. Waterfall listeners must call next() to keep the chain going.

Agent loop vs subagents vs Agent Teams — which one do I need?

For one session's work, the loop alone is enough. Spawn a subagent when a task deserves its own loop; reach for Agent Teams when several agents need a shared roster, task board, and mailbox.

Can the agent loop run away? How do I guard it?

The loop itself ends when nothing is owed, but plugins that keep injecting input can keep it alive. Official discussion #4819 documents a real case; the standard guards are round limits, no-progress detection, a same-error circuit breaker, and an explicit exit gate.

Do I need to learn Cordis before I can follow the loop?

No. The turn flow is readable on its own — the event names and their order are enough for most plugins. Cordis matters when you want to replace services, such as swapping the loop driver itself.

Keep reading

Guides that sit beside the loop or drive it: subagents, modes, presets, and context compaction.

Sources & evidence

Every mechanism on this page follows the official architecture documentation, discussion #4819, and the v0.2.1-alpha.1 release notes — version-specific behavior always defers to the official changelog. The Loop Guard invariants are a community protocol.

DSH Plugins is an independent community directory of DeepSeek Harness plugins. Not affiliated with or endorsed by DeepSeek. Third-party plugins are not security-audited — review the source before installing.

New DeepSeek Harness plugins, weekly. No spam.