Files
poimen-workflows/ROUTING_WORKFLOW_SPEC.md
T
Test 25a4787022 feat(routing): implement WorkflowSpec and CronWorkflowSpec types
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)
2026-08-31 19:15:28 -07:00

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:

  1. Which activities are needed
  2. What order to execute them
  3. What timeout for each activity
  4. What retry policy for each activity
  5. 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)