Inside DeepSeek Harness: the agent loop is a replayable protocol, not a while-loop #2269
bobleer
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
DeepSeek Harness has attracted attention for “everything is a plugin.” The more consequential design, however, is lower in the stack: its agent loop treats a run as a replayable protocol over durable events, and delegates policy to capability seams around that protocol.
Disclosure: I contribute to BitFun. This note is based on the current
deepseek-ai/deepseek-harnesssource tree. The comparison near the end explains a different engineering trade-off; it is not an official DeepSeek document.1. The unit of work is not one prompt
DSH separates a turn from a step. A step is one model request plus the tool calls emitted by that request. A turn can contain multiple steps and closes only when no tool result, injected context, or queued next-step input is owed.
The durable path is roughly:
This distinction matters. Streaming chunks, the assembled assistant message, tool calls, tool results, and turn boundaries are not merely UI state. They are session facts.
deriveMessages()reconstructs the next request from that log, so resume, fork, transcript, telemetry, and UI replay share one source of truth.The architectural invariant is explicit: anything model-visible must be reconstructable from the log. A plugin cannot silently inject context into a request and leave no durable explanation for a later replay.
Source trail:
docs/architecture.md— Turn flow, Session logpackages/core/agent-loop/src/agent.ts—turn(),step(),buildRequest()2. The loop is small because the seams carry policy
The driver owns sequencing: claim input, open a turn, open a step, call the model, record output, dispatch tools, decide whether another step is owed. It does not hard-code every product rule.
Live waterfalls surround the durable path:
agent/pre-stepcan reject or rewrite claimed input;agent/requestcan adapt a model request;llm/streamselects the provider path;agent/request-errorcan decide whether to retry;agent/turn-stoppingcan enqueue final work before the turn closes.This division gives DSH its real composability. The loop stays legible while model routing, goals, prompt assembly, and error policy remain replaceable. The cost is that registration order and
next()semantics become part of system behavior. “Plugin” here is not just package distribution; it is control-flow composition.3. Tool execution is a staged protocol
A model-emitted tool call does not jump directly into an executor. The registry takes it through six boundaries:
Each stage has a distinct job.
pre-executeis the extensible allow / deny / ask gate.tools/executeis an around-dispatch seam for timeout, retry, metrics, or sandbox wrappers.post-executemay inspect or replace a normalized result and attach deferred context.finalizeContentbelongs to the tool definition and controls final model-facing content.tools/resultobserves the settled fact; it does not rewrite history.That separation prevents a common agent-runtime failure: one hook both executes an action, mutates policy, formats the result, and updates the UI, leaving no stable boundary for replay or auditing.
Source trail:
packages/core/tools/README.mdpackages/core/tools/src/index.ts4. native, code, and both are presentation choices—not different safety models
DSH can present tools to the model in three forms:
native: normal function definitions;code: only the reservedrun_codetransport plus a generated SDK;both: both surfaces.In Code Mode, a direct model call to another tool is rejected before policy. But an SDK sub-call made inside
run_codere-enters the same tool registry and execution pipeline. Code Mode changes how the model composes calls; it does not grant a bypass around approval or result normalization.This is a useful design restraint. Tool presentation and tool authority remain separate axes.
It also prevents an easy marketing mistake: Code Mode is not automatically cheaper. The direct schema surface shrinks, but the generated SDK moves into the system prompt. Total request cost has to be measured across prompt text and tool schemas together.
5. Cancellation and timeout are deliberately cooperative
A tool may declare
timeoutMs, but the declaration alone does not enforce anything. The timeout-policy wrapper must be mounted, and it propagates a derived abort signal. A tool that ignores that signal does not stop.This is not a defect hidden in the implementation; the package documents it as a boundary. Same-process JavaScript cannot be safely hard-killed by an arbitrary wrapper. The runtime therefore distinguishes cancellation notification from termination ownership.
Likewise, user approval is a channel-neutral one-shot seam. It records asked/decided audit events, fails closed when no answerer is available, and currently grants only the requested action once. It is not a durable “always allow” store.
Source trail:
packages/guard/timeout-policy/README.mdpackages/interaction/user-approval/README.md6. Where BitFun makes a different trade-off
BitFun also treats tool execution as a product boundary, but moves more decisions into typed Rust contracts and a product-level pipeline.
Before execution, it validates the allowed-tool list, runtime restrictions, deferred-tool generation, input shape, and provider-neutral
PermissionIntents. It then partitions calls by contextual concurrency safety: consecutive safe calls may run together, while writes and other unsafe calls become ordered barriers. Retry, timeout ownership, streaming state, hooks, remembered project grants, and result materialization are integrated into the same pipeline.The difference is not “DSH has plugins, BitFun has safety.” DSH also has sandbox and approval. It is a question of where variability lives:
That is why we are exploring DSH interoperability rather than reimplementing DSH inside BitFun. A composable plugin graph and an operable workstation solve different parts of the problem.
BitFun source trail:
src/crates/execution/tool-contracts/src/execution_gate.rssrc/crates/execution/tool-contracts/src/permission_intent.rssrc/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rssrc/crates/assembly/core/src/agentic/execution/round_executor.rs7. What to measure next
A fair harness comparison should fix the model, provider configuration, repository, task, and source revision. Then measure at least:
Without those controls, “the same model feels smarter in harness A” mixes model quality, prompt assembly, tool presentation, and execution semantics into one anecdote.
DSH is still a developer preview, so these boundaries may change. That is precisely why its current source is useful: it exposes which invariants the authors are choosing before the ecosystem hardens around them.
BitFun is MIT-licensed at https://github.com/GCWing/BitFun. If this kind of source-level comparison is useful, a star is a clear signal to keep publishing the experiments and the code behind them.
All reactions