omgbase 0.3.0

omgbase: a versioned, addressable graph layer over authored Markdown — the `omgbase` binary (`omgbase mcp` serves the tool catalog over MCP stdio, with the filesystem watcher and the background embed drain)
omgbase-0.3.0 is not a library.

omgbase

The omgbase binary: a versioned, addressable graph layer over authored Markdown, served to agents as an MCP tool catalog. Rust, over omgbase-surface.

omgbase mcp [--workspace DIR] [--repo SLUG] [--no-watch]   serve the tool catalog over MCP stdio
omgbase --version

omgbase mcp locates the workspace (--workspace, $OMGBASE_WORKSPACE, or the nearest .omgbase/ walking up from the current directory — spec/sync §1), selects the default repo (--repo, the only repo, or the one whose root contains the current directory), wires the embedding provider named by the repo's embedding.* settings when it can be spawned (otherwise semantic() fails semantic_unavailable), and speaks newline-delimited JSON-RPC 2.0 on stdin/stdout: initialize, tools/list, tools/call, ping. Every tool result is one text content item holding JSON; an error result carries isError: true and the envelope { error, message, data?, retriable } (spec/surface §4). stdout is the protocol channel; every diagnostic goes to stderr.

Thin by design: there is no business logic here beyond argument parsing, the transport loop and the two long-lived helpers below, which mirror the reference omg mcp.

The watcher

Unless --no-watch is given, the server tries to take the workspace's watch lease (.omgbase/watch.lock, spec/sync §7). When another live watcher holds it (omg watch, another mcp) it serves without one and says so (watcher elsewhere). Otherwise, for a repo with an fs root, it runs one priming freshness sweep (§4.3) so the session starts fresh, spawns the repo's registered adapter (the fs source's `omgbase-fs-adapter --root

The adapter must be installed. The registry's command is the omgbase-fs-adapter bin of @omgbase/fs-adapter (npm i -g @omgbase/fs-adapter, or any way that puts it on PATH). OMGBASE_FS_ADAPTER overrides the command line — e.g. node /path/to/omgbase/packages/fs-adapter/dist/src/bin.js from a checkout after pnpm build. When the adapter cannot be started (not found, bad handshake, no watch capability) the server degrades to no watch (adapter unavailable) with a clear stderr line, releases the lease and serves; the priming sweep has already run by then.

The MCP loop is synchronous on stdin and owns the surface's store; the watch stream arrives on another thread that owns its own store connection over the same SQLite file (WAL) and takes the workspace writer lock (.omgbase/writer.lock) around each checkpoint — the same lock the loop takes around every tool that can commit (Surface::is_write_tool), so a checkpoint and a write never interleave inside one commit, in this process or across processes. Every connection carries a 5 s busy timeout for the read-over-commit overlaps SQLite serializes itself.

The embed drain

When a provider is configured and live, a background drainer keeps embeddings timely (spec/search §2.6): every successful non-dry-run write tool call and every watcher checkpoint that ingested or deleted something schedules a drain; the drainer thread debounces 500 ms, runs one drain at a time (blocks first, then documents — build_embed_tasks → embed_process → build_doc_embed_tasks → embed_process_docs), re-runs once more when a schedule arrived mid-drain, and logs [mcp] embedded N block(s). Errors go to stderr and are never fatal (the next schedule retries). The thread owns its own store connection and its own provider instance (a second spawn of the configured command); the main thread's provider serves semantic() queries only. With no provider configured there is no thread.

Shutdown

stdin EOF, SIGINT or SIGTERM: stop the watcher (unwatch, close the adapter, join the thread), release the lease, flush the drainer (a final drain if anything is pending) and close it, then drop the surface (which kills its provider process), exit 0.

Conformance seams and the interop harness

Two environment variables are the test seams of spec/surface §7.1, for cross-engine conformance runs only (never set them in production):

  • OMGBASE_SPEC_MINTER=sequential installs the fixture id minter (d_0, d_1, …, each prefix counting from 0, fresh at process start and shared by every thread of the process) for the workspace and the store; any other value is a startup error.
  • OMGBASE_SPEC_CLOCK=<RFC 3339> makes that instant "now" for every commit a tool or the watcher stamps (stored as UTC milliseconds, …Z; a ±HH:MM offset is converted).

Both are announced on stderr. tests/interop.rs is the §7 harness: every case of spec/surface/cases/interop.json for every (writer, reader) in {typescript, rust}², both engines as child processes over MCP stdio, both with --no-watch (the seed is the harness's observe_many, not a priming sweep). The TypeScript peer is $OMGBASE_TS_MCP or node <repo>/packages/cli/dist/src/main.js (pnpm build); OMGBASE_INTEROP=skip skips the pairs that need it. tests/mcp_watch.rs drives the binary as a process: --no-watch, the adapter-unavailable degradation, the lease, a SIGTERM shutdown, and — when node and packages/fs-adapter/dist exist — a watched run where a file written under the workspace reads back through the server.

License

MIT.