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

5.9 KiB

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

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

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

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

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