Files
gbrain/test
Garry TanandClaude Opus 4.7 cba823f797 feat(autopilot): per-source fan-out dispatch (the headline parallelism win)
The headline change of the v0.38 federated-sync wave. Replaces autopilot's
single-job-per-tick dispatch with per-source fan-out so a 5-source
federated brain refreshes in ~5min wall-clock instead of ~25min sequential.

src/commands/autopilot-fanout.ts (new) — pure-function dispatch helper:
- resolveFanoutMax(engine): PGLite=1 (codex P1-3 — preserves single-writer
  invariant), Postgres=4, operator override via autopilot.fanout_max_per_tick
- readLastFullCycleAt(src): JSONB→Date with NULL/unparseable safety
- isSourceStale(src, now?, floorMin?): 60-min default freshness floor
- selectSourcesForDispatch(sources, fanoutMax): stale-only + oldest-first
  + alphabetical tiebreaker (deterministic for tests)
- dispatchPerSource(engine, queue, opts): the orchestrator

src/commands/autopilot.ts (modified): the existing shouldFullCycle branch
calls dispatchPerSource. Behavior preserved:
- Healthy + recent (60min floor) → sleep (unchanged)
- Targeted-plan path → unchanged (uses computeRecommendations)
- Full-cycle path → NOW fans out per-source rather than ONE job for default

Per-source dispatch shape:
- Idempotency key: `autopilot-cycle:<source_id>:<slot>` — two ticks for
  the same source within one slot coalesce; different sources never collide
- pull: !!source.config.remote_url — remotes pull, local-only don't
- maxWaiting: 1 per submit — backpressure when worker can't drain
- Per-submit try/catch (codex E1 F1) — one source's failure doesn't
  abort the tick; surfaces as fanout_submit_failed event

Fallback path: empty `sources` table (pre-v0.18 brain or fresh install
before `gbrain sources add`) falls back to the legacy single autopilot-
cycle job with no source_id, preserving today's single-source behavior.

JSON event stream extended:
- `dispatched` event gains source_id + mode='per_source' fields
- new `fanout_summary` event per tick with dispatched/skipped_fresh/
  skipped_cap arrays so operators can see what the tick did
- new `fanout_cap_reached` event when sources overflow the cap

Caveat (intentional, codex r1 P0-1 scope): per-source LOCKS let two
cycles RUN concurrently, but several phases (embed, orphans, purge,
resolve_symbol_edges, grade_takes, calibration_profile) still walk the
brain globally inside each cycle. PHASE_SCOPE taxonomy from the prior
commit documents this. Genuine per-phase per-source isolation is the
deferred Phase 2 follow-up.

27 unit tests in test/autopilot-fanout.test.ts pin every branch (stale
gate, cap behavior, idempotency keys, legacy fallback, per-submit error
isolation, oldest-first sort, alphabetical tiebreaker).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 07:56:04 -07:00
..