Skip to content

fix(agent): carry a parallel pipeline through sorted, limit, parallel() and the other lambda-free stages (#1172) - #1227

Open
PIsberg wants to merge 1 commit into
mainfrom
fix/1172-unwoven-stages
Open

PIsberg wants to merge 1 commit into
mainfrom
fix/1172-unwoven-stages

Conversation

@PIsberg

@PIsberg PIsberg commented Nov 8, 2026

Copy link
Copy Markdown
Owner

Stacked on #1225 and #1226: review the last commit (d65a6d8c0). The first two commits are theirs and drop out of this diff as they merge. All three change docs/CHANGELOG.md, which is why they are chained.

Partly addresses #1172. What is still not followed stays there (listed below).

Why

A lambda reached the common pool's workers only while every stage between it and the terminal operation was woven. sorted, distinct, limit, skip, parallel(), sequential() and the as*Stream conversions were not. So list.stream().map(...).parallel().sorted().toList(), a common shape, ran its map lambda unattributed, and a race in it was seen only on the caller's own elements. The lambdas of flatMap, takeWhile, dropWhile and the primitive-to-primitive maps (IntStream.mapToLong and the rest) ran unattributed too.

What changed

  • Stages that run no lambda carry the slots behind them to the stream they return: sorted(), distinct, limit, skip, parallel(), sequential() on all four stream kinds, a Stream's unordered(), and asLongStream/asDoubleStream. A Stream's parallel, sequential and unordered are declared by BaseStream, so their call sites expect a BaseStream back, and those hooks return exactly that.
  • The remaining lambda stages wait in a slot like map's: flatMap, takeWhile and dropWhile on all four kinds, and the six primitive-to-primitive mapTo*.
  • A sorted(Comparator) comparator runs as a combine: a parallel sort compares elements other subtasks produced, after a join the agent does not see, so it is ordered after the elements that built them, like a reduce operator.
  • 47 additive hooks, @since 1.14.2, and 47 weaver rows.

How it was verified

  • Tests first: four new cases in AgentParallelStreamHooksTest failed to compile on the missing hooks. All 23 cases in the class pass now.
  • Mutations, each restored afterwards. Dropping the sorted() carry turns aLambdaStaysLentBehindTheCarryingStages red. Running the comparator as a plain element instead of a combine turns aSortedComparatorIsOrderedAfterTheElementsItCompares red: a correct parallel sort was then reported as a race.
  • Corpus, through the real agent: idiom_parallelMapThenSorted_plainCounter (MUST_FIRE) and its locked twin (MUST_STAY_SILENT). The idiom lane passed 88/88 and every other lane is green.
  • Library suite 3967 and agent suite 118 green. The static gate chain is green. The first library run failed one case, JavadocDescribesRatherThanRestatesTest, on a new @param comparator the comparator; I reworded it and that class and the stream class pass. The agent suite was skipped in that first run because the reactor stopped, so I ran it on its own afterwards (118 green).

Docs

The docs/CHANGELOG.md #1172 entry, docs/agent/limitations.md and docs/analysis/corpus-eval-future-improvements.md now list what is followed and what is not. No VibeTags guardrail was added: the hooks class's contract-frozen rule covers the new hooks, and no external contract changed.

Checklist

  • A behaviour change ships with a test that fails without the fix
  • A new or changed detector is tested against the buggy code and its correctly synchronized twin (no detector changed; the corpus pair covers both directions of the feed)
  • Docs the change makes wrong are updated in this same change
  • Nothing was hand-edited between VIBETAGS-START / VIBETAGS-END markers
  • Public API changes are intentional and carry the right version bump: 47 additive hooks, @since 1.14.2
  • Commits an agent authored carry the Co-Authored-By trailer
  • A new dependency was proposed with a reason (none added)
  • CI is green, or the failure is explained below

Still not followed (tracked in #1172)

A mapMulti lambda, a stage behind a primitive stream's unordered() or onClose, a pipeline ended by iterator(), and a Collector's own functions.

🤖 Generated with Claude Code

…() and the other lambda-free stages (#1172)

A lambda followed to the pool workers only while every stage between it
and the terminal operation was woven. sorted, distinct, limit, skip,
parallel(), sequential() and the as*Stream conversions were not, so
list.stream().map(...).parallel().sorted().toList(), a common shape, ran
its map lambda unattributed, and a race in it was seen on the caller's own
elements alone. flatMap, takeWhile, dropWhile and the primitive-to-primitive
maps left their own lambdas unattributed too.

The lambda-free stages now carry the slots behind them to the stream they
return (a Stream's parallel, sequential and unordered return BaseStream,
which is what their call sites expect). flatMap, takeWhile, dropWhile and
the remaining mapTo* lambdas wait in slots like map's, and a sorted
comparator runs as a combine, since a parallel sort compares what other
subtasks produced. 47 additive hooks, @SInCE 1.14.2.

Tests first: four new cases in AgentParallelStreamHooksTest failed to
compile on the missing hooks; all 23 pass. Dropping the sorted() carry, or
running the comparator as a plain element, each turns its own case red.
Corpus: parallelMapThenSorted with and without a lock; idiom lane 88/88,
every lane green. Library 3967 and agent 118 tests green, static gate chain
green. The javadoc restatement gate caught one "@PARAM comparator the
comparator" on the way; reworded.

Still not followed: mapMulti, a pipeline ended by iterator(), and a
Collector's own functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant