LearnNewsExamplesServices
Frontmatter
id12012
titleOrchestrator hard-exits on missing sqlite — initializeDatabase should self-bootstrap or fail-with-clear-log
stateClosed
labels
enhancementai
assigneesneo-opus-ada
createdAtMay 26, 2026, 9:22 AM
updatedAtJun 7, 2026, 7:15 PM
githubUrlhttps://github.com/neomjs/neo/issues/12012
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 27, 2026, 2:32 PM

Orchestrator hard-exits on missing sqlite — initializeDatabase should self-bootstrap or fail-with-clear-log

neo-opus-ada
neo-opus-ada commented on May 26, 2026, 9:22 AM

Parent / Discovered During

Surfaced while implementing #11948 (workspace-safety integration test, Sub-5 AC5 of #11837). The test pre-creates a minimal sqlite schema as a workaround so downstream assertions can fire; this ticket tracks the substrate fix.

The Problem

ai/daemons/bridge/queries.mjs::initializeDatabase opens with fileMustExist: true:

// ai/daemons/bridge/queries.mjs:5-15
export function initializeDatabase(dbPath) {
    try {
        const db = new Database(dbPath, { fileMustExist: true });
        db.pragma('journal_mode = WAL');
        db.pragma('busy_timeout = 5000');
        return db;
    } catch (err) {
        console.error(`[Bridge Daemon] Failed to open database at ${dbPath}. Is the graph initialized?`);
        process.exit(1);
    }
}

The Orchestrator daemon also consumes this primitive (ai/daemons/orchestrator/Orchestrator.mjs:476this.db = this.initializeDatabaseFn(this.dbPath);). In a true fresh npx-neo-app workspace where the user has not yet booted the Memory Core MCP server, the sqlite file does not exist; the orchestrator daemon hard-crashes with process.exit(1) before any of its downstream Neo-team-substrate-aware lane gates can run their degrade-with-log paths.

Three independent shape issues entangled in this single function:

  1. Misnamed for orchestrator consumption: the error prefix says [Bridge Daemon] regardless of caller — surfaces as confusing log noise when the orchestrator triggers it.
  2. Hard process.exit(1): removes the orchestrator's lane-isolated failure-recovery discipline. The orchestrator otherwise treats lane failures as per-task, recoverable, and log-visible; this primitive bypasses that.
  3. fileMustExist: true is wrong for the orchestrator path: unlike the bridge daemon (which is a child task assumed to inherit a pre-initialized DB), the orchestrator IS one of the primary daemons an external user spawns in a fresh workspace.

The Architectural Reality

ai/graph/storage/SQLite.mjs::initAsync() already implements the right shape: ensureDir(dirname(dbPath)) + open without fileMustExist + initSchema() on connect. That path is what the MC MCP server uses on first boot; the orchestrator could either reuse that primitive directly OR have its own initializeDatabase mirror the same self-bootstrap discipline.

The Fix (suggestion, not prescription)

Option A (minimal): change orchestrator's initializeDatabaseFn default to (dbPath) => { fs.ensureDirSync(dirname(dbPath)); return new Database(dbPath); } plus a follow-up call to run the SQLite schema migrations — keeps the bridge daemon's stricter contract intact.

Option B (consolidating): retire bridge/queries.mjs::initializeDatabase in favor of a shared ai/graph/storage/SQLite::open(dbPath) primitive that both callers use, with the bridge daemon adding its own fileMustExist wrapper if it wants the stricter contract.

Option C (boundary): keep bridge/queries.mjs::initializeDatabase as-is for the bridge daemon child task; have the orchestrator use a different DB-open path that handles missing-file gracefully.

Operator + reviewer can weigh in on which shape best matches the substrate's intent.

Contract Ledger

Per ticket-intake-workflow.md §7 — backfilled per @neo-gpt intake-readiness handoff 2026-05-27T06:46Z. Documents operator-visible daemon-boot behavior + consumed Agent OS startup semantics before branch/code work.

Adapted from GPT's suggested matrix in issue intake comment with one additional row (schema-creation surface).

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
Orchestrator.start() SQLite open path #12012 + #11948 workspace-safety follow-up Fresh workspace with absent sqlite path creates + initializes the graph sqlite (via shared self-bootstrap primitive or in-orchestrator equivalent) and reaches [Orchestrator] Started. log line Invalid/malformed dbPath fails with orchestrator-scoped log (not bridge-prefixed) + structured error; no process.exit(1) from the orchestrator path — let lane-isolated failure-recovery handle it JSDoc/comments on the chosen open helper (or split, if Option B taken) Integration test in fresh-workspace fixture proving absent-file boot creates schema + reaches Started. line + asserts dbPath exists post-boot
bridge/queries.mjs::initializeDatabase() bridge-daemon path Existing bridge daemon wake-substrate contract (child task assumed inheriting pre-initialized DB) Existing behavior preserved when sqlite already exists: open with WAL + busy_timeout pragmas, return db handle If strict bridge contract retained: still fails loudly for missing inherited DB (matches current fileMustExist: true + process.exit(1) semantic). If shared helper introduced (Option B): bridge wraps with explicit fileMustExist enforcer JSDoc updated only if helper behavior changes (Option B path) Existing unit coverage showing bridge query consumers still open an existing DB without regression
workspaceSafety.spec.mjs fixture workaround #11948 Sub-5 AC5 + #12012 AC4 After orchestrator self-bootstrap lands: initSqliteSchema() workaround function deleted from spec; test asserts the orchestrator's own bootstrap creates the schema Test must still isolate temp workspace state + terminate daemon cleanly; assert post-boot file/schema presence Test summary updated if probe scope changes (e.g., moves from pre-create to post-boot assertion) Integration test no longer pre-creates sqlite; asserts created sqlite + schema after boot completes
Schema-creation surface (ai/graph/storage/SQLite.mjs::initSchema() or equivalent) Existing Memory Core MCP boot path (already invokes this primitive on first boot) Orchestrator boot path invokes the same schema-creation primitive Memory Core uses, ensuring schema-shape parity between MC-first-boot and orchestrator-first-boot workspaces If migration system exists post-#nnn (none known today), schema-creation MUST be idempotent — repeat invocation on existing schema must no-op safely Reference docstring on Orchestrator.start() citing SQLite.initSchema as the shared substrate Integration test proves orchestrator-bootstrapped schema is byte-equivalent (or semantically equivalent) to MC-bootstrapped schema — no divergence on day-2 workspace bootstrap

Option-selection signal (operator/lead decision)

Three implementation shapes are open per the ticket body's "The Fix" section:

  • Option A (minimal) — change orchestrator's initializeDatabaseFn default + add follow-up schema-migration call. Smallest delta; preserves bridge contract; some code duplication between orchestrator-side + MC-side bootstrap.
  • Option B (consolidating) — retire bridge/queries.mjs::initializeDatabase in favor of shared ai/graph/storage/SQLite::open(dbPath); bridge daemon adds its own fileMustExist wrapper if needed. Cleaner long-term; small risk of bridge-daemon regression if wrapper subtly differs.
  • Option C (boundary) — keep bridge as-is; orchestrator uses different DB-open path. Most-isolated; some duplication.

Implementation PR should pick one + cite which + document rationale; this ledger covers all three shapes since the Contract is about behavior not which file holds the implementation.

Intake disposition

Ticket is intake-ready with this ledger. Open for claim per @neo-gpt author-yield (he's over FAIR-band 18/30; under-band peers favored). I (the ticket author) am willing to claim implementation in a follow-up turn pending session-energy assessment; operator override welcome.

Acceptance Criteria

  • AC1 — Orchestrator daemon spawning in a fresh workspace (no .neo-ai-data/sqlite/ dir, no sqlite file) boots without process.exit(1).
  • AC2 — Boot reaches [Orchestrator] Started. log line AND the sqlite schema is created on disk (post-boot probe assertion).
  • AC3 — Existing bridge daemon child task behavior preserved (still spawns + connects when the DB does exist, either via its own strict path or via a self-bootstrap path).
  • AC4 — The initSqliteSchema fixture helper in test/playwright/integration/ai/daemons/workspaceSafety.spec.mjs can be deleted once this ticket lands.
  • AC5 — Test coverage for the new boot path (either unit test on the orchestrator's initializeDatabaseFn injection seam, or extending the #11948 integration test by removing the fixture helper and asserting the daemon-created sqlite file exists).

Empirical anchor

Smoke-tested 2026-05-26 against agent/11948-workspace-safety-integration-test branch: spawning node ai/daemons/orchestrator/daemon.mjs with NEO_AI_DB_PATH=/tmp/fresh-workspace/memory-core-graph.sqlite (file absent) produces [Bridge Daemon] Failed to open database at /tmp/fresh-workspace/memory-core-graph.sqlite. Is the graph initialized? on stderr followed by process.exit(1) within ~1.5s. Pre-creating the sqlite with new Database(path); db.exec('CREATE TABLE ...') lets the orchestrator boot cleanly past the open and reach [Orchestrator] Started. at ~2s.

Out of Scope

  • Pre-#11948 substrate behavior (the bridge daemon's stricter contract works correctly for its actual child-task usage; only the orchestrator's reuse of the primitive is the issue).
  • Generalizing this to all daemons (KB MCP server, etc.) — file separate tickets if those exhibit the same pattern.

Labels

enhancement, ai, daemon, workspace-safety

Authority

Surfaced during #11948 implementation per the parent ticket's "Out of Scope — file separate sub-tickets if discovered" clause.

Authored by: Claude Opus 4.7 (Claude Code).

tobiu referenced in commit bca5230 - "fix(orchestrator): self-bootstrap sqlite + schema instead of hard-exit on missing file (#12012) (#12085) on May 27, 2026, 2:32 PM
tobiu closed this issue on May 27, 2026, 2:32 PM