Skip to Content

NestJS x AI

Loopstack is a framework for production-grade agents and durable AI workflows with tool calling, sub-agents, human control and more.

Open SourceTypeScriptNestJS GitHub
app.module.ts
@Module({
  imports: [LoopstackModule.forRoot(options)],
  providers: [MyAgent, SearchTool],
})
export class AppModule {}

What You Can Build

AI Agents

Agent harnesses with tool calling, context and message history

AI Workflows

Stateful automations that chain tools, transform data, and route dynamically

Orchestration

Compose complex systems by spawning nested agents and workflows

Secure Execution

Run code and access files in isolated sandboxed environments

Human-in-the-Loop

Pause workflows for human review, input, or confirmation

...built directly into your NestJS backends.

How It Works

Workflows, Tools, Documents and Modules

triage.workflow.ts
@Workflow({ schema: TriageSchema })
export class TriageWorkflow extends BaseWorkflow {
  // inject tools here

  @Transition({ from: 'start', to: 'classified' })
  async classify(state, ctx) {
    const result = await this.llm.call({
      outputSchema: ClassificationSchema,
      prompt: ctx.args.ticket,
    });
    return { ...state, classification: result.data };
  }

  @Transition({ from: 'classified', to: 'end' })
  async notifyTeam(state) {
    await this.notify.call(state.classification);
  }
}

Workflows

Workflows are TypeScript state machines with explicit states and transitions. Inject tools, call LLMs, save documents — each step is checkpointed. If something fails, resume from exactly where you left off.

Learn more

Run and Interact in the browser

Use the built-in ReactJS frontend for running, debugging and organizing automations.

Loopstack Studio workflow example 1

Key Benefits

Agentic Meets Deterministic

Drop an LLM call into any point in a deterministic workflow, or nest agents inside structured pipelines. One system, not two bolted together.

Built-in Human Interaction

Approvals, forms, confirmations, and clarifications ship as framework primitives. Pause for human input for hours or days and resume cleanly.

Framework, Not a Platform

Extend it like any NestJS app — add your own modules, providers, and entities. No vendor lock-in, no external execution engine. Free and open source under the MIT license.

Responsible AI

Every state transition, tool call, and LLM decision is recorded. State is checkpointed so failures resume cleanly. Explicit state machines make workflows auditable and easy to reason about.

See It In Action

Agents With Full Control

Use a ready made agent or create your own flow. Same system, just the right level of abstraction.

Call an Agent

orchestrator.workflow.ts
await this.agent.run(
  {
    system: 'Review the codebase and write a summary.',
    tools: ['read', 'glob', 'grep'],
    userMessage: 'Focus on the auth module.',
  },
  { callback: { transition: 'agentDone' } },
);

Build Your Own

review-agent.workflow.ts
@Transition({ from: 'ready', to: 'tools_executed' })
async llmTurn(state) {
  const result = await this.llmGenerateText.call({
    system: 'Review the codebase and write a summary.',
    tools: ['read', 'glob', 'grep'],
  });
  return { ...state, llmResult: result.data };
}

@Transition({ from: 'tools_executed', to: 'ready' })
@Guard('hasToolCalls')
async loop(state) { return state; }

// agent done
@Transition({ from: 'tools_executed', to: 'end' })
async notify(state) {
  await this.notify.call(state.llmResult);
}

Tool calling, error recovery, and cancellation built in. Need custom exit logic, setup phases, or human interaction? Copy the agent and make it yours.

Nested Agents and -Workflows

Let agents launch sub-agents: Just wrap them in tools.

1. Define a workflow

test-runner.workflow.ts
@Workflow({ schema: TestRunnerSchema })
export class TestRunnerWorkflow extends BaseWorkflow {
  // runs tests, returns results
}

2. Wrap it as a tool

run-tests.tool.ts
@Tool({ name: 'run_tests', schema: RunTestsSchema })
export class RunTestsTool extends BaseTool {
  // inject workflow via constructor

  async handle(args, ctx, options) {
    const result = await this.testRunner.run(
      args, { callback: options?.callback },
    );
    return { pending: { workflowId: result.workflowId } };
  }
}

3. Let the agent use it

build-agent.workflow.ts
await this.llmGenerateText.call({
  system: 'You are a build agent.',
  tools: ['read', 'write', 'run_tests'],
});

The LLM decides when to launch sub-workflows. Each one runs in the background and reports back. Nest workflows as you need.

Built-In Error Recovery

Auto-retry, timeout, and custom error states.

Auto-Retry

workflow.ts
@Transition({
  from: 'fetching',
  to: 'done',
  retry: 3,
})
async fetchData(state) {
  await this.http.call({ url });
  return state;
}

Retries 3 times with exponential backoff. State rolls back between attempts.

Timeout

workflow.ts
@Transition({
  from: 'analyzing',
  to: 'done',
  timeout: 5000,
})
async analyze(state) {
  await this.analyzer.call({ data });
  return state;
}

Kills the transition after 5s. Combine with retry to auto-retry on timeout.

Error States

workflow.ts
@Transition({
  from: 'deploying',
  to: 'deployed',
  retry: { place: 'deploy_failed' },
})
async deploy(state) {
  await this.deployer.call({});
  return state;
}

Routes to a custom error state with recovery transitions.

Documents and state roll back automatically on failure. Every error is recorded as an audit trail. Manual retry is always available as a fallback.

Human-in-the-Loop

Pause for human input. Resume hours or days later. State is always preserved.

Ask a Question

ask-user.workflow.ts
@Transition({ from: 'start', to: 'waiting' })
async showQuestion(state, ctx) {
  await this.documentStore.save(
    AskUserDocument,
    { question: ctx.args.question },
  );
  return state;
}

// pauses until user responds
@Transition({ from: 'waiting', to: 'end', wait: true })
async userAnswered(state, payload) {
  return { answer: payload.answer };
}

Render the UI

ask-user-options.ui.yaml
widget: choices
options:
  transition: userAnswered

Documents define the data, YAML configures how it renders. The workflow sleeps until the user responds — no polling, no timeouts, no lost state.

Configurable UI

Documents define your data. A YAML config controls how they render - choices, forms, buttons, markdown. No frontend code needed.

The workflow renders the UI, pauses execution, the user responds, and the workflow continues.

ask-user-options.ui.yaml
widget: choices
options:
  transition: userAnswered
Rendered choices widget in Loopstack Studio

Community Registry

Browse packages on npm — modules, tools, and workflow examples for your Loopstack app.

Terminal
$ npm install @loopstack/agent

Get Started

Use in any NestJS app. Docker environment included.

$npm install @loopstack/loopstack-module
NestJS backend React Studio PostgreSQL + Redis

Supported by:

IFB IS Logo