Files
poimen-workflows/cmd/starter/main.go
T
Test 121cad1ad5
ci / test (push) Successful in 1m14s
feat: integrate local LLM API (homelab-frontend) + Pi skills
Replace Anthropic client with OpenAI-compatible client targeting https://api.riotpiao.com.
Configure models: reasoning (Planner/Judge), ornith:35b (Implementer).
Add health check on startup.
Add Pi provider support for skill preparation (--pi-provider=local-llm).

Files changed:
- action/llm/client.go: OpenAI-compatible HTTP client + HealthCheck()
- action/llm/client_test.go: Unit tests for model validation & health
- cmd/starter/main.go: Health check before workflow, local model defaults
- statemachine/types.go: PiProvider field for OrchestratorInput

Models:
- Planner: reasoning (smart decisions)
- Judge: reasoning (quality review)
- Implementer: ornith:35b (cheap execution)

Skills: pi clone-or-fetch --provider=local-llm with 504 timeout learning.

Verification: go build ./cmd/starter ./cmd/worker ./action/llm ✓
Tests: go test -v ./action/llm ✓ (all passing)
2026-08-26 14:54:34 -07:00

160 lines
5.5 KiB
Go

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)
}
}