fix(dsh): make extraction drain resilient to LLM stalls and long batches - #95
fix(dsh): make extraction drain resilient to LLM stalls and long batches#95evilh2019 wants to merge 2 commits into
Conversation
The DSH adapter's extractPending loop pulled a fixed 50 unextracted messages per batch with no length bound, built an unbounded existingNames hint list, and on any error left the whole batch unextracted while stopping the drain. A batch that stalled the LLM stream (no finish/error chunk) was retried on every restart forever, pinning the backlog. - bound each extraction request by accumulated normalized characters (8K) then message count (15); a single long message always fits so the drain always progresses - cap the existingNames hint list (150 entries / 3K chars) - hard-bound the LLM stream with a 180s timeout (previously none) - retry transient failures with backoff (5s/15s), then bisect the batch, and mark a lone failing message extracted as a last resort so the drain can never deadlock Verified: 129 vitest tests pass; a backlog that previously stalled forever now drains to zero on a real deployment.
…th capping) Three new adapter tests drive a real in-memory-file store through the backfill -> scheduleExtract -> drainBatch path: - a transient LLM failure is retried and the backlog drains to zero - a permanently failing batch is bisected and each singleton is marked extracted (no deadlock), with the drain still progressing - an oversized message batch is split by accumulated normalized length so no single extraction request exceeds the size cap Also expose extractionStreamTimeoutMs / extractionRetryDelaysMs as adapter config so hosts can tune (and tests can shorten) the resilience knobs.
|
Follow-up commit adds three adapter tests (test/dsh-adapter.test.ts) that drive the real backfill → scheduleExtract → drainBatch path against a file-backed store:
Also exposed / as adapter config so hosts can tune the resilience knobs. Full suite: 132 tests passed. |
|
Thank you @evilh2019 and @penggaolai for finding the extraction-backlog stall and contributing the initial bounded batching, retry, timeout and bisect work. Maintainer integration #97 preserved both of your commits and their original authorship, then addressed the review blockers found with official DSH and real model testing: lossless single-message segmentation, durable quarantine instead of false extracted flags, exact acknowledgements, cancellation, retry tooling, retention safety, optional Headless services, and the awaited agent/turn-stopping lifecycle drain. #97 has passed Node 22/24 CI and is merged. I am closing this PR as superseded by that integration, not as a rejection of the contribution. Thanks again. |
Problem
The DSH adapter's
extractPendingloop (indsh.ts) drains unextracted session messages into the knowledge graph in fixed-size batches:with no length bound on the batch, an unbounded
existingNameshint list (getBySession(db, sid).map(n => n.name)— can reach tens of thousands of chars), and acomplete()LLM call that had no stream timeout at all.On any error the whole drain stops and the batch is deliberately left unextracted ("so a later turn/restart can retry"). In practice that means:
extractPendinghangs (no timeout), or with a provider-side timeout the batch fails.extracted=0.Observed on a real deployment: a 47K-message session with a backlog stuck at
extracted=0across many restarts; batches of ~18.7K chars timed out while an ~18.6K batch succeeded, confirming the failure is content/stall-driven, not a simple fixed threshold.Fix
Bounded, resilient drain with guaranteed progress:
markExtracted(upToTurn)max-turn marking stays correct.existingNames: cap at 150 entries / 3K chars so the hint list cannot blow the prompt.complete()at 180s (previously unbounded — a stall could pin the session's extraction chain forever).AbortController-style race with timer cleanup.drainBatch):gm_messages) so the drain can never deadlock.Validation
npm test→ 129 tests pass (one pre-existing file-level setup failure onnode:sqlitetypings, unrelated to this change).retry → split → DONEsequences in logs; node/edge counts grow steadily.Small, self-contained change:
dsh.ts+ rebuiltdist/dsh.js(the repo tracksdist/).