package main import ( "context" "flag" "fmt" "log" "strings" "time" "go.temporal.io/sdk/client" "github.com/rockliang/poimen/workflows/action/llm" "github.com/rockliang/poimen/workflows/internal/config" "github.com/rockliang/poimen/workflows/internal/health" "github.com/rockliang/poimen/workflows/internal/logging" "github.com/rockliang/poimen/workflows/statemachine" ) func main() { 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") 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)") healthCheck = flag.Bool("health", false, "check health and exit") ) flag.Parse() // Initialize structured logging if err := logging.InitLogger(); err != nil { log.Fatalf("failed to initialize logger: %v", err) } defer logging.Sync() // Load configuration first cfg, err := config.LoadConfig() if err != nil { logging.Fatal("failed to load config", logging.Err(err)) } // Connect to Temporal logging.Info("connecting to Temporal", logging.String("hostPort", cfg.Temporal.HostPort), logging.String("namespace", cfg.Temporal.Namespace)) c, err := client.Dial(client.Options{ HostPort: cfg.Temporal.HostPort, Namespace: cfg.Temporal.Namespace, }) if err != nil { logging.Fatal("failed to connect to temporal", logging.Err(err)) } defer c.Close() // If health check requested, do it and exit if *healthCheck { logging.Info("running health check") healthChecker := health.NewChecker(c) report := healthChecker.Check(context.Background()) jsonReport, _ := report.ToJSON() fmt.Println(string(jsonReport)) if report.Status != health.StatusHealthy { logging.Fatal("health check failed") } return } // Validate required flags for workflow start if *repoPath == "" || *remoteURL == "" { logging.Fatal("--repo and --remote flags are required") } // Build OrchestratorInput input := statemachine.OrchestratorInput{ TargetRepoPath: *repoPath, RemoteURL: *remoteURL, Milestone: *milestone, DryRun: *dryRun, MaxCyclesBeforeCAN: 100, PiProvider: *piProvider, 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(), }, } // 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)) // Start workflow workflowID := "orch-" + strings.ReplaceAll(*repoPath, "/", "-") logging.Info("starting orchestrator workflow", logging.String("workflowID", workflowID), logging.String("repo", *repoPath)) run, err := c.ExecuteWorkflow(context.Background(), client.StartWorkflowOptions{ ID: workflowID, TaskQueue: "poimen-taskqueue", }, statemachine.OrchestratorWorkflow, input) if err != nil { logging.Fatal("failed to start workflow", logging.Err(err)) } fmt.Printf("\n=== Workflow Started ===\n") fmt.Printf("Workflow ID: %s\n", workflowID) fmt.Printf("Task Queue: poimen-taskqueue\n") 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) } }