168 lines
5.9 KiB
Markdown
168 lines
5.9 KiB
Markdown
# 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
|