AI
Agents
Open Source

DeepSeek Harness Agent Loop 2026: Inside the Replaceable Plugin That Drives Every Turn

A deep dive into the DeepSeek Harness agent loop: ReactLoopAgent, the turn/step model, the idle-maintenance-running phase machine, every durable session event, and the four extension points where plugins intercept requests, tools, and turns.

CurateClick Teamยท

title: "DeepSeek Harness Agent Loop 2026: Inside the Replaceable Plugin That Drives Every Turn" description: "A deep dive into the DeepSeek Harness agent loop: ReactLoopAgent, the turn/step model, the idle-maintenance-running phase machine, every durable session event, and the four extension points where plugins intercept requests, tools, and turns." slug: "deepseek-harness-agent-loop-2026" canonical: "/blog/deepseek-harness-agent-loop-2026" keywords:

  • deepseek harness agent loop
  • deepseek harness agent loop internals
  • deepseek harness reactloopagent
  • deepseek harness plugins
  • dsh plugins date: "2026-08-13"

DeepSeek Harness Agent Loop 2026: Inside the Replaceable Plugin That Drives Every Turn

๐ŸŽฏ Core Takeaways (TL;DR)

  • The DeepSeek Harness agent loop is itself a replaceable plugin: packages/core/agent-loop ships ReactLoopAgent as the default implementation of the Agent interface defined by core/agent โ€” other plugins depend only on the interface, never on the loop, so the whole loop can be swapped out.
  • The DeepSeek Harness agent loop operates on two levels: a turn (zero or more steps, opened when the first input arrives and closed when the model owes nothing) and a step (one model request plus the tools it calls). Model-visible history is never stored separately โ€” it is projected from the append-only session log via deriveMessages().
  • A phase state machine (idle | maintenance | running) gates the driver, and every boundary โ€” turn/start, step/start, user/message, assistant/chunk, tool/call, tool/result, step/end, turn/end โ€” is written to the durable session log, so fork, resume, replay, and telemetry all derive from one event stream.
  • Four extension points make the DeepSeek Harness agent loop interceptable without modification: agent/pre-step (waterfall), agent/request (waterfall), agent/request-error (waterfall), and agent/turn-stopping (serial). Tool execution runs through a three-stage pipeline: tools/pre-execute โ†’ tools/execute โ†’ tools/post-execute.
  • If you are building plugins for the DeepSeek Harness agent loop, the DSH Plugins directory is the place to discover, list, and share them with the ecosystem.

Table of Contents

  1. What Is the DeepSeek Harness Agent Loop?
  2. Two Levels: Turns and Steps
  3. The Phase State Machine
  4. The Full Loop, Step by Step
  5. Tool Execution: The Three-Stage Pipeline
  6. Durable Events vs. Extension Points
  7. Why a Replaceable Agent Loop Matters
  8. The DSH Plugins Ecosystem
  9. FAQ
  10. Conclusion

What Is the DeepSeek Harness Agent Loop?

The DeepSeek Harness agent loop is the driver that keeps an agent working: it reads inputs, asks the model, executes tools, and decides when the agent owes the model another request. The defining fact about it โ€” and the reason it deserves a deep dive โ€” is that the loop itself is a plugin.

In packages/core/agent-loop, the default implementation is ReactLoopAgent, which implements the Agent interface defined by core/agent. Other plugins depend only on that interface, never on the concrete loop class. That single architectural decision means the DeepSeek Harness agent loop can be replaced wholesale: mount a different plugin that implements Agent, and every consumer keeps working. The interface is the contract; the loop is the implementation; the harness is the composition.

This is consistent with the broader "everything is a plugin" philosophy of DeepSeek Harness, which we covered in our earlier guide. The agent loop is not a privileged core component โ€” it is one plugin among many, and it participates in the same event system as everything else.

๐Ÿ’ก Professional Tip: When you read the code, start from the Agent interface in core/agent before reading ReactLoopAgent. Everything the loop does is a response to that contract, and the interface is what your own loop plugin must satisfy.

Two Levels: Turns and Steps

The DeepSeek Harness agent loop organizes work into two nested levels: turns and steps.

  • A turn is zero or more steps. It opens when the first input arrives and closes when the loop "no longer owes the model anything" โ€” that is, when there is no pending input and no tool result that requires another model request.
  • A step is one model request plus the tools it calls. Each step is a single round trip through the model, followed by whatever tool calls the model requested.

The model-visible history is not stored separately. Instead, it is projected from the append-only session log by deriveMessages(). Everything the model can see must be reconstructible from the log โ€” this is a hard invariant of the architecture, documented in docs/architecture.md. The practical consequence: the session log is the single source of truth, and the model's context window is always a derived view of it.

LevelDefinitionBoundary Events
TurnZero or more steps; opened by first input, closed when the model owes nothingturn/start, turn/end
StepOne model request + the tools it callsstep/start, step/end
MessageA user or assistant message within a stepuser/message, assistant/message

The Phase State Machine

The driver of the DeepSeek Harness agent loop is gated by a small state machine: Phase = idle | maintenance | running.

  • idle โ€” no driver is running; the agent is waiting for input.
  • maintenance โ€” a transient state used while the driver is being torn down or prepared.
  • running โ€” the driver is active across the entire drain interval, which can span multiple consecutive turns.

State transitions emit agent/status events, so any plugin can observe the loop's lifecycle without touching it. This is a recurring pattern in the DeepSeek Harness agent loop: state changes are events, and events are the extension surface.

The Full Loop, Step by Step

Here is the complete flow of the DeepSeek Harness agent loop, mapped to the actual code in packages/core/agent-loop/src/agent.ts.

Step 0: Wake and drive

Inputs enter through send, followup, steer, or inject, which push messages into the Inbox's two ordered queues (next-turn and next-step). followup and steer also wake the driver.

  • wakeDriver() โ€” when idle, it claims a running phase (fresh AbortController, turn = previous turn, step = 0) and runs kick() inside ctx.agents.withInitiator(this, โ€ฆ).
  • kick() โ€” while (await this.turn()) {}: as long as there is pending input, a new turn is opened.

Step 1: Open a turn

turn() first appends turn/start to the session โ€” the durable open boundary. Then it loops over steps.

Step 2: Pre-step โ€” claim input and interception decision

preStep() does three things:

  1. inbox.claim(target, turn) โ€” claims the input batch for this step (all next-step messages, plus one next-turn message at turn boundaries). Claiming is a pure splice-delete; each claimed message emits agent/inbox/claimed.
  2. ctx.systemPrompt.assemble(...) โ€” assembles the prompt sections and tool schema.
  3. dispatch.waterfall('agent/pre-step', โ€ฆ) โ€” the first extension point. Listeners can reject (no step opens; the turn ends as blocked) or enter and rewrite the message batch; the default enter uses the claimed messages. The result is { reject } or { enter, messages, assembly }.

๐Ÿ’ก Special case: if the first step is rewritten to empty, the turn still occupies its boundary but does not spend a model call โ€” it ends as completed.

Step 3: Open a step and persist user messages

  • session.append('step/start', { turn, step }) โ€” durable.
  • Each message in decision.messages is appended as user/message โ€” durable. Claimed inputs become durable user messages only at this point.

Step 4: Build the request, stream, assemble, call tools

step() runs an inner loop (which supports retries):

4a. Build the request โ€” buildRequest():

  • dispatch.waterfall('agent/request', โ€ฆ) โ€” the second extension point. Listeners can replace the frozen call configuration (provider, model, reasoningEffort, maxTokens); the default uses agent options or the recorded header.
  • ctx.llm.prepareCall(config, signal) โ€” binds to the concrete adapter and materializes exact-model defaults.
  • session.append('request/header', โ€ฆ) and request/context (only when it changes) โ€” durable, so the log can rebuild the request.
  • The frozen request is assembled: config + deriveMessages() history + system + tools + sessionId + signal.

4b. Stream:

  • preparedCall.stream(request) ?? ctx.llm.stream(request) โ€” send the request and read the stream.
  • for await chunk โ€” each chunk is appended as assistant/chunk (durable, preserving raw stream fidelity for replay and UI) and fed to the BlockAssembler.

4c. Finish dispatch โ€” assembler.finish:

  • error / aborted โ†’ dispatch.waterfall('agent/request-error', โ€ฆ) โ€” the third extension point. A listener returning { kind: 'retry' } (without calling next) retries the step; the default undefined lets the failure terminate (throwing LlmError).
  • max-tokens โ†’ append assistant/message, return { kind: 'max-tokens' }.
  • Normal โ†’ append assistant/message with { turn, step, message, usage } and sourceEventSeqs referencing the corresponding chunks โ€” durable.

4d. Tool calls:

  • Filter tool-call blocks from the assistant message. None โ†’ return { kind: 'completed' } (the step ends; the model owes nothing).
  • Some โ†’ executeToolCalls(...) in tool-calls.ts.

Step 5: Close the step, decide whether to open another

  • finally: session.append('step/end', { turn, step }) โ€” durable.
  • If the step produced a terminal result (turnEnds is non-null) and the inbox has no next-step input:
    • dispatch.serial('agent/turn-stopping', { turn, signal }) โ€” the fourth extension point (serial, no next). A listener that objects calls agent.steer(...) to inject steering; the machine re-reads the inbox and opens another step. If nobody objects, the turn closes.
  • If there is next-step input โ†’ target = 'next-step', back to step 2 for another step (tool continuation).

Step 6: Close the turn, decide whether to open another

  • finally: session.append('turn/end', { turn, reason: turnEnds }) โ€” durable. reason is one of completed / max-tokens / blocked / aborted / error.
  • If the inbox still has pending input โ†’ reset the AbortController, step = 0, return true (open a new turn). Otherwise โ†’ back to idle.

Tool Execution: The Three-Stage Pipeline

Tool scheduling in the DeepSeek Harness agent loop groups calls by execution mode: mutually exclusive calls are a barrier, parallel calls use a bounded rolling pool (maxParallelToolCalls). Each call runs through a three-stage pipeline, with events mounted on the ctx.tools scheduler โ€” the attachment point for policy, timeouts, and observability:

tools/pre-execute โ†’ tools/execute โ†’ tools/post-execute
  • prepare (pre-execute) may short-circuit into a direct result.
  • dispatch (execute) runs the tool.
  • finalize / finish (post-execute) wrap up.

Two details are worth calling out. First, results are committed in model order (commitReady advances across consecutive slots), not in completion order โ€” the model's view of the world stays consistent. Second, tool/call is appended before dispatch (durable), and tool/result is appended after post-execute (durable, referencing the corresponding call seq). A result's additionalContexts go into the next-step inbox and become the context for the next step boundary; a result with concludesTurn ends the turn early.

The return value is { concluded }: concluded โ†’ { kind: 'completed' }; otherwise null is returned, meaning the tools still owe the model a request โ€” back to 4a for another round.

Durable Events vs. Extension Points

The whole design of the DeepSeek Harness agent loop can be summarized in one diagram: durable events (the record) versus extension points (the seams).

turn/start (durable)
  claim inbox + assemble prompt
  โ”€ agent/pre-step (waterfall) reject | enter(messages) โ”€
  step/start (durable)
  user/message* (durable)
  โ”€ agent/request (waterfall) swap config โ”€
  llm/stream โ†’ assistant/chunk* (durable) โ†’ assistant/message (durable)
  tool/call* (durable) โ†’ tools/pre-execute โ†’ tools/execute โ†’ tools/post-execute โ†’ tool/result* (durable)
  โ”€ agent/request-error (waterfall) retry on failure โ”€
  step/end (durable)
  tools still owe a request or next-step input โ†’ open another step
  โ”€ agent/turn-stopping (serial) close turn unless continued โ”€
turn/end (durable)
Extension PointKindWhat a Plugin Can Do
agent/pre-stepwaterfallReject the step, or enter and rewrite the message batch
agent/requestwaterfallReplace the frozen call config (provider, model, effort, tokens)
agent/request-errorwaterfallReturn { kind: 'retry' } to retry the failed step
agent/turn-stoppingserialObject by steering; the loop re-reads the inbox and opens another step

The lines marked โ”€ โ”€ are extension points where plugins can hook in (waterfalls must call next() to pass through). Everything else marked (durable) is a persisted event written to the session log โ€” fork, resume, transcription, and telemetry all derive from this one stream. The meaning of the design: swapping an adapter, adding policy, or intercepting requests, tools, and turns is a matter of mounting events or replacing a provider โ€” never of modifying the loop itself.

Why a Replaceable Agent Loop Matters

The DeepSeek Harness agent loop being a plugin is not an implementation detail; it is the product. Three consequences follow:

  1. No fork required. Teams that need a different loop behavior โ€” a different retry policy, a different tool-scheduling strategy, a different turn-closing heuristic โ€” write a plugin that implements Agent, rather than maintaining a fork of the harness.
  2. Policy lives at the seams. Timeouts, approval gates, rate limits, and observability attach to the ctx.tools scheduler and the four extension points, not inside the loop's code.
  3. The log is the contract. Because every model-visible artifact is reconstructible from the append-only session log, any loop implementation โ€” default or custom โ€” can be audited, replayed, and resumed through the same tooling.

โœ… Best Practice: Before writing a custom loop, check whether your need is actually a policy that can be mounted on an existing extension point. The DeepSeek Harness agent loop is designed so that most customizations never touch the loop at all.

The DSH Plugins Ecosystem

Because the DeepSeek Harness agent loop and every other capability are plugins, the ecosystem around them matters as much as the core. The DSH Plugins directory is the community hub for discovering and listing plugins for DeepSeek Harness โ€” including loop replacements, tool adapters, model providers, and policy plugins. If you are building for the DeepSeek Harness agent loop, listing your plugin there makes it discoverable to the wider community, and browsing it before you build can save you from reinventing an existing plugin.

FAQ

Q: Is the DeepSeek Harness agent loop really a plugin?

A: Yes. packages/core/agent-loop ships ReactLoopAgent as the default implementation of the Agent interface from core/agent. Other plugins depend only on the interface, so the entire loop can be replaced by mounting a different plugin.

Q: What is the difference between a turn and a step in the DeepSeek Harness agent loop?

A: A turn is zero or more steps, opened when the first input arrives and closed when the model owes nothing. A step is one model request plus the tools it calls. Turns and steps both have durable start/end events in the session log.

Q: Where does the model's history come from?

A: It is not stored separately. The DeepSeek Harness agent loop projects the model-visible history from the append-only session log via deriveMessages(). Everything the model can see must be reconstructible from the log.

Q: What extension points does the DeepSeek Harness agent loop expose?

A: Four: agent/pre-step (waterfall โ€” reject or rewrite the input batch), agent/request (waterfall โ€” replace the call config), agent/request-error (waterfall โ€” retry on failure), and agent/turn-stopping (serial โ€” object to closing the turn by steering).

Q: How are tool results ordered?

A: Results are committed in model order (commitReady advances across consecutive slots), not in completion order, so the model's view of the world stays consistent.

Q: Where can I find plugins for the DeepSeek Harness agent loop?

A: The DSH Plugins directory lists community plugins for DeepSeek Harness, including loop replacements, tool adapters, and policy plugins.

Conclusion

The DeepSeek Harness agent loop is a masterclass in replaceable architecture. By defining an Agent interface, shipping ReactLoopAgent as one implementation among many, persisting every boundary to an append-only log, and exposing four waterfall/serial extension points, the loop turns what is usually the most rigid part of an agent harness into the most flexible one.

The practical takeaway for developers: you do not need to understand the loop to use it, but understanding it unlocks the seams. Intercept a request at agent/request, retry a failure at agent/request-error, gate a turn at agent/turn-stopping, or replace the whole loop with your own Agent implementation. And when you build something worth sharing, the DSH Plugins directory is where the ecosystem finds it. The loop is the heart of DeepSeek Harness โ€” and it is a heart you can transplant.