Modules & Workspaces
Loopstack uses NestJS modules to organize your application. Workflows and tools are registered as standard NestJS providers.
Modules
A module groups related workflows, tools, and services together.
Basic Module
import { Module } from '@nestjs/common';
import { ClaudeModule } from '@loopstack/claude-module';
import { MyTool } from './tools/my.tool';
import { MyWorkflow } from './workflows/my.workflow';
@Module({
imports: [ClaudeModule],
providers: [MyWorkflow, MyTool],
exports: [MyWorkflow, MyTool],
})
export class MyFeatureModule {}Key Rules
LoopCoreModuleis global — registered once byLoopstackModule.forRoot(), do not import it in feature modules- Import feature modules like
ClaudeModulefor AI,SandboxToolModulefor Docker sandboxes, etc. - Documents are NOT providers — they are plain DTOs and don’t need registration
- Export workflows and tools that other modules might need
Registering in AppModule
Add your module to the main AppModule:
import { Module } from '@nestjs/common';
import { LoopstackModule } from '@loopstack/loopstack-module';
import { MyFeatureModule } from './my-feature/my-feature.module';
@Module({
imports: [LoopstackModule.forRoot(), MyFeatureModule],
})
export class AppModule {}Multi-Module Example
For larger applications, split functionality across modules:
// analytics.module.ts
@Module({
imports: [ClaudeModule],
providers: [AnalyticsWorkflow, DataFetchTool],
exports: [AnalyticsWorkflow],
})
export class AnalyticsModule {}
// notifications.module.ts
@Module({
providers: [NotificationWorkflow, EmailTool],
exports: [NotificationWorkflow],
})
export class NotificationsModule {}
// app.module.ts
@Module({
imports: [LoopstackModule.forRoot(), AnalyticsModule, NotificationsModule],
})
export class AppModule {}Module Configuration (forRoot / forFeature)
Many Loopstack modules support forRoot() and forFeature() for configuring defaults. This follows the standard NestJS dynamic module pattern.
forRoot(config)— sets global defaults for the module. Call once in your rootAppModule.forFeature(config)— overrides defaults for a specific feature module. Tools in that module use the override instead of the global.
// app.module.ts — global default: all LLM calls use claude-sonnet-4-6
@Module({
imports: [
LoopstackModule.forRoot(),
LlmProviderModule.forRoot({ model: 'claude-sonnet-4-6' }),
ClaudeModule,
MyFeatureModule,
],
})
export class AppModule {}
// my-feature.module.ts — this module's LLM calls use claude-opus-4-6 instead
@Module({
imports: [LlmProviderModule.forFeature({ model: 'claude-opus-4-6' })],
providers: [MyWorkflow],
})
export class MyFeatureModule {}Modules that support this pattern include LlmProviderModule, RemoteClientModule, SecretsModule, and others. Per-call config (via options.config) always takes priority over module defaults.
Dependency Injection
Workflows and tools use standard NestJS constructor injection:
@Workflow({
widget: __dirname + '/chat.ui.yaml',
})
export class ChatWorkflow extends BaseWorkflow {
constructor(
private readonly llmGenerateText: LlmGenerateTextTool,
private readonly myTool: MyCustomTool,
) {
super();
}
}Sub-workflows are also injected via constructor:
export class ParentWorkflow extends BaseWorkflow {
constructor(private readonly subWorkflow: SubWorkflow) {
super();
}
}Using in Loopstack Studio
Once registered:
- Open Loopstack Studio at
http://localhost:5173 - Your workspace appears in the sidebar
- Click a workflow to create a new run
- Fill in the input form and start the workflow
Registry References
- chat-example-workflow — Example module with ClaudeModule import
- custom-tool-example-module — Module with custom tools, services, and workflow providers
- run-sub-workflow-example — Module registering both parent and sub-workflow providers