Aviary
Reference

Configuration

The full AgentModule.forRoot() options reference, forRootAsync, and how @Agent-decorated classes register into the AgentRegistry.

Every option below is passed to AgentModule.forRoot({ … }). model and actorResolver are required — everything else has a default. See Architecture for how these pieces fit together, and packages/nestjs for the package itself.

AgentModule.forRoot(options)

Model & infra

OptionTypeDefaultDescription
modelModelProvider— (required)The LLM provider (e.g. a Vercel AI SDK wrapper).
storeAgentStorenonePersistence adapter. Omit it and import a store module (e.g. MikroOrmAgentStoreModule.forFeature()) to bind AGENT_STORE globally instead — passing it here takes precedence within this module's scope.
sinkTokenStreamSinkin-memory sinkLive token transport. The default is single-process only — for multi-replica, use @dudousxd/nestjs-agent-transport-redis's RedisTokenStreamSink instead.
toolsFunctionalTool[][]Static functional tools ({ spec, handler }) registered at boot. DI-dependent tools use provideAgentTool(factory, inject) in a module's providers instead.
toolTimeoutMsnumbernone (no timeout)Abort a single tool call that runs longer than this and record it as failed — the model gets the timeout as the tool's result and can adapt, instead of the turn hanging. See Tools.
retrieval{ mode: 'inject'; retriever: Retriever; topK?: number }none (off)Always-on RAG: before each turn, retrieve passages for the user message and augment the system prompt with them. For agentic retrieval (the model decides when to search) don't set this — expose a tool instead: provideAgentTool(createRetrievalTool(retriever)).
attachments{ maxBytes?: number; allowedContentTypes?: string[]; upload?: boolean }upload: falseBounds/allowlist for the optional POST /agent/attachments upload controller. maxBytes defaults to 20 MiB; allowedContentTypes defaults to common multimodal types (image/png, image/jpeg, image/gif, image/webp, application/pdf, text/plain, text/csv). upload: true is static, build-time wiring (controllers are wired at module build) — set it explicitly; it fails boot loudly if nothing is bound to AGENT_ATTACHMENT_STAGING.
guardsType<CanActivate>[][]Guard(s) stamped uniformly on every controller this module mounts (chat, threads, tool-call, quota, agents, and — when attachments.upload is set — attachments) via @nestjs/common's @UseGuards, with REPLACE semantics — a repeated registration never accumulates guards. Without this, every route is open beyond whatever actorResolver itself enforces. Guard classes are added to the module's providers for DI.

Identity & authz

OptionTypeDefaultDescription
actorResolverActorResolver— (required)Resolves the acting Actor per request — the identity seam. Compile-time required: no default fabricates a caller. Supply an ActorResolver (or the opt-in HeaderActorResolver).
rolesPolicyRolesPolicyDefaultRolesPolicyTool authorization gate. A tool that declares no roles reaches the policy with roles: undefined; DefaultRolesPolicy applies defaultRoles at that point (not baked into the tool spec earlier).
defaultRolesstring[]['ADMIN']Roles DefaultRolesPolicy requires when a tool's own roles is omitted.

Quota

OptionTypeDefaultDescription
quotaQuotaStorenoneDaily token budget, explicit store. Omit both quota and quotaLimitTokens to disable quotas entirely. Wins over quotaLimitTokens if both are set.
quotaLimitTokensnumbernoneDaily token budget, no extra wiring: turns on quotas backed by the built-in LedgerQuotaStore, which reads the persisted usage ledger.

Routing

OptionTypeDefaultDescription
pathstring'agent'Route prefix the controllers mount under (→ /agent/chat, /agent/threads, …).

Durable

OptionTypeDefaultDescription
durablebooleanfalseRun each turn as a durable workflow instead of in-process. Requires importing AgentDurableModule from @dudousxd/nestjs-agent/durable alongside a configured DurableModule — otherwise boot throws a clear error instead of an unresolved-dependency crash.
dispatchedStepsbooleantrue when durable: true, otherwise inertDispatch the turn's model call and tool executions as routed durable steps (AgentRunSteps.llm / AgentRunSteps.tool) instead of in-process ctx.localSteps. Defaults ON under durable: true — the run leaves its pod during the two long steps and the llm step gets engine retry, which is the correct production posture; AgentRunSteps is always registered regardless of this flag, so the routed groups are never unserved. Set false to keep the turn's steps in-process localSteps instead. Setting it true without durable: true throws at module build. See Inline → durable.

Multi-pod fleets still need a cross-process sink

The cross-process-sink requirement is a property of durable: true itself, not of dispatchedSteps — even with dispatchedSteps: false, the turn already runs on whichever worker takes agent.run, which may not be the pod holding the SSE connection. Multi-pod fleets should wire a cross-process TokenStreamSink (e.g. @dudousxd/nestjs-agent-transport-redis's RedisTokenStreamSink) either way; a boot warning fires when durable: true is on with the default in-process sink, naming dispatchedSteps: false as the (incomplete) alternative.

Follow-ups

OptionTypeDefaultDescription
followUpsboolean | { count: number }falseAfter the final turn, make one extra model call proposing short follow-up questions, stored on the assistant message's followUps and recorded as a follow_ups usage row. true proposes 3; pass { count } to change that. Off by default — it costs an extra model call. See Cost & Governance.

Default agent

OptionTypeDefaultDescription
defaultAgentstringnoneThe name of the agent a turn uses when the caller doesn't select one explicitly. Omit → the single discovered @Agent (when there is exactly one), else 'default' (a bare assistant if no @Agent is registered at all).

Agents are declared as classes, not as forRoot options

There is no forFeature(definitions) and no inline agent-config object anymore — an agent is an ordinary Nest provider decorated @Agent({ name, description?, systemPrompt?, model?, maxSteps?, tools?, handoff? }), discovered at boot via DiscoveryService and registered into the shared AgentRegistry. Being a class means it gets constructor DI (its retriever, schema service, policy) instead of a static config blob. A dynamic prompt is a @SystemPrompt() method on the class instead of a string; a @SystemPromptContributor() method on any provider appends a section after every agent's base prompt app-wide. See Multi-agent for @Agent({ handoff }) composing an orchestrator, and Identity & authorization for actorResolver/rolesPolicy. There is no persona system — GET /agent/agents (backed by AgentCatalogEntry[]) is the picker catalog now, replacing the old personas catalog.

@Agent(options)

FieldTypeDescription
namestringUnique agent name. Referenced by the chat body's agent, handoff, and the GET /agent/agents catalog.
descriptionstringHuman-readable summary, shown to an orchestrator that may hand off to this agent and surfaced on the catalog entry.
systemPromptstringA flat base prompt. For a dynamic prompt (reads services, the turn's PromptContext), add a @SystemPrompt() method instead — it takes precedence when present.
modelstringAccounting label for the model this agent uses (the model provider itself is shared module-wide).
maxStepsnumberCap on model→tool iterations for this agent's turn. Default 8.
toolsstring[]Allow-list of global tool names this agent may use. Omit → every tool its role allows.
handoffType[]Other @Agent classes this agent may hand off to — auto-exposed as handoff tools. Naming a non-@Agent class throws at boot. Delegation depth is capped at MAX_DELEGATION_DEPTH (5).

AgentModule.forRootAsync(options)

interface AgentModuleAsyncOptions {
  imports?: unknown[];
  inject?: unknown[];
  useFactory: (...args: never[]) => AgentModuleOptions | Promise<AgentModuleOptions>;
  path?: string;
  durable?: boolean;
  dispatchedSteps?: boolean;
  externalStore?: boolean;
  guards?: Type<CanActivate>[];
  attachmentsUpload?: boolean;
}

Why these fields live outside the factory

path, durable, dispatchedSteps, guards, and attachmentsUpload are all static wiring metadata — which controllers exist, which routes they expose, and how AgentRunWorkflow builds its hooks are all decided at module build time, before the async factory resolves. Passing them on AgentModuleAsyncOptions (not inside the resolved AgentModuleOptions) is what makes that possible. dispatchedSteps: true without durable: true throws at module build, same as forRoot.

forRootAsync always binds AGENT_STORE locally from the factory result (its factory reads the resolved options) unless externalStore: true — set that to defer to a globally-imported store module (e.g. MikroOrmAgentStoreModule.forFeature()) instead of returning store from useFactory.

On this page