omgbase
The omgbase binary: a versioned, addressable graph layer over authored
Markdown — the omg CLI of spec/cli rendered in Rust over
omgbase-surface, and the MCP tool catalog
served to agents by omgbase mcp.
omgbase [--json|--jsonl|--ids] [-C dir] [--repo slug] <command> [args]
omgbase <command> --help the command's card
omgbase mcp [-C <workspace-dir>] [--repo <slug>] [--no-watch] serve the tool catalog over MCP stdio
omgbase --version
The verbs are the reference omg's (spec/cli/README.md §6: init,
source, repos, status, ls, outline, cat, show, find,
query/q, run, log, hist, diff, links, the mutators, shell,
sync, mcp, the admin verbs and version — which engine and which
versions a host is talking to, remotely with --server), with the same
argv grammar, bytes and exit codes — the harness
tests/cli_spec.rs runs every case of spec/cli/cases against this binary
behind the allowlist tests/cli-spec-passing.txt. The code lives in
src/cli/: argv (global flags anywhere in argv, -- passthrough,
per-command options with parseArgs semantics), context (workspace
discovery, repo selection, the surface, the freshness sweep every read
runs), output (errors, JSON as JSON.stringify prints it, the hit table),
render (the plain tier: columns, wordmark, relative time), help, and one
module per verb group under cmd/.
omgbase mcp locates the workspace (-C / --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
launches the fs adapter (omgbase-fs-adapter --root <dir>, §5), sends
watch, waits for the adapter's ready (up to 30 s), then runs one priming
freshness sweep (§4.3) so the session starts fresh, and reconciles every
batch of the adapter's watch stream into a checkpoint (§6
reconcile_changes), logging [watch] checkpoint: +N -M.
The adapter must be installed. The launcher is omgbase-fs-adapter on
PATH: the Rust one (cargo install omgbase-fs-adapter, the
crates/omgbase-fs-adapter crate — a node-free install) or the node one
(npm i -g @omgbase/fs-adapter); both speak the same §5 protocol byte for
byte. 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 /
spec/cli §2.6, honored by every verb, for conformance runs only (never
set them in production):
OMGBASE_SPEC_MINTER=sequentialinstalls 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 usage error (exit 2).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:MMoffset is converted).
mcp announces both 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.