OMGDB DOCS
// For agents

Introspection

Self-inspection commands that let an agent understand an OMGDB database without guessing — describe, inspect, dump, explain, diagnose, and verify.


OMGDB is built so an agent can understand a database it has never seen before, without guessing. The introspection surface answers five questions directly: what collections and fields exist, what are the write rules and indexes, how many documents are there, what is the canonical state, and why does a query behave the way it does. Every command reads through the public store API and produces output meant to be consumed by a human or an agent.

describe and inspect report structure; dump exports state; explain and diagnose explain query behavior; verify proves the store is internally consistent. All of them operate on a store directory passed as the first positional argument (the examples use app.omgdb).

describe — a live Markdown manual

omgdb describe app.omgdb

describe renders a Markdown “manual” of the whole database. The database name is taken from the store directory’s file name (falling back to the literal database if it can’t be resolved). The output contains, in order:

  • A heading # Database: <name> followed by a blank line.
  • A line <N> collection(s).
  • For each collection: a ## <ns> (<count> document(s)) heading, an inferred-schema table, the collection’s indexes, its validation spec, and one sample document.

The schema table has three columns — field, types, and present — and one row per top-level field. The present column is a <present>/<count> ratio showing how many of the sampled documents contained that field. The indexes and validation spec are the collection’s write contract: what an agent must know before inserting — which writes will be rejected (validation rules, unique constraints) and which queries will be fast (indexes). When a collection has at least one document, a Sample: line is followed by a fenced json block containing the first document rendered as canonical JSON.

# Database: mydb

1 collection(s).

## users (2 document(s))

| field | types | present |
|-------|-------|---------|
| _id | objectId | 2/2 |
| age | long | 1/2 |
| email | string | 2/2 |
| name | string | 2/2 |

Indexes:
- `email` — unique

Validation spec (enforced on writes):

```json
{"required":["email"],"fields":{"email":"string"}}
```

Sample:

```json
{"_id":{"$oid":"66f0a1b2c3d4e5f6a7b8c9d0"},"name":"ana","email":"ana@x","age":30}
```

Index lines carry their qualifiers inline: compound indexes list their fields (`tenant, email`), unique indexes append — unique, and partial indexes append their filter (— partial filter: {...}). See schema validation for the spec format and indexes for index semantics.

Note: Vector embeddings live in ordinary <collection>.__vectors sidecar collections, so they appear in describe and inspect like any other collection — see vector search.

An empty collection still prints its table header, but with no rows and no sample block.

describe_json — the machine-readable form

The library’s describe_json returns the same information as canonical JSON — {"collections":[{name, count, fields:[{name,types,present}], indexes:[{fields,unique,partialFilter?}], validation:{...}}]} — for callers that want to consume the manual programmatically rather than parse Markdown. (indexes and validation appear only when the collection has them.)

Schema inference: types and presence

The schema table comes from a scan over the collection. For each document, every top-level field is recorded: the distinct value types observed, and a presence counter. The result per field is:

ColumnMeaning
fieldThe top-level field name.
typesThe distinct type-name tokens observed, sorted and de-duplicated. A field seen as both a number and a string lists both.
presentHow many sampled documents contained the field, over the total document count.

Type tokens are MongoDB-style names (the same ones used by the $type query operator): null, bool, long (an i64 integer), double (an f64 float), string, binData (bytes), array, object, objectId, and date. Note that integers report long, not int.

Limitation: Schema inference is top-level only. Nested object fields and array element fields are not descended into, so a field whose value is an embedded document appears as a single object type with no breakdown of its inner keys.

inspect — collections and counts

omgdb inspect app.omgdb
omgdb inspect app.omgdb --json

inspect is the lightweight counterpart to describe: it lists every collection and its document count, without sampling fields. By default it prints one text line per collection in the form <ns>: <n> docs (or (empty store) when there are no collections).

With --json it emits a machine-readable object as canonical JSON:

{"collections":[{"name":"users","count":2}]}
FlagDescription
--jsonEmit {"collections":[{name,count}]} as canonical JSON instead of text lines.

Because vector embeddings are stored as ordinary collections, a synced field shows up here as a sidecar — for example a docs collection synced via vsync produces a docs.__vectors entry visible in inspect.

dump — deterministic canonical export

omgdb dump app.omgdb

dump produces a deterministic, line-oriented export of the entire logical state. It writes one line per document, formatted as the collection name, a tab, then the document as canonical JSON:

<collection>\t<canonical-json>

Documents are emitted in collection iteration order, then in _id order within each collection. The output is stable across runs: two successive dump calls on an unchanged store are byte-for-byte identical. This determinism is the basis of invariant I3 — a property that lets you snapshot, diff, and compare database states reliably (see transactions and durability for the broader integrity model).

c	{"_id":1,"v":"a"}
c	{"_id":2,"v":"b"}

explain — the query plan

omgdb explain app.omgdb users '{"name":"ana"}'

explain compiles a MongoDB-style filter and reports, in plain language, how find will execute it. The filter argument is a JSON string.

The possible plans:

Plan stringWhen it applies
index scan: equality on \.` (secondary index, candidate(s)), then filter`A top-level equality predicate is served by a single-field index.
index range scan: range on \.` (secondary index, candidate(s)), then filter`A top-level range predicate ($gt/$gte/$lt/$lte) with scalar bounds is served by a single-field index.
compound index scan: equality on \.()` (secondary index, candidate(s)), then filter`Equality predicates cover a compound index’s fields.
compound index range scan: optional equality prefix and range(s) on \.()` (secondary index, candidate(s)), then filter`An equality prefix plus range predicate(s) fit a compound index.
full collection scan of \` ( documents), then filter`No usable index; N is the live document count.

Every index plan names the index, reports how many candidate documents the index narrows to, and ends with “then filter” — index scans return a candidate superset that is always re-filtered for exact semantics.

When the query falls back to a full scan and it contains a top-level equality on a field that is not indexed, the plan appends a self-repair hint:

full collection scan of `users` (1000 documents), then filter; no index on `name` — suggest `omgdb create-index users name`

The hint names the exact command to create the index that would accelerate the query. For broader, machine-readable recommendations (including sort-aware compound shapes), omgdb suggest-indexes analyzes a filter and proposes index definitions the planner can actually use. See indexes for which predicates are eligible for index acceleration.

diagnose — the why-not debugger

omgdb diagnose app.omgdb users '{"age":{"$gte":25}}'

explain tells you how a query runs; diagnose tells you why it returns what it returns. It is a “why-not” debugger: for each top-level field predicate it counts how many documents satisfy that predicate alone, so an agent can immediately see which condition is the limiting (or eliminating) one. The output is canonical JSON with this shape:

{
  "collection": "users",
  "totalDocuments": 100,
  "matched": 12,
  "predicates": [
    { "field": "role", "matched": 80 },
    { "field": "age", "matched": 12 }
  ]
}

The fields are:

FieldMeaning
collectionThe namespace diagnosed.
totalDocumentsTotal documents in the collection.
matchedHow many documents satisfy the full filter.
predicatesOne entry per top-level field predicate, with the field name and its standalone match count.

When a single predicate matches zero documents, that entry additionally reports the field’s observed value range — observedMin and observedMax under the engine’s total value order — so the agent can see how far the predicate’s threshold is from any real data:

// filter: {"role":"admin","age":{"$gt":90}}
{
  "collection": "users",
  "totalDocuments": 2,
  "matched": 0,
  "predicates": [
    { "field": "role", "matched": 2 },
    { "field": "age", "matched": 0, "observedMin": 30, "observedMax": 40 }
  ]
}

Here the report makes the failure obvious: role alone matches both documents, but age > 90 matches none — and the observed range (30–40) shows the threshold is far above any stored value.

Tip: Logical operators are not diagnosed per field. Any top-level key beginning with $ (such as $or, $and, $nor, $expr) is skipped in the predicates list, so diagnose is most useful on the conjunction of field conditions that makes up the body of a filter.

Missing collections fail loudly

A typo in a collection name can never produce confidently wrong advice. explain, diagnose, and suggest-indexes error when the collection does not exist, naming the nearest match and the collections that do:

omgdb explain app.omgdb userz '{"name":"ana"}'
// error: collection `userz` does not exist — did you mean `users`? (existing: users)

find, by contrast, keeps MongoDB’s empty-result semantics — querying a missing collection prints nothing and succeeds — but it still warns on stderr (warning: collection `userz` does not exist — did you mean `users`?), so an agent parsing stdout sees clean output while the likely typo is surfaced on the diagnostic stream.

verify — proving the store is consistent

omgdb verify app.omgdb

verify is introspection turned on the engine itself: it re-reads the op-log and proves that replaying it reproduces the live state — every record, every document, every collection — and then attests the five derived cache sidecars against the log: the primary _id, live-document, secondary-index, and metadata caches each get their contents checked and their checkpoint + tail-replay proof verified (that the persisted checkpoint plus the log tail beyond it equals a full replay — the boundary recorded by the fifth sidecar, the open-checkpoint proof). At 50,000 documents the whole proof takes about a second.

OK: 50000 record(s), 50000 document(s) in 3 collection(s); log reproduces state
metadata cache: OK (2 collection spec(s), 3 index definition(s), derived)
metadata checkpoint+tail: OK (0 tail record(s), derived)
primary _id cache: OK (50000 entry(s), derived)
primary _id checkpoint+tail: OK (0 tail record(s), derived)
secondary index cache: OK (3 index(es), 41 bucket(s), derived)
secondary index checkpoint+tail: OK (0 tail record(s), derived)
live document cache: OK (50000 document(s), derived)
document checkpoint+tail: OK (0 tail record(s), derived)

A stale or malformed cache is a warning, not a failure — every sidecar is derived and rebuildable, so the only fatal outcome is INCONSISTENT: replaying the log does not reproduce the live state. See architecture for the cache design and transactions for the invariants verify enforces.

Structured errors and did-you-mean suggestions

Introspection extends into error reporting. When a filter passed to find, explain, or diagnose uses an operator OMGDB does not recognize, the compiler does not fail silently — it returns an UnknownOperator error naming the bad token and, when a close match exists, a deterministic “did you mean” suggestion. The suggestion is the closest known operator within a Levenshtein edit distance of 2.

{"age":{"$gtee":1}}
// error: $gtee (did you mean `$gte`?)

This lets an agent self-repair a malformed query in a single step rather than guessing. The known query operators are: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $type, $not, $and, $or, $nor, $expr, $size, $all, $mod, $elemMatch, and $regex — see query operators for their full semantics. The same did-you-mean mechanism covers update operators ($sett suggests $set), aggregation stages, accumulators, and expression operators, and even $type’s type names (strnig suggests string).

Putting it together

A typical agent workflow uses these commands in sequence:

# 1. Learn the shape of an unfamiliar database — schema, indexes, write rules.
omgdb describe app.omgdb

# 2. Get a quick count of every collection.
omgdb inspect app.omgdb --json

# 3. Write a query; check how it will run.
omgdb explain app.omgdb users '{"age":{"$gte":25}}'

# 4. If it returns nothing unexpected, find out why.
omgdb diagnose app.omgdb users '{"age":{"$gte":25}}'

# 5. Snapshot the exact state for diffing.
omgdb dump app.omgdb

# 6. Prove the whole store is consistent.
omgdb verify app.omgdb

Together these turn a database from an opaque blob into a self-describing artifact: an agent can read its structure and write contract, count its contents, reason about query plans, debug empty result sets, capture a deterministic snapshot, and prove the state — all without prior knowledge of the schema.

View this page as raw Markdown →