Repository navigation
feat(workflow-executor): run steps once per record of a Multiple related-records step - #2026
hercemer42 wants to merge 12 commits into
Conversation
30 new issues
|
hercemer42
left a comment
There was a problem hiding this comment.
Spec (PRD-1693): conforms. PRD-1693 has 5 unvalidated decisions: not final.
Applies to: the PR as a whole
Claude Opus 5.5 (claude-opus-5-5): Preferential: the run lock renewal before each record has no ADR, so the next reader cannot learn why a step calls the orchestrator mid-step.
Code
Released runtimes call renew-run-lock on another repository, a reader asks why a step skips handing back, and two alternatives were turned down.
A way out: record it with /adr.
|
Claude Opus 5.5 (claude-opus-5-5): The missing ADR is now |
|
Closed, the run lock renewal is now an ADR in How it was checkedThe predicate is a hard-to-reverse decision no ADR records. |
1b131d7 to
03c3f0c
Compare
…ted-records step A Load Related Record step could load one record, so a KYC case with several UBOs needed one workflow per UBO. A Multiple step now loads up to 50 records the operator picks (the AI pre-checks its pick; Full AI loads it only when it saw every candidate and is sure), and every Get Data, Update Data, Trigger Action and single Load Related Record sourced from it runs once per record, in order, each record independently through the unchanged single-record executor. Failed records are retried or skipped on their own, and a Decision reading per-record data branches on any, every or no record, pausing for the operator when a record has no value. A record loop can outlast the run lock, so the runner renews it before each record and yields the run when stopping. A step re-run after an error reports the error cleared, since the orchestrator merges each report into the context the error left. Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t run time The run view never switches the relation of a step loading several records, and a request to would load another relation's records than the step was built for. A relation preview or a confirmation naming another relation now fails the step instead. Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- "none" on a single-record source negates its one value, as it does over records. - A record picked twice is refused, so no step runs twice on it. - Each mode refuses the other's pick, and a relation override needs one record. - An answered record runs before the run is handed back, so the answer is not lost. - A record's schema read for its AI label no longer fails the whole step. - Lint formatting on the files this change touches. Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rator's branch - A record run abandoned at its timeout now has its saves refused, so a mutation it had not started stops at its write-ahead marker instead of running after the record was marked failed. - A Decision completed by the operator replays that branch when dispatched again, instead of evaluating the same unchecked records and pausing anew. - Lint formatting on the tests added in the previous round. Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… gate found untested The iteration store's pass-through saves, deletes and rows, a Multiple step on a to-one relation, a Multiple confirmation that picks nothing or whose relation no longer resolves, an AI pick outside the list, a Load Related Record and a Trigger Action running once per record, and an unexpected failure of a step running once per record. Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… steps A record whose change was cut off by a restart or the step timeout read as a plain failure, and Retry ran the change again with nothing telling the operator it may already have landed. A record timing out left no log line. A Sub-workflow pinned to a Multiple step failed saying that step loaded no record. The step error dropped the kind every failed record shared, and the runner logged an abort as a lost lock. The cut-off record now says its change may already be applied, the timeout logs the record, a Sub-workflow on a Multiple step fails as a call-site error, the step carries the shared kind, and the factory and the record executor find a pinned step through one lookup. The AI candidate ranking moves to related-record-picker.ts and the two persist methods merge, taking the Load Related Record executor back under 1,000 lines. The per-record tests now assert which records ran and as whom, and cover the call frame and a per-record Load Related Record offered as candidates. The run lock renewal is recorded as an ADR. Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…requires Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fter a Multiple step Only the routing in StepExecutorFactory keeps an MCP or Guidance step after a Multiple step from running once per record, and no test covered it: a change that sent every step after a Multiple step through PerRecordStepExecutor would have passed the suite. The new cases fail on exactly that change. Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… several records
The AI picking a Multiple step's records was told which collection they come
from only in the field selection, not in the record selection. It then judged
whether each candidate was what the request named: on "the dvds priced above
$8" over titles reading like bicycles, 3 of 8 runs marked a correct pick as
unsure ("I'm not confident they are DVDs"), which makes Full AI pause instead
of loading, and one picked none. With the collection named, 20 of 20 picks
were sure and correct.
Refs: PRD-1693
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… over through the runtime
Handle the rest manually completed the step at the orchestrator, so a record
that had failed before it was dropped with no retry or skip, unlike handling
each record by hand. The runtime now takes { unfinishedRecords:
"handle-manually", sourceRecordId } on the paused record: every record not
finished is handed over at once, as a decline would hand over one, and the
step then ends as usual, in error when a record failed.
Refs: PRD-1693
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a value that cannot change its branch A quantified condition paused the Decision whenever one record had no value, even when the records read already answered it: "any UBO is a PEP" with one PEP found waited for the operator over a UBO that could only add another. The records read now settle a condition when they can (one match for any and none, one non-match for every), conditions and options combine three-valued, and the Decision pauses only when an unchecked record could change the branch. Each quantified condition lists its unchecked records in its trace. Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt requires Refs: PRD-1693 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
655947a to
e29023f
Compare

Phase 1 of multiple related records, Forest Runtime side. Part of PRD-1693 (phase 1 of PRD-24). Nothing reaches customers until the go-live ticket PRD-1695 sets the front's version floors: before that the editor never offers Multiple, so no definition carries
multipleorquantifier.What changes
preRecordedArgs.multiple, the step lists up to 50 candidates (hasMoreRecordswhen the list is cut), the AI pre-checks its pick insuggestedRecords, told which collection the candidates come from, the confirmation'sselectedRecordIdsloads every record picked, each among those offered, and the step savesexecutionResult: { relation, records }. A Multiple step's relation cannot be switched at run time, and a record picked twice is refused. Full AI loads its pick only when it saw every candidate, picked some and is sure, and pauses with the pick pre-checked otherwise.iterations[]on their row. Every record is attempted, the step errors when any failed, and{ failedRecords: "retry" | "skip", sourceRecordId? }re-runs or skips the failed ones.{ unfinishedRecords: "handle-manually", sourceRecordId }on the paused record hands every record not finished over at once, as a decline hands over one, so the step still errors over a record that failed. The step timeout applies per record, and a timed-out record's late result is never saved.quantifierand records{ matched, evaluated, unchecked? }in its trace. A record without a value leaves it undecided unless the records read settle it (one match for any and none, one non-match for every), conditions and options combine three-valued, and only when an undecided condition could change the branch does the Decision decide nothing: it reports awaiting-input withexecutionParams.unchecked, and the operator's{ selectedOption }completes it withselectedBy: "operator".POST /renew-run-lockbefore each record, stops the step without reporting when the orchestrator refuses (aborted or lost), and hands the run back when stopping. A step dispatched while carrying an error sends that error cleared when it next succeeds or pauses; every other report is unchanged.Release plan
Merges and deploys after ForestAdmin/forestadmin-server#8634, which adds the renewal route. Against an orchestrator without it the renewal fails as unavailable and the step carries on under the lock it holds, so an early release degrades to today's lock behaviour rather than breaking.
Decisions
Tests
Red before the code (
Tests: 18 failed, 112 passed, 130 total, 11 suites failing on the missing members), green after across the 19 files covering the changed sources (Test Suites: 19 passed, 19 total,Tests: 1081 passed, 1081 total). New:per-record-step-executor.test.ts, throughStepExecutorFactory.createwith the in-memory store. Later commits added 12 tests, each seen red before its fix: the relation-switch refusal and the review findings. None reaches shared state.Definition of Done
General
Security
Squash message
🤖 Generated with Claude Code
Note
Add per-record execution for steps sourced from a Multiple related-records step
multipleflag to Load Related Record step definitions. Steps pinned to such a source (ReadRecord, UpdateRecord, TriggerAction, LoadRelatedRecord) now run once per selected record via a newPerRecordStepExecutorwith iteration-level storage, summaries, and wire serialization (step-executor-factory.ts, step-execution-data.ts)any,every,none) with new typed errors for invalid configurations (related-record-picker.ts, errors.ts)Runner.executeStepanddispatchDetachednow take the dispatch object instead of separate step/token arguments; stale errors are cleared only for steps markedcarriesError: true. Sub-workflow calls pinned to a multi-record source now fail withSourceLoadsSeveralRecordsErrorMacroscope summarized e29023f.