2026-08-21 15:58:46 -07:00
package main
2026-08-21 18:07:12 -07:00
import (
"context"
"flag"
"fmt"
"log"
"strings"
"time"
"go.temporal.io/sdk/client"
2026-08-26 14:54:02 -07:00
"github.com/rockliang/poimen/workflows/action/llm"
2026-08-21 18:07:12 -07:00
"github.com/rockliang/poimen/workflows/internal/config"
2026-08-23 16:31:33 -07:00
"github.com/rockliang/poimen/workflows/internal/health"
2026-08-23 16:33:49 -07:00
"github.com/rockliang/poimen/workflows/internal/logging"
2026-08-21 18:07:12 -07:00
"github.com/rockliang/poimen/workflows/statemachine"
)
2026-08-21 15:58:46 -07:00
func main () {
2026-08-21 18:07:12 -07:00
var (
repoPath = flag . String ( "repo" , "" , "target repo path" )
remoteURL = flag . String ( "remote" , "" , "remote URL" )
milestone = flag . String ( "milestone" , "T0" , "milestone ID" )
dryRun = flag . Bool ( "dry-run" , false , "disable git push/merge" )
2026-08-26 14:54:02 -07:00
plannerModel = flag . String ( "planner-model" , "reasoning" , "planner model ID (local-llm)" )
judgeModel = flag . String ( "judge-model" , "reasoning" , "judge model ID (local-llm)" )
implementerModel = flag . String ( "implementer-model" , "ornith:35b" , "implementer model ID (local-llm ornith)" )
piProvider = flag . String ( "pi-provider" , "local-llm" , "pi provider name for skills (local-llm)" )
2026-08-23 16:31:33 -07:00
healthCheck = flag . Bool ( "health" , false , "check health and exit" )
2026-08-21 18:07:12 -07:00
)
flag . Parse ()
2026-08-23 16:33:49 -07:00
// Initialize structured logging
if err := logging . InitLogger (); err != nil {
log . Fatalf ( "failed to initialize logger: %v" , err )
}
defer logging . Sync ()
2026-08-23 16:31:33 -07:00
// Load configuration first
2026-08-21 18:07:12 -07:00
cfg , err := config . LoadConfig ()
if err != nil {
2026-08-23 16:33:49 -07:00
logging . Fatal ( "failed to load config" , logging . Err ( err ))
2026-08-21 18:07:12 -07:00
}
// Connect to Temporal
2026-08-23 16:33:49 -07:00
logging . Info ( "connecting to Temporal" , logging . String ( "hostPort" , cfg . Temporal . HostPort ), logging . String ( "namespace" , cfg . Temporal . Namespace ))
2026-08-21 18:07:12 -07:00
c , err := client . Dial ( client . Options {
HostPort : cfg . Temporal . HostPort ,
Namespace : cfg . Temporal . Namespace ,
})
if err != nil {
2026-08-23 16:33:49 -07:00
logging . Fatal ( "failed to connect to temporal" , logging . Err ( err ))
2026-08-21 18:07:12 -07:00
}
defer c . Close ()
2026-08-23 16:31:33 -07:00
// If health check requested, do it and exit
if * healthCheck {
2026-08-23 16:33:49 -07:00
logging . Info ( "running health check" )
2026-08-23 16:31:33 -07:00
healthChecker := health . NewChecker ( c )
report := healthChecker . Check ( context . Background ())
jsonReport , _ := report . ToJSON ()
fmt . Println ( string ( jsonReport ))
if report . Status != health . StatusHealthy {
2026-08-23 16:33:49 -07:00
logging . Fatal ( "health check failed" )
2026-08-23 16:31:33 -07:00
}
return
}
// Validate required flags for workflow start
if * repoPath == "" || * remoteURL == "" {
2026-08-23 16:33:49 -07:00
logging . Fatal ( "--repo and --remote flags are required" )
2026-08-23 16:31:33 -07:00
}
2026-08-21 18:07:12 -07:00
// Build OrchestratorInput
input := statemachine . OrchestratorInput {
TargetRepoPath : * repoPath ,
RemoteURL : * remoteURL ,
Milestone : * milestone ,
DryRun : * dryRun ,
MaxCyclesBeforeCAN : 100 ,
2026-08-26 14:54:02 -07:00
PiProvider : * piProvider ,
2026-08-21 18:07:12 -07:00
Config : statemachine . OrchestratorConfig {
SystemPrompt : "You are an expert software developer orchestrating multi-agent work." ,
Skills : [] statemachine . SkillRef {},
RolePrompts : map [ string ] statemachine . PromptSpec {
"planner" : {
TemplateRef : "planner/default.tmpl" ,
Model : statemachine . ModelSpec {
ModelID : * plannerModel ,
Thinking : "adaptive" ,
Effort : "high" ,
},
},
"judge" : {
TemplateRef : "judge/default.tmpl" ,
Model : statemachine . ModelSpec {
ModelID : * judgeModel ,
Thinking : "adaptive" ,
Effort : "high" ,
},
},
"implementer" : {
TemplateRef : "implementer/default.tmpl" ,
Model : statemachine . ModelSpec {
ModelID : * implementerModel ,
},
},
},
Tuning : statemachine . NewActivityTuning (),
},
}
2026-08-26 14:54:02 -07:00
// Health check: verify local LLM API is reachable
logging . Info ( "checking local LLM API connectivity" , logging . String ( "url" , "https://api.riotpiao.com" ))
llmClient , err := llm . NewClient ()
if err != nil {
logging . Fatal ( "failed to create LLM client" , logging . Err ( err ))
}
if err := llmClient . HealthCheck ( context . Background ()); err != nil {
logging . Fatal ( "local LLM API health check failed" , logging . Err ( err ), logging . String ( "hint" , "ensure homelab-frontend gateway is running and accessible" ))
}
logging . Info ( "local LLM API is reachable" , logging . String ( "planner-model" , * plannerModel ), logging . String ( "judge-model" , * judgeModel ), logging . String ( "implementer-model" , * implementerModel ))
2026-08-21 18:07:12 -07:00
// Start workflow
workflowID := "orch-" + strings . ReplaceAll ( * repoPath , "/" , "-" )
2026-08-23 16:33:49 -07:00
logging . Info ( "starting orchestrator workflow" , logging . String ( "workflowID" , workflowID ), logging . String ( "repo" , * repoPath ))
2026-08-21 18:07:12 -07:00
run , err := c . ExecuteWorkflow ( context . Background (), client . StartWorkflowOptions {
ID : workflowID ,
2026-08-22 10:18:11 -07:00
TaskQueue : "poimen-taskqueue" ,
2026-08-21 18:07:12 -07:00
}, statemachine . OrchestratorWorkflow , input )
if err != nil {
2026-08-23 16:33:49 -07:00
logging . Fatal ( "failed to start workflow" , logging . Err ( err ))
2026-08-21 18:07:12 -07:00
}
fmt . Printf ( "\n=== Workflow Started ===\n" )
fmt . Printf ( "Workflow ID: %s\n" , workflowID )
2026-08-22 10:18:11 -07:00
fmt . Printf ( "Task Queue: poimen-taskqueue\n" )
2026-08-21 18:07:12 -07:00
fmt . Printf ( "\n=== Model Configuration ===\n" )
fmt . Printf ( "Planner Model: %s\n" , * plannerModel )
fmt . Printf ( "Judge Model: %s\n" , * judgeModel )
fmt . Printf ( "Implementer Model: %s\n" , * implementerModel )
fmt . Printf ( "\n=== Monitoring ===\n" )
fmt . Printf ( "Web UI: http://%s:8080/namespaces/%s/workflows/%s\n" ,
strings . Split ( cfg . Temporal . HostPort , ":" )[ 0 ], cfg . Temporal . Namespace , workflowID )
// Optionally wait for completion (with timeout)
ctx , cancel := context . WithTimeout ( context . Background (), 1 * time . Minute )
defer cancel ()
var result statemachine . OrchestratorOutput
if err := run . Get ( ctx , & result ); err != nil {
fmt . Printf ( "\nWorkflow initiated (execution in progress).\n" )
fmt . Printf ( "Check the Web UI for real-time status updates.\n" )
} else {
fmt . Printf ( "\nWorkflow completed: %+v\n" , result )
}
2026-08-21 15:58:46 -07:00
}