Repository navigation
control-plane: an offline node keeps its executions until it comes back - #1179
Conversation
A node that registers with metadata.custom.holds_executions_offline = true (the codeaf factory on a laptop that sleeps) no longer has its running work timed out by the stale sweep just because nothing renewed it while it was away. Before the sweeps run, each cleanup pass marks the quiet executions of an opted-in, registered node whose lease has lapsed as held: status stays running, status_reason is node_offline, and the stale, workflow-stale and stale-retry sweeps all skip it. A hold ends four ways. The node's next lease lists the execution as inflight: released, it carries on. The lease does not list it: failed now as node_lost_execution. The node is no longer registered: failed as node_removed. The hold outlives offline_hold_ceiling (default 168h, env AGENTFIELD_EXECUTION_OFFLINE_HOLD_CEILING): failed as node_offline_too_long. Nodes that did not opt in are reaped exactly as before, and an opted-in node that is online but stops listing an execution is still reaped. Assisted-by: CodeAF Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>
📊 Coverage gateThresholds from
✅ Gate passedNo surface regressed past the allowed threshold and the aggregate stayed above the floor. |
📐 Patch coverage gateThreshold: 80% on lines this PR touches vs
✅ Patch gate passedEvery surface whose lines were touched by this PR has patch coverage at or above the threshold. |
santoshkumarradha
left a comment
There was a problem hiding this comment.
Thanks for carrying this behavior through the cleanup and lease paths. The required coverage-summary check is currently failing because patch coverage is 58%, below the repository’s 80% floor. Please add focused tests for the uncovered offline-hold branches, then rerun the coverage gate. Once that is green, this should be ready for another look.
Carries #1178 (merged into the prototype branch
proto/harness-watchon 2026-11-10) tomain; it is the one commit of that branch that #1174 did not bring over. Cherry-picked unchanged, with its author kept.Why
When a node whose work survives process stops or machine sleep (such as a local factory node on a laptop that sleeps overnight) goes offline, nothing renews its lease while it is away. Previously, the control plane's stale-execution sweep timed out those running executions once
stale_execution_timeoutelapsed, even though the node still held the work on disk and would resume reporting on it as soon as it reconnected.What Changes
metadata.custom.holds_executions_offline: true(types.AgentMetadataHoldsExecutionsOffline, checked by(*AgentNode).HoldsExecutionsOffline()).ExecutionCleanupServicechecks registered nodes. If an opted-in node's heartbeat/lease has lapsed (last_seenolder than the lease TTL), its quiet running/pending/queued executions (on bothexecutionsandworkflow_executions) are marked held (statusstaysrunning,status_reasonis set tonode_offline, andupdated_atrecords when the hold began).MarkStaleExecutions,MarkStaleWorkflowExecutions, andRetryStaleWorkflowExecutionsexclude rows withstatus_reason = 'node_offline'.PATCH /api/v1/nodes/:node_id/status, including linkhello/heartbeat), any held execution listed ininflightis released (status_reasoncleared toNULL,updated_atrefreshed) and continues running.inflightlist fails immediately withstatus_reason = "node_lost_execution".status_reason = "node_removed".offline_hold_ceiling(default168h/ 7 days, configurable viaagentfield.execution_cleanup.offline_hold_ceilinginagentfield.yamlorAGENTFIELD_EXECUTION_OFFLINE_HOLD_CEILING), its held executions fail withstatus_reason = "node_offline_too_long".Who Is Unaffected and Why
metadata.custom.holds_executions_offline: true. Nodes that do not set this flag returnfalsefromHoldsExecutionsOffline(), never have their executions markednode_offline, and have their quiet executions reaped by the stale sweeps (MarkStaleExecutions,MarkStaleWorkflowExecutions,RetryStaleWorkflowExecutions) identically to before.inflight, the execution is not held and is timed out by the stale sweep as before.offline_hold_ceilingdefaults to168hwhen unset in YAML or database config) and no database schema migration.Rollback
Revert the commit. There is no schema migration or new table/column to undo; any execution currently marked
status_reason = 'node_offline'remains a standardrunningrow inexecutionsandworkflow_executionsand will fall back under the ordinary stale-execution sweep on the next cleanup pass.What Was Run
go build ./...(withGOFLAGS=-buildvcs=false) incontrol-planego vet ./...incontrol-planegolangci-lint runon the touched packages (./internal/handlers,./internal/storage,./internal/config,./internal/server,./pkg/types)go test -tags sqlite_fts5 ./internal/handlers ./internal/storage ./internal/config ./internal/server ./pkg/typesincontrol-plane🤖 Generated with Claude Code