Each task includes: - Scope: what to build - Implementation: code sketches + details - Verification: concrete test criteria - Done criteria: acceptance checklist
79 lines
3.5 KiB
Markdown
79 lines
3.5 KiB
Markdown
# 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/<TaskID>.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
|