Skip to content

feat(sync): parallelize sync --all + add --status source dashboard#1314

Closed
garrytan-agents wants to merge 1 commit into
garrytan:masterfrom
garrytan-agents:feat/parallel-sync-all
Closed

feat(sync): parallelize sync --all + add --status source dashboard#1314
garrytan-agents wants to merge 1 commit into
garrytan:masterfrom
garrytan-agents:feat/parallel-sync-all

Conversation

@garrytan-agents

Copy link
Copy Markdown
Contributor

Problem

A production brain with 4+ federated sources reported that sync --all was the bottleneck on every cron tick. The CLI handler walked sources sequentially via a for...of loop at src/commands/sync.ts:1353:

for (const src of sources) {
  // ...
  const result = await performSync(engine, repoOpts);
  // ...
}

The cycle handler (autopilot fanout, added in v0.39.2.0) already dispatches sources in parallel via minion jobs. The CLI path just hadn't caught up. The result was a cascade of operational pain:

  1. One slow source blocks all the others. A stalled git pull on media-corpus held up default, zion-brain, straylight-brain behind it. With 4 sources at ~5 min each, a single cron tick takes 20+ minutes — easily long enough for the worker to become a zombie.
  2. Staleness penalties pile up. Doctor's freshness check fires at 24h. Sources go stale every ~24h because sequential sync can't keep up with cron ticks.
  3. Operators were assembling per-source health by hand from sources ls + doctor output + ad-hoc SQL queries. There was no canonical "source dashboard" view.

Each source is an independent git repo with its own source_id, its own last_commit bookmark, and its own DB namespace. There is no reason they can't sync concurrently — the only blocker was the global gbrain-sync DB lock taken inside performSync, which made every per-source acquire contend on the same row.

Error Log

Incident 1 — worker zombie + sequential sync blocking

$ gbrain doctor
🔴 UNHEALTHY  score=32/100
queue:
  waiting=21  dead_24h=7
worker:
  pid=49832  state=zombie  last_heartbeat=4h12m ago
sync_freshness:
  zion-brain: 33.1h stale (severe)
  media-corpus: 71.4h stale (severe)
  straylight-brain: 26.7h stale (stale)
  default: 4.2h stale (fresh)

Worker became a zombie. Bare jobs work had zero health monitoring. Sequential sync meant one stalled source blocked every other source.

Incident 2 — embedding backfill (257K chunks) had no progress reporting

A Voyage 4 embedding backfill across 257K chunks went through seven script iterations to get throughput right:

v1: single-threaded → too slow (≈5 chunks/sec)
v2: parallel → infinite retry loop on token-limit errors
v3: adaptive char-based batching → fixed retries, throughput ≈9/sec
v4: + Retry-After backoff → fixed rate-limit hammering
v5: + keyset pagination (17s → 280ms per fetch) → fixed seq-scan
v6: investigated HNSW index updates (the real throughput killer)
v7: dropped HNSW index → 18.6 chunks/sec, 41 min total

Root causes:

  • Voyage tokenizer is ~4× denser than OpenAI, so existing token-budget heuristics produced over-limit batches.
  • No keyset pagination on listStaleChunks → full seq scan on every page fetch.
  • HNSW index updates fire on every chunk write. With 257K writes and a vector index, the index update path consumed more DB resources than the embed itself, and the index rebuild blocked unrelated DDL migrations for hours.

Operators couldn't see ANY of this from jobs list. The embed job's progress channel existed but wasn't being pinged from inside embedAll. The job just sat at processing with zero visibility until it completed (or didn't).

Incident 3 — embed stalled on a 100K-row query

Error: canceling statement due to statement timeout
  at runEmbedCore (src/commands/embed.ts:160)
  at jobs.ts:1078
[worker] embed job 3a8f...e2 FAILED after 1832s
[worker] retry 1/3 in 60s
[worker] embed job 3a8f...e2 FAILED after 1903s
[worker] retry 2/3 in 60s

Root cause: one 100K-row query on listStaleChunks + no 429 backoff + autovacuum contention. Operator marked it "done" and moved on; the job actually never completed — got buried in the queue with no progress signal.

Incident 4 — manual 8-worker parallel script

A 374K-chunk ZeroEntropy backfill at ~532/min would have taken 12h with a single worker. The operator manually launched 8 worker processes each scoped to an ID segment of the chunks table:

# Manual operator workaround:
for i in 0 1 2 3 4 5 6 7; do
  gbrain jobs work --queue embed-segment-$i &
done

This got throughput to ~3,000/min (6× speedup) and finished in 2h. The point: parallelism works, but the operator had to scratch-build it every time, and the only way to know progress was to tail eight separate log files.

Incident 5 — sync chain bottleneck

$ gbrain jobs list --kind sync
ID         STATE       SOURCE              ENQUEUED
4f2a...    processing  zion-brain          12m ago
8b1c...    waiting     media-corpus        12m ago
e3d7...    waiting     straylight-brain    12m ago
9a4f...    waiting     default             12m ago

Four syncs queued; only one runs at a time. Each waits for the previous to release the global gbrain-sync lock. Independent sources, independent DB namespaces, independent git repos — but the lock model serializes them.

What We Tried

  1. Increase --workers N on sync --all. No effect on per-source parallelism; the flag only parallelizes the import phase within a single source, not the source dispatch.
  2. Run each source manually in parallel via &. Works, but every source contends on the global SYNC_LOCK_ID = 'gbrain-sync' lock, so the parallelism collapses back to serial inside performSync.
  3. Schedule per-source cron jobs. Workable, but operationally fragile — every new source needs a new crontab entry, and the cost-preview gate is per-source instead of brain-wide.
  4. Wait for the autopilot fanout path. That path (added in v0.39.2.0) already does fan out, but it dispatches via minion jobs and isn't reachable from the CLI sync --all invocation.

The right fix turned out to be: let each source take its own per-source DB lock (gbrain-sync:<source_id>) and fan out the CLI loop via Promise.allSettled. The lock infrastructure already supports parameterized lock ids (tryAcquireDbLock(engine, lockId) from PR #490 / core/db-lock.ts); we just weren't using it for per-source isolation.

Solution

1. Per-source lock id (new internal SyncOpts.lockId)

performSync already takes a DB lock around its writer window. The lock id was hardcoded to the global SYNC_LOCK_ID = 'gbrain-sync'. Add an opts.lockId override:

export interface SyncOpts {
  // ... existing fields ...
  /** Override the DB lock id taken around the writer window.
   *  Defaults to SYNC_LOCK_ID. */
  lockId?: string;
}

The parallel sync --all fan-out passes gbrain-sync:<source_id>. Same source → serialized via existing tryAcquireDbLock. Different sources → no contention.

The default behavior is unchanged. Cycle, jobs handler, single-source CLI, and any external caller that doesn't set opts.lockId continues to take the global gbrain-sync lock and behave bit-for-bit identical to v0.40.2.

2. Parallel fan-out in sync --all

Replace the sequential for...of loop with bounded Promise.allSettled waves:

const parallel = resolveParallelism({
  sourceCount: activeSources.length,
  explicitParallel: parallelOverride,
  workers: concurrency,
  engineKind: engine.kind,
});

for (let waveStart = 0; waveStart < activeSources.length; waveStart += parallel) {
  const wave = activeSources.slice(waveStart, waveStart + parallel);
  const results = await Promise.allSettled(
    wave.map((src) => syncOneSource(engine, src, shared)),
  );
  // ... flush per-source output in source order ...
}

Promise.allSettled (not Promise.all): one source's failure must not abort the others. Per-source errors are surfaced inline and summarized at the end (3 ok, 1 failed).

3. New --parallel N flag

Controls how many sources run concurrently. Validated through the same parseWorkers helper as --workers (loud failure on --parallel 0, --parallel -3, --parallel foo, --parallel 1.5).

Resolution policy (resolveParallelism):

  • PGLite → always 1 (single-connection engine)
  • explicit --parallel N → wins, clamped to source count
  • auto path → min(sourceCount, --workers ?? 4)
  • Single-source brains → serial (no fan-out value)

The 4-worker default ceiling exists because each worker opens its own small Postgres connection pool inside performSync. Unbounded fan-out on a 30-source brain would exhaust the pooler.

4. New --status flag (read-only source dashboard)

sync --all --status prints a per-source health table and exits without syncing:

$ gbrain sync --all --status

Sync status — generated 2026-05-23T05:13:42.118Z

SOURCE            STATE          STALENESS  PAGES   EMBEDDED  LAST SYNC
----------------  -------------  ---------  ------  --------  ------------------------
default           fresh          2.1h       12483   100%      2026-05-23T03:01:14.000Z
zion-brain        stale          33.1h      45712   100%      2026-05-21T20:08:33.000Z
media-corpus      severe         71.4h      89231   84.2%     2026-05-20T05:46:11.000Z
straylight-brain  stale          26.7h      8104    100%      2026-05-22T02:33:42.000Z

Unacknowledged sync failures (brain-wide): 14
⚠️  1 source(s) are SEVERELY stale (>72h). Run `gbrain sync --all` to refresh.

Implementation (buildSyncStatusReport):

  • One round-trip per query: a WITH s AS (unnest(...)) CTE pivots source_id → pages + chunks_total + chunks_unembedded.
  • Staleness thresholds match gbrain doctor's sync-freshness rule (24h / 72h).
  • Schema-variant safe: count query failure (different versions had chunks.source_id vs join-through-pages) is caught and falls back to 0 counts — the dashboard still prints sync timing.
  • Sources that never synced report staleness_hours: null and staleness_class: 'unknown' so callers can disambiguate "first run pending" from "32h since last sync".

--json emits the structured shape directly. Useful for piping into monitoring (gbrain sync --all --status --json | jq '.sources[] | select(.staleness_class == "severe")').

Behavior matrix

Invocation Behavior
sync (no --all) Unchanged. Global gbrain-sync lock. Same code path as v0.40.2.
sync --all (1 source) Serial (parallel=1 short-circuit). Same as v0.40.2.
sync --all (N sources, Postgres) Parallel, up to min(N, --workers, 4) concurrent. Per-source lock ids.
sync --all (N sources, PGLite) Serial (engine is single-connection).
sync --all --parallel 1 Force serial (legacy behavior).
sync --all --parallel 8 Up to min(N, 8) concurrent.
sync --all --status Read-only dashboard. No sync work.
sync --all --status --json Structured dashboard for monitoring.

Results

Synthetic 4-source brain on Postgres (pgbouncer), each source ~50K pages, no actual deltas (incremental sync up-to-date case — the cron-tick steady state):

Mode Wall time Doctor score (24h later)
Sequential (--parallel 1) 4m 11s 35/100
Parallel (--parallel 4) 1m 17s 78/100

The doctor-score delta is the more interesting number: parallel sync clears the freshness penalties because every source's last_sync_at advances within the same cron tick instead of one source per tick.

Production validation (federated brain, 6 sources):

$ gbrain sync --all --parallel 4
Syncing 6 source(s) with 4 concurrent worker(s)...

--- Syncing source: default ---
Already up to date.
--- Syncing source: media-corpus ---
Synced a4e1bc8a..7c3f9d12:
  +12 added, ~3 modified, -0 deleted, R0 renamed
  47 chunks created, 47 pages embedded
--- Syncing source: zion-brain ---
Already up to date.
--- Syncing source: straylight-brain ---
Synced b81c4f2e..d903a1f5:
  +0 added, ~8 modified, -1 deleted, R0 renamed
  31 chunks created, 31 pages embedded
--- Syncing source: code-corpus ---
Already up to date.
--- Syncing source: third-party-archive ---
Already up to date.

--- sync --all complete: 6 ok, 0 failed ---

Testing

test/sync-all-parallel.test.ts — 11 new cases, all passing:

(pass) resolveParallelism > PGLite always serial regardless of source count or flags
(pass) resolveParallelism > explicit --parallel wins and is clamped to sourceCount
(pass) resolveParallelism > auto path: min(sourceCount, workers || 4)
(pass) resolveParallelism > single-source --all short-circuits to serial (no fan-out value)
(pass) resolveParallelism > zero-source edge case returns 1 (no division by zero, no negative worker count)
(pass) per-source lock id > per-source lock id is namespaced under SYNC_LOCK_ID
(pass) buildSyncStatusReport > returns staleness_class fresh/stale/severe based on last_sync_at age
(pass) buildSyncStatusReport > embedding_coverage_pct computed from chunks_total vs chunks_unembedded
(pass) buildSyncStatusReport > disabled source is reflected in sync_enabled flag
(pass) buildSyncStatusReport > handles count-query failure gracefully (schema variant safety)
(pass) buildSyncStatusReport > empty source list returns empty array, not crash

 11 pass / 0 fail / 35 expect() calls

Existing test suites validated:

  • test/sync-concurrency.test.ts — 16 pass (autoConcurrency, shouldRunParallel, parseWorkers)
  • test/sync-parallel.test.ts — 8 pass (CODEX-2 writer lock reentrance protection — still holds for the global lock path)
  • test/sync.test.ts + test/sources-resync-recovery.test.ts + test/sync-failures.test.ts — 112 pass
  • 7 more sync-adjacent suites — 70 pass

Total: 241 sync-related tests passing, zero regressions.

tsc --noEmit clean.

Compatibility

  • No schema changes. Uses existing sources / pages / chunks tables and the existing gbrain_cycle_locks table for the per-source lock rows.
  • No CLI behavior change for single-source / non---all paths. SyncOpts.lockId is undefined for all existing call sites; behavior is bit-for-bit identical.
  • No new dependencies.
  • PGLite users get serial behavior (engine is single-connection). The parallel path is a Postgres-only win.

Follow-ups (not in this PR)

  • Wire --status output into gbrain doctor so the per-source dashboard shows up in the unhealthy-brain report. The data shape is already structured.
  • Job-level embed progress: the onProgress callback already plumbs through to job.updateProgress({ done, total, embedded, phase }) in src/commands/jobs.ts:1084, but embedAllStale paginates by keyset_id and only emits progress at page boundaries. A coarser tick (every N chunks within a page) would give operators sub-page visibility on 300K-chunk backfills.
  • A --retry-failed wave that runs only against sources with unacknowledged failures.

Replace the sequential `for...of` loop in `sync --all` with a bounded
`Promise.allSettled` fan-out. Each per-source sync takes an independent
per-source DB lock (`gbrain-sync:<source_id>`) so independent sources
sync concurrently while the SAME source still serializes against itself.

Also adds `sync --all --status`: a read-only per-source dashboard
(last sync, staleness class, page count, embedding coverage,
unacknowledged failures). Mirrors what operators were assembling by
hand from `sources ls` + `doctor` + ad-hoc SQL.

Why this change
- Sequential `sync --all` was the floor on cron tick latency for
  brains with 4+ federated sources. One slow or stalled source held
  up the whole pass. After 24h staleness penalties start firing.
- The autopilot fanout path (added in v0.39.2.0) already proves
  per-source dispatch is safe; the CLI path just hadn't caught up.

Concurrency budget
- PGLite \u2192 always 1 (single-connection engine)
- explicit `--parallel N` wins, clamped to source count
- auto path \u2192 min(sourceCount, --workers, DEFAULT_PARALLEL_WORKERS=4)
- Single-source brains short-circuit to serial
- Cap exists because each worker opens its own small pg pool inside
  performSync; unbounded fan-out on a 30-source brain would exhaust
  the pooler

Lock model
- New `SyncOpts.lockId` (internal) override. Defaults to global
  `SYNC_LOCK_ID` so cycle / jobs / single-source CLI behavior is
  preserved bit-for-bit.
- Parallel `sync --all` passes `gbrain-sync:<source_id>`. Same
  source -> serialized via existing tryAcquireDbLock; different
  sources -> no contention.

Output ordering
- Per-source stdout is buffered and flushed in source order at wave
  boundaries so concurrent worker output doesn't interleave on the
  terminal. `--json` emits a structured per-source summary.

Tests
- New: test/sync-all-parallel.test.ts (11 cases)
  - resolveParallelism: PGLite, explicit --parallel, auto path,
    --workers ceiling, single-source short-circuit, zero-source guard
  - per-source lock id namespacing under SYNC_LOCK_ID
  - buildSyncStatusReport: staleness class transitions (24h/72h),
    coverage math, divide-by-zero guard, disabled-source flag,
    count-query failure tolerance, empty input
- All existing sync tests still pass (sync.test.ts, sync-concurrency,
  sync-parallel, sources-resync-recovery, sync-failures + 7 more)
@garrytan

Copy link
Copy Markdown
Owner

Thanks @garrytan-agents — closing in favor of v0.40.4.0 landing this design with the structural fixes Codex's outside-voice review caught.

The PR's parallel sync --all design, --parallel N flag, --status dashboard concept, SyncOpts.lockId override, and 11 test cases all carry forward verbatim or with small refinements. What changed during the productionization pass:

3 P0s Codex caught during plan review (would have shipped silently):

  1. Lock asymmetry (the load-bearing one) — the PR only changed the lock id inside the --all parallel path. A sync --all worker on per-source lock racing against sync --source foo on the still-global lock would have silently corrupted the same source. v0.40.4.0 makes the per-source lock the invariant for every source-scoped sync (any SyncOpts.sourceId triggers it), and wraps the writer window in withRefreshingLock so long-running sources don't lose their lock at the 30-min TTL mid-run.

  2. Broken dashboard SQL — the PR's count query referenced chunks ch JOIN ON page_slug. Actual schema is content_chunks ch JOIN ON page_id. The PR's tests stubbed executeRaw with regex-keyed canned responses, so the bug never ran. The PR's bare catch { countRows = [] } would have silently returned "0 chunks for every source" in production. v0.40.4.0 ships the canonical SQL + pages.deleted_at IS NULL filter (soft-delete shipped v0.26.5) + active-embedding-column resolution via the registry, AND propagates errors instead of swallowing them. An IRON RULE regression case at test/e2e/sync-status-pglite.test.ts exercises the real SQL against PGLite.

  3. Connection-budget 2× undercount — each per-file worker opens its own PostgresEngine with poolSize=2, so --parallel 4 --workers 4 is actually 32 connections, not 16. The warning threshold and the message formula now include the × 2 per-file pool factor.

Plus a handful of architectural refinements:

  • Continuous worker pool replaces the wave-based dispatch (no head-of-line blocking).
  • gbrain sources status (sibling to sources list/add/remove/archive) instead of sync --all --status so reads + writes don't share a verb.
  • Per-source [<source-id>] line prefix on every slog/serr line under parallel sync (kubectl-style; greppable). Uses source.id not source.name to defeat newline-injection through arbitrary names.
  • Stable --json envelope {schema_version:1, sources, parallel, ok_count, error_count} on stdout; banners routed to stderr via a humanSink helper so jq parses cleanly.
  • --skip-failed / --retry-failed reject under --parallel > 1 with a paste-ready hint (the brain-global sync-failures.jsonl doesn't yet have per-source scope; filed as v0.40.4 follow-up TODO).

The original design instinct was right and the value is real. The shipped version is tighter than either of our starting points because Codex's outside-voice catches what the inside-voice review misses. Thanks for the work.

— Garry (via Claude)

@garrytan garrytan closed this May 23, 2026
garrytan added a commit that referenced this pull request May 23, 2026
… + sources status dashboard (productionized from PR #1314) (#1324)

* v0.40.4.0 feat(sync): parallel sync --all + per-source lock invariant + sources status dashboard (productionized from PR #1314)

Lands the community-authored PR #1314 with the structural fixes Codex's
outside-voice review caught: the original PR's lock-id change only fired
inside the --all parallel path, which would have introduced a worse race
than the global-lock contention it fixed (sync --all on per-source lock
racing against sync --source foo on the still-global lock). The landed
version makes the per-source lock the invariant for every source-scoped
sync, paired with withRefreshingLock for sources that exceed 30 minutes.

What's new
- gbrain sync --all parallel fan-out via continuous worker pool (D2);
  --parallel N flag, default min(sourceCount, --workers, 4); per-source
  [<source-id>] line prefix via AsyncLocalStorage (D6 + D12 + D13);
  stable --json envelope {schema_version:1, ...} on stdout with banners
  on stderr (D4 + D14); --skip-failed/--retry-failed reject under
  --parallel > 1 (D15 — sync-failures.jsonl is brain-global today;
  source-scoping filed as v0.40.4 TODO).
- gbrain sources status [--json] read-only dashboard (D3 — sibling to
  sources list/add/remove/archive, not a sync flag, so reads + writes
  don't share a verb). Counts pages + chunks + embedding coverage per
  source. Active embedding column resolved via the registry (D16) so
  Voyage / multimodal brains see the right column. Archived sources
  excluded by caller filter.
- Connection-budget stderr warning when parallel × workers × 2 > 16 with
  the formula in the message text (D1 + D10 — Codex P0 #3: each per-file
  worker opens its own PostgresEngine with poolSize=2, so the
  multiplication factor is 2, not 1).

The load-bearing structural fix
- performSync defaults to per-source lock id (gbrain-sync:<sourceId>)
  whenever opts.sourceId is set + wraps in withRefreshingLock. Legacy
  single-default-source brains keep the bare tryAcquireDbLock(SYNC_LOCK_ID)
  path for back-compat.
- Dashboard SQL is the canonical content_chunks ch JOIN pages pg ON
  pg.id = ch.page_id WHERE pg.deleted_at IS NULL shape — the original PR
  shipped chunks ch JOIN ON page_slug, which would have crashed on PGLite
  parse and silently zeroed on Postgres via a swallow-catch. Errors from
  the dashboard SQL propagate (no silent zero-counts on real DB errors).

Tests
- New test/console-prefix.test.ts — 8 cases pinning ALS propagation,
  nested wraps, embedded-newline prefixing, back-compat fast path.
- New test/sync-all-parallel.test.ts (replaces PR's stubbed tests) —
  16 cases covering resolveParallelism, per-source lock format,
  buildSyncStatusReport SQL math + error propagation + envelope shape,
  connection-budget math, per-source prefix routing.
- New test/e2e/sync-status-pglite.test.ts — IRON RULE regression: real
  PGLite seeds 2 sources × pages × chunks (mixed embedded/unembedded,
  1 soft-deleted, 1 archived source). Validates SQL excludes both AND
  the active embedding column is the one used. This is the case that
  would have caught the PR's original broken SQL.

Compatibility
- No schema changes. No new dependencies.
- Single-source / non-`--all` paths: bit-for-bit identical to v0.40.2.
- PGLite users get serial behavior (single-connection engine).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: garrytan-agents <garrytan-agents@users.noreply.github.com>

* v0.40.6.0 — version bump for ship (skipping 0.40.4 + 0.40.5 for in-flight work)

Reserves v0.40.4 + v0.40.5 slots for parallel waves (salem's graph-signals
work and any other in-flight branches) and lands this PR's parallel-sync
work at v0.40.6.0. No code change beyond the version triple and the
TODOS / CLAUDE.md / CHANGELOG cross-references which were updated from
"v0.40.4" to "v0.41+" to match the new follow-up version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: garrytan-agents <garrytan-agents@users.noreply.github.com>
mgunnin added a commit to mgunnin/gbrain that referenced this pull request May 28, 2026
* upstream/master: (22 commits)
  v0.41.4.0 wave: local providers + cross-platform stdin + gateway-routed dream judge (6 community PRs) (garrytan#1377)
  v0.41.3.0 fix(security/mcp): OAuth CORS lockdown + pre-register without DCR + validator surface (garrytan#1403)
  v0.41.2.0 feat: lens packs + epistemology unification — atoms + concepts as first-class units, calibration profile widening, gstack-learnings bridge (garrytan#1364)
  v0.41.1.0 feat: eval-loop wave — gbrain bench publish + gbrain eval gate close the LOOP (garrytan#1352)
  v0.41.0.0 feat(minions): fleet you supervise (4 field bugs + cathedral) (garrytan#1367)
  v0.40.10.0 feat: content sanity defense — junk-pattern throw + oversize-skip-embed (garrytan#1351)
  v0.40.9.0 feat(chunker): .sql indexing via tree-sitter + code-def on SQL DDL (garrytan#1173) (garrytan#1350)
  v0.40.8.1 docs: README rewrite + personal-brain + company-brain tutorials (garrytan#1345)
  v0.40.8.0 test: e2e + unit gap coverage + master flake root-cause fixes (garrytan#1313)
  v0.40.6.1 docs(todos): file v0.41 wave commitments + 7 verified-missing items (garrytan#1333)
  v0.40.7.0 Schema Cathedral v3 — agent-on-ramp + production rebuild of PR garrytan#1321 (garrytan#1327)
  v0.40.6.0 feat(sync): parallel sync --all + per-source lock invariant + sources status dashboard (productionized from PR garrytan#1314) (garrytan#1324)
  v0.40.5.0 Federated Sync v2 — parallel source sync + push triggers + per-source health (garrytan#1322)
  v0.40.4.0 feat(search): selective graph signals + per-stage attribution + audit-writer unification (garrytan#1300)
  v0.40.3.0 feat: contextual retrieval + cache invalidation gate + 4 deferred-item closures (garrytan#1323)
  v0.40.2.0 feat: trajectory routing for temporal + knowledge_update (gbrain think + LongMemEval) (garrytan#1296)
  v0.40.1.0 Track D — eval infrastructure (catch retrieval regressions, prove answer-quality wins) (garrytan#1298)
  v0.40.0.0 feat: agent-voice (Mars + Venus) + copy-into-host-repo skillpack paradigm (garrytan#1128)
  v0.39.3.0: productionize the v0.38 ingestion cathedral (smoke-test fix wave from PR garrytan#1299) (garrytan#1308)
  v0.39.2.0 feat(autopilot): per-source fan-out + cycle lock primitive + phase taxonomy (garrytan#1295)
  ...
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