feat(agent): auto-complete tasks when all plan steps are terminal
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

The #1 remaining model reliability gap: the agent does the work (proposes
plan, executes all steps, writes back) but forgets to call complete_task,
leaving the session stuck in 'executing'. The eval showed 3/8 failures
with this pattern.

Fix: autoCompleteIfPlanDone — a structural safety net that fires at both
chat exit paths (normal completion + maxIterations). If the session has a
goal, the agent didn't call complete_task, and ALL plan steps are in a
terminal state (done/failed/replaced/skipped/blocked), auto-complete with
the agent's final text as the summary. Mirrors autoCompleteTrivialTask
but for structured tasks where the work is provably done.

Also: bump maxLLMRetries from 2 to 3 (complex multi-turn flows benefit
from one more retry on empty responses).
This commit is contained in:
2026-07-15 13:06:47 +02:00
parent 3c3b12df5e
commit e3b5fdc358
3 changed files with 66 additions and 0 deletions

View File

@@ -879,6 +879,25 @@ func (s *store) bumpCompletionNudge(ctx context.Context, sessionID string) error
return err
}
// allPlanStepsTerminal reports whether every plan step for this session is in
// a terminal state (done/failed/replaced/skipped/blocked) — i.e. no step is
// still pending or running. Used by autoCompleteIfPlanDone to auto-close a
// task when the agent did all the work but forgot to call complete_task.
// Returns false if there are no plan steps at all (no plan was proposed).
func (s *store) allPlanStepsTerminal(ctx context.Context, sessionID string) bool {
if s == nil || sessionID == "" || sessionID == "ephemeral" {
return false
}
var total, terminal int
if err := s.pool.QueryRow(ctx,
`SELECT COUNT(*), COUNT(*) FILTER (WHERE status IN ('done', 'failed', 'replaced', 'skipped', 'blocked'))
FROM session_plan_steps WHERE session_id = $1`,
sessionID).Scan(&total, &terminal); err != nil {
return false
}
return total > 0 && total == terminal
}
// planStep is a persisted plan step, as returned to the frontend for hydration
// (the panel otherwise only sees steps live via plan.proposed/plan.step.*).
type planStep struct {