# Poimen Workflows **Temporal-powered orchestration that transforms natural language into durable, scalable workflow executions.** [![CI Status](https://github.com/your-org/workflows/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/your-org/workflows/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/your-org/workflows)](https://goreportcard.com/report/github.com/your-org/workflows) [![Test Coverage](https://img.shields.io/badge/coverage-45.9%25-orange)](./docs/coverage.md) ## What is Poimen? **Poimen** (Greek: Ποιμήν) means **"shepherd"** — a guide who tends, orchestrates, and reconciles. In this project, Poimen acts as an intelligent **orchestration shepherd** for distributed agent workflows: - 🐑 **Shepherds workflows** — Guides activities from intent to completion - 🔄 **Reconciles state** — Ensures consistency across distributed steps - 🎯 **Routes intelligently** — Uses LLM reasoning to choose the best path forward - 💾 **Remembers lessons** — Semantic memory prevents repeated mistakes - 🛡️ **Fault-tolerant** — Temporal's durable execution keeps workflows safe from failure Like a shepherd managing a flock, Poimen manages orchestrated agent deployments at scale—keeping tasks aligned, recovering from chaos, and learning from each execution. ## Overview Poimen Workflows orchestrates complex agent-driven tasks using **LLM-powered routing**, **semantic memory integration**, and **durable state machines**. Write your intent in plain English—the system generates executable Temporal workflows, manages retries and error handling, and scales to thousands of concurrent executions. **Core Promise:** Any activity (code scan, deployment, notification) becomes a composable building block in a larger reconciliation pipeline, with intelligent ordering, memory-augmented context, and automatic fault recovery. --- ## Architecture & Data Flow ``` User Request ↓ ├─→ [1. LLM Router] │ • Analyze intent & extract parameters │ • Query semantic memory for domain knowledge │ • Build WorkflowSpec with activity graph │ • Inject retry policies & error handlers │ ├─→ [2. Memory Retrieval] │ • RetrieveMemoryActivity queries poimen-memory (Rust semantic search) │ • Returns relevant skills, lessons, best practices │ • Augments LLM prompts with domain context │ ├─→ [3. Workflow Validation] │ • StateGraphValidator: Check DAG structure │ • ActivityAvailabilityValidator: Verify all activities registered │ • TimeoutValidator: Validate timeout formats │ • OutputMatchValidator: Ensure parameter bindings exist │ ├─→ [4. Temporal Execution] │ • RoutingWorkflow executes state machine │ • JSONPath parameter chaining: ${Step1.output.path} │ • Automatic retries for transient failures │ • Catch blocks for graceful error recovery │ • Durable execution guarantees (fault-tolerant) │ └─→ Completed Workflow • Results stored in activity output • Memory updated with execution logs • Notifications sent via DeploymentPreCheckActivity ``` --- ## Philosophy: The Shepherd's Role Poimen embodies three core principles: 1. **Orchestration as Shepherding** - Not rigid task scheduling, but intelligent guidance - Activities work together toward a common goal - The system learns and adapts from each execution 2. **Reconciliation as Healing** - Workflows reconcile state across distributed systems - Errors are opportunities to learn, not failures to hide - Memory persists lessons so future workflows are smarter 3. **Scale through Composition** - One shepherd (Poimen) tends infinite flocks (workflows) - Skills compose into larger behaviors - Knowledge compounds—each registration makes routing better **The metaphor:** Just as a shepherd doesn't dictate each sheep's movement but guides the flock toward pasture, Poimen doesn't hard-code workflows but learns from context, remembers lessons, and guides agents through complex, adaptive orchestration. --- ## Key Features ### 🧠 LLM-Powered Routing - **Natural Language Input:** `"Scan repo for security issues and notify team"` - **Intelligent Workflow Generation:** Reasoning model generates executable WorkflowSpec - **Activity Knowledge Base:** 9 registered activities inform LLM about timeouts, retry policies, dependencies - **Error Handling Strategies:** Automatic catch blocks and fallback flows ### 💾 Memory-Augmented Context - **Semantic Search Integration:** Query poimen-memory before routing decisions - **Domain Knowledge Injection:** Skills and lessons automatically augment LLM prompts - **Skill Reusability:** Registered knowledge base prevents redundant work - **Learning Loop:** Execution results feed back into memory for continuous improvement ### 🔄 Generic State Machine Executor - **JSON WorkflowSpec:** Any activity graph becomes executable - **Parameter Chaining:** JSONPath-based data flow (`${ActivityA.output.field}`) - **Automatic Retries:** Configurable retry policies per activity - **Catch Blocks:** Error recovery with fallback states - **Durable Guarantees:** Temporal ensures exactly-once execution + resume-on-failure ### 🔌 Extensible Architecture - **LLMProvider Interface:** Swap providers (OpenAI, Claude, local models) at runtime - **SpecBuilder Interface:** Multiple strategies for workflow generation (default, cron, custom) - **WorkflowValidator Interface:** Composable validation pipeline - **ActivityExecutor Interface:** Pluggable execution strategies - **Decorator Pattern:** Caching & retry wrappers for any provider --- ## Registered Activities (Knowledge Base) | Activity | Purpose | Timeout | Retry | |----------|---------|---------|-------| | **AssumeRoleActivity** | Request temporary JWT token (like AWS AssumeRole) | 30s | 2x | | **CloneRepo** | Clone git repository | 30s | 3x | | **AnalyzeCode** | Static analysis (SAST) | 120s | 2x | | **SecurityScan** | Dependency & vulnerability scan | 60s | 2x | | **GenerateReport** | Compile results into report | 30s | 1x | | **DeploymentPreCheck** | Validation before deployment | 45s | 2x | | **NotifyStatus** | Send notifications (Slack, email) | 15s | 1x | | **ApproveWorkflow** | Manual approval gate | 3600s | 0x | | **ArchiveResults** | Store results to S3/KV | 30s | 2x | | **RetrieveMemory** | Query semantic memory service | 10s | 1x | 👉 See [`internal/routing/activity_knowledge_base.json`](./internal/routing/activity_knowledge_base.json) for detailed specs. --- ## Getting Started ### Prerequisites - Go 1.21+ - Temporal Server (local or cloud) - Docker (for running Temporal locally) ### Installation ```bash git clone https://github.com/your-org/workflows.git cd workflows go mod download ``` ### Running Locally **1. Start Temporal (via Docker Compose):** ```bash docker-compose up -d temporal ``` **2. Register activities and workflow:** ```bash cd cmd/worker go build -o worker ./worker ``` **3. Trigger a workflow via CLI:** ```bash cd cmd/starter go build -o starter ./starter --route "Analyze repository for security vulnerabilities" ``` **4. Or trigger programmatically:** ```go import "github.com/your-org/workflows/pkg/client" ctx := context.Background() c := client.NewClient(...) run, err := c.ExecuteWorkflow(ctx, &client.ExecuteWorkflowOptions{ ID: "security-scan-1", TaskQueue: "routing", }, "RoutingWorkflow", &routing.WorkflowInput{ UserRequest: "Scan repo X for CVEs", }) // Wait for result var result routing.WorkflowOutput run.Get(ctx, &result) ``` --- ## Usage Patterns ### Pattern 1: Simple CLI Routing ```bash ./starter --route "Deploy service to staging" ``` ### Pattern 2: Direct Spec Execution ```bash ./starter --spec workflow.json ``` ### Pattern 3: Service Integration ```go // From your application code client := temporal.NewClient() result, err := client.ExecuteWorkflow(ctx, options, "RoutingWorkflow", input) // Implements durable retry + failure recovery ``` ### Pattern 4: Custom Validator Chain ```go config := &routing.LLMRouterConfig{ Validators: []routing.WorkflowValidator{ routing.NewStateGraphValidator(), routing.NewActivityAvailabilityValidator(), routing.NewTimeoutValidator(), routing.NewOutputMatchValidator(), }, } router := routing.NewLLMRouter(config) ``` --- ## Registerable Skills & Knowledge ### The Shepherd's Knowledge Just as a good shepherd knows the terrain, remembers which paths work, and learns from past journeys, Poimen's intelligence comes from **accumulated knowledge**: - **What can be done?** (Activity Knowledge) — Registry of available skills - **How has it been done well?** (Domain Knowledge) — Lessons from past executions - **What patterns work?** (Workflow Patterns) — Proven sequences and error recovery - **What went wrong before?** (Error Knowledge) — Lessons to avoid mistakes - **How should recovery work?** (Execution Policies) — Graceful fallbacks When you register a skill, you're teaching the shepherd about a new capability. When you contribute domain knowledge, you're sharing wisdom so future workflows avoid mistakes. ### What is a Registerable Skill? A **skill** is a composable unit of work that: 1. **Solves a specific problem** (code analysis, deployment, notification) 2. **Has well-defined inputs/outputs** (Git repo URL → vulnerability report) 3. **Encodes domain knowledge** (retry policies, timeouts, dependencies) 4. **Scales independently** (can be retried, cached, load-balanced) 5. **Is discovered by the router** (LLM knows when/how to use it) ### Types of Registerable Knowledge #### 1. Activity Knowledge (What can be done?) - **Definition:** Capabilities, inputs, outputs, timeout/retry policies - **Stored in:** `activity_knowledge_base.json` - **Used by:** LLM router to decide *which* activities to use ```json { "name": "SecurityScan", "timeout": "60s", "maxRetries": 2, "inputs": ["repo_url", "branch"], "outputs": ["report", "severity_count"] } ``` #### 2. Domain Knowledge (How should it be done?) - **Definition:** Best practices, error patterns, optimization strategies - **Stored in:** Semantic memory service (`poimen-memory`) - **Used by:** LLM router to augment prompts before spec generation ```go type MemoryContext struct { Skills []string // Relevant skills Lessons []string // Best practices ErrorPatterns []string // Common failures & fixes } ``` #### 3. Workflow Patterns (What sequences work?) - **Definition:** Proven workflows for common scenarios - **Stored in:** LLM prompt templates (`agent-prompts/router/AGENTS.md`) - **Used by:** SpecBuilder to generate efficient workflows ```yaml Patterns: - name: "Security Review" activities: ["CloneRepo", "SecurityScan", "GenerateReport", "NotifyStatus"] conditions: {"branch": "main"} ``` #### 4. Skill Dependencies (What needs what?) - **Definition:** Activity prerequisites and data flow - **Stored in:** Activity specs + JSONPath parameter bindings - **Used by:** Workflow validator to check graph validity ```json { "activity": "AnalyzeCode", "parameters": { "source_path": "${CloneRepo.output.repo_path}" }, "depends_on": ["CloneRepo"] } ``` #### 5. Execution Policies (When should it fail/retry?) - **Definition:** Retry strategies, error handlers, fallbacks - **Stored in:** Activity specs + catch blocks in workflows - **Used by:** StateTransitioner to decide recovery actions ```json { "activity": "NotifyStatus", "retry_policy": { "max_attempts": 3, "initial_interval": "1s", "backoff_coefficient": 2.0 }, "catch_blocks": [ { "error_type": "AuthenticationError", "action": "log_and_continue" } ] } ``` ### How LLM Router Uses Registered Skills #### Flow: User Request → Knowledge-Augmented Routing ``` User: "Scan repository for security issues and deploy if safe" ↓ [1. Retrieve Memory] Query for "security scan + deployment" patterns Returns: Best practices, error handlers, proven sequences ↓ [2. Augment LLM Prompt] System message includes: - Available activities (from knowledge_base.json) - Relevant patterns (from memory) - Lessons learned (from memory) ↓ [3. Generate WorkflowSpec] LLM reasons: - "SecurityScan is first (needs fresh clone)" - "If vulnerabilities found, stop; don't deploy" - "Cache results per commit for 5 minutes" ↓ [4. Validate Spec] - All activities registered? ✓ - All parameters have sources? ✓ - Timeouts reasonable? ✓ ↓ [5. Execute] RoutingWorkflow with automatic retries + error recovery ``` ### Registering a New Skill (Step-by-Step) #### Step 1: Implement the Activity ```go // action/my_new_skill.go package action type MyNewSkillInput struct { Input1 string `json:"input1"` Input2 int `json:"input2"` } type MyNewSkillOutput struct { Success bool `json:"success"` Message string `json:"message"` Data string `json:"data"` } func MyNewSkill(ctx context.Context, input *MyNewSkillInput) (*MyNewSkillOutput, error) { // Implement logic return &MyNewSkillOutput{ Success: true, Message: "Executed successfully", Data: "result...", }, nil } ``` #### Step 2: Add to Knowledge Base Edit `internal/routing/activity_knowledge_base.json`: ```json { "name": "MyNewSkill", "description": "Does something important", "inputs": [ { "name": "input1", "type": "string", "description": "First input parameter", "required": true, "examples": ["example1", "example2"] } ], "outputs": [ { "name": "success", "type": "boolean", "description": "Whether executed successfully" } ], "timeout": "30s", "maxRetries": 2, "retryPolicy": "exponential", "dependencies": [], "tags": ["domain", "category"], "knowledge": { "bestPractices": [ "Always validate input format", "Log all errors" ], "errorHandling": "On ValidationError, return empty data", "performance": "Caches results for 1 minute", "constraints": "Requires internet connection" } } ``` #### Step 3: Register with Temporal Worker ```go // cmd/worker/main.go worker.RegisterActivity(action.MyNewSkill) ``` #### Step 4: Test Routing ```bash go build -o worker ./cmd/worker ./worker & ./starter --route "Use MyNewSkill to process my data" ``` ### Skill Registration Checklist ✅ - [ ] **Well-defined interface** — Inputs/outputs clearly typed - [ ] **Timeout is reasonable** — Not too short (flaky), not too long (blocking) - [ ] **Retry policy is sensible** — Network failures get retries; auth failures don't - [ ] **Error handling is graceful** — Returns partial results, doesn't crash - [ ] **Dependencies are clear** — Knows what other skills it needs - [ ] **Knowledge is explicit** — Best practices documented in spec - [ ] **Idempotent or tagged** — Safe to retry without side effects - [ ] **Observable** — Logs important milestones - [ ] **Tested** — Unit tests + integration tests - [ ] **Documented** — Clear description for LLM prompts ### The Virtuous Cycle ``` 1. Register Skill A ↓ 2. Router uses Skill A in workflows ↓ 3. Execution logs captured → lessons learned ↓ 4. Register Domain Knowledge from Skill A's lessons ↓ 5. Future workflows route smarter (memory-augmented) ↓ 6. Register Skill B (improved by A's knowledge) ↓ 7. Repeat → System gets smarter ``` **The system is self-improving.** Each addition makes it better for everyone. --- ## Project Structure ``` workflows/ ├── cmd/ │ ├── worker/ # Temporal worker (registers activities & workflows) │ └── starter/ # CLI for triggering workflows ├── action/ # Activity implementations │ ├── analysis.go # Code analysis activity │ ├── memory.go # Memory retrieval activity │ ├── notification.go # Status notifications │ ├── logger.go # Structured logging │ └── router.go # Router activity orchestration ├── internal/routing/ │ ├── types.go # WorkflowSpec, ActivitySpec types │ ├── knowledge_base.go # Activity knowledge base loader │ ├── llm_client.go # LLM provider interface │ ├── llm_router.go # Natural language → WorkflowSpec │ ├── provider.go # LLMProvider implementations (caching, retry) │ ├── spec_builder.go # SpecBuilder interface & implementations │ ├── executor.go # Validators, executors, state transitioners │ ├── activity_knowledge_base.json # Activity registry │ └── *_test.go # Unit tests ├── statemachine/ │ └── routing_workflow.go # RoutingWorkflow state machine executor ├── agent-prompts/ │ └── router/AGENTS.md # LLM prompts for routing decisions ├── k8s/ │ ├── worker-deployment.yaml │ └── configmap.yaml ├── examples/ │ ├── service_integration.go │ └── wait_for_task_test.go ├── tests/ │ └── *_test.go # Integration & E2E tests ├── docs/ │ └── *.md # Architecture & design docs └── README.md # This file ``` --- ## Configuration ### Environment Variables ```bash # LLM Configuration LLM_ENDPOINT="https://api.riotpiao.com/v1/chat/completions" LLM_MODEL="reasoning" LOCAL_LLM_BASE_URL="https://api.riotpiao.com" # Override for local dev # Temporal Configuration TEMPORAL_HOST_URL="localhost:7233" TEMPORAL_NAMESPACE="default" # Memory Service MEMORY_SERVICE_URL="http://poimen-memory.poimen.svc.cluster.local:8080" # Logging LOG_LEVEL="info" ``` ### Authentication & Federation Poimen supports multiple authentication methods to federate LLM access across customers and tenants: #### 1. Bearer Token (JWT/OAuth2) ```go auth := &routing.LLMAuth{ Type: routing.AuthTypeBearer, Token: "eyJhbGciOiJIUzI1NiIs...", // JWT token from your auth provider } client := routing.NewLLMClientWithAuth(auth) ``` #### 2. API Key ```go auth := &routing.LLMAuth{ Type: routing.AuthTypeAPIKey, APIKey: "sk-xxx-yyy-zzz", // API key from provider } client := routing.NewLLMClientWithAuth(auth) ``` #### 3. Custom Header ```go auth := &routing.LLMAuth{ Type: routing.AuthTypeCustom, HeaderName: "X-Custom-Auth", HeaderValue: "custom-token-value", } client := routing.NewLLMClientWithAuth(auth) ``` #### 4. Router Configuration with Auth ```go config := &routing.LLMRouterConfig{ Provider: openaiProvider, KnowledgeBase: kb, Auth: &routing.LLMAuth{ Type: routing.AuthTypeBearer, Token: jwtToken, }, } router := routing.NewLLMRouter(config) ``` #### Per-Deployment Auth Each Poimen deployment gets its own LLM token: ```go // In K8s secret/vault LLM_AUTH_TOKEN="eyJhbGciOiJIUzI1NiIs..." // In code func InitializeRouter() (*routing.LLMRouter, error) { token := os.Getenv("LLM_AUTH_TOKEN") if token == "" { return nil, fmt.Errorf("LLM_AUTH_TOKEN not set") } auth := &routing.LLMAuth{ Type: routing.AuthTypeBearer, Token: token, } config := &routing.LLMRouterConfig{ Provider: &routing.LLMClient{}, KnowledgeBase: kb, Auth: auth, } return routing.NewLLMRouter(config) } ``` #### Token Refresh & Rotation For long-running workflows, update auth at runtime: ```go client := routing.NewLLMClientWithAuth(oldAuth) // Token expires, refresh it newAuth := &routing.LLMAuth{ Type: routing.AuthTypeBearer, Token: refreshedToken, // New token from OAuth2 provider TenantID: customerID, } client.UpdateAuth(newAuth) // Subsequent requests use new token result, err := client.Chat(ctx, systemPrompt, userMsg) ``` #### Headers Sent to LLM API | Header | Set When | Value | Purpose | |--------|----------|-------|---------| | `Authorization` | Bearer auth | `Bearer {token}` | JWT/OAuth2 authentication | | `X-API-Key` | API Key auth | `{api-key}` | API key authentication | | Custom header | Custom auth | `{headerValue}` | Custom authentication scheme | ### LLMRouter Configuration (Programmatic) ```go config := &routing.LLMRouterConfig{ KnowledgeBase: kb, LLMProviders: []routing.LLMProvider{ openai.NewOpenAIProvider("gpt-4"), claude.NewClaudeProvider("claude-3"), local.NewLocalProvider(":8000"), }, SpecBuilders: []routing.SpecBuilder{ routing.NewDefaultSpecBuilder(), routing.NewCronSpecBuilder(), }, Validators: []routing.WorkflowValidator{ routing.NewStateGraphValidator(), routing.NewActivityAvailabilityValidator(), }, CacheProvider: routing.NewCachingLLMProvider(cache), } router := routing.NewLLMRouter(config) ``` --- ## Testing ### Unit Tests ```bash go test ./... -v ``` ### Coverage Report ```bash go test ./... -cover -coverprofile=coverage.out go tool cover -html=coverage.out ``` ### Integration Tests ```bash # Requires Temporal running go test -tags=integration ./tests/... ``` ### Current Coverage - `internal/routing`: 45.9% - `action`: ~60% - `statemachine`: ~50% See [`poimen-docs/CRAP_ANALYSIS.md`](../poimen-docs/CRAP_ANALYSIS.md) for complexity metrics. --- ## CRAP Score Improvements Refactoring focused on reducing cyclomatic complexity via helper extraction and strategy patterns: | Function | Before | After | Improvement | |----------|--------|-------|-------------| | `buildCronSpec` | 156 | 5 | 97% ↓ | | `buildParameters` | 90 | 6 | 93% ↓ | | `RoutingWorkflow` | 132 | 7 | 95% ↓ | | `DeploymentPreCheckActivity` | 110 | 6 | 95% ↓ | **Target:** CRAP < 10 for all production functions ✅ --- ## CI/CD Pipeline ### GitHub Actions - **On Push:** Run tests, lint, coverage checks - **On PR:** Automated tests + code review gates - **On Release:** Build binaries for macOS/Linux/Windows, publish to registry ```yaml # Typical workflow 1. Unit tests (all packages) 2. Go vet & lint checks 3. Integration tests (Temporal required) 4. Coverage validation (>40% threshold) 5. Deploy staging worker (if main branch) ``` ### Build & Deploy ```bash # Local build make build # Builds cmd/worker and cmd/starter # Docker build (K8s deployment) docker build -t your-registry/poimen-worker:latest . # Deploy to K8s kubectl apply -f k8s/worker-deployment.yaml ``` --- ## Architecture Decisions ### Why Temporal? - **Durable Execution:** Survives crashes, network splits, process restarts - **Visibility:** Full workflow history in UI + API - **Failure Handling:** Built-in retry, timeout, compensation logic - **Scalability:** Millions of concurrent workflows ### Why LLM Router? - **Intent Understanding:** Captures user intent without rigid form parsing - **Adaptive Workflows:** LLM optimizes step ordering based on knowledge base - **Extensibility:** New activities auto-discovered; no hardcoding - **Memory Augmentation:** Context-aware decisions via semantic search ### Why Generic State Machine? - **Composability:** Any activity becomes a workflow step - **Reusability:** One state machine handles all workflow patterns - **Testability:** Validates JSON specs independently of execution - **Debuggability:** JSONPath-based tracing for data flow ### Plugin Architecture Benefits - **Provider Swapping:** Switch LLM backends without code changes - **Strategy Injection:** Multiple spec builders, validators in pipeline - **Decorator Pattern:** Caching, retries, logging as independent layers - **Backward Compatibility:** `NewLLMRouterDefault()` maintains old API --- ## Security & Token Management ### Never Hardcode Tokens **Use secure secret management:** ```go // ❌ DON'T DO THIS auth := &routing.LLMAuth{ Type: routing.AuthTypeBearer, Token: "eyJhbGciOiJIUzI1NiIs...", // Hardcoded! } // ✅ DO THIS token := os.Getenv("LLM_AUTH_TOKEN") auth := &routing.LLMAuth{ Type: routing.AuthTypeBearer, Token: token, } ``` **Recommended secret management:** - **Kubernetes Secrets** (development) — stored in etcd - **HashiCorp Vault** (production) — centralized secret management - **AWS Secrets Manager** (cloud) — managed service - **GCP Secret Manager** (cloud) — managed service - **Sealed Secrets** or **Sealed Policies** — encrypted in git ### Token Rotation & Refresh For long-running Temporal workflows, refresh tokens before they expire: ```go client := routing.NewLLMClientWithAuth(auth) // Later: token expires newToken := os.Getenv("LLM_AUTH_TOKEN_REFRESHED") newAuth := &routing.LLMAuth{ Type: routing.AuthTypeBearer, Token: newToken, } client.UpdateAuth(newAuth) ``` ### Authorization: LLM API Side The LLM provider (api.riotpiao.com) should: - Validate JWT signature & expiration - Enforce API rate limits per token - Log all requests with token identity - Support token revocation / blacklisting --- ## AssumeRoleActivity: Temporary LLM Token Grants **Like AWS AssumeRole**, AssumeRoleActivity requests temporary credentials for accessing LLM APIs: ```go // 1. User requests temporary token assumeRoleInput := &routing.AssumeRoleInput{ Identity: "user@company.com", // Who is accessing Scope: "llm:read llm:write", // What permissions DurationSeconds: 1800, // 30 minutes } // 2. Activity exchanges with auth server → returns JWT output, err := temporalClient.ExecuteActivity(ctx, routing.AssumeRoleActivity, assumeRoleInput) // 3. Extract token from result var tokenOutput *routing.AssumeRoleOutput output.Get(&tokenOutput) // 4. Use token in LLM Router auth := &routing.LLMAuth{ Type: routing.AuthTypeBearer, Token: tokenOutput.Token, // ← JWT valid for 30 minutes } router := routing.NewLLMRouter(config) ``` ### Workflow Pattern: AssumeRole → LLM Router → Activities ```go // Step 1: Get temporary credentials assumeRoleResult := workflow.ExecuteActivity(ctx, routing.AssumeRoleActivity, &routing.AssumeRoleInput{ Identity: workflowInput.UserID, Scope: "llm:read llm:write", DurationSeconds: 1800, }) var token *routing.AssumeRoleOutput assumeRoleResult.Get(&token) // Step 2: Use token for all LLM router calls routerInput := &routing.LLMRouterInput{ Message: "Analyze code for security", Context: map[string]interface{}{"repo": "myrepo"}, } routerOutput := workflow.ExecuteActivity(ctx, routing.LLMRouterActivity, routerInput) // LLMRouter automatically uses the token from LLMRouterConfig // Step 3: Execute generated workflow with same token // (token baked into all activity calls) ``` ### Configuration: Credentials from Vault Never hardcode credentials. Use Kubernetes Secrets or Hashicorp Vault: ```bash # In K8s secret kubectl create secret generic llm-oauth-creds \ --from-literal=OAUTH_CLIENT_ID="client-xxx" \ --from-literal=OAUTH_CLIENT_SECRET="secret-yyy" \ --from-literal=AUTH_SERVER_URL="https://auth.company.com" # Pod reads from secret env: - name: OAUTH_CLIENT_ID valueFrom: secretKeyRef: name: llm-oauth-creds key: OAUTH_CLIENT_ID ``` In code, AssumeRoleActivity reads from environment: ```go input := &routing.AssumeRoleInput{ Identity: "user@company.com", Scope: "llm:read", // clientId, clientSecret, authServerUrl read from env automatically } result, _ := AssumeRoleActivity(ctx, input) ``` ### Token Lifecycle | Stage | Duration | Action | |-------|----------|--------| | **Request** | T+0s | User calls AssumeRoleActivity with identity + scope | | **Grant** | T+1s | Auth server validates, issues JWT (default: 1hr validity) | | **Use** | T+1s to T+3600s | LLMRouter uses token for all api.riotpiao.com calls | | **Refresh** | Before expiry | If workflow > 1hr, request new token via AssumeRole again | | **Revoke** | On demand | Auth server can immediately revoke token if needed | ### Scopes & Access Control Scopes define granular permissions: ```go // Read-only access (safe for analytics) asScope: "llm:read" // Full access (for agent workflows) scope: "llm:read llm:write" // Admin access (for operator/setup) scope: "llm:admin" ``` The LLM API validates scopes on every request. AssumeRoleActivity can't escalate privileges—scopes returned by auth server are trusted. --- ## Documentation - **[Routing Workflow Spec](./docs/ROUTING_WORKFLOW_SPEC.md)** — Complete spec format reference - **[Activity Knowledge Base](./docs/ACTIVITY_KNOWLEDGE_BASE.md)** — Activity definitions & capabilities - **[LLM Router Prompts](./agent-prompts/router/AGENTS.md)** — System prompts for routing decisions - **[CRAP Analysis](../poimen-docs/CRAP_ANALYSIS.md)** — Code complexity refactoring details - **[Implementation Notes](./README_IMPLEMENTATION.md)** — Design decisions & trade-offs --- ## Contributing ### Adding a New Activity 1. Implement `Activity` interface in `action/` 2. Add spec to `internal/routing/activity_knowledge_base.json` 3. Register in `cmd/worker/main.go` 4. Test with CLI: `./starter --route "..."` ### Extending the Router 1. Implement `LLMProvider`, `SpecBuilder`, or `WorkflowValidator` interface 2. Register in `LLMRouterConfig` 3. Add unit tests in `internal/routing/*_test.go` 4. Update docs ### Code Style - Run `gofmt` and `go vet` before commit - Write tests for all exported functions - Keep CRAP score < 10 - Document non-obvious logic --- ## Troubleshooting ### "Activity not registered" - Ensure `cmd/worker` is running - Check `activity_knowledge_base.json` includes activity name - Verify activity name in workflow spec matches registration ### "Workflow failed to execute" - Check Temporal logs: `temporal workflow show -w ` - Review activity output in Temporal UI (`:8081`) - Enable debug logging: `LOG_LEVEL=debug` ### "LLM router timeout" - Increase `LLMRequestTimeout` in config - Check API endpoint reachability: `curl https://api.riotpiao.com/v1/chat/completions` - Review LLM prompt length (may be too complex) ### "Memory service not responding" - Verify `MEMORY_SERVICE_URL` endpoint - Check K8s pod: `kubectl logs -l app=poimen-memory` - Graceful fallback: RetrieveMemoryActivity is optional --- ## Roadmap - [ ] **Real Cron Scheduling** — Implement Temporal ScheduleClient for recurring workflows - [ ] **Additional LLM Providers** — OpenAI, Claude, local model integrations - [ ] **Provider Auto-Discovery** — Config-driven provider registration - [ ] **Improved Coverage** — Target 60%+ in all packages - [ ] **Web Dashboard** — Real-time workflow execution dashboard - [ ] **Approval Workflows** — Multi-stage human approval gates --- ## License [Your License Here] ## Support - **Issues:** GitHub Issues - **Discussions:** GitHub Discussions - **Security:** See SECURITY.md --- **Built with ❤️ using Temporal, Go, and LLMs.**