From f135ab89ce6710a4f20049e6d0d8e914d8e2e402 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sun, 8 Mar 2026 22:24:12 +0000 Subject: executor: fix sandbox git fetch + inject prior failure history Fix: use file:// prefix in git fetch during sandbox teardown to force pack-protocol transfer. The local optimization uses hard links which fail across devices and with mixed-owner object stores. Feature: before running a task, query prior failed/timed-out executions and prepend their error messages to the agent's --append-system-prompt. This tells the agent what went wrong in previous attempts so it doesn't repeat the same mistakes. Co-Authored-By: Claude Sonnet 4.6 --- internal/executor/executor.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'internal/executor/executor.go') diff --git a/internal/executor/executor.go b/internal/executor/executor.go index d1c8e72..df222f8 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "path/filepath" + "strings" "sync" "time" @@ -416,6 +417,10 @@ func (p *Pool) execute(ctx context.Context, t *task.Task) { p.mu.Unlock() }() + // Inject prior failure history so the agent knows what went wrong before. + priorExecs, priorErr := p.store.ListExecutions(t.ID) + t = withFailureHistory(t, priorExecs, priorErr) + // Run the task. err = runner.Run(ctx, t, exec) exec.EndTime = time.Now().UTC() @@ -478,6 +483,43 @@ var terminalFailureStates = map[task.State]bool{ task.StateBudgetExceeded: true, } +// withFailureHistory returns a shallow copy of t with prior failed execution +// error messages prepended to SystemPromptAppend so the agent knows what went +// wrong in previous attempts. +func withFailureHistory(t *task.Task, execs []*storage.Execution, err error) *task.Task { + if err != nil || len(execs) == 0 { + return t + } + + var failures []storage.Execution + for _, e := range execs { + if (e.Status == "FAILED" || e.Status == "TIMED_OUT") && e.ErrorMsg != "" { + failures = append(failures, *e) + } + } + if len(failures) == 0 { + return t + } + + var sb strings.Builder + sb.WriteString("## Prior Attempt History\n\n") + sb.WriteString("This task has failed before. Do not repeat the same mistakes.\n\n") + for i, f := range failures { + fmt.Fprintf(&sb, "**Attempt %d** (%s) — %s:\n%s\n\n", + i+1, f.StartTime.Format("2006-01-02 15:04 UTC"), f.Status, f.ErrorMsg) + } + sb.WriteString("---\n\n") + + copy := *t + copy.Agent = t.Agent + if copy.Agent.SystemPromptAppend != "" { + copy.Agent.SystemPromptAppend = sb.String() + copy.Agent.SystemPromptAppend + } else { + copy.Agent.SystemPromptAppend = sb.String() + } + return © +} + // waitForDependencies polls storage until all tasks in t.DependsOn reach COMPLETED, // or until a dependency enters a terminal failure state or the context is cancelled. func (p *Pool) waitForDependencies(ctx context.Context, t *task.Task) error { -- cgit v1.2.3