@loopstack/secrets-examples
Secrets workflow examples for the Loopstack automation framework.
Two ways to manage workspace secrets: a scripted request/verify flow, and an agent loop where the LLM decides when to ask the user for credentials.
Install as Source (Recommended)
Examples are meant to be read, copied, and adapted. Pull the source straight into your project with giget :
npx giget@latest gh:loopstack-ai/loopstack/registry/examples/secrets-examples src/secrets-examplesAfter copying, register the module in your app:
import { Module } from '@nestjs/common';
import { LoopstackModule } from '@loopstack/loopstack-module';
import { SecretsExamplesModule } from './secrets-examples/secrets-examples.module';
@Module({
imports: [LoopstackModule.forRoot(), SecretsExamplesModule],
})
export class AppModule {}Install as a Dependency
npm install @loopstack/secrets-examplesimport { SecretsExamplesModule } from '@loopstack/secrets-examples';Required app-module configuration
The Agentic example calls LlmGenerateTextTool from @loopstack/llm-provider-module. That module is @Global and must be configured once in your root module to set the default model:
import { Module } from '@nestjs/common';
import { LoopstackModule } from '@loopstack/loopstack-module';
import { LlmProviderModule } from '@loopstack/llm-provider-module';
import { SecretsExamplesModule } from '@loopstack/secrets-examples';
@Module({
imports: [
LoopstackModule.forRoot(),
LlmProviderModule.forRoot({ model: 'claude-sonnet-4-6' }),
SecretsExamplesModule,
],
})
export class AppModule {}SecretsExamplesModule already re-imports ClaudeModule (provider) and SecretsModule.forFeature() (entity + REST surface). LlmProviderModule.forRoot(...) sets the default model the agent dispatches to. The Deterministic example doesn’t call an LLM and works without it.
Environment
The agentic example requires Claude credentials:
ANTHROPIC_API_KEY=sk-ant-...Examples
| Example | Studio title | Description |
|---|---|---|
| Deterministic | Secrets - Deterministic Example | Scripted request/verify flow — no LLM involved |
| Agentic | Secrets - Agentic Example | LLM agent that checks, requests, and verifies secrets via tool calls |
Deterministic
A scripted workflow that requests two secrets from the user, waits for them to be stored, then verifies they were saved. No LLM involved.
What it demonstrates
- Calling
RequestSecretsToolwith a list of expected keys - Persisting a
SecretRequestDocumentso the user sees the prompt in Studio - A
wait: truetransition that resumes once the user submits the secrets - Reading current key availability via
GetSecretKeysTool
Key code
@Transition({ to: 'requesting_secrets' })
async requestSecretsFromUser() {
await this.requestSecrets.call({
variables: [{ key: 'EXAMPLE_API_KEY' }, { key: 'EXAMPLE_SECRET' }],
});
await this.documentStore.save(SecretRequestDocument, {
variables: [{ key: 'EXAMPLE_API_KEY' }, { key: 'EXAMPLE_SECRET' }],
});
}
@Transition({ from: 'requesting_secrets', to: 'verifying', wait: true })
async secretsSubmitted() {
const result = await this.getSecretKeys.call();
this.assignState({ secretKeys: result.data });
}Files
deterministic-example.workflow.ts— workflow classtemplates/secretsVerified.md— Handlebars summary template
Agentic
An agent workflow where the LLM decides when to check for existing secrets and when to request new ones. The user can also send follow-up messages mid-loop.
What it demonstrates
- Configuring an LLM call with allowed tools via
config: { tools: ['get_secret_keys', 'request_secrets_task'] } - A delegate/tool-result loop using
LlmDelegateToolCallsTool+LlmUpdateToolResultTool - Guard-based routing on
stopReason === 'tool_use'vs end-of-turn - A
prompt-inputwidget that lets the user inject messages while the agent runs
Key code
@Transition({ from: 'ready', to: 'prompt_executed' })
async llmTurn() {
const result = await this.llmGenerateText.call(
{},
{
config: {
provider: 'claude',
model: 'claude-haiku-4-5-20251001',
tools: ['get_secret_keys', 'request_secrets_task'],
system: this.render(join(__dirname, 'templates', 'system.md')),
},
},
);
this.assignState({ llmResult: result.data });
}
@Transition({ from: 'prompt_executed', to: 'awaiting_tools', priority: 10 })
@Guard('hasToolCalls')
async executeToolCalls(state) {
const result = await this.llmDelegateToolCalls.call({
message: state.llmResult!.message,
callback: { transition: 'toolResultReceived' },
});
this.assignState({ delegateResult: result.data });
}Files
agentic-example.workflow.ts— workflow classagentic-example.ui.yaml—prompt-inputwidget for follow-up messagestemplates/system.md— system prompttemplates/systemMessage.md— initial user/context message
About
Author: Jakob Klippel
License: MIT