feat(coding-agents): add maxParallelRetains config and 429-aware drain - #3390
Draft
seppaleinen wants to merge 1 commit into
Draft
feat(coding-agents): add maxParallelRetains config and 429-aware drain#3390seppaleinen wants to merge 1 commit into
seppaleinen wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The coding-agents integration floods the Hindsight API with concurrent retain-related requests and receives HTTP 429s:
drain()—HindsightClient.drain()polls every pending operation withPromise.all([...pending].map(fetch)). When a session enqueues many async retains, every op is polled at once, every 5s cycle. No concurrency cap.if (!r.ok) return;), leaving the op pending and re-polling the full set again 5s later. The client hammers the API harder while it is rate-limiting.CONCURRENCY = 4that could not be tuned, and the drain it waits on was uncapped.Root-cause evidence: a single
GET /operations/<id>returns 200, so the rate limiter is burst/concurrency-triggered, not volume-triggered.drain()issues N concurrent GETs (N = pending ops) every cycle with no cap — exactly the burst pattern that trips it. There is noRetry-Afterhandling, so the client re-bursts 5s later instead of backing off.Change Summary
maxParallelRetainsoption (number, default10) — the cap on concurrent retain-related requests (drain op polls + deepen retain pools). A single request returning 200 while bursts get 429s means the server is rate-limiting concurrency, so this is the knob to turn down.maxParallelRetainsin~/.hindsight/coding-agent.jsonHINDSIGHT_MAX_PARALLEL_RETAINSENV_KEYSandENV_NUMBERSinsrc/core/config.ts; resolved viaresolveConfig().drain()(src/core/hindsight.ts): rewired the per-cycle poll fromPromise.allover every pending op to the existing boundedpool(items, n, fn)helper, capped atmaxParallelRetains. Added 429 handling:Retry-Afterheader (parsed as delta-seconds or HTTP-date), with a 10s floor when the header is absent or shorter.maxMsbound are preserved.retryAfterMs()helper parses the header; newDEFAULT_MAX_PARALLEL_RETAINSconstant.ClientOptsacceptsmaxParallelRetains(default 10); threaded from config at every construction site —deepen.ts,plugin-entry.ts(opencode/kilo),cline.ts,mcp-server.ts,status.ts, and thehook.ts/retain-hook.ts/session-start.tsmakeClientseams.src/deepen.ts): removed hardcodedCONCURRENCY = 4; chat ingestion and the git-diff retain pool now use the configuredcfg.maxParallelRetains(pool semantics unchanged).maxParallelRetains/HINDSIGHT_MAX_PARALLEL_RETAINSto the package README configuration reference.Testing Done
src/core/hindsight.test.ts, 11 tests):global.fetch, track in-flight count; 5 ids against a 2-wide pool hits exactly the cap).Retry-After: 30→ backs off 30s (not the 10s floor) before the next cycle.Retry-After: 2→ uses the 10s floor.Retry-After→ 10s floor.retryAfterMsparses delta-seconds / HTTP-date / garbage.src/core/config.test.ts, 4 tests): default 10, file override, env number parse, malformed env ignored.npm test(vitest): 448 passed, 1 failed — the single failure (knowledge-tools.test.tshindsight_diagnose) is pre-existing and environment-dependent: it assertsapi_token_configured: falsebut the test shell exportsHINDSIGHT_API_TOKEN, soloadConfigsees a token. Same failure on the untouched baseline (433 passed before this change).npm run build(tsup): success (ESM bundles + DTS).dist/is gitignored and not committed.npx tsc --noEmit: clean.Notes
HindsightClientclass is defined inhindsight-integrations/coding-agents/src/core/hindsight.tsitself (not imported from@vectorize-io/hindsight-all), so all changes are contained in the coding-agents package.node_modulessymlink tracked by the repo is untouched.