Repository navigation
Step results are binary: no partial-success envelope, so a retry re-runs work that already succeeded #468
Description
Activity
- addedseverity:lowMinor surpriseMinor surprisetype:design-questionLanguage-level design decision, not a simple fixLanguage-level design decision, not a simple fixtype:enhancementEnhancement or new featureEnhancement or new featurefound-in:aider-researchSurfaced by the C:\codev\Aider research cross-check against Nodus 5.0.4Surfaced by the C:\codev\Aider research cross-check against Nodus 5.0.4
on Aug 18, 2026 Filed together from one cross-check of the four
C:\codev\Aider researchdocuments against 5.0.4 (AIDER_AINDY_LENS_AUDIT.md,AIDER_ON_AINDY_RUNTIME_PORTABILITY_ANALYSIS.md,ACCURACY_CHECK_vs_aindy-runtime_2.4.0.md;AIDER_ARCHITECTURAL_AUDIT.mdcontains no Nodus claims). The set:- Add a plan-then-act handoff pattern to the stdlib: the primitives exist, the pattern does not #465 — stdlib plan-then-act handoff pattern
- std:retry has no validation combinator: retry-until-predicate exists only at the workflow altitude #466 —
std:retryvalidation combinator (the function-altitude half of goal as a stopping condition, and the DSL thesis: how does the language stop being incidental to the orchestration? #409) - allowed_paths has no read/write distinction: FS_READ is declared and never used, and the jail ignores both #467 — read/write path authority (
fs_scope; the sharpest of the four) - Step results are binary: no partial-success envelope, so a retry re-runs work that already succeeded #468 — partial-success step envelope
Ranked: #467 first, then #466, then #465, then #468.
Nothing else the documents raised is Nodus work — the remaining corrections are aindy-runtime pinning
nodus-lang==5.0.1and needing 5.0.4 (#411 annotation forgery, #185 shared memory store, #390 workflow ownership all land in 5.0.2/5.0.3, and 5.0.3 alone breaks nodus-sdk).- added a commit that references this issue
on Aug 19, 2026 Decided — superseded by #480. Recommending closure
Taken with the other seven open workflow-DSL questions in one session. Decisions:
docs/design/workflow-dsl/00-cluster-decisions.md(D6), PR #610.Read next to #480, this is the same requirement at a finer granularity.
- This issue: a retry re-attempts the whole step, including the parts that already
worked. - A running graph cannot grow: no dynamic node expansion, so runtime fan-out is invisible to the graph #480: an in-step loop makes "per-item retry, per-item timeout and per-item
caching all unavailable."
Those are one requirement, and neither issue says so because they were filed from
different corpora four apart.Why #480's answer is the better mechanism
The per-unit machinery already exists — one level up.
TaskNodecarries
max_retries,retry_delay_ms,timeout_ms,cache,cache_key,on_states,
whenandallow_failureper node. #480's decided model (Airflow's mapped
node: the graph does not grow, a declared node's cardinality is discovered at run
time) expands one node to N instances, and each gets all of those for free.So "retry only the two that failed" becomes the ordinary retry path rather than a
new concept, and three of this issue's own open questions dissolve rather than
being answered:- "Is
partiala run status or a step result field?" Neither. This issue is
right that an eighth run status "touches every store, the CLI, the HTTP
endpoints, and any consumer switching on status" — and a mapped node needs none
of it. Worth adding that there are two closed vocabularies, not one: seven
run statuses innodus_lang_workflow/models.pyand seven task statuses in
TASK_STATUSES, disjoint, the latter pinned by
tests/test_status_vocabulary.py. - "What is a sub-unit?" The item in the list the producer returned. Still
author-declared, as this issue correctly insists — just declared as a fan-out
rather than as an envelope shape. - "Interaction with
@exactly_once." This is the one that mattered most and
the one a mapped node removes entirely. The issue's own framing — "the two
features disagree about what 'done' means" — is a consequence of introducing a
second definition of done. Each mapped instance is a whole action; the ledger
keys it exactly as it keys any other. No disagreement to resolve.
What survives, unchanged
The workaround this issue documents stays valid and D5 does not take it away.
Below mapped-node granularity — three of five hunks inside one file edit — the
sub-unit is not worth a durable record, and "a step that writes its own progress
into workflowstateand skips completed units on resume" already works. This
issue says so itself.What would reopen this
A case where the sub-units genuinely cannot be nodes and the
state
workaround genuinely fails. Neither this issue nor #480 has produced one — Aider's
"3 of 5 hunks applied" is precisely the sub-node granularity the workaround
covers.If that case appears, reopen against a mapped-node baseline rather than against
today's, because the argument changes: the question would then be whether a
node needs a partial envelope, not whether a step does.Recorded on #480 as well.
- This issue: a retry re-attempts the whole step, including the parts that already
- added a commit that references this issue
on Aug 26, 2026
Summary
A workflow step result is binary: it succeeded or it failed. There is no shape for
"3 of 5 sub-units applied; here are the 2 that did not and why" — so a retry
re-attempts the whole step, including the parts that already worked.
Current state
Run status is a closed set of seven (
src/nodus_lang_workflow/models.py):pending,running,waiting,retry_scheduled,completed,failed,dead_lettered. Nothing betweencompletedandfailed.The consequence is on the retry path, not the reporting path. When a step defers
(
retry_scheduled) the sweeper resumes it and it re-runs whole. If the step wrotethree of five files, the retry writes all five again — correct only because the
writes happen to be idempotent, which the runtime does not know and cannot check.
What to add
A partial terminal shape carrying per-sub-unit outcomes, and a retry that consumes
it:
with the retry re-attempting only
failed. Sketch of the decisions this needs:partiala run status or a step result field? Adding an eighth run statustouches every store, the CLI (
nodus workflow runs|inspect|dead-letters), theHTTP endpoints, and any consumer switching on status. A result-map field is far
cheaper and probably sufficient — the run itself is still
retry_scheduledorcompleted.(the step returns the envelope) rather than runtime-inferred; the runtime's job is
to carry it across the defer/resume boundary and hand
failedback on resume,which is the part a step cannot do for itself.
@exactly_once. The effect ledger currently keys a wholeaction. Partial application means an action can be half-recorded, and the ledger
needs to say what it will do about that before this ships — otherwise the two
features disagree about what "done" means.
Provenance
C:\codev\Aider research\AIDER_ON_AINDY_RUNTIME_PORTABILITY_ANALYSIS.md§9,primitive B2 — "Partial-success envelope —
{status: "partial", applied, failed}with per-sub-unit ledger records", tagged New. AlsoAIDER_AINDY_LENS_AUDIT.md§5, where it is the second absorb item for the runtime:"Aider's '3 of 5 hunks applied; here are the 2 that failed and why' is exactly the
structured failure payload a retry path wants."
The docs file it against aindy-runtime's syscall envelope, which is likewise binary
(
syscall_dispatcher.py:22). Nodus has the same shape and the same gap, and theNodus half is the one that owns step retry, so it is filed here on its own merits
rather than as a downstream consequence.
Lowest priority of the four issues from this cross-check: it only bites steps whose
work divides into independently-failing units, and the workaround — a step that
writes its own progress into workflow
stateand skips completed units on resume —already works today, since goal iterations resume the same graph
(
src/nodus/vm/vm.py:1433-1448).Affected versions
v5.0.4 (current when filed). Not a regression.