@dudousxd/nestjs-agent
The umbrella NestJS module — AgentModule, @AiTool discovery, provideAgentTool, the inline runner, HeaderActorResolver, and the /durable subpath.
pnpm add @dudousxd/nestjs-agent @dudousxd/nestjs-agent-corenpm install @dudousxd/nestjs-agent @dudousxd/nestjs-agent-core@dudousxd/nestjs-agent is the package you import in a Nest app. It wires
-core's loop and SPIs into NestJS DI: AgentModule, the @AiTool
discovery mechanism, the /agent controllers, and (opt-in) the durable runner under /durable.
import { Module } from '@nestjs/common';
import { AgentModule, HeaderActorResolver } from '@dudousxd/nestjs-agent';
import { aiSdkModel } from '@dudousxd/nestjs-agent-ai-sdk';
import { anthropic } from '@ai-sdk/anthropic';
@Module({
imports: [
AgentModule.forRoot({
model: aiSdkModel(anthropic('claude-sonnet-4-6')),
actorResolver: new HeaderActorResolver(),
defaultRoles: ['ADMIN'],
}),
],
})
export class AppModule {}AgentModule
| Static method | Purpose |
|---|---|
forRoot(options: AgentModuleOptions) | Register the module synchronously — model, store, sink, quota, rolesPolicy, actorResolver, path, durable, tools, defaultAgent. |
forRootAsync(options: AgentModuleAsyncOptions) | Register via a factory (imports / inject / useFactory); path stays static since routes mount before the factory resolves. |
There is no forFeature — named agents beyond the default are declared as @Agent-decorated
providers and discovered at boot (see multi-agent), not passed as a
definition array.
model and actorResolver are the two required fields — no safe default exists for either, so
compile-time errors catch a forgotten one instead of a throwing placeholder at runtime. Everything
else has a default. The full option-by-option reference lives in
Reference → Configuration; this page covers the surface, not
every field.
AgentModule also binds AGENT_APPROVAL_PORT unconditionally (@Global, no config needed) to an
internal AgentApprovalPortAdapter that routes through the same decision path chat approvals use
(AgentService.signalToolCall) — this is what lets @dudousxd/nestjs-agent-dashboard's approvals
inbox work out of the box whenever AgentModule is imported anywhere in the app.
Dispatched steps are on by default under durable: true
dispatchedSteps defaults to true whenever durable: true — the turn's model call and tool
executions are routed through AgentRunSteps.llm/AgentRunSteps.tool (see Durable
subpath below) instead of in-process ctx.localSteps,
so the run isn't pinned to the pod that started it. Set dispatchedSteps: false to opt back into
in-process steps. Either way, durable: true already means the turn may resume on a different worker
than the one holding the SSE connection — multi-pod fleets must wire a cross-process
TokenStreamSink (e.g. RedisTokenStreamSink from the /sink-redis subpath) via
AgentModule.forRoot({ sink }). Boot logs a warning when the effective config is durable with the
default in-process sink.
@AiTool and discovery
@AiTool({ name, kind, description, input, roles?, ability? }) marks an ordinary provider as a tool.
AiToolDiscoveryService walks every provider at boot, reads the decorator metadata, and registers it
into the shared ToolRegistry — there's no separate array to keep in sync. See
Tools for the full decorator surface and the read vs action split.
provideAgentTool
For a functional tool (a plain { spec, handler } pair, like the one createExecuteSqlTool returns
from -data) that needs DI-resolved dependencies:
import { provideAgentTool } from '@dudousxd/nestjs-agent';
providers: [
provideAgentTool(
(pool: ReadOnlyPool) => createExecuteSqlTool({ runner: { run: (sql) => pool.query(sql) }, /* … */ }),
[ReadOnlyPool],
),
];A dependency-free functional tool can also be passed straight into AgentModule.forRoot({ tools: [...] }).
InlineAgentRunner and HeaderActorResolver
| Export | What it is |
|---|---|
InlineAgentRunner | The default AgentRunner — runs a turn in-process, no extra dependencies. Swapped for DurableAgentRunner when durable: true. |
HeaderActorResolver | An ActorResolver reading x-actor-id / x-actor-role / x-tenant-ref. Only safe behind a trusted gateway that strips and re-sets those headers — see Identity & Authorization. |
Durable subpath — @dudousxd/nestjs-agent/durable
Opt into the durable runner by importing the subpath alongside a configured DurableModule and
setting durable: true. Each turn then runs as the agent.run @dudousxd/nestjs-durable workflow:
every model and tool call becomes a checkpointed step, and human-in-the-loop approval becomes a real
durable waitForSignal suspend instead of a held connection.
import { Module } from '@nestjs/common';
import { DurableModule } from '@dudousxd/nestjs-durable';
import { AgentModule } from '@dudousxd/nestjs-agent';
import { AgentDurableModule } from '@dudousxd/nestjs-agent/durable';
@Module({
imports: [
DurableModule.forRoot({ /* … */ }),
AgentModule.forRoot({ /* model, store, actorResolver, … */ durable: true }),
AgentDurableModule,
],
})
export class AppModule {}| Export | What it is |
|---|---|
agentDurable(options: AgentModuleOptions) | One-import helper — returns [AgentModule.forRoot({ ...options, durable: true }), AgentDurableModule.forRoot()] to spread into imports. |
AgentDurableModule | @Global module registering the agent.run workflow and binding AGENT_DURABLE_RUNNER (which AgentModule wires to AGENT_RUNNER). Always registers AgentRunSteps regardless of dispatchedSteps, so the worker group is never left unserved by a config flag. |
DurableAgentRunner | The AgentRunner implementation backing durable turns — same interface as InlineAgentRunner. |
AgentRunSteps | The two dispatched-step handlers, @Step-decorated: llm (@Step({ retries: 3 }), resolves model/tools/sink from the serving worker's own DI) and tool (@Step(), no retries — tool idempotency is the app's concern; applies the tool timeout handler-side). Under the default dispatchedSteps: true these are what actually execute the turn's long steps, on whichever worker takes the routed step; dispatchedSteps: false keeps them unused (the workflow runs ctx.localStep instead). |
Using the helper collapses the two imports above to one line:
imports: [DurableModule.forRoot({ /* … */ }), ...agentDurable({ model, store, actorResolver })];Forgetting AgentDurableModule fails loudly
Setting durable: true without importing AgentDurableModule (or its agentDurable helper) throws a
clear error at boot rather than silently falling back to the inline runner.
Cross-process token sink — /sink-redis
import { RedisTokenStreamSink } from '@dudousxd/nestjs-agent/sink-redis';The default TokenStreamSink buffers tokens in-process. Under durable: true (dispatched steps or
not), a turn's agent.run workflow — and, with dispatching on, the llm step itself — may execute on
any worker in the fleet, not the pod holding the client's SSE connection. A multi-pod deployment must
bind a cross-process sink such as RedisTokenStreamSink (Redis pub/sub) via
AgentModule.forRoot({ sink: new RedisTokenStreamSink(redis) }); boot logs a warning when it detects
the default in-process sink under an effectively-dispatched config.
Run reliability recording
AgentRunWorkflow calls AgentStore.recordRunStart/recordRunEnd (both optional on the SPI — see
-core) as checkpointed steps: start is recorded with the run's
promptHash, end with duration on success or an error code/message on failure. DispatchedLlmInput
carries the runId so a dispatched llm step's retries can eventually be attributed back to the run
via bumpRunRetries once the durable runtime exposes the attempt number to remote step handlers. This
feeds the governance reads (runMetrics, runsByAgent, …) documented in
-dashboard and the Telescope Agent tab's Reliability section.
Related
- Getting Started — install, declare a tool, register the module
- Tools — the
@AiTooldecorator surface in full - Human-in-the-loop & Durability — what changes under
durable: true - Concepts → Runners — inline vs durable, one
AgentRunnerSPI - Reference → Configuration — every
AgentModuleOptionsfield
@dudousxd/nestjs-agent-core
The framework-agnostic agent loop, tool registry, and every SPI a provider, store, or dashboard implements against.
@dudousxd/nestjs-agent-ai-sdk
aiSdkModel adapts any Vercel AI SDK v7 LanguageModel to the ModelProvider SPI — streaming, tool-call translation, cache-aware usage, and gateway cost, with zero provider code.