Task 1.1 COMPLETE ✅ Core type definitions for routing workflows: - WorkflowSpec: One-time workflow specification - CronWorkflowSpec: Scheduled workflow specification - State: Individual step in workflow (Task/Pass/Fail) - RetryPolicy: Retry configuration with backoff - CatchClause: Error handling - ExecutionContext: Tracks state during execution - ActivityMetadata: Describes activity capabilities - Supporting types: PollParams, Heartbeat, Result All types support JSON marshaling/unmarshaling. 8 unit tests covering complex scenarios (9/9 PASS). Acceptance criteria met: ✅ All types compile without errors ✅ JSON marshaling/unmarshaling works correctly ✅ Unit tests pass (complex workflow examples) ✅ Ready for next phase (Knowledge Base) Effort: 2 hours Files: internal/routing/types.go (159 lines) internal/routing/types_test.go (286 lines)
32 KiB
Poimen Routing Workflow Specification
Status: Complete Standard
Version: 1.0
Architecture: User Message → LLM-Router → JSON WorkflowSpec → RoutingWorkflow
OVERVIEW
The Flow
User Message: "Analyze this GitHub repo for security and quality"
↓
[llm-router Activity] ← LLM-powered intelligent agent
Reads: ActivityKnowledgeBase.json
↓
Uses LLM to understand intent:
- What is user asking for?
- Which activities are needed?
- What order?
- What timeout for each?
- What retry strategy?
↓
Generates JSON WorkflowSpec
{
"name": "repo-analysis",
"input": {"repo": "https://...", "branch": "main"},
"states": [
{
"name": "Clone",
"type": "Task",
"resource": "CloneRepoActivity",
"parameters": {"repo": "${input.repo}"},
"timeout": "5m", ← LLM decided
"retry": {...}, ← LLM decided
"next": "Analyze"
},
{...more states...}
]
}
↓
[RoutingWorkflow] ← Generic executor
Takes the JSON spec from llm-router
Executes states in order
Respects timeout/retry
↓
Final Results
Key Insight
Intelligence is distributed:
- ActivityKnowledgeBase.json: What activities exist, their constraints, capabilities
- llm-router Activity: Uses LLM to reason about user intent + pick activities + decide settings
- RoutingWorkflow: Dumb executor that just runs what the LLM decided
PART 1: ACTIVITY KNOWLEDGE BASE
What is it?
Metadata about all available activities. The llm-router reads this to make intelligent decisions.
File Location
internal/routing/activity_knowledge_base.json
Example Format
{
"activities": [
{
"name": "CloneRepoActivity",
"description": "Clone a Git repository to local filesystem",
"category": "source-control",
"inputs": {
"repo": {
"type": "string",
"description": "Repository URL (https://...)",
"required": true
},
"branch": {
"type": "string",
"description": "Branch name to clone",
"required": false,
"default": "main"
}
},
"outputs": {
"path": {
"type": "string",
"description": "Local filesystem path to cloned repo"
},
"commit": {
"type": "string",
"description": "Current commit hash"
}
},
"constraints": {
"defaultTimeout": "5m",
"isFlaky": false,
"recommendedRetries": 2,
"retryBackoff": 1.5,
"dependencies": [],
"notes": "Fast operation, rarely fails"
}
},
{
"name": "AnalyzeCodeActivity",
"description": "Analyze code for quality issues, complexity, patterns",
"category": "analysis",
"inputs": {
"path": {
"type": "string",
"description": "Path to code directory",
"required": true
},
"language": {
"type": "string",
"description": "Programming language (go, python, js, etc)",
"required": false,
"default": "auto-detect"
}
},
"outputs": {
"quality": {
"type": "number",
"description": "Quality score 0-1"
},
"issues": {
"type": "array",
"description": "List of issues found"
},
"complexity": {
"type": "number",
"description": "Cyclomatic complexity"
}
},
"constraints": {
"defaultTimeout": "10m",
"isFlaky": true,
"recommendedRetries": 3,
"retryBackoff": 2.0,
"dependencies": ["CloneRepoActivity"],
"notes": "Can timeout on large codebases, retry recommended"
}
},
{
"name": "SecurityScanActivity",
"description": "Scan code for vulnerabilities and security issues",
"category": "security",
"inputs": {
"path": {
"type": "string",
"description": "Path to scan",
"required": true
}
},
"outputs": {
"vulnerabilities": {
"type": "array",
"description": "List of vulnerabilities found"
},
"score": {
"type": "number",
"description": "Security score 0-1"
}
},
"constraints": {
"defaultTimeout": "15m",
"isFlaky": false,
"recommendedRetries": 2,
"retryBackoff": 1.5,
"dependencies": ["CloneRepoActivity"],
"notes": "Longer runtime, can be parallelized with other scans"
}
},
{
"name": "PerformanceAnalysisActivity",
"description": "Analyze runtime performance, bottlenecks, optimization opportunities",
"category": "analysis",
"inputs": {
"path": {
"type": "string",
"description": "Path to code",
"required": true
}
},
"outputs": {
"performance": {
"type": "number",
"description": "Performance score 0-1"
},
"bottlenecks": {
"type": "array",
"description": "Performance bottlenecks"
}
},
"constraints": {
"defaultTimeout": "10m",
"isFlaky": true,
"recommendedRetries": 2,
"retryBackoff": 2.0,
"dependencies": ["CloneRepoActivity"],
"notes": "Can timeout on complex analysis"
}
},
{
"name": "JudgeActivity",
"description": "Make final judgment/recommendation based on analysis results",
"category": "judgment",
"inputs": {
"quality": {
"type": "object",
"description": "Code quality analysis result",
"required": true
},
"security": {
"type": "object",
"description": "Security scan result",
"required": false
},
"performance": {
"type": "object",
"description": "Performance analysis result",
"required": false
}
},
"outputs": {
"verdict": {
"type": "string",
"description": "approved, needs-changes, rejected"
},
"score": {
"type": "number",
"description": "Overall score"
},
"recommendations": {
"type": "array",
"description": "Recommendations for improvement"
}
},
"constraints": {
"defaultTimeout": "5m",
"isFlaky": false,
"recommendedRetries": 1,
"retryBackoff": 1.0,
"dependencies": ["AnalyzeCodeActivity"],
"notes": "Fast operation, only runs after analysis"
}
},
{
"name": "CombineResultsActivity",
"description": "Combine multiple analysis results into final report",
"category": "aggregation",
"inputs": {
"results": {
"type": "array",
"description": "Array of previous step results",
"required": true
}
},
"outputs": {
"report": {
"type": "object",
"description": "Final combined report"
}
},
"constraints": {
"defaultTimeout": "2m",
"isFlaky": false,
"recommendedRetries": 1,
"retryBackoff": 1.0,
"dependencies": [],
"notes": "Fast, runs at the end"
}
},
{
"name": "SendNotificationActivity",
"description": "Send notification with results to user",
"category": "notification",
"inputs": {
"result": {
"type": "object",
"description": "Results to send",
"required": true
},
"channel": {
"type": "string",
"description": "Where to send (email, slack, etc)",
"required": false,
"default": "email"
}
},
"outputs": {
"sent": {
"type": "boolean",
"description": "Was notification sent?"
}
},
"constraints": {
"defaultTimeout": "2m",
"isFlaky": true,
"recommendedRetries": 3,
"retryBackoff": 2.0,
"dependencies": [],
"notes": "External dependency, retry recommended"
}
}
]
}
Knowledge Base Fields Explained
| Field | Purpose | Used By |
|---|---|---|
| name | Unique activity identifier | llm-router for routing |
| description | What the activity does | llm-router for reasoning |
| category | Type of activity (analysis, security, etc) | llm-router for grouping |
| inputs | What the activity accepts | llm-router to chain activities |
| outputs | What the activity produces | llm-router to connect outputs to next activity inputs |
| constraints.defaultTimeout | Recommended timeout | llm-router to decide timeout |
| constraints.isFlaky | Does it fail often? | llm-router to decide retry count |
| constraints.recommendedRetries | How many retries? | llm-router to configure retry |
| constraints.retryBackoff | Backoff multiplier | llm-router to configure exponential backoff |
| constraints.dependencies | Must run after X | llm-router to order activities |
| constraints.notes | Special handling notes | llm-router for reasoning |
PART 2: LLM-ROUTER ACTIVITY
What is it?
An Activity that uses an LLM to intelligently decide:
- Which activities are needed
- What order to execute them
- What timeout for each activity
- What retry policy for each activity
- How to chain parameters (JSONPath)
Input to llm-router
{
"message": "Analyze this GitHub repo for security and code quality",
"repo": "https://github.com/rockliang/poimen",
"branch": "main"
}
Output from llm-router
{
"name": "repo-analysis-workflow",
"input": {
"repo": "https://github.com/rockliang/poimen",
"branch": "main"
},
"states": [
{
"name": "Clone",
"type": "Task",
"resource": "CloneRepoActivity",
"parameters": {
"repo": "${input.repo}",
"branch": "${input.branch}"
},
"timeout": "5m",
"retry": {
"maxAttempts": 2,
"backoffRate": 1.5,
"initialInterval": "1s"
},
"next": "Analyze"
},
{
"name": "Analyze",
"type": "Task",
"resource": "AnalyzeCodeActivity",
"parameters": {
"path": "${Clone.output.path}"
},
"timeout": "10m",
"retry": {
"maxAttempts": 3,
"backoffRate": 2.0,
"initialInterval": "1s"
},
"catch": [
{
"errorEquals": ["Timeout"],
"next": "HandleTimeout"
}
],
"next": "SecurityScan"
},
{
"name": "SecurityScan",
"type": "Task",
"resource": "SecurityScanActivity",
"parameters": {
"path": "${Clone.output.path}"
},
"timeout": "15m",
"retry": {
"maxAttempts": 2,
"backoffRate": 1.5,
"initialInterval": "1s"
},
"next": "Combine"
},
{
"name": "Combine",
"type": "Task",
"resource": "CombineResultsActivity",
"parameters": {
"results": [
"${Analyze.output}",
"${SecurityScan.output}"
]
},
"timeout": "2m",
"retry": {
"maxAttempts": 1,
"backoffRate": 1.0,
"initialInterval": "1s"
},
"next": "SendResults"
},
{
"name": "SendResults",
"type": "Task",
"resource": "SendNotificationActivity",
"parameters": {
"result": "${Combine.output.report}",
"channel": "email"
},
"timeout": "2m",
"retry": {
"maxAttempts": 3,
"backoffRate": 2.0,
"initialInterval": "1s"
},
"end": true
},
{
"name": "HandleTimeout",
"type": "Fail",
"error": "AnalysisTimeout",
"cause": "Code analysis timed out after 10 minutes"
}
]
}
LLM-Router Logic (Pseudo-code)
def llm_router_activity(message: str, repo: str, branch: str) -> WorkflowSpec:
# 1. Load knowledge base
knowledge_base = load_activity_knowledge_base()
# 2. Use LLM to understand intent
intent = llm.analyze_intent(message)
# intent = {
# "needs_security_scan": true,
# "needs_code_analysis": true,
# "needs_performance_analysis": false,
# "needs_final_judgment": true,
# }
# 3. Select activities based on intent
selected_activities = []
if intent.needs_security_scan:
selected_activities.append(knowledge_base["SecurityScanActivity"])
if intent.needs_code_analysis:
selected_activities.append(knowledge_base["AnalyzeCodeActivity"])
if intent.needs_performance_analysis:
selected_activities.append(knowledge_base["PerformanceAnalysisActivity"])
# 4. Order activities by dependencies
ordered = topological_sort_by_dependencies(selected_activities)
# 5. Build workflow states with intelligent settings
states = []
# Always start with Clone
clone_state = {
"name": "Clone",
"type": "Task",
"resource": "CloneRepoActivity",
"parameters": {
"repo": "${input.repo}",
"branch": "${input.branch}"
},
"timeout": "5m", # From knowledge base
"retry": {
"maxAttempts": 2,
"backoffRate": 1.5,
"initialInterval": "1s"
},
"next": ordered[0]["name"]
}
states.append(clone_state)
# Add selected activities in order
for i, activity in enumerate(ordered):
state = {
"name": activity["name"].replace("Activity", ""),
"type": "Task",
"resource": activity["name"],
"parameters": {
# Use LLM to map inputs
# For each input, decide: use output from previous step? use input param? use constant?
},
"timeout": activity["constraints"]["defaultTimeout"],
"retry": {
"maxAttempts": activity["constraints"]["recommendedRetries"],
"backoffRate": activity["constraints"]["retryBackoff"],
"initialInterval": "1s"
},
"next": ordered[i+1]["name"] if i+1 < len(ordered) else "SendResults"
}
# Add error handling if flaky
if activity["constraints"]["isFlaky"]:
state["catch"] = [
{
"errorEquals": ["Timeout"],
"next": "HandleTimeout"
}
]
states.append(state)
# Add final notification
states.append({
"name": "SendResults",
"type": "Task",
"resource": "SendNotificationActivity",
"parameters": {
"result": "${" + ordered[-1]["name"].replace("Activity", "") + ".output}",
"channel": "email"
},
"timeout": "2m",
"retry": {"maxAttempts": 3, "backoffRate": 2.0, "initialInterval": "1s"},
"end": true
})
# Add error handler
states.append({
"name": "HandleTimeout",
"type": "Fail",
"error": "AnalysisTimeout",
"cause": "Activity timed out"
})
# 6. Return complete workflow spec
return WorkflowSpec(
name="generated-workflow",
input={"repo": repo, "branch": branch},
states=states
)
Go Implementation Skeleton
// cmd/worker/activities/llm_router.go
type LLMRouterInput struct {
Message string
Repo string
Branch string
}
func LLMRouterActivity(ctx context.Context, input LLMRouterInput) (routing.WorkflowSpec, error) {
// 1. Load activity knowledge base
kb, err := LoadActivityKnowledgeBase()
if err != nil {
return routing.WorkflowSpec{}, err
}
// 2. Use LLM to understand intent
// Call memory service to get LLM analysis
intent, err := llm.AnalyzeIntent(ctx, input.Message, kb)
if err != nil {
return routing.WorkflowSpec{}, err
}
// 3. Build workflow spec intelligently
spec := buildWorkflowSpec(intent, input, kb)
return spec, nil
}
func buildWorkflowSpec(intent Intent, input LLMRouterInput, kb ActivityKnowledgeBase) routing.WorkflowSpec {
// Create workflow with intelligent settings from knowledge base
// ...
}
PART 3: ROUTING WORKFLOW (The Executor)
What is it?
A generic Temporal workflow that executes ANY WorkflowSpec generated by llm-router.
Input
type RoutingWorkflowInput struct {
WorkflowSpec routing.WorkflowSpec // Generated by llm-router
}
Implementation
func RoutingWorkflow(ctx workflow.Context, spec routing.WorkflowSpec) (Result, error) {
logger := workflow.GetLogger(ctx)
// Initialize context
ec := &ExecutionContext{
Input: spec.Input,
StepResults: make(map[string]interface{}),
}
// Find first state
currentStateName := FindFirstState(spec.States)
// State machine loop
for {
logger.Info("Executing state", "state", currentStateName)
state := FindStateByName(spec.States, currentStateName)
if state == nil {
return Result{}, fmt.Errorf("state not found: %s", currentStateName)
}
// Execute state (Task, Pass, Fail, etc)
result, err := ExecuteState(ctx, state, ec)
// Error handling
if err != nil {
for _, catchClause := range state.Catch {
if MatchesError(err, catchClause.ErrorEquals) {
logger.Info("Error caught", "handler", catchClause.Next)
currentStateName = catchClause.Next
break
}
}
return Result{}, fmt.Errorf("state %s failed: %w", currentStateName, err)
}
// Store result
ec.StepResults[state.Name] = result
// Check if done
if state.End {
logger.Info("Workflow completed")
return Result{
FinalOutput: result,
Status: "COMPLETED",
}, nil
}
// Move to next
currentStateName = state.Next
}
}
PART 4: WORKFLOW SPEC FORMAT
State Types
The llm-router generates states with these types:
Task (Execute Activity)
{
"name": "Clone",
"type": "Task",
"resource": "CloneRepoActivity",
"parameters": {
"repo": "${input.repo}",
"branch": "${input.branch:main}"
},
"timeout": "5m",
"retry": {
"maxAttempts": 3,
"backoffRate": 2.0,
"initialInterval": "1s"
},
"catch": [
{
"errorEquals": ["Timeout", "NetworkError"],
"next": "HandleError"
}
],
"next": "Analyze"
}
LLM decides:
- Which activity to use
- How to chain parameters (${Clone.output.path})
- Timeout (from knowledge base + LLM reasoning)
- Retry policy (from knowledge base + isFlaky flag)
- Error handling (catch blocks for flaky activities)
Pass (Static Output)
{
"name": "CombineResults",
"type": "Pass",
"result": {
"status": "success",
"report": "Analysis complete"
},
"next": "SendResults"
}
Fail (Terminate)
{
"name": "HandleError",
"type": "Fail",
"error": "AnalysisFailed",
"cause": "Security scan timed out"
}
PART 5: EXECUTION FLOW EXAMPLE
User Request
"Analyze the GitHub repo rockliang/poimen for security vulnerabilities and code quality"
Step 1: llm-router Activity
Input: {message: "...", repo: "https://github.com/rockliang/poimen", branch: "main"}
LLM Analysis:
- User wants: security analysis + code quality
- Sequence: Clone → Analyze (quality) → SecurityScan → CombineResults → Notify
- Timeouts: Use defaults from knowledge base
- Clone: 5m (not flaky)
- Analyze: 10m (flaky, so 3 retries)
- SecurityScan: 15m (not flaky, so 2 retries)
- Combine: 2m (not flaky, 1 retry)
- Error handling: Add catch blocks for flaky activities (Analyze)
Output: WorkflowSpec (as JSON above)
Step 2: RoutingWorkflow Executes
State 1: Clone
Execute: CloneRepoActivity({repo: "https://...", branch: "main"})
Timeout: 5m (from llm decision)
Result: {path: "/tmp/cloned-repo"}
↓
State 2: Analyze
Execute: AnalyzeCodeActivity({path: "/tmp/cloned-repo"})
Timeout: 10m
Retry: 3 attempts (from llm decision, isFlaky=true)
Attempt 1: TIMEOUT → Wait 1s
Attempt 2: TIMEOUT → Wait 2s
Attempt 3: SUCCESS → {quality: 0.85, issues: [...]}
↓
State 3: SecurityScan
Execute: SecurityScanActivity({path: "/tmp/cloned-repo"})
Timeout: 15m
Retry: 2 attempts (from llm decision, isFlaky=false)
Result: {vulnerabilities: [], score: 0.95}
↓
State 4: CombineResults
Execute: CombineResultsActivity({results: [...]})
Result: {report: {...}}
↓
State 5: SendResults
Execute: SendNotificationActivity({result: {...}, channel: "email"})
Result: {sent: true}
↓
✅ WORKFLOW COMPLETED
PART 6: GO TYPE DEFINITIONS
package routing
import "time"
// WorkflowSpec is generated by llm-router (one-time execution)
type WorkflowSpec struct {
Name string `json:"name"`
Input map[string]interface{} `json:"input"`
States []State `json:"states"`
}
// CronWorkflowSpec is generated by llm-router (scheduled execution)
type CronWorkflowSpec struct {
Name string `json:"name"`
Type string `json:"type"` // "CronWorkflow"
Schedule string `json:"schedule"` // Cron expression (e.g., "0 2 * * *")
Timezone string `json:"timezone"` // "UTC", "America/New_York", etc
Input map[string]interface{} `json:"input"` // Fixed input for each run
States []State `json:"states"` // Workflow states
MaxConcurrent int `json:"maxConcurrent,omitempty"` // Max parallel runs (default 1)
Timeout string `json:"timeout,omitempty"` // Overall timeout per run
EnableHistory bool `json:"enableHistory,omitempty"` // Keep execution history
}
// State is a step in the workflow
type State struct {
Name string
Type StateType // "Task", "Pass", "Fail"
// Task fields
Resource string
Parameters map[string]interface{}
Timeout string
Retry *RetryPolicy
Catch []CatchClause
// Pass fields
Result interface{}
// Fail fields
Error string
Cause string
// Transition
Next string
End bool
}
type StateType string
const (
StateTypeTask StateType = "Task"
StateTypePass StateType = "Pass"
StateTypeFail StateType = "Fail"
)
// RetryPolicy (LLM decides this from knowledge base)
type RetryPolicy struct {
MaxAttempts int32 `json:"maxAttempts"`
BackoffRate float64 `json:"backoffRate"`
InitialInterval string `json:"initialInterval"`
MaxInterval string `json:"maxInterval,omitempty"`
}
// CatchClause for error handling
type CatchClause struct {
ErrorEquals []string `json:"errorEquals"`
Next string `json:"next"`
}
// ExecutionContext tracks state
type ExecutionContext struct {
Input map[string]interface{}
StepResults map[string]interface{}
}
// Result is final output
type Result struct {
FinalOutput interface{}
Status string
Error error
}
PART 7: CRON JOB SCHEDULING
What is it?
llm-router can generate workflows that execute on a schedule (cron jobs), not just one-time.
Example: Daily Security Scan
User: "Scan all repositories for security vulnerabilities every day at 2 AM"
llm-router generates:
{
"name": "daily-security-scan",
"type": "CronWorkflow",
"schedule": "0 2 * * *",
"timezone": "UTC",
"input": {
"repos": [
"https://github.com/rockliang/poimen",
"https://github.com/rockliang/workflow-engine"
]
},
"states": [
{
"name": "Clone",
"type": "Task",
"resource": "CloneRepoActivity",
"parameters": {
"repo": "${input.repos[0]}"
},
"timeout": "5m",
"retry": {"maxAttempts": 2, "backoffRate": 1.5, "initialInterval": "1s"},
"next": "SecurityScan"
},
{
"name": "SecurityScan",
"type": "Task",
"resource": "SecurityScanActivity",
"parameters": {"path": "${Clone.output.path}"},
"timeout": "15m",
"retry": {"maxAttempts": 2, "backoffRate": 1.5, "initialInterval": "1s"},
"next": "SendAlert"
},
{
"name": "SendAlert",
"type": "Task",
"resource": "SendNotificationActivity",
"parameters": {
"result": "${SecurityScan.output}",
"channel": "slack"
},
"timeout": "2m",
"retry": {"maxAttempts": 3, "backoffRate": 2.0, "initialInterval": "1s"},
"end": true
}
]
}
Cron Format
Standard Unix cron syntax:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6, 0 = Sunday)
│ │ │ │ │
│ │ │ │ │
* * * * *
Examples:
0 2 * * * = Every day at 2:00 AM
0 */6 * * * = Every 6 hours
0 9 * * 1-5 = Weekdays at 9 AM
0 0 1 * * = First day of month at midnight
30 2 * * 0 = Every Sunday at 2:30 AM
*/15 * * * * = Every 15 minutes
0 12 * * * = Every day at noon
Cron Workflow Fields
type CronWorkflowSpec struct {
Name string `json:"name"`
Type string `json:"type"` // "CronWorkflow"
Schedule string `json:"schedule"` // Cron expression
Timezone string `json:"timezone"` // "UTC", "America/New_York", etc
Input map[string]interface{} `json:"input"` // Fixed input for each run
States []State `json:"states"` // Same as regular workflow
// Optional
MaxConcurrent int `json:"maxConcurrent,omitempty"` // Max parallel executions (default 1)
Timeout string `json:"timeout,omitempty"` // Overall timeout per run (default 1h)
EnableHistory bool `json:"enableHistory,omitempty"` // Keep execution history?
}
Temporal Cron Job Implementation
func SubmitCronWorkflow(cronSpec CronWorkflowSpec) error {
client, _ := client.Dial(client.ClientOptions{
HostPort: "temporal-frontend.temporal:7233",
})
// For cron, we still use RoutingWorkflow
// But Temporal handles the scheduling
_, err := client.ExecuteWorkflow(
context.Background(),
client.StartWorkflowOptions{
ID: cronSpec.Name,
TaskQueue: "poimen-taskqueue",
CronSchedule: cronSpec.Schedule, // ← Temporal cron support
},
RoutingWorkflow,
cronSpec.States,
)
return err
}
Example Use Cases
1. Daily Security Scan (0 2 * * *)
Input: {repos: [...]}
Runs: Every day at 2 AM
↓
Clone all repos → Security scan → Send alert
2. Hourly Code Quality Check (0 * * * *)
Input: {repo: "https://github.com/rockliang/poimen"}
Runs: Every hour
↓
Clone → Analyze → Judge → Log results
3. Weekly Performance Benchmark (0 3 * * 0)
Input: {repos: [...], benchmark: true}
Runs: Every Sunday at 3 AM
↓
Clone → Performance test → Compare with baseline → Report
4. Every 15 minutes Health Check (*/15 * * * *)
Input: {service: "api"}
Runs: Every 15 minutes
↓
Check service health → Send metrics → Alert if down
llm-router for Cron Jobs
User: "Run daily security scan at 2 AM UTC on all our repositories"
llm-router decision:
def llm_router_activity(message: str) -> WorkflowSpec:
intent = llm.analyze_intent(message)
# intent = {
# "is_scheduled": True,
# "schedule": "0 2 * * *", # LLM extracts from "2 AM"
# "timezone": "UTC",
# "needs_security_scan": True,
# "target": "all repositories"
# }
if intent.is_scheduled:
return CronWorkflowSpec(
name="daily-security-scan",
type="CronWorkflow",
schedule=intent.schedule, # "0 2 * * *"
timezone=intent.timezone, # "UTC"
input={"repos": get_all_repos()},
states=[
# Same states as one-time workflow
]
)
else:
return OneTimeWorkflowSpec(...)
Execution History
When enableHistory=true, Temporal tracks all cron executions:
Cron Workflow ID: daily-security-scan
Execution 1: 2025-02-01 02:00:00 UTC → COMPLETED
Execution 2: 2025-02-02 02:00:00 UTC → COMPLETED
Execution 3: 2025-02-03 02:00:00 UTC → FAILED (timeout)
Execution 4: 2025-02-04 02:00:00 UTC → COMPLETED
Execution 5: 2025-02-05 02:00:00 UTC → COMPLETED (in progress...)
PART 8: VALIDATION
Before RoutingWorkflow executes the spec, validate:
✅ All state names are unique
✅ All state.next references exist
✅ All catch.next references exist
✅ All Task states have resource defined
✅ No circular loops (A→B→C→A)
✅ At least one path leads to end
✅ No dead ends
✅ Timeout format is valid (e.g., "5m")
✅ Retry settings are valid
✅ JSONPath expressions are syntactically valid
SUMMARY
The Intelligence is in llm-router
llm-router activity:
- ✅ Reads ActivityKnowledgeBase.json
- ✅ Uses LLM to understand user intent
- ✅ Intelligently selects activities to run
- ✅ Orders them by dependencies
- ✅ Decides timeout for EACH activity (from knowledge base + LLM reasoning)
- ✅ Decides retry policy for EACH activity (flaky? retry 3x : 1x)
- ✅ Chains parameters using JSONPath (${Clone.output.path})
- ✅ Adds error handling (catch blocks)
- ✅ Detects scheduled workflows (cron jobs) and generates CronWorkflowSpec
- ✅ Returns complete JSON WorkflowSpec (one-time or recurring)
The Executor is Generic
RoutingWorkflow:
- ✅ Takes JSON spec from llm-router
- ✅ Executes states in order
- ✅ Respects timeout/retry
- ✅ Handles errors with catch blocks
- ✅ Works for both one-time and cron workflows
- ✅ Returns results
Execution Modes
| Mode | Triggered By | Execution | Example |
|---|---|---|---|
| One-Time | API call / CLI / Direct | Runs once, returns immediately | Analyze repo, generate report |
| Cron Job | Schedule | Runs repeatedly on schedule | Daily security scan at 2 AM |
| Blocking | User waits | Synchronous, returns results | Execute template, get output |
| Async | User polls | Asynchronous, check status later | Submit workflow, poll status |
Files Needed
1. internal/routing/activity_knowledge_base.json
↑ Static metadata about activities (timeouts, retry, flaky, etc)
2. cmd/worker/activities/llm_router.go
↑ LLM-powered activity that generates WorkflowSpec or CronWorkflowSpec
3. statemachine/routing_workflow.go
↑ Generic executor for any spec (one-time or cron)
4. internal/routing/validator.go
↑ Validate specs before execution
↑ Validate cron expressions
5. cmd/api-server/cron_handlers.go (optional)
↑ Endpoints to manage cron workflows
↑ GET /api/v1/cron/{id}/status
↑ DELETE /api/v1/cron/{id} (cancel)
This is the correct, complete architecture! 🎯
Quick Feature Checklist
- ✅ One-time workflows (API/CLI submit)
- ✅ Scheduled workflows (Cron jobs with schedule)
- ✅ Intelligent routing (LLM decides what to run)
- ✅ Smart timeouts (From activity knowledge base)
- ✅ Smart retries (Based on isFlaky flag)
- ✅ Error handling (Catch blocks for flaky activities)
- ✅ Parameter chaining (JSONPath resolution)
- ✅ Execution history (For cron jobs)
- ✅ Temporal durability (Automatic replay)