Context
A downstream tool (gstack /sync-gbrain + /setup-gbrain) is building a zero-config
"freshness" contract on top of gbrain: after one setup, every gbrain query
(semantic, symbol, call-graph) should be correct and fresh with no flags,
self-healing across crashes and reboots. Reading gbrain 0.41.38.0 source, two
things already work great and are NOT being asked to change: file-change detection
(SHA-256 content hash, src/core/import-file.ts:405-449) and the cross-platform
auto-restarting daemon (gbrain autopilot --install writing launchd KeepAlive /
systemd Restart / cron, src/commands/autopilot.ts:810-1015). Thank you for both.
Three gaps remain that make a brain silently fail to build its call graph for an
unattended/zero-config consumer. Two are real; the third is optional.
Gap 1 (real): code-* return a silent empty result, indistinguishable from "still indexing"
code-callers / code-callees / code-def / code-refs return count: 0, results: []
in three situations a caller cannot tell apart: (1) symbol graph not built yet for
the source, (2) source never synced, (3) graph complete and the symbol genuinely
has no callers. Evidence: src/commands/code-def.ts:29-84 (empty row set when
content_chunks has no match) and :120-143 (prints "No definitions found" / emits
{count:0,results:[]}, exits 0 regardless); callers/callees/refs share the pattern.
Impact: an agent queries code-callers foo, gets count:0, concludes "no callers"
when the graph is still building or never built. No signal to wait vs trust.
Request: add a typed readiness field, e.g. ready: false (or
status: "indexing"|"not_built"|"ready") when the source has no built symbol graph.
Then count:0 + ready:true means "genuinely none"; ready:false means "ask later."
Gap 2 (real): gbrain init does not validate the embedding key, so a brain can silently yield embedded=0
gbrain init persists --embedding-model to ~/.gbrain/config.json but never checks
the provider key is present/working. Evidence: src/commands/init.ts:181-240
(resolveAIOptions saves the model string; no key check, no test embed). It fails
only at first sync (embedBatch throws EMBEDDING_NO_CREDS, page imports but
embedded=0), and combined with Gap 1 the call graph silently never builds.
Request: validate the embedding provider/key at init (ideally a tiny test-embed so
a missing/invalid key fails fast at setup), or at minimum a loud warning. The
existing --no-embedding escape preserves the deferred-setup path.
Gap 3 (optional, low): cycle-lock takeover is TTL-only; a crashed holder blocks cycles for up to the full TTL
gbrain_cycle_locks is taken over only when ttl_expires_at < NOW() (~30 min).
Evidence: src/core/db-lock.ts:51-95. A crashed holder blocks new cycles for up to
the TTL. gbrain sync --break-lock already does the liveness check
(process.kill(pid,0) + age gate, src/commands/sync.ts:831-867); it just is not
automatic. Request (optional): same-host + provably-dead pid + small grace window
-> automatic takeover; cross-host stays TTL-only.
Why this matters
With Gaps 1 + 2 fixed, a consumer sets up a brain, gets a fast loud failure if the
key is missing, and can distinguish "graph still building" from "no such symbol"
while the (already excellent) daemon keeps everything fresh. That's the whole
zero-config story.
Context
A downstream tool (gstack /sync-gbrain + /setup-gbrain) is building a zero-config
"freshness" contract on top of gbrain: after one setup, every gbrain query
(semantic, symbol, call-graph) should be correct and fresh with no flags,
self-healing across crashes and reboots. Reading gbrain 0.41.38.0 source, two
things already work great and are NOT being asked to change: file-change detection
(SHA-256 content hash, src/core/import-file.ts:405-449) and the cross-platform
auto-restarting daemon (gbrain autopilot --install writing launchd KeepAlive /
systemd Restart / cron, src/commands/autopilot.ts:810-1015). Thank you for both.
Three gaps remain that make a brain silently fail to build its call graph for an
unattended/zero-config consumer. Two are real; the third is optional.
Gap 1 (real): code-* return a silent empty result, indistinguishable from "still indexing"
code-callers / code-callees / code-def / code-refs return count: 0, results: []
in three situations a caller cannot tell apart: (1) symbol graph not built yet for
the source, (2) source never synced, (3) graph complete and the symbol genuinely
has no callers. Evidence: src/commands/code-def.ts:29-84 (empty row set when
content_chunks has no match) and :120-143 (prints "No definitions found" / emits
{count:0,results:[]}, exits 0 regardless); callers/callees/refs share the pattern.
Impact: an agent queries code-callers foo, gets count:0, concludes "no callers"
when the graph is still building or never built. No signal to wait vs trust.
Request: add a typed readiness field, e.g. ready: false (or
status: "indexing"|"not_built"|"ready") when the source has no built symbol graph.
Then count:0 + ready:true means "genuinely none"; ready:false means "ask later."
Gap 2 (real): gbrain init does not validate the embedding key, so a brain can silently yield embedded=0
gbrain init persists --embedding-model to ~/.gbrain/config.json but never checks
the provider key is present/working. Evidence: src/commands/init.ts:181-240
(resolveAIOptions saves the model string; no key check, no test embed). It fails
only at first sync (embedBatch throws EMBEDDING_NO_CREDS, page imports but
embedded=0), and combined with Gap 1 the call graph silently never builds.
Request: validate the embedding provider/key at init (ideally a tiny test-embed so
a missing/invalid key fails fast at setup), or at minimum a loud warning. The
existing --no-embedding escape preserves the deferred-setup path.
Gap 3 (optional, low): cycle-lock takeover is TTL-only; a crashed holder blocks cycles for up to the full TTL
gbrain_cycle_locks is taken over only when ttl_expires_at < NOW() (~30 min).
Evidence: src/core/db-lock.ts:51-95. A crashed holder blocks new cycles for up to
the TTL. gbrain sync --break-lock already does the liveness check
(process.kill(pid,0) + age gate, src/commands/sync.ts:831-867); it just is not
automatic. Request (optional): same-host + provably-dead pid + small grace window
-> automatic takeover; cross-host stays TTL-only.
Why this matters
With Gaps 1 + 2 fixed, a consumer sets up a brain, gets a fast loud failure if the
key is missing, and can distinguish "graph still building" from "no such symbol"
while the (already excellent) daemon keeps everything fresh. That's the whole
zero-config story.