Skip to Content

@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.

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-examples

After 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-examples
import { 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

ExampleStudio titleDescription
DeterministicSecrets - Deterministic ExampleScripted request/verify flow — no LLM involved
AgenticSecrets - Agentic ExampleLLM 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 RequestSecretsTool with a list of expected keys
  • Persisting a SecretRequestDocument so the user sees the prompt in Studio
  • A wait: true transition 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 class
  • templates/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-input widget 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 class
  • agentic-example.ui.yamlprompt-input widget for follow-up messages
  • templates/system.md — system prompt
  • templates/systemMessage.md — initial user/context message

About

Author: Jakob Klippel 

License: MIT

Last updated on