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-loopshipsReactLoopAgentas the default implementation of theAgentinterface defined bycore/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), andagent/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
- What Is the DeepSeek Harness Agent Loop?
- Two Levels: Turns and Steps
- The Phase State Machine
- The Full Loop, Step by Step
- Tool Execution: The Three-Stage Pipeline
- Durable Events vs. Extension Points
- Why a Replaceable Agent Loop Matters
- The DSH Plugins Ecosystem
- FAQ
- 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
Agentinterface incore/agentbefore readingReactLoopAgent. 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.
| Level | Definition | Boundary Events |
|---|---|---|
| Turn | Zero or more steps; opened by first input, closed when the model owes nothing | turn/start, turn/end |
| Step | One model request + the tools it calls | step/start, step/end |
| Message | A user or assistant message within a step | user/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()โ whenidle, it claims arunningphase (freshAbortController,turn= previous turn,step= 0) and runskick()insidectx.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:
inbox.claim(target, turn)โ claims the input batch for this step (allnext-stepmessages, plus onenext-turnmessage at turn boundaries). Claiming is a pure splice-delete; each claimed message emitsagent/inbox/claimed.ctx.systemPrompt.assemble(...)โ assembles the prompt sections and tool schema.dispatch.waterfall('agent/pre-step', โฆ)โ the first extension point. Listeners canreject(no step opens; the turn ends asblocked) orenterand rewrite the message batch; the defaultenteruses 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.messagesis appended asuser/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', โฆ)andrequest/context(only when it changes) โ durable, so the log can rebuild the request.- The frozen
requestis 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 asassistant/chunk(durable, preserving raw stream fidelity for replay and UI) and fed to theBlockAssembler.
4c. Finish dispatch โ assembler.finish:
error/abortedโdispatch.waterfall('agent/request-error', โฆ)โ the third extension point. A listener returning{ kind: 'retry' }(without callingnext) retries the step; the defaultundefinedlets the failure terminate (throwingLlmError).max-tokensโ appendassistant/message, return{ kind: 'max-tokens' }.- Normal โ append
assistant/messagewith{ turn, step, message, usage }andsourceEventSeqsreferencing the corresponding chunks โ durable.
4d. Tool calls:
- Filter
tool-callblocks from the assistant message. None โ return{ kind: 'completed' }(the step ends; the model owes nothing). - Some โ
executeToolCalls(...)intool-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 (
turnEndsis non-null) and the inbox has nonext-stepinput:dispatch.serial('agent/turn-stopping', { turn, signal })โ the fourth extension point (serial, nonext). A listener that objects callsagent.steer(...)to inject steering; the machine re-reads the inbox and opens another step. If nobody objects, the turn closes.
- If there is
next-stepinput โ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.reasonis one ofcompleted/max-tokens/blocked/aborted/error.- If the inbox still has pending input โ reset the
AbortController,step = 0, returntrue(open a new turn). Otherwise โ back toidle.
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 Point | Kind | What a Plugin Can Do |
|---|---|---|
agent/pre-step | waterfall | Reject the step, or enter and rewrite the message batch |
agent/request | waterfall | Replace the frozen call config (provider, model, effort, tokens) |
agent/request-error | waterfall | Return { kind: 'retry' } to retry the failed step |
agent/turn-stopping | serial | Object 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:
- 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. - Policy lives at the seams. Timeouts, approval gates, rate limits, and observability attach to the
ctx.toolsscheduler and the four extension points, not inside the loop's code. - 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.