omh 0.1.0

Launch any coding harness, in a sandbox, with your setup already there.
# Sessions

A session is not a launch. It is a **running container, a git worktree, and a
branch**, which many harnesses take turns inhabiting.

```
       omh claude ──┐
       omh opencode ┼── exec ──┐
       omh attach ──┘  (ssh)   │
 ┌──────────────────────────────────────────────────────┐
 │ SESSION  omh-<repo>-s01          detached, long-lived │
 │                                                       │
 │  sshd 127.0.0.1:49201 ──── your editor attaches here  │
 │  codebase-memory-mcp ──── daemon: the index stays warm │
 │                                                       │
 │  /work  ← worktree (host dir, bind-mounted)           │
 │  staged profile, read-only                            │
 │  /omh/cache ← volume keyed by REPO, not harness       │
 └──────────────────────────────────────────────────────┘
```

Making the session the unit of work — rather than the launch — is what keeps the
index warm and makes switching harness instant. `omh claude`, then `omh opencode`
against the same session, and everything the graph learned is still there.

## What the agent can reach

Only the worktree. **Your checkout is never mounted**, so the agent cannot see
your uncommitted work, cannot touch `main`, and cannot reach anything outside
the branch it was given.

Review is therefore just git:

```console
$ omh s diff
$ git log omh/s01
```

Two things are writable — the worktree, because that is the work, and
credentials, because tokens refresh in place. **Nothing else is**, and a test
asserts exactly that rather than asserting a list of mount strings.

Worth being precise about what this buys: a sandbox protects your *host*. What
protects your *repo* is the worktree branch. That is why `omh s rm` never
deletes a branch.

### Worktrees live outside the repo

```
~/.omh/worktrees/<repo>/<session>/
```

Nested inside the repo, your IDE would index every session's full copy of the
codebase — three sessions, four indexes, and a machine that sounds like it is
about to take off.

## Persistence

A long-lived container is not a long-lived *session*. Plain `exec` ties the
harness's lifetime to your terminal: close the lid and the agent is hung up on
mid-task while the container keeps running around the corpse.

So every harness is wrapped:

```
dtach -A /omh/sock/<session>-<harness>  <harness> [args…]
```

Detaching is your terminal closing. Reattaching is running `omh <harness>` again
— `-A` attaches to a live session or creates one, so a second invocation never
starts a second agent. The socket path is a pure function of session and
harness; anything variable in it would silently fork a duplicate.

Some harnesses ship their own resume. Relying on that would be exactly the
per-harness behaviour omh exists to abstract, so persistence is uniform and
lives in the distribution. Turn it off with `persistence = "none"`.

### Why not tmux

tmux is a multiplexer **and** a persistence tool, and omh needs only the second
half — `omh attach` means SSH already gives you as many shells against a session
as you want.

Adopting tmux would buy one feature we need and one we already have, while
importing costs that land exactly where this project is most fragile: a prefix
key competing with harness TUI bindings, nested tmux for anyone already running
it on the host, and another translation layer over mouse, resize and paste.

`dtach` does persistence in about a thousand lines with no prefix key, no config
file, and nothing sitting between you and the harness.

**Open question.** Watching several things in one session — agent, dev server,
shell — is a real want that `dtach` cannot serve. Whether that calls for tmux
inside the sandbox, host-side panes over SSH, or a supervisor model (`omh run` /
`omh logs` for services you do not watch) is unresolved. Recorded rather than
answered.

## Lifecycle

```console
$ omh s ls                # sessions, branches, state
$ omh s down s01          # stop the container, keep worktree and branch
$ omh s rm s01            # remove the session — branch survives
```

Sessions idle longer than `policy.idle_timeout` are stopped on the next launch.
N sessions means N containers, so this is not a nicety — see
[risks](design/risks.md).

Only the **container** stops; the worktree and branch survive, so
`omh <harness>` resumes exactly where you left off. The clock measures when you
last *launched into* or *attached to* a session, not the agent's own writes — a
session left running after you walked away is what this exists to reap, and one
where an agent is working unattended is one you started recently.

Unset by default: nothing is stopped unless you ask for it. A session that has
no recorded use — from before this existed, or after clearing `~/.omh/run` — is
left alone rather than stopped on a guess.

`down` and `rm` differ in what they leave behind, and both leave the branch. To
actually discard agent work you delete the branch yourself, deliberately, with
git.

## Several sessions at once

Normal, and the reason sessions are numbered. Each gets its own container,
worktree and branch.

One thing they **share** is the code graph store, keyed by repo. That is what
keeps the index warm across a harness switch, and it means an agent in one
session can query another session's graph. Mitigated, not prevented — see
[Code graph](code-graph.md#what-the-agent-can-still-see).