Skip to content

fix(pickled-core): make mine features stage --max-parallel actually parallelize - #32

Merged
bartrosa merged 1 commit into
mainfrom
cursor/critical-correctness-bugs-57b3
Aug 26, 2026
Merged

bartrosa merged 1 commit into
mainfrom
cursor/critical-correctness-bugs-57b3

Conversation

@cursor

@cursor cursor Bot commented Jun 9, 2026

Copy link
Copy Markdown

Bug and impact

pickled-spec mine features (and mine all) advertises --max-parallel N to overlap LLM drafts, but the flag had no effect: every run was 1-way serial regardless of the value passed.

Blast radius is real wallclock time on every mining run that exercises the features stage. With 50 surfaces and 5s LLM calls, --max-parallel 8 should finish in ~63s but actually took ~250s — a ~4× regression on an advertised CLI flag, paid in LLM-latency dollars and developer wait time.

Reproduction (before fix): a FakeDrafter.draft_from_story that records peak concurrent invocations and sleeps 0.5s shows max_concurrent observed: 1 and ~4.0s elapsed for 8 jobs with --max-parallel 4 — exactly the serial-execution profile.

Root cause

In packages/pickled-core/src/pickled_core/mine/features_stage.py, _draft_one was an async def that invoked the synchronous FeatureDrafter.draft_from_story directly:

async def _draft_one(...):
    ...
    result = drafter.draft_from_story(story_text)  # sync HTTP, blocks event loop

_run_parallel then await-ed _draft_one through an asyncio.Semaphore. Because the coroutine never yielded to the event loop during the LLM round-trip, the semaphore never released to a waiting task, and asyncio.gather ran the coroutines effectively in series.

The stories stage was already correct because it dispatched the same kind of sync work through asyncio.to_thread; only the features stage missed that handoff.

Fix

  • Extract _draft_one_sync as a synchronous helper.
  • Parallel path: dispatch through asyncio.to_thread(_draft_one_sync, ...).
  • Interactive (non-quick) path: call _draft_one_sync directly (drops the asyncio.run(...) round-trip that served no purpose now that the function is sync).

Validation

  • Standalone repro before fix: max_concurrent observed: 1, elapsed ~4.0s.
  • Same repro after fix: max_concurrent observed: 4, elapsed ~1.0s.
  • Added test_max_parallel_actually_overlaps_llm_calls regression test in packages/pickled-core/tests/test_mine_features_stage.py that monkeypatches FeatureDrafter to track peak concurrent in-flight calls and asserts both peak ≥ 2 and elapsed < 1.5s for 8 jobs × 0.2s with --max-parallel 4.
  • Full workspace test suite: all green (uv run pytest -q).
  • ruff check and mypy clean on touched files.
Open in Web View Automation 

…arallelize

Calling FeatureDrafter.draft_from_story (sync HTTP) from inside an
`async def` blocked the event loop for the entire LLM round-trip, so
the asyncio.Semaphore never released to a waiting coroutine. The
advertised `--max-parallel N` flag silently degraded to 1-way serial
execution despite the loop and semaphore looking correct on paper.

Concrete blast radius: a workspace with 50 surfaces and 5s LLM
calls and `--max-parallel 8` should mine in ~63s but actually took
~250s — a 4× regression on a CLI flag advertised to do parallel
work, paid in real LLM-latency wallclock. The stories stage was
already correct because it scheduled the same kind of sync work via
asyncio.to_thread; only the features stage missed that handoff.

Fix: extract `_draft_one_sync` as a synchronous helper and dispatch
it through `asyncio.to_thread` from the parallel runner. The
interactive (non-quick) path now also calls `_draft_one_sync`
directly instead of round-tripping through `asyncio.run` — a small
cleanup that lets a test reliably observe the LLM call without
dragging in event-loop machinery.

Adds a regression test that monkeypatches FeatureDrafter to track
peak concurrent in-flight calls; with the fix peak >= 2 and 8 jobs
of 0.2s finish in well under 1.5s, while the previous code pinned
peak at 1 and took ~1.6s on the same hardware.

Co-authored-by: Bartłomiej Rosa <bartrosa@users.noreply.github.com>
@bartrosa
bartrosa marked this pull request as ready for review August 26, 2026 10:42
@bartrosa
bartrosa merged commit 010b324 into main Aug 26, 2026
0 of 4 checks passed
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.

2 participants