From a Prompt to a Durable Turn
In DeepSeek Harness, the important unit of work is not a single request sent to a model. It is a lifecycle that admits input, prepares a route, records what became visible, runs any requested tools, and reaches a defined stopping point. This chapter follows that lifecycle so readers can distinguish a turn from a step, see where cancellation and retry enter, and understand why a live stream is not the same thing as durable history.
That distinction is practical. A browser wants text as soon as it arrives. A persistence backend needs a settled record that can be read after a restart. A retry mechanism must know whether a failed request has already produced model-visible history. DeepSeek Harness handles these needs by giving live control to the Agent and its loop while making the Session log the authority for the request history that the model can see.
A turn opens around admitted work
The public Agent handle is the entry point for live work. A caller can queue an ordinary follow-up, steer the next step, inject context that will be used later, cancel the current activity, or wait for the whole Agent to become idle. The handle does not mean that a particular message has already run. A follow-up enters an inbox, and the loop decides when that input can be claimed.
A turn is one drain of admitted work. It starts before the loop claims the next input and ends when the model and its tools have stopped, or when a terminal policy closes it. A turn can contain no steps—for example, if a pre-step policy rejects the proposed input—and it can contain several steps when tool calls or new next-step messages require more model requests.
A step is narrower: one model request plus the tool executions caused by its response. The difference gives each operation a useful owner. Retry belongs to a failed model request inside an open step. Compaction pressure can be checked before a new step. A tool call can require another model request without creating a new turn. If an engineer treats every request as a separate turn, they lose the meaning of queued work, tool loops, and terminal outcomes.
At the start of a turn, the driver claims the pending next-step input and one queued prompt. Claiming removes those items from the inbox. Messages inserted after that claim remain pending rather than being silently mixed into the current batch. Between steps, the loop claims next-step input only. This is how DeepSeek Harness separates work that is already admitted to the current execution from work that must wait for a later boundary.
The loop then assembles the current system-prompt sections and visible tool schemas and projects the runtime context for the proposed step. The assembly can include global contributions and agent-scoped contributions. The result is not yet automatically a committed model request. It passes through the live agent/pre-step waterfall.
Pre-step admission is a real decision
The pre-step stage can accept the proposed messages, rewrite them, or reject them. A policy or hook may decide that no model request should be made. A context provider may add a message that belongs in the next admitted step. Compaction may replace an overgrown surface before request derivation. The returned decision is authoritative for that step.
This ordering protects the Session log from claims that were never admitted. A rejected first batch closes the turn without a step. If the first batch becomes empty after a rewrite, the same result applies. If cancellation wins while the loop is still in prompt assembly or request preparation, the pending system prompt and accepted users are not committed. The model should not later see a user message merely because a caller submitted it before an asynchronous operation was cancelled.
Once the decision enters, the loop opens step/start and runs the request waterfall. It then asks the LLM service to prepare a call for the selected provider and model. This is where the actual route—not just an earlier setting or remembered provider name—can validate its capabilities.
Request preparation binds the route
DeepSeek Harness puts prepareCall() before model-visible admission because the exact model affects what the request is allowed to contain. The adapter can validate a reasoning effort, resolve an output-token default, report a context window, declare image support, and identify how a system-prompt update is interpreted. If a requested capability is not supported, the request fails before provider I/O rather than being silently changed.
Preparation also binds the call to the adapter generation that will dispatch it. The loop retains the prepared route through request metadata, history admission, and streaming. This matters when settings or a plugin tree can change while work is in flight: one request must not begin under one provider configuration and finish under another.
Only after preparation succeeds does the loop reconcile the rendered system prompt with the Session’s existing system-message surface, append the accepted user batch on the first attempt, and record the request header and route context when needed. The request header contains the non-history envelope and visible tool schemas. The system prompt is represented as a model-visible Session node rather than as a hidden field attached to the request.
The loop then derives and freezes the request from the log. This gives the provider a stable set of messages and schemas to read. The adapter translates that provider-neutral request into its own wire format. The loop does not ask a provider to rewrite the request after the fact; provider-specific behavior belongs behind the LLM seam.
This arrangement also explains why retries do not repeat everything. A retry of the same failed step prepares a call and reconciles the retained assembly again, but it does not re-run the original pre-step decision or append the same user messages a second time. If a recovery plugin changes the surface through compaction, the replacement generation can make the next request different; the change is recorded as a new surface state rather than hidden inside retry control.
The sequence shows the commitment points. Input is claimed before admission, but users and prompt state are committed only after the request route is prepared. The model stream can feed a live client while it is in progress, yet the loop also settles the attempt in the Session. Tool calls remain inside the step. A request failure can return to the LLM inside the same open step; a completed turn ends only after no further work is owed.
Streaming has two audiences
The LLM service exposes a provider-neutral stream of chunks: block starts, text or reasoning deltas, tool-call fragments, usage, and a terminal finish. An adapter translates its own wire protocol into this sequence. The stream always has one terminal outcome, whether the provider succeeds, reports an in-band error, or is aborted. Stable failure codes let the loop and recovery plugins distinguish a rate limit from an unsupported request or a context-window failure.
The live audience is a UI or protocol listener. DeepSeek Harness emits process-local agent/assistant-stream frames so a client can display reasoning or text incrementally. These frames are useful for responsiveness, but they are not the source from which a later process reconstructs the conversation. A process can disappear before a settlement is recorded, and a live frame alone cannot make that partial state durable.
The durable audience is the Session log. After an attempt settles, a successful response becomes an assistant/message event containing the assembled assistant message, usage when available, and a compact timed record of the provider stream. A failed, retried, cancelled, or stream-error attempt that reaches settlement without a surface message becomes an assistant/attempt. This preserves evidence of the attempt without fabricating an assistant message that the next model should treat as conversation history.
A cancelled stream is handled more carefully than a simple discarded promise. If text or reasoning has already been delivered, the loop can commit the visible prefix as an interrupted assistant message. A later request then knows what the user saw. If no visible assistant message was committed, a failed attempt can remain diagnostic rather than becoming model history. The precise result depends on where cancellation wins and which durable settlement is reached.
The same principle applies to tools. The assistant response can contain several tool calls. The loop records a tool/call event before executing one and later records one model-facing tool/result. Internal policy decisions, execution wrappers, and nested PTC subcalls may be observable to plugins but do not automatically become separate messages in the model transcript.
Tool scheduling keeps order where it matters
After an assistant response is settled, the loop classifies pending tool calls. Calls whose definitions are safe to run in parallel can share a bounded pool. Exclusive calls form barriers and run alone. The classification is based on each tool’s declared execution behavior; it is intentionally conservative because the safety of one call can depend on a resource or sibling call that a unary classifier cannot compare.
Even when execution overlaps, the model-facing results remain ordered. A later call should not make the model see a result before an earlier result merely because it finished faster. Policy, durable result recording, and additional context follow the model’s order. When tool work produces another request, that request is another step in the same turn rather than an unrelated conversation.
This is why the tool pipeline can stay outside the loop’s implementation. The loop knows when a tool call is due and when its result is ready. The tool service and its listeners decide validation, permission, sandboxing, execution wrapping, result transformation, and observation. The next chapter examines that seam in detail; here, the important lifecycle fact is that tool work is enclosed by the step and its result is logged before the next model derivation.
Failure, retry, and cancellation change the path
A model-request failure reaches agent/request-error as a recoverable extension point. A retry plugin can inspect the stable failure code and provider-owned policy, record that a retry was scheduled, wait with cancellation, and return a retry action. The loop then runs the failed step again inside the open turn. A retry is another provider request and may incur another input charge, but its scheduling record is not model-visible and does not itself change the conversation history.
The failure path is deliberately narrower than “catch everything and try again.” A provider or adapter can identify a rate limit, server failure, timeout, transport error, or context overflow. A malformed plugin result, an extension failure, or a tool-policy failure remains a thrown extension failure and can close the turn directly. Treating every exception as a retry would hide bugs and could repeat side effects outside the model request.
Cancellation is cooperative and has an owner. agent.cancel() aborts the active signal and, by default, clears pending inbox work as well. A caller can preserve pending messages when the intended operation is to stop the current activity but leave queued work. The tool body, model adapter, and subprocess provider must observe the signal they receive. Work that was never dispatched does not disappear without explanation: the loop can record synthetic aborted tool call/result pairs so a replayed history tells the next model that a requested call was skipped before dispatch.
Disposal is stronger than cancellation. The Agent handle stops and drains the driver, then its scoped registrations and Session ownership are unwound. A live client may see an idle or disposed state, but an automation caller must not assume that one status transition is the result of one prompt. Several queued messages, steering actions, injected contexts, or child activities may share one running interval. Protocols that need a result define their own interval from admission through a chosen quiescent point.
What the lifecycle makes possible
DeepSeek Harness’s turn machine turns an otherwise vague loop—ask the model, run tools, repeat—into a set of observable ownership decisions. The Agent owns admission and live control. The prepared LLM call owns the provider route and capability validation. The Session owns the durable model-visible record. Tools own their execution pipeline and results. Retry plugins can intervene at a failed request boundary without taking over the whole loop.
The result is not a guarantee that every provider or plugin has the same behavior. Adapters still differ in wire capabilities, and the repository is developer-preview software. But the lifecycle gives those differences a place to appear: before admission as a route error, during streaming as a terminal provider outcome, inside tools as a policy or execution result, or at teardown as a quiescence requirement.
Once this path is clear, the central architecture can be tested against a concrete question: where does each fact live, which lifecycle owns it, and which extension point should change when behavior needs to evolve? That is the purpose of the next checkpoint before the series moves deeper into capability seams and policy.