# T0.6: TaskUnit Workflow ## Scope Implement `statemachine/taskunit.go` with retry loops, timeout escalation, and lessons injection. ## Implementation ### File: `statemachine/taskunit.go` ```go func TaskUnitWorkflow(ctx workflow.Context, in TaskUnitInput) (TaskUnitOutput, error) // 1. Call GitWorktreeAddActivity(ctx, {RepoPath, TaskID}) → get worktree path // 2. Lessons file init: try ReadLessonsActivity, may be empty on first run // 3. Retry loop: // timeoutAttempt := 1 // for judgeAttempt := 1; judgeAttempt <= in.MaxJudgeRetries; judgeAttempt++ // // SetupActivity options: // StartToCloseTimeout: in.BaseTimeout * time.Duration(timeoutAttempt) // HeartbeatTimeout: (in.BaseTimeout * time.Duration(timeoutAttempt)) / 4 // RetryPolicy: {MaximumAttempts: 1} // NO retries; we manage them in the loop // // Call ImplementerActivity(ctx, {Lessons: lessons, ...}) // If isStartToCloseTimeout(err): // timeoutAttempt++ // judgeAttempt-- // don't consume a judge retry on timeout // continue // next loop iteration has longer timeout // If err != nil: // return TaskUnitOutput{Verdict: "fail", Critique: err.Error()}, nil // // Call RunIntegrationTestActivity(ctx, {WorktreePath, TestCmd}) // If integration test fails: // // Some tasks don't have tests; pass if no test defined // // Call JudgeActivity(ctx, {Diff, IntegrationTestResult}) // If judge.Verdict == "pass": // Call GitCommitActivity(ctx, {WorktreePath, "T0.x: implementation"}) // return TaskUnitOutput{Verdict: "pass", Branch: "task/T0.x"} // Else: // Call UpdateLessonsActivity(ctx, {Critique}) // lessons = ReadLessonsActivity() // updated lessons for next attempt // continue // next judgeAttempt with lessons injected // End retries // // 4. If we exit loop without pass: return fail verdict ``` ## Retry Logic Detail **TimeoutAttempt vs JudgeAttempt:** - TimeoutAttempt: activity ran out of time, next attempt has longer StartToCloseTimeout - JudgeAttempt: activity finished but output wrong, lessons injected, duration doesn't change - They're independent counters so timeout escalation doesn't consume judge retries **Lessons injection:** - Before each ImplementerActivity, render prompt with Lessons appended: "Known errors from prior attempts:\n{lessons}" - On judge failure, append new lesson to lessons file - Lessons persist within TaskUnitWorkflow (shared memory) - Lessons also written to disk (tasks/.orchestrator/lessons/.jsonl) for Planner's review ## Verification ```bash cd /Users/rockliang/workplace/Poimen/workflows go test -v ./tests -run TestTaskUnit # Test file: tests/taskunit_workflow_test.go ``` Test cases (mocked activities): 1. **Pass on first try:** Implementer→TestPass→JudgePass → return pass 2. **Fail then pass after lesson:** Implementer→TestPass→JudgeFail (append lesson) → Implementer (lessons injected)→TestPass→JudgePass → return pass 3. **Retries exhausted:** Implementer→JudgeFail 3 times → return fail 4. **Timeout escalation:** Implementer timeout 1st → BaseTimeout*1 fails, Implementer timeout 2nd → BaseTimeout*2 succeeds → continue ## Done Criteria - `go test ./tests -run TestTaskUnit` passes all 4 cases - Split timeout/judge-fail counters work correctly - Lessons inject into prompt without error - No infinite loops on mocked failures