Files
Test 11a1c1196a Add testable task descriptions: T0.1 through T0.9
Each task includes:
- Scope: what to build
- Implementation: code sketches + details
- Verification: concrete test criteria
- Done criteria: acceptance checklist
2026-08-20 22:41:54 -07:00

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