Oh
Install Oh
Theme
Appearance

Documentation

Get started with Oh

Install the CLI, store and verify one record, choose a store and space, fix first-run errors, call the TypeScript SDK, and give Oh to a coding agent.

Install and first run

The archive below is 0.14.1, the verified public release.

Bun 1.3.14 or newer is required for the CLI, the SDK, and the SQLite store. The runtime-neutral store interfaces and the direct libSQL store also run on Node 24 and in serverless functions. Install the release from npm:

bun add --global @hraness/oh@latest
oh --help

The same package bytes and their checksum are attached to the immutable GitHub Release: hraness-oh-0.14.1.tgz and SHA256SUMS.

The package runs wherever Bun runs. Its native SQLite snapshot helper (@hraness/oh/sqlite-snapshot) is prebuilt for macOS on Apple silicon and Intel, Linux x64 and arm64, and Windows x64.

Oh writes to .oh/oh.sqlite and the default space unless you choose another path or space. oh init, oh put, and oh sync import create that directory, file, and space if they are missing; reading commands stop with No Oh store at .oh/oh.sqlite instead. Keep .oh/ out of source control.

oh init
oh put \
  --kind entity \
  --key entity:ada-lovelace \
  --value '{"name":"Ada Lovelace","role":"mathematician"}'
oh get entity:ada-lovelace
oh search "mathematician"
oh verify

This stores one entity, reads it back, finds it through the keyword index, and replays the log to check it. It needs no account, hosted model, remote database, or semantic-search package. In a terminal each command prints a short result, such as ✓ Saved entity:ada-lovelace (generation 1). Add --json for canonical JSON; oh get entity:ada-lovelace --json prints the record on one line, shown here with line breaks added:

{
  "dependencies": [],
  "key": "entity:ada-lovelace",
  "kind": "entity",
  "recordSha256": "fcfe318e7248366d2408d1fa392268ac16490e72487079956c20db96b8369449",
  "v": 1,
  "value": { "name": "Ada Lovelace", "role": "mathematician" }
}

The digest is the same on every machine because it covers only the record. oh verify --json ends with "operations":1,"records":1,"sqliteIntegrity":"ok".

The CLI may print an occasional note about optional development support on stderr. It never changes stdout or exit codes, CI turns it off, and Optional development support explains how to switch it off yourself.

Choose the store and space

OptionDefaultEffect
--db <path>.oh/oh.sqlite relative to your current directorySelects the SQLite file for this command.
--space <id>defaultSelects the graph and operation history within that file.
--jsonAutomatic in a recognized agent environmentPrints canonical JSON instead of terminal text.

Repeat --db and --space on each command when you use a nondefault store. For example, oh get entity:ada-lovelace --db /absolute/path/memory.sqlite --space default reads that file regardless of your current directory. Continue with the TypeScript SDK or working-memory guide when you need to use these records from an application.

Troubleshooting the first run

SymptomWhat to check next
No Oh store at ...Check your current directory and --db path. Run oh init only if you intend to create a store there; reads do not create one.
No record named ... (exit 3)Run oh list with the same --db and --space as the write, then check the record key.
JSON appears instead of terminal textAn agent environment selects JSON automatically. Set HRANESS_AUDIENCE=human for terminal text, or pass --json when parsing results.
OhConflictError: The expected head does not match the current space head.Another write moved the head after you read it. Read the new head and records, reconcile your change, and submit it again with the new head. See Handle errors.

oh verify checks saved history and SQLite integrity, not the truth of a fact. If verification fails, preserve the database and error output rather than reinitializing it. See the storage specification for the record and history contracts.

How Oh behaves

Commands print short text for people and canonical JSON with --json. When an agent runs Oh (Claude Code, Codex, Cursor, Gemini CLI, or AI_AGENT is set), JSON is the default; HRANESS_AUDIENCE=human or agent overrides the guess. oh sync export and oh contract always print JSON. A missing oh get or oh tombstone record exits with status 3. A mistyped command or option exits with status 2. A missing store, an integrity failure, or a concurrent head conflict exits with status 1 and leaves the log as it was. Errors name what went wrong and the command to run next; with --json they are one {"ok":false,"error":{...}} object on stdout.

oh contract prints the ontology, graph, schema, and SQLite versions compiled into the installed runtime. Opening a database checks that the contract stored in it matches that runtime, and refuses to open it otherwise.

A space holds one current graph and one append-only chain of operations:

  • A record has a stable key, one declared kind, ordered dependencies, any canonical JSON content, and a SHA-256 digest over all of that.
  • An operation puts or tombstones (deletes) records in one BEGIN IMMEDIATE transaction. Before the head moves, the store compares the caller’s expected generation and operation digest with the current head and refuses a stale one.
  • Each operation’s digest covers its parent operation, the resulting graph revision and record set, the contract, the actor, the timestamp, and the sequence number.
  • The SQLite records and the operation log are the source of truth. The FTS5 keyword index and local embedding files are copies that can be rebuilt.
  • Sync exchanges size-limited bundles of operations after both sides confirm the same contract. Only a history that extends the other side’s is applied automatically; a divergent history stops with a conflict. Fast-forward sync shows the libSQL and Turso transport. For offline transfer, oh sync export writes a bundle to stdout and oh sync import --file <path> checks it and applies it in one transaction, or not at all.

Version 1 of the ontology names seven core ideas: entity, statement, assertion, evidence, context, inquiry, and projection. The graph format also carries schema, vocabulary, review, rights, edition, and activity records. Meaning specific to an application belongs in registered codecs and versioned schema records, where anyone reading the data can find it.

Use the SDK

For a project dependency, pin the same immutable release in package.json:

{
  "dependencies": {
    "@hraness/oh": "0.14.1"
  }
}

The base package has no required runtime dependencies. Keyword search, ontology parsing, SQLite storage, replay verification, and sync need no hosted model.

import { Oh } from "@hraness/oh/sdk";

const oh = Oh.open({
  databasePath: ".oh/research.sqlite",
  spaceId: "paper-one",
});

try {
  const head = oh.head();
  oh.put({
    expectedHead: head,
    key: "entity:ada-lovelace",
    kind: "entity",
    value: { name: "Ada Lovelace" },
  });

  const result = await oh.search("Ada", { mode: "keyword" });
  console.log(result.results[0]?.record);
  // Recall fuses several searches and can add records from a date window: spec/v1/recall.md
  console.log((await oh.recall(["Ada", "engine"], { asOf: null })).results.length);
  console.log(oh.verify());
} finally {
  await oh.close();
}

Pass the head you reviewed when concurrent writers matter. After an OhConflictError, read the new head and records, reconcile your change, and submit a new operation; retrying the same call fails the same way.

Call Oh from TypeScript covers every SDK method, batch writes, error classes, and the table of entry points (@hraness/oh/store, @hraness/oh/libsql, @hraness/oh/sqlite, @hraness/oh/sync, @hraness/oh/projection, @hraness/oh/memory, and the rest), with the runtimes each one is tested under.

Use the best configured retrieval

With no mode, search and recall pick the strongest path you have configured: local reranking when a reranker is present, hybrid keyword and semantic search when a semantic backend is present, and keyword search otherwise. There is no experimental switch. Pass mode when you need a particular policy or a reproducible comparison. A backend that is missing adds a diagnostic to the response and leaves the other results in place. Search and recall shows how to add local embeddings, a local reranker, or the hosted cache, and what each costs.

Give Oh to a coding agent

The repository includes an installable Agent Skill at skills/oh. The skill teaches an agent to read the contract and current head, write with the expected generation, verify the replay, and sync only where you tell it to.

Install the skill from this release with the skills CLI:

npx skills add hraness/oh#v0.14.1 --skill oh

The installer sets up the agents it finds, asking you to choose when there are several, and asks whether the skill is for the current project or for all your projects; --global chooses all projects. With Claude Code and Codex selected, it puts the skill in .agents/skills/oh, or ~/.agents/skills/oh for all projects, where Codex reads it, and links .claude/skills/oh or ~/.claude/skills/oh to that folder for Claude Code.

To install it by hand, copy the skills/oh folder from the installed npm package (in a project that depends on Oh, it is inside node_modules) to ~/.claude/skills/oh for Claude Code or ~/.agents/skills/oh for Codex. Run /skills in either agent to check that oh is listed, then ask for it by name: /oh in Claude Code or $oh in Codex. These locations and commands come from the Claude Code and Codex skills documentation, checked October 4, 2026.

You can also give an agent this prompt:

Install @hraness/oh@0.14.1 from npm and use its packaged Oh Agent Skill. The
exact npm tarball and SHA256SUMS are mirrored by the immutable v0.14.1 Release at
https://github.com/hraness/oh/releases/tag/v0.14.1. Verify the CLI with
`oh --help` and `oh version`.
Do not create or modify an Oh database until I name its path and ask you to.

Limits

  • Digests detect changed contract, record, operation, and bundle bytes. They do not encrypt data, authenticate an actor, authorize a write, or prove that a research statement is true.
  • Oh does not redact record values. Protect the database, filesystem, backups, and any sync destination according to the sensitivity of what you store.
  • The optional QMD semantic cache holds text derived from records. Its model and inference stay local, but the cache needs the same care as the records.
  • The libSQL sync transport checks the contract and that histories extend each other. You handle credentials, transport security, access control, tenant isolation, backup, retry, and remote availability.
  • Divergent histories never merge automatically. Oh reports the conflict and leaves reconciliation to you.
  • Derived rows and proofs are disposable output. Publishing one as knowledge takes an application-level review and a new write to the graph.

Read SECURITY.md for the full public threat model.