The NestJS-native runtime for governed AI agents
Define agents and tools with NestJS, enforce policy before side effects, and keep model integrations replaceable.
Most agent frameworks introduce a separate runtime and application boundary. nestjs-agentic keeps agent-facing capabilities inside the NestJS module and dependency-injection system:
NestJS service
-> @ToolSet and @Tool
-> context-bound ResolvedTool
-> allow / deny / require_approval policy decision
-> RuntimeAdapter
Application services remain ordinary NestJS providers. The model runtime receives governed tool closures rather than direct access to services or application-owned security context.
The current release line is 0.6.x. Core primitives, persistence adapters, and durable execution checkpoints are production-intent; higher-order orchestration packages remain experimental while their contracts stabilize.
| Area | Status | Scope |
|---|---|---|
| Agents, tools, and NestJS DI | Available | Decorators, discovery, feature registration, and context-bound tools. |
| Tool governance & HITL | Available | allow, deny, and require_approval before execution; resumes durably via ApprovalStore. |
| Model Context Protocol (MCP) | Available | @nestjs-agentic/mcp for Stdio and SSE remote tool discovery, authorization, and execution. |
| Built-in runtime & Model Cascading | Available | Loop execution, streaming, budgets, and FrugalGPT confidence-threshold model cascading. |
| OpenAI & Chat-Completions adapter | Available | @nestjs-agentic/openai for OpenAI, Azure, Ollama, vLLM, Groq, and OpenRouter. |
| Cognitive Memory & SOP Playbooks | Available | @nestjs-agentic/memory for Stanford Tri-Factor scoring, SOP playbooks, and reflection. |
| U-Shaped Context Assembler | Available | @nestjs-agentic/rag & @nestjs-agentic/core for Lost-in-the-Middle attention mitigation. |
| Codebase AST & GraphRAG | Available | @nestjs-agentic/rag for AST code splitting, hybrid vector store, and graph traversal. |
| Debiased Evaluation & Trajectory Metrics | Available | @nestjs-agentic/evaluation for MT-Bench position-debiased judge and AgentBench metrics. |
| Persistence & Durable Checkpoints | Available | In-memory, Redis, and PostgreSQL drivers for Session, State, Approval, and Idempotency. |
| Sub-Agent Orchestration | Available | @nestjs-agentic/orchestration for parallel delegation, bounded concurrency, and refinement. |
See the product roadmap for milestones and production-readiness criteria.
| Package | Purpose |
|---|---|
nestjs-agentic |
Meta package that re-exports the core framework |
@nestjs-agentic/core |
Agents, tools, policies, approvals, the built-in runtime, and the adapter contracts |
@nestjs-agentic/mcp |
Model Context Protocol (MCP) client transport and tool provider |
@nestjs-agentic/openai |
OpenAI ModelAdapter, also covering Chat Completions compatible endpoints |
@nestjs-agentic/memory |
Stanford Tri-Factor cognitive scoring, procedural SOP playbooks, and experience reflection |
@nestjs-agentic/rag |
Retrieval strategies, vector stores, and knowledge-graph primitives |
@nestjs-agentic/orchestration |
Sub-agent delegation, parallel execution, and refinement loops |
@nestjs-agentic/evaluation |
Metrics, benchmark execution, and reporting |
npm install nestjs-agenticConnect a model provider:
npm install @nestjs-agentic/openai openaiOptional packages:
npm install @nestjs-agentic/mcp
npm install @nestjs-agentic/memory
npm install @nestjs-agentic/rag @nestjs-agentic/memory
npm install @nestjs-agentic/orchestration
npm install @nestjs-agentic/evaluationThe example uses MockModelAdapter, so the full tool-calling loop runs deterministically without an API key. Swap in your own ModelAdapter to talk to a real provider.
import { Injectable } from '@nestjs/common';
import {
AgentContext,
Context,
Param,
PolicyResult,
Tool,
ToolPolicy,
ToolSet,
UsePolicies,
} from 'nestjs-agentic';
@Injectable()
export class RefundLimitPolicy implements ToolPolicy {
async evaluate(
_ctx: AgentContext,
_toolName: string,
args: Record<string, unknown>,
): Promise<PolicyResult> {
return Number(args.amount) > 500
? { decision: 'require_approval', reason: 'Refund exceeds $500.' }
: { decision: 'allow' };
}
}
@ToolSet({ name: 'orders' })
export class OrderTools {
@Tool({ name: 'refundOrder', description: 'Refund an order' })
@UsePolicies(RefundLimitPolicy)
async refundOrder(
@Param('orderId') orderId: string,
@Param('amount', { type: 'number' }) amount: number,
@Context() ctx: AgentContext,
) {
return { orderId, amount, requestedBy: ctx.security.userId };
}
}import { Module } from '@nestjs/common';
import {
Agent,
AgentConfig,
AgenticModule,
AgentProvider,
MockModelAdapter,
} from 'nestjs-agentic';
@Agent({ name: 'support', description: 'Handles support requests' })
export class SupportAgent implements AgentProvider {
constructor(private readonly orderTools: OrderTools) {}
define(): AgentConfig {
return {
instructions: 'Help the user while respecting tool policies.',
tools: [this.orderTools],
};
}
}
const model = new MockModelAdapter();
model
.whenAsked('Refund $600 for order #42')
.callTool('refundOrder', { orderId: '42', amount: 600 })
.reply('That refund needs approval before I can complete it.');
@Module({
imports: [
AgenticModule.forRoot({
defaultModel: { provider: 'mock', model: 'deterministic' },
modelAdapter: model,
limits: { maxIterations: 6 },
}),
AgenticModule.forFeature({
agents: [SupportAgent],
toolSets: [OrderTools],
policies: [RefundLimitPolicy],
}),
],
})
export class AppModule {}AgenticModule.forFeature() registers these classes inside AgenticModule. Keep an agent, its tool sets, and its policies in a single forFeature() call, and export any application services they inject from a @Global() module.
import { Body, Controller, Param, Post } from '@nestjs/common';
import { AgentRunner, ApprovalService } from 'nestjs-agentic';
@Controller('support')
export class SupportController {
constructor(
private readonly runner: AgentRunner,
private readonly approvals: ApprovalService,
) {}
@Post('chat')
chat(@Body() body: { sessionId: string; message: string }) {
return this.runner.run('support', {
sessionId: body.sessionId,
message: body.message,
context: {
userId: 'user_123',
tenantId: 'acme',
},
});
}
@Post('approve/:id')
approve(@Param('id') id: string) {
return this.approvals.approve(id);
}
@Post('reject/:id')
reject(@Param('id') id: string) {
return this.approvals.reject(id);
}
}runner.runStream() exposes structured token, tool_start, tool_result, approval_required, and complete events.
Each run is bounded. Pass limits and a signal to cap iterations, tool calls, tokens, and wall-clock time, or to cancel work in flight:
await runner.run('support', {
sessionId,
message,
limits: { maxIterations: 4, maxToolCalls: 8, timeoutMs: 30_000 },
signal: abortController.signal,
});RateLimitPolicyβ process-local sliding-window limits by tenant, user, and tool.CostLimitPolicyβ numeric allow, approval, and deny thresholds.LoggingPolicyβ configurable tool-attempt logging with field masking.
These are framework primitives, not replacements for distributed rate limiting, durable audit storage, or application authorization.
For OpenAI and any Chat Completions compatible endpoint, use the published adapter:
import { AgenticModule } from 'nestjs-agentic';
import { OpenAiModelAdapter } from '@nestjs-agentic/openai';
AgenticModule.forRoot({
defaultModel: { provider: 'openai', model: 'gpt-4o-mini' },
modelAdapter: new OpenAiModelAdapter({ apiKey: process.env.OPENAI_API_KEY }),
});The same adapter targets local and third-party servers by pointing baseUrl at them, for example http://localhost:11434/v1 for Ollama. See @nestjs-agentic/openai for Azure, reasoning models, and compatibility notes.
For any other provider, implement ModelAdapter directly. It handles only provider communication; the framework owns the loop, validation, policies, budgets, and streaming.
import type { ModelAdapter, ModelRequest, ModelResponse } from 'nestjs-agentic';
export class MyModelAdapter implements ModelAdapter {
async generate(request: ModelRequest): Promise<ModelResponse> {
const completion = await callProvider({
model: request.model.model,
messages: request.messages,
tools: request.tools,
signal: request.signal,
});
return {
content: completion.text,
toolCalls: completion.toolCalls,
usage: completion.usage,
finishReason: completion.toolCalls.length ? 'tool_calls' : 'stop',
};
}
}The core package does not import external model SDKs. Custom model adapters implement ModelAdapter directly, while the framework manages loop execution, policy enforcement, budgets, and streaming.
Runnable examples are available in examples.
nestjs-agentic is an open-source framework dedicated to production-grade, governed AI agent systems in NestJS. If you or your organization find value in the project, consider supporting ongoing development:
- π Sponsor on GitHub
- β Star the repository on GitHub
- π€ Contribute features, adapters, and improvements
