description: "The default agent driver for users and maintainers choosing, configuring, or debugging how agents are created and how turns and steps run."
English | 中文
dsh-agent-loop creates agents — fresh or resumed from persisted history — and runs the turn and step lifecycle that claims prompts, assembles requests, streams model responses, dispatches tool calls, and appends every result back to the session log. As the default driver it implements the Agent interface from dsh-agent and registers its factory there, so plugins create and drive agents through ctx.agents without depending on this package. Declarative config entries start agents automatically at boot, and maxParallelToolCalls caps how many parallel-safe tool calls run at once. It is the harness's only concrete loop — everything beyond "call the model, run the tools, repeat" belongs to plugins listening on the event taxonomy. Choose it as the driver for standard compositions; swap it by implementing Agent and registering through ctx.agents.
Mount dsh-agent-loop in any composition that should run agents. It supplies the driver behind ctx.agents and starts any agents you declare in its config; both dsh-base and dsh-sdk-minimal mount it as an explicit row.
Agents declared in the config start automatically when the plugin loads. Each entry needs an id label; a model call additionally requires both provider and model (agent/request may supply a missing pair before dispatch).
- name: '@deepseek-ai/dsh-agent-loop'
config:
maxParallelToolCalls: 10
agents:
- id: 'main'
provider: deepseek
model: deepseek-chat
reasoningEffort: high
cwd: /workspace
| Field | Default | Meaning |
|---|---|---|
maxParallelToolCalls |
10 |
Parallel-safe tool calls in flight per step; 1 is serial |
agents[].id |
required | Stable label; a fresh session mints ${id}-session-<uuid> unless sessionId is set |
agents[].provider / agents[].model |
— | Model route; both required before dispatch |
agents[].reasoningEffort |
— | Non-empty initial reasoning effort; agent/request may override it |
agents[].maxTokens |
— | Positive per-request output-token cap |
agents[].cwd |
— | Workspace directory for a fresh session |
agents[].sessionId |
— | Exact identity: first use creates, a remount resumes materialized history |
agents[].resumeSessionId |
— | Load this persisted session instead of creating one; mutually exclusive with sessionId |
The generated configuration catalog is the exhaustive source for every accepted field. The adapter validates the effective reasoning effort and the loop records it in the request header. maxParallelToolCalls is also the whole agent-loop settings section, so a user layer over this entry caps the next tool group without a restart.
Plugins and hosts create agents through ctx.agents.create() and resume persisted sessions through ctx.agents.resume(); both return an AgentHandle whose dispose() owns exact teardown. The loop runs each created agent to completion — the handle is only needed when the caller must tear the agent down itself.
const handle = await ctx.agents.create({
sessionId,
agentOptions: { provider: 'deepseek', model: 'deepseek-chat' },
setup: (agentCtx) => { /* scoped tools, prompt sections, listeners */ },
})
Each step sends the agent's rendered system prompt, its visible tool schemas, and the session's derived history; the model's tool calls run through the guarded tool pipeline and every accepted fact is appended to the session log before the next step derives from it. Parallel-safe calls may overlap up to maxParallelToolCalls; exclusive calls run alone as ordering barriers. Cancellation is cooperative: agent.cancel() aborts the current activity and, unless keepInbox is set, clears pending work; a cancelled stream finalizes the text already delivered to the user.
The package-level contract is enough for most consumers; read these when you need the surrounding domain and the design rationale.
Agent handle, registry, and agent/* events this loop implements.For each step, the loop sends the rendered per-agent system prompt, the visible tool schemas, and the session's derived messages. It supplies provider, model, and cwd variable values but no additional fixed prose.
System text and schemas are paid again on every step. Per-agent scoping chooses the contributions, while the authoritative assembly waterfall can alter the final request and makes its listener responsible for protocol coherence.
Append-only only while system text, schemas, and earlier history remain byte-identical under the same provider and model route. A token-bearing assembly rewrite or composition change may invalidate reuse from the first altered request token.
Accepted user messages, assistant messages, tool calls and results, injected context, and steering are logged and sent on later steps. Raw stream chunks, lifecycle boundaries, and other log-only events are excluded.
Input grows with every surface message until a compaction replacement shadows older nodes; a multi-step tool turn resends the accumulated history each step.
Ordinary history growth is append-only and preserves reusable entries. A surface replacement or compaction invalidates reuse from the first shadowed history token.
If a later request replays an aborted step, each tool call that cancellation prevented from dispatching has error code ABORTED_BEFORE_DISPATCH and result text Error: tool call aborted before dispatch.
One fixed error result per skipped call remains in history until compaction shadows it.
Append-only; each synthetic result follows the reusable request prefix and does not invalidate existing KV-cache entries.
These limits define when the loop needs special care. They are current package constraints, not a task backlog.
sessionId creates a fresh ${id}-session-<uuid> on every startup; exact resume-or-create behavior requires an explicit stable sessionId, while resumeSessionId requires existing persisted history.ctx.agents.create() / resume() factory options.agent/turn-stopping.