description: "The Agent handle, live registry, process-local initiator scope, and agent/* event vocabulary for plugins, UI, and orchestrators building or extending agents."
English | 中文
Use dsh-agent to create or resume live agents, send follow-up or steering input, inject model-facing context, cancel work, and wait for idle completion. Plugins, UI, hooks, and orchestrators can also observe or intercept agent activity and apply capabilities to one agent without affecting others. Choose it when code needs to control or extend live agents through the public Agent API. Pair it with an agent driver such as dsh-agent-loop; this package does not create model requests by itself. Initiator attribution is process-local and must be carried explicitly across workers, processes, durable queues, and restarts.
Mount dsh-agent wherever live agents exist: it provides ctx.agents and the Agent handle that plugins, UI, hooks, and orchestrators work against. The service is inert until a driver registers a factory — the shipped driver is dsh-agent-loop, so the smallest useful composition loads both.
ctx.agents.create() builds a fresh agent and session under one identity; ctx.agents.resume() loads a persisted session and rebuilds the agent on it. Both delegate to the registered factory and return an AgentHandle — the only object that can tear that agent down. Set parentAgent in either operation's options to make the result a runtime child; omit it for a runtime root. get(id), list(), and roots() find live agents, and isOwnedBy(id, parent) tests that exact live relation.
const handle = await ctx.agents.create({
sessionId,
agentOptions: { provider: 'deepseek', model: 'deepseek-chat' },
})
// later:
await handle.dispose() // stops the loop, unregisters, removes the session, unwinds the scope
AgentOptions supplies the initial provider/model route, optional adapter-owned reasoningEffort, and optional positive maxTokens output cap. The loop validates exact-model reasoning support, resolves adapter defaults, records the effective values in the request header, and applies them to each conversation request. An optional setup(agentCtx, agent) callback composes the agent's scoped world before it is published: agentCtx owns registrations, while the explicit unpublished Agent provides its Session; the Context has no reverse Agent property. Scoped tools, prompt sections, and listeners exist before any creation announcement. Setup is composition-only: drive the agent only after creation resolves.
The handle's methods route identified user-role messages into the agent's inbox. followup() queues an ordinary next-turn prompt and wakes the driver; steer() submits next-step input and wakes it; inject() adds model-facing context without waking the driver, so it lands in the next admitted step. cancel(cause) aborts the active activity and, unless keepInbox is set, clears pending work; whenIdle() resolves after the whole agent reaches quiescence.
handle.agent.followup({
content: [{ type: 'text', text: 'Summarize this workspace.' }],
source: { kind: 'user' },
})
handle.agent.steer({
content: [{ type: 'text', text: 'Focus on the tests.' }],
source: { kind: 'plugin', plugin: 'my-plugin' },
})
await handle.agent.whenIdle()
Agent.ctx is the agent's scoped context: registrations made through it (tools, prompt sections, variables, event listeners, restrictions) apply to that agent alone and unwind on disposal. The same mechanism is what agent presets use to give one session a different capability set without affecting its neighbors.
The agent/* events let plugins act on live work without depending on the loop package. agent/pre-step can reject a proposed step or replace the messages entering it; agent/request-error lets a listener retry a failed model request; agent/turn-stopping runs before an otherwise completed turn closes and can steer to keep it open. agent/assistant-stream carries one process-local Assistant attempt's ordered start, transient chunk, and end frames. Start names the attempt's turn and step, chunk indexes are dense from zero, and end.index is the next chunk position. The loop commits the complete compact stream as one assistant/message or assistant/attempt before a committed end frame, so the live event remains presentation data rather than the replay source. agent/status, agent/created, and agent/disposed drive UI and coordination state, and the per-message agent/inbox/* notifications keep inbox projections in sync. Exact signatures, dispatch modes, and payload contracts live in the generated region of the core subsystem page.
The package-level contract is enough for most consumers; read these when you need the surrounding domain and the design rationale.
Agent handle, interception decisions, and generated service API.followup, steer, and inject feed the owning session as identified user-role messages; accepted content becomes part of the derived history the model reads on later steps. agent/pre-step and the other declared events let plugins reject a proposed step or add durable request material. installModelSelection adds [model changed: assistant turns above this point were generated by <previous>; the session continues with <next>] to the first step assembled for a different provider/model route that would send a model request; provider names appear only when the switch crosses providers, and reasoning-effort-only changes add nothing. An empty first decision and a decision that removes offered messages remain no-request results. If a request step fails before logging its header, the next request step receives the notice again because the durable previous route has not changed.
Accepted content becomes retained history or a repeated session prefix; blocked content contributes no request tokens. Each emitted model-switch notice adds its text to retained history. Size is caller- and plugin-dependent.
Accepted history and steering are append-only; a blocked submission sends no request. A session prefix remains stable within its loop instance, while a new or resumed instance may establish a different prefix.
Registrations through agent.ctx can shadow prompt sections or tools and can install agent-only interceptors during unpublished setup, so one agent sees a different prompt and tool set than its neighbors. Model selection captures one provider/model/effort value before prompt assembly and applies it to the same step's request; a later concurrent change waits for another step.
Each provider/model switch adds one short retained user-role notice. Other scoped contributions affect only that agent and disappear on disposal.
The switch notice appends after the previous history, preserving that prefix, while the route change can prevent the new provider or model from reusing it. Setup or reload that changes prompt sections, tool definitions, or request listeners may invalidate reuse from the first affected request token.
These limits define when this package needs special care. They are current package constraints, not a task backlog.
agent.status, cancellation, and the owning capability contract before lifecycle-sensitive work.agent/session-start cannot gate startup — it remains a synchronous, veto-less notification; async composition that must finish before publication belongs in the factory's setup(agentCtx, agent) transaction instead.cancel() clears the inbox by default — it aborts the in-flight turn plus queued and steering work; cancel(cause, { keepInbox: true }) aborts only the turn and preserves pending items, and there is no step-only abort that keeps the turn running.UserMessage carries exactly one MessageSource — contributions from several plugins merged onto one message collapse under one source, so the message cannot name several producers.