Aviary
Packages

@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-core
npm 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 methodPurpose
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

ExportWhat it is
InlineAgentRunnerThe default AgentRunner — runs a turn in-process, no extra dependencies. Swapped for DurableAgentRunner when durable: true.
HeaderActorResolverAn 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 {}
ExportWhat 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.
DurableAgentRunnerThe AgentRunner implementation backing durable turns — same interface as InlineAgentRunner.
AgentRunStepsThe 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.

On this page