taskrecover

package
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package taskrecover repairs tasks left in_progress by a run that died.

A run process can disappear between the moment its agent finishes speaking and the moment that outcome reaches the database. The window is small but it is not rare: the control plane is killed and the orphaned run inherits a closed stdout pipe (Go makes SIGPIPE fatal on fd 1 and 2, so the next status line is lethal), the host OOMs, the machine reboots. Whatever the cause, the process is gone and the row still says in_progress.

Both recovery paths used to answer that the same way: reset the task to pending and run it again. That is right when the agent never finished, and destructive when it did. Task 63 of the liebid project is the case that motivated this package — an hour of work, a full result, `TASK_DONE` on the last line, and the only surviving copy sitting in the live artifact while the plan prepared to do all of it a second time. Re-running a task whose work is already committed is not a neutral retry; it is an agent rediscovering its own finished changes and deciding what to do about them.

Why the live artifact is trustworthy evidence

The orchestrator streams every token into .cloop/artifacts/<id>_output.txt as it arrives, so the file is complete the instant the agent stops talking — strictly before the bookkeeping this package exists to replace. It is opened O_TRUNC at the start of each execution, so it always describes the newest attempt and never an older one.

Adoption then applies exactly the contract the live path applies: pm.CheckTaskSignal over the last five lines. This package cannot invent a completion the orchestrator would not itself have recognised, because it is asking the same function the same question about the same bytes. What it adds is one guard the live path does not need — that the artifact is no older than the execution being recovered — so a leftover file from a previous attempt can never be read as this attempt's result.

The rule

Adopt a terminal signal; re-queue everything else. Silence is not success: an artifact that stops mid-sentence means the agent was interrupted, and that task genuinely has to run again.

Callers must guarantee no process owns the project directory. Reconcile is a repair for the dead, and running it against a live run would reset a task out from under the process executing it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LogOutcome

func LogOutcome(workDir string, oc Outcome)

LogOutcome appends one reconciliation to the project's event journal.

An adopted task is recorded as its real outcome — task_done, task_failed, task_skipped — rather than as some recovery-specific event type, because that is what it is: the completion the dying run failed to write. Recording it as anything else would leave the timeline showing a task that never finished. The message carries the provenance so nobody reads it as work this run performed, and the timestamp is when the agent actually stopped, not when we got around to noticing.

Best-effort, like every other event write: observability must never be the reason a repair fails.

Types

type Action

type Action string

Action says what reconciliation did with a task.

const (
	// ActionAdopted means the agent had finished and its outcome was taken
	// from the live artifact instead of being thrown away.
	ActionAdopted Action = "adopted"
	// ActionRequeued means no terminal signal was found, so the task was
	// reset to pending to be executed again.
	ActionRequeued Action = "requeued"
)

type Outcome

type Outcome struct {
	TaskID int
	Title  string
	Action Action
	// Status is the status the task now has: the adopted signal, or pending.
	Status pm.TaskStatus
	// CompletedAt is when the agent actually stopped writing, for adopted
	// tasks only. Taken from the artifact's mtime rather than from the clock,
	// because reconciliation may happen days after the fact and "completed
	// when we noticed" would be a lie in the timeline.
	CompletedAt time.Time
	// Reason explains a re-queue in words a UI can show.
	Reason string
}

Outcome is what happened to one recovered task. Callers use it to log, broadcast, and decide whether the plan changed enough to save.

func Reconcile

func Reconcile(workDir string, plan *pm.Plan) []Outcome

Reconcile repairs every task in the plan left in_progress by a run that no longer exists, and returns one Outcome per task it touched.

It mutates the plan in place and never persists: the caller owns the state handle and decides when to save, which keeps this package free of pkg/state and testable with nothing but a temp dir.

The caller must have established that no process is executing this project.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL