# T0.8: Worker & Starter CLIs ## Scope Implement `cmd/worker/main.go`, `cmd/starter/main.go`, and `internal/config` for env-based loading. ## Implementation ### File: `internal/config/config.go` ```go package config type TemporalConfig struct { HostPort string // default: temporal.riotpiao.com:7233 Namespace string // default: default TLSCert string // env: TEMPORAL_TLS_CERT (file path) TLSKey string // env: TEMPORAL_TLS_KEY (file path) } type AppConfig struct { Temporal AppConfig AnthropicAPIKey string // env: ANTHROPIC_API_KEY } func LoadConfig() (AppConfig, error) // Read from env variables (TEMPORAL_*, ANTHROPIC_API_KEY) // Return filled config ``` ### File: `cmd/worker/main.go` ```go func main() { cfg, err := config.LoadConfig() if err != nil { panic(err) } // Connect to Temporal c, err := client.Dial(client.Options{ HostPort: cfg.Temporal.HostPort, Namespace: cfg.Temporal.Namespace, // TLS options if provided }) if err != nil { panic(err) } defer c.Close() // Create worker w, err := worker.New(c, "default", worker.Options{}) if err != nil { panic(err) } // Register all workflows w.RegisterWorkflow(statemachine.OrchestratorWorkflow) w.RegisterWorkflow(statemachine.TaskUnitWorkflow) // Register all activities w.RegisterActivity(action.CloneRepoActivity) w.RegisterActivity(action.GitWorktreeAddActivity) w.RegisterActivity(action.GitCommitActivity) w.RegisterActivity(action.GitPushActivity) w.RegisterActivity(action.GitSquashMergeActivity) w.RegisterActivity(action.PrepareSkillsActivity) w.RegisterActivity(action.PlanningActivity) w.RegisterActivity(action.ImplementerActivity) w.RegisterActivity(action.JudgeActivity) w.RegisterActivity(action.RunIntegrationTestActivity) w.RegisterActivity(action.UpdateLessonsActivity) w.RegisterActivity(action.ReadLessonsActivity) // Run worker if err := w.Run(worker.InterruptCh()); err != nil { panic(err) } } ``` ### File: `cmd/starter/main.go` ```go 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", "claude-opus-5", "planner model ID") judgeModel = flag.String("judge-model", "claude-opus-5", "judge model ID") implementerModel = flag.String("implementer-model", "claude-sonnet-5", "implementer model ID") ) flag.Parse() cfg, err := config.LoadConfig() if err != nil { panic(err) } // Connect to Temporal c, err := client.Dial(client.Options{ HostPort: cfg.Temporal.HostPort, Namespace: cfg.Temporal.Namespace, }) if err != nil { panic(err) } defer c.Close() // Build OrchestratorInput input := statemachine.OrchestratorInput{ TargetRepoPath: *repoPath, RemoteURL: *remoteURL, Milestone: *milestone, 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.ActivityTuning{ ImplementerBaseTimeout: 10 * time.Minute, ImplementerMaxRetries: 3, JudgeTimeout: 5 * time.Minute, PiRetry: statemachine.PiRetryPolicy{ ScheduleToCloseTimeout: 5 * time.Minute, InitialInterval: 2 * time.Second, MaximumInterval: 30 * time.Second, BackoffCoefficient: 2.0, StreamTimeout: 30 * time.Second, StreamTimeoutMax: 2 * time.Minute, }, }, }, DryRun: *dryRun, MaxCyclesBeforeCAN: 100, } // Start workflow workflowID := "orch-" + strings.ReplaceAll(*repoPath, "/", "-") run, err := c.ExecuteWorkflow(context.Background(), client.StartWorkflowOptions{ID: workflowID, TaskQueue: "default"}, statemachine.OrchestratorWorkflow, input) if err != nil { panic(err) } fmt.Printf("Started workflow %s\n", workflowID) fmt.Printf("Monitor at: temporal.riotpiao.com:8080/namespaces/default/workflows/%s\n", workflowID) // Optionally wait for completion // var result OrchestratorOutput // err = run.Get(context.Background(), &result) } ``` ## Verification ```bash cd /Users/rockliang/workplace/Poimen/workflows # Test build go build ./cmd/worker go build ./cmd/starter # Test worker registration (mock/local test): go test -v ./tests -run TestWorkerRegistration # Manual test (requires temporal.riotpiao.com running): # 1. Start worker: go run ./cmd/worker & # 2. In another terminal, start workflow: go run ./cmd/starter --repo /tmp/fixture --remote file:///tmp/remote --dry-run # 3. Check Temporal Web UI: should show workflow execution ``` ## Done Criteria - `go build ./cmd/worker` succeeds - `go build ./cmd/starter` succeeds - `go test ./tests -run TestWorkerRegistration` passes - Manual test: `go run ./cmd/worker` connects to temporal.riotpiao.com:7233 without error (or test Temporal instance) - Manual test: `go run ./cmd/starter --dry-run` returns workflow ID and URL immediately