← plans · onedrive-walk-concurrency · NOS-21123 Jul 2026

OneDrive walk concurrency — implementation plan

Status: In progress · Author: Claude (NOS-211 orchestrator) · Date: 2026-07-23 · Repo: north-os
Related: 2026-07-23-onedrive-walk-concurrency-design.html (the spec — read it first; §1–§4 define the invariants verbatim) · Linear NOS-211

Work item 1 — agent-runtime: level-batched BFS + parallel fan-out/widen + cancellation

Files: packages/agent-runtime/src/tools/matter-drive-documents.ts (+ test file), optional sibling helper walk-limiter.ts (+ test). No public tool API change.

  1. WALK_CONCURRENCY = 6; a small promise-based FIFO counting semaphore createLimiter(max) whose queue is cancellation-aware (a queued task checks the pool's stop state when its permit arrives and returns without running).
  2. searchMatterTree: mapped roots serial in DB order; within a root, deterministic BFS level batches — take frontier FIFO, submit up to the batch to the limiter, and per node inside its permit, synchronously: stop/deadline/signal check → per-root seen check+insert → budget check-and-decrement → visitedCount++ → start listChildren. Settle the batch (allSettled), assemble children in parent order (hits recorded in assembly order — insertion order identical to the serial walk), refill the frontier, repeat.
  3. Pool-local AbortController per walk chained to upstream signal + deadlineAt. Target found / substantive error / deadline → stop admission, abort in-flight, await settle, then return/throw. Substantive (non-abort) error wins over cancellation noise. Timebox settles { complete: false, interruption: "timebox" }; budget exhaustion "walk_budget".
  4. Return visitedCount (= budget actually consumed by this walk). Accept an optional shared limiter so one tool call spans all its walks with ≤6 total in-flight listings.
  5. Thread config.signal into the single-matter search (:1487) and findDocumentUnderMatter (containment walk) — today they pass none.
  6. Multi-matter branch (:1255): resolve all chosen matters serially, in input order, before any walk (deadline checks as today → preserves the pinned unauthorized-user barrier and the delayed-RLS interrupted_matter_id behavior; resolution-only outcomes still count completed). Then walk the authorized matters concurrently under the ONE budget + limiter. Assemble sections in input order after settling. Envelope per spec §2: completed / pending / interrupted_matter_id = first started-incomplete in input order / interruptedMatterWalkItems = that walk's visitedCount / terminal detection unchanged / meta.resultIds + coverage key restricted to completed scopes.
  7. collectConnexWiden: siblings resolved serially through the disclosure gate (unchanged), then walked concurrently under the same shared budget + limiter; blocks/matches assembled in candidate order.

Tests (extend matter-drive-documents.test.ts): the gate list in spec §Gates, notably: serial-prefix visit subset at budget exhaustion; slow same-depth sibling (fast branch must NOT descend early — level barrier); limiter-queued node never starts after deadline; deadline during RLS resolution; error-while-sibling-hangs → abort + substantive error; target short-circuit with queued+in-flight siblings (no orphan); upstream cancellation via single-matter + containment paths; multiple incomplete matters envelope + terminal + section order + resultIds restriction; unauthorized in batch → zero OneDrive calls; equal-key hits across overlapping roots; global ≤6 concurrent listings (instrument the fetch mock with an in-flight counter). Concurrency-affected tests must use URL/query-aware fetch + SQL mocks, not mockResolvedValueOnce sequences; convert the fan-out/widen tests that break under reordering (update only order-encoding assertions — semantics stay pinned, except the deliberately-updated "solo retry after prior matters consumed the budget" distribution test per spec §2).

Work item 2 — connectors-api: /children bundle cache + Graph cancellation

Files: new apps/connectors-api/src/lib/bundle-cache.ts (+ test), apps/connectors-api/src/routes/drives.ts, apps/connectors-api/src/lib/drives.ts (signal threading).

  1. createBundleCache({ fence, ttlMs: 30_000, maxEntries }) — fence function injected for testability (defaults to getRefreshedBundle). Separate inFlight and value maps; TTL stamped on successful settle; in-flight joined even past TTL; rejection propagates to all joined waiters and evicts with identity comparison (never a newer replacement); errors never cached; expired entries pruned opportunistically on access + hard entry cap. A waiter's abort must not cancel the shared fence run. ConnectorNotFoundError passes through untouched.
  2. Wire into the /children route only (drives.ts:121 call site). The other four call sites keep per-request fences.
  3. Cancellation: /children passes c.req.raw.signallistDriveItems(bundle, driveId, itemId, { signal })graph.api(path).options({ signal }).get() (supported, SDK v3.0.7). Optional param — other callers unchanged.
  4. Tests (bundle-cache.test.ts + a drives route test): concurrent gets → one fence run; TTL-from-success expiry → re-fence; slow fence past TTL still joined; rejection → all waiters reject, next get retries, identity-safe eviction; distinct keys isolated; entry-cap pruning; route test: aborting the request propagates the signal into the (mocked) Graph fetch.

Worker split

Gates & benchmark