yao/agent/robot/DESIGN-V2-REVIEW-FINDINGS.md
Max bc4787f857 Update executor to support V2 execution model and enhance event handling
- Implement V2 execution model in the standard executor, simplifying task execution to a single call without validation loops.
- Introduce support for resuming suspended executions, allowing for human input during task processing.
- Enhance event handling by pushing task completion and failure events to the event bus for better tracking and integration.
- Update tests to reflect changes in execution flow and ensure robust handling of task statuses and results.
2026-02-25 18:40:48 +08:00

263 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DESIGN-V2 Line-by-Line Review Findings
**Review Date:** 2026-02-25
**Files Reviewed:** `runner.go`, `run.go`
---
## File 1: runner.go
### 1. ExecuteTask: Is it truly single-call? No retry loop? No validation?
**✅ PASS** — Lines 65108
- Non-assistant: single call to `executeNonAssistantTask` (L74), no loop
- Assistant: single call to `executeAssistantTask` (L89), no loop
- No `validator` import or call anywhere in the file
- Comment at L6263: "V2 simplified: single call, no validation loop"
---
### 2. Does it correctly split assistant vs non-assistant at the top?
**✅ PASS** — Lines 7286 vs 88107
- L72: `if task.ExecutorType != robottypes.ExecutorAssistant` — non-assistant branch first
- L88+: assistant branch follows
- Clear split at the top of the function
---
### 3. For non-assistant: does it call executeNonAssistantTask which handles MCP and Process?
**✅ PASS** — Lines 74, 110119
- L74: `output, err := r.executeNonAssistantTask(task, taskCtx)`
- `executeNonAssistantTask` (L110119): switch on `ExecutorMCP` and `ExecutorProcess`, delegates to `ExecuteMCPTask` and `ExecuteProcessTask`
---
### 4. For assistant: does it call executeAssistantTask which returns (output, *CallResult, error)?
**✅ PASS** — Lines 89, 123145
- L89: `output, callResult, err := r.executeAssistantTask(task, taskCtx)`
- L123: `func (r *Runner) executeAssistantTask(...) (interface{}, *CallResult, error)`
- L144: `return output, turnResult.Result, nil`
---
### 5. Does executeAssistantTask use conv.Turn() (single turn, not multi-turn)?
**✅ PASS** — Lines 126, 138
- L126: `conv := NewConversation(task.ExecutorID, chatID, 1)` — maxTurns=1
- L138: `turnResult, err := conv.Turn(r.ctx, input)` — single `Turn` call, no loop
---
### 6. Does detectNeedMoreInfo properly check result.Next for map with "status" == "need_input"?
**✅ PASS** — Lines 149166
- L150151: nil checks for `result` and `result.Next`
- L154: type assertion to `map[string]interface{}`
- L157159: `status, _ := m["status"].(string); if status != "need_input" return false`
- Matches DESIGN §16.5 protocol
---
### 7. Does it extract "question" from the map? What happens if question is empty?
**✅ PASS** — Lines 161165
- L161: `question, _ := m["question"].(string)`
- L163164: `if question == "" { question = result.GetText() }` — fallback to `CallResult` text
- Empty question handled via fallback
---
### 8. Are result.NeedInput and result.InputQuestion set correctly?
**✅ PASS** — Lines 102105
- L102104: `if needInput, question := detectNeedMoreInfo(callResult); needInput { result.NeedInput = true; result.InputQuestion = question }`
- Set only when `detectNeedMoreInfo` returns true
---
### 9. Does result.Duration get set in all paths (success and failure)?
**✅ PASS** — Lines 77, 84, 92, 98
- Non-assistant error: L77
- Non-assistant success: L84
- Assistant error: L92
- Assistant success: L98
- All paths set `result.Duration = time.Since(startTime).Milliseconds()`
---
### 10. Does buildResult helper exist or is result construction inline?
**✅ PASS (inline)** — Lines 68107
- No `buildResult` helper; construction is inline
- DESIGN §16.4 pseudocode uses `buildResult`; inline construction is acceptable and used here
---
### 11. Any edge cases: what if task.ExecutorType is empty or unknown?
**✅ PASS** — Lines 72, 112118
- Empty/unknown: `!= ExecutorAssistant` is true → non-assistant branch
- L116117: `default` returns `fmt.Errorf("unsupported executor type: %s (expected mcp or process)", task.ExecutorType)`
- Error returned and propagated; no silent failure
---
### Additional Finding (runner.go)
**⚠️ Minor:** DESIGN §9.1 shows `event.Push("robot.task.failed", ...)` inside `ExecuteTask` when `err != nil`. Implementation pushes `TaskFailed` from `run.go` (L113120) when `result.Success` is false. Behavior is equivalent; only location differs.
---
## File 2: run.go
### 1. DefaultRunConfig — ContinueOnFailure defaults to true?
**✅ PASS** — Lines 2126
- L2325: `return &RunConfig{ ContinueOnFailure: true }`
- Matches DESIGN §6.3
---
### 2. RunExecution — does it check exec.ResumeContext for startIndex and PreviousResults?
**✅ PASS** — Lines 6066
- L6164: `if exec.ResumeContext != nil { startIndex = exec.ResumeContext.TaskIndex; exec.Results = exec.ResumeContext.PreviousResults }`
- L72: loop starts at `startIndex`
- Matches DESIGN §9.2, §16.3
---
### 3. Does it NOT reset Results when ResumeContext is present?
**✅ PASS** — Lines 6265
- When `ResumeContext != nil`: `exec.Results = exec.ResumeContext.PreviousResults` — restores, does not reset
- When `ResumeContext == nil`: `exec.Results = make(...)` — fresh slice
---
### 4. Does it set task.Status to TaskRunning before execution?
**✅ PASS** — Lines 8689
- L87: `task.Status = robottypes.TaskRunning`
- L8889: `task.StartTime = &now`
- Set before `ExecuteTask` (L98)
---
### 5. Does it call e.updateTasksState to persist running state?
**✅ PASS** — Line 92
- L92: `e.updateTasksState(ctx, exec)` immediately after setting task status
- Persists running state before execution
---
### 6. Does result.NeedInput trigger e.Suspend(ctx, exec, i, result.InputQuestion)?
**✅ PASS** — Lines 100103
- L100102: `if result.NeedInput { return e.Suspend(ctx, exec, i, result.InputQuestion) }`
- Correct parameters and early return
---
### 7. Is the result NOT appended before Suspend (avoiding duplicate results per §16.15)?
**✅ PASS** — Lines 100103, 124
- L100102: `NeedInput` branch returns before any append
- L124: `exec.Results = append(exec.Results, *result)` is after the `NeedInput` check
- No append on suspend; matches DESIGN §16.15
---
### 8. Does it push event.Push for TaskFailed when a task fails?
**✅ PASS** — Lines 113120
- L113120: `event.Push(ctx.Context, robotevents.TaskFailed, robotevents.NeedInputPayload{...})` when `!result.Success`
- Event is pushed on task failure
**⚠️ Minor:** Uses `NeedInputPayload` with `Question: result.Error`. DESIGN §7.2 does not define a TaskFailed payload. `ExecPayload` (with `Error`) might be more appropriate; `NeedInputPayload.Question` is reused for the error message. Functionally acceptable but semantically odd.
---
### 9. Does it skip remaining tasks when ContinueOnFailure is false?
**✅ PASS** — Lines 129137
- L129: `if !result.Success && !config.ContinueOnFailure`
- L131134: marks remaining tasks as `TaskSkipped`
- L136: `return fmt.Errorf(...)` — stops execution
- Matches DESIGN §9.2
---
### 10. Does it clear exec.Current and exec.ResumeContext after completion?
**✅ PASS** — Lines 141143
- L142143: `exec.Current = nil; exec.ResumeContext = nil` after loop completes
- Only on normal completion (no early return from Suspend or failure)
---
### 11. Is there any event.Push for TaskCompleted?
**❌ FINDING** — run.go
- No `event.Push(robotevents.TaskCompleted, ...)` when a task succeeds
- DESIGN §7.2 defines `EventTaskCompleted = "robot.task.completed"`
- DESIGN-V2 §20.4 (B5) notes missing TaskCompleted event constant; implementation also does not push it
- **Recommendation:** Add `event.Push(ctx.Context, robotevents.TaskCompleted, payload)` when `result.Success` (e.g. after L110)
---
### 12. Does getRunConfig properly handle nil data?
**✅ PASS** — Lines 5055
- No separate `getRunConfig`; config is obtained inline
- L5155: `if cfg, ok := data.(*RunConfig); ok && cfg != nil { config = cfg } else { config = DefaultRunConfig() }`
- Handles: `data == nil`, wrong type, `cfg == nil` → falls back to `DefaultRunConfig()`
---
### Additional Finding (run.go)
**⚠️ Order of operations:** Task status update (L109120) and result append (L124) occur *after* the NeedInput check. Flow is correct: NeedInput → Suspend (return) → no append, no status update for that task.
---
## Summary
| Category | runner.go | run.go |
|----------|-----------|--------|
| **PASS** | 11/11 | 11/12 |
| **Minor** | 1 | 1 |
| **Finding** | 0 | 1 (TaskCompleted not pushed) |
### Action Items
1. **run.go L109110:** Add `event.Push(ctx.Context, robotevents.TaskCompleted, payload)` when `result.Success` for per-task completion events.
2. **run.go L113:** Consider introducing a `TaskFailedPayload` (or using `ExecPayload` with `Error`) instead of `NeedInputPayload` for TaskFailed events.