Decisions
Every load-bearing choice omh has made, and the reason for it. If you are about to change one of these, the reason is what you need to argue with.
Status: a ledger, not a design. Every entry records a choice already made and the reason it was made. The Status column in the base set is the component’s; the design for each lives on its own page.
At a glance
Section titled “At a glance”| Decision | Choice |
|---|---|
| Category | a distribution |
| Code graph | wire an existing MCP server |
| LLM routing | not ours |
| Language | Rust |
| Runtime | a pluggable backend |
| Repo exposure | a git worktree, auto-branched |
| Git in the sandbox | a repository of its own |
| Untracked files | a carry_in allowlist |
| Unit of work | a long-lived session |
| Session persistence | dtach, not tmux |
| IDE access | SSH into the session |
| Naming a session | sNN first, one form |
| Getting work back | omh sNN commit, squash or --keep |
| Staying current | omh sNN sync, merged on the host |
| Content scope | one personal catalogue |
| Repo content | hooks, and nothing else |
| Settings scope | 2 layers in the repo, plus a template |
| Selection | an allowlist, [use] |
| Absent selection | means everything |
| Command scope | omh settings is you, this checkout is the rest |
| Write default | the key decides, not the command |
| Capability floor | superset, adapters degrade |
| Hook vocabulary | closed, translated at staging |
| Tool vocabulary | one closed set, per-adapter map |
| Portability | store the standard where one exists |
Also on this page: the base set as a decision, what was deliberately not decided, what the work loop refuses to do, a ratified deviation and three reversals.
What omh is
Section titled “What omh is”Category: a distribution
Section titled “Category: a distribution”omh is a distribution. The parts exist already; assembly and subtraction are the value. → Why a distribution
Code graph: wire an existing MCP server
Section titled “Code graph: wire an existing MCP server”omh wires an existing MCP server rather than writing a graph. Distributions package; they don’t reinvent. Its browsable website is scoped to the repo, not the session — see the first reversal.
LLM routing: not ours
Section titled “LLM routing: not ours”omh does not route models. The harness logs in as itself and reaches whatever it is configured to; omh passes no model and proxies nothing. See also which model you use.
Language: Rust
Section titled “Language: Rust”omh is written in Rust. It ships as a single binary, and omh wraps every
invocation, so startup is felt.
The sandbox
Section titled “The sandbox”Runtime: a pluggable backend
Section titled “Runtime: a pluggable backend”The runtime is a pluggable backend: Docker unless you say otherwise.
podman and sbx are opt-in through runtime. sbx’s capabilities were
measured against sbx 0.39.0, but no full omh doctor run has accepted it yet. No
vendor lock. → Runtime backends
Repo exposure: a git worktree, auto-branched
Section titled “Repo exposure: a git worktree, auto-branched”Each session gets a git worktree on a branch of its own. The agent cannot
reach your checkout or main, and review is git diff. →
Sessions
Git in the sandbox: a repository of its own
Section titled “Git in the sandbox: a repository of its own”The sandbox has a repository of its own. Your checkout is never mounted, so the agent gets a gitdir holding one commit and none of your history.
Untracked files: a carry_in allowlist
Section titled “Untracked files: a carry_in allowlist”Untracked files enter only through carry_in. Worktrees are pure git, so
secrets enter only by declaration. → carry_in
Sessions
Section titled “Sessions”Unit of work: a long-lived session
Section titled “Unit of work: a long-lived session”The unit of work is a long-lived session. It keeps the index warm and makes harness switching instant. → Sessions
Session persistence: dtach, not tmux
Section titled “Session persistence: dtach, not tmux”Sessions persist through dtach, not tmux. omh needs detach and reattach,
and SSH already provides multiplexing. → Sessions
IDE access: SSH into the session
Section titled “IDE access: SSH into the session”Your editor reaches the session over SSH. One dependency tree, shared with the agent. → Editors
Naming a session: sNN first, one form
Section titled “Naming a session: sNN first, one form”The session selector comes first, in one form. Several sandboxes of one repo are reached from one place, so the selector leads and everything after it is unchanged. → The shape of the CLI
Getting work back: omh sNN commit
Section titled “Getting work back: omh sNN commit”Work comes back through omh sNN commit, squashed or with --keep. You
choose at commit time whether the agent’s own commits and messages land. How
--keep lands them departs from its design — see
the ratified deviation.
Staying current: omh sNN sync, merged on the host
Section titled “Staying current: omh sNN sync, merged on the host”Trunk reaches a session through omh sNN sync, merged on the host. No
commit of yours may enter the sandbox, so it receives files and conflict markers
rather than history. →
omh sNN sync
Configuration
Section titled “Configuration”Content scope: one personal catalogue
Section titled “Content scope: one personal catalogue”Your configuration lives in one personal catalogue. “Where is this skill” had three answers, and a union can add but never subtract. → Configuration
Repo content: hooks, and nothing else
Section titled “Repo content: hooks, and nothing else”A repository carries hooks and nothing else. A hook binds to a repo’s commands; a skill is a way you work.
Settings scope: two layers in the repo, plus a template
Section titled “Settings scope: two layers in the repo, plus a template”A repo’s settings are two layers in the repo, plus a template. A repo’s
behaviour has to be explained by files the repo contains. ~/.omh/default.toml
seeds a new one and decides nothing after that. →
Configuration
Selection: an allowlist, [use]
Section titled “Selection: an allowlist, [use]”What a repo takes from your catalogue is an allowlist, [use]. Removing
something is deleting its name, with no include/exclude pair to reconcile. →
[use]
Absent selection: means everything
Section titled “Absent selection: means everything”No [use] means everything. Upgrading changes nothing, and a new checkout
is useful before it is configured.
Command scope: omh settings is you
Section titled “Command scope: omh settings is you”omh settings is you; this checkout is the rest. It was config is you,
repo is here, on the argument that the two wanted opposite write defaults,
and one --layer flag cannot express both. →
The command surface
Write default: the key decides
Section titled “Write default: the key decides”Where a write lands is decided by the key, not the command. src/key.rs
records, per key, whether a value there can name a credential. Committed is the
default now — most settings are facts about the project — and that table is
what keeps a secret out of it.
What the rule was, and why it moved
It was a value never lands in the committed file; a name may, which held while
repo set sent every value to the gitignored file. omh set serves every key
from one command, so the judgement moved into src/key.rs. The full story, and
the second reversal it forced, is under
Reversals.
Harnesses
Section titled “Harnesses”Capability floor: a superset, adapters degrade
Section titled “Capability floor: a superset, adapters degrade”The catalogue is the superset of what any harness can express; an adapter degrades by leaving keys out. omh must never cost you a feature you already had. → Adapters
Hook vocabulary: closed, translated at staging
Section titled “Hook vocabulary: closed, translated at staging”Hooks are written in omh’s closed vocabulary and translated when they are
staged. event, matcher and the payload are one harness’s words, and there
is no runtime shim. → Hooks
Tool vocabulary: one closed set
Section titled “Tool vocabulary: one closed set”Tool names are one closed set, mapped per adapter. They are the one thing skills, subagents and hooks all leak.
Portability: store the standard where one exists
Section titled “Portability: store the standard where one exists”omh stores the standard format where one exists. Skills and rules already travel; only hooks and subagents need omh.
The base set
Section titled “The base set”This is the product. Everything else is a place to put it.
omh init → base system. no questions. → a toolchain and hooks, derived from the stack init detected.omh settings mcp add … → the archive is still there, one command away, and not in your face.| Component | Status | Justification |
|---|---|---|
| sandbox + worktree branch | ✅ | safety; non-negotiable |
| rules, composed with the repo’s own | ✅ | omh’s sections are base-set entries composed into every session; it writes no AGENTS.md |
omh sNN attach |
✅ | IDE attach |
codegraph |
✅ | structural queries instead of re-grepping every task |
| test-on-stop + format-on-edit hooks | ✅ | init detects the commands and wires them |
| memory | ✅ | survives harness switches — one store per repo, shared by its sessions and never committed; hub pages do not ship, and the committed layer was removed in 0.14 |
| egress allowlist | ⬜ | inherited from the runtime |
If a further entry needs a paragraph to justify, it belongs in your catalogue, not the base set.
The set lives in a versioned manifest rather than in the binary, and the justification is enforced by a test rather than by convention — see the base set. It is also the part of omh with the least evidence behind it; see the honest weak spot.
Decisions deliberately not made
Section titled “Decisions deliberately not made”- Which model you use. The harness’s own setting. Routing is a solved problem with several good solutions and no reason for omh to have an opinion.
- Which editor you use. Editors are data, and the integration point is an SSH config include rather than a plugin, so editors omh has never heard of work anyway.
- Whether to trust the sandbox over the branch. Both. A sandbox protects the host; the worktree branch protects the repo. They are not substitutes, and conflating them is how people end up surprised.
What the work loop refuses to do
Section titled “What the work loop refuses to do”Reading a session’s work, landing it and staying current with trunk are omh’s. These are not:
- No omh verb for anything you could run on the host. No
omh sNN rebase,cherry-pick,stashorblame. The moment you want real git you are on the host with real git; omh’s job is to make that always possible and never dangerous. - No merge UI. Conflicts are markers in files. The agent fixes them, or you do, with your own tools.
Considered and not built, with the reason:
- Moving work between sessions (
omh s01 take --into s03). A real workflow, and omh is well placed for it — but it is a second way to land work, and one landing path is why-mand--keepare mutually exclusive. - Forking a session from another. Cheap, and useful for parallel exploration, but it is a launch flag rather than a git feature.
- Comparing two sessions (
omh s01 diff --against s03). The payoff of parallel exploration, and the one worth revisiting soonest. Held back whilediffwas growing a patch and a checkpoint argument.
And one that is open rather than refused:
- Naming sessions.
omh sNN push <name>refuses to invent a branch name, correctly. A session that carried a name from the start would answer that once instead of at the end — butomh new claudeasking a question at launch would cost the thinginitexists to protect.
Deviations from a written design, ratified
Section titled “Deviations from a written design, ratified”A --keep selection is a cherry-pick, not a generated rebase todo (#56).
The design
specified the todo, delivered through GIT_SEQUENCE_EDITOR pointed at omh’s own
binary. The two are user-visibly identical, and cherry-pick is the smaller
mechanism, so it was kept — on measurement.
The one real difference is a raised git floor. cherry-pick --empty= is
newer than rebase --empty=, so a selection fails on an older git where the
todo would have worked. The exact version is unmeasured — only 2.55 was
available — and pinning it is part of omh doctor learning git.
The justification that failed, and the measurement that replaced it
The first justification given for cherry-pick was wrong: it claimed the
designed mechanism could not be reached by a test, when tests/cli.rs runs the
real binary and memory::deliver in this repo already injects current_exe for
exactly that reason.
It was kept anyway, on measurement rather than on the argument that failed. Both
mechanisms were run against the same history: --keep 3,1 lands three then
one under each, and a selected merge is refused by each — 'pick' does not accept merge commits from the todo, is a merge but no -m option was given
from cherry-pick — so omh’s own up-front refusal is correct either way and the
user never sees either message.
cherry-pick is the smaller mechanism: no editor, no sh -c quoting, no second
entry point into omh, no hide = true subcommand for RESERVED to carry.
Reversals worth knowing about
Section titled “Reversals worth knowing about”Three decisions were made, shipped, and undone. All are more instructive than the choices that held.
Per-session graph website → one per repo. Every session’s graph lives in one
volume, so a per-session server displayed every other session’s graph anyway — N
identical websites on N ports, each running inside a container that held a
writable worktree and live credentials. Scoping the service to the repo removed
the duplication, removed the exposure, and deleted the pgrep guard, detached
exec and pkill that the per-session version needed — each of which had been a
bug before it worked. Choosing the right scope removed them rather than fixing
them.
Credentials mounted at a private path → mounted where the harness reads. The
first version mounted a credential volume at $HOME/.omh-creds, which no
harness has ever read. It tested green. See
accounts.
The write default lived in the command → it lives in the key. config set
and repo set had opposite defaults and one flag could not express both, so
the safety came from the destination: repo set sent every value to the
gitignored file and nothing could reach git unasked. That was also why a
teammate cloning a repo got none of the settings it had chosen. One command
serving every key cannot work that way, so the judgement moved into
src/key.rs, per key, and the committed file became the default.
The rule is now one function that four commands call — every repo layer that already holds the key, else the committed file, except a key that can name a credential.
The second reversal the write default forced, inside one release
omh set first shipped with a rule of its own — the committed file, unless the
gitignored one already held the key — while omh use, omh unuse and the
feature switch each answered differently. Three answers to one question, and
every difference was a place for a command to report success over something it
had not changed: omh unset carry_in left a map to a credential standing in a
committed file while saying it had removed it.
Each of the three original answers was defensible alone; only asking whether they were answers to the same question exposed them.
- Architecture
Images, runtime backends, and what lives where on disk. - Sessions
The session model, and how it persists. - Configuration
The layers, the catalogue and[use]. - Roadmap
What comes next, and what gates it.