# ADR 0004 — OMS v0.2 skill-first runtime

## Status

Accepted for implementation on branch `ralph/oms-v02-skill-first`.

## Context

v1 grew a large control plane (~49k TypeScript lines, many public commands, dual ledgers, and a general schedule surface). The product goal is a simple skill-first experience: run `oms`, type a natural-language goal, and keep one attended durable campaign running for up to 24 wall-clock hours.

Safety invariants from v1 must not regress: interactive-only Claude, strict read-only conductor MCP tools, worktree isolation, protected verification authority, non-force push, draft-PR-only delivery, no automatic merge, untrusted ingress, atomic journaled state, and offline/no-contact defaults.

## Decision

Build a version-isolated v2 kernel under `.oms/runtime-v2/` beside v1, prove the invariant matrix, then cut over with one atomic action. v2 stays inactive while any v1 mutator is reachable.

### Atomic cutover (locked simplification)

Do **not** spread active-runtime pointer checks across every v1 writer as the one-writer proof. Per-call guards are TOCTOU-prone and cannot constrain already-loaded old processes.

The single cutover action must, in order:

1. Refuse if a v1 campaign is still active.
2. Disable/remove v1 timers, hooks, tmux sessions, and CLI mutation entry points.
3. Wait for zero v1 processes and held locks.
4. Archive v1 state.
5. Atomically write `active_version=2` as the **final** step before starting v2.

After cutover, **delete or disable** the legacy mutation surface — do not merely hide commands. Hiding is not authorization. Keep at most a pure read-only legacy status/export reader for one release; it may not initialize, repair, lock, advance cursors, contact providers/GitHub, or change schedules.

Do **not** wire partial active-runtime guards into v1 mutators during foundation work (that sprawl failed full regression and is not the one-writer proof). Keep any pointer helpers for v2/cutover parsing only. Implement dual-writer safety only in the atomic cutover phase.

### Public shell contract

| Command | Behavior |
| --- | --- |
| `oms` | Idempotent setup/repair, start or attach the named tmux campaign, open the interactive read-only conductor |
| `oms status` | Concise goal, tasks, panes, blocks, quota, draft PRs, watchdog ownership |
| `oms doctor` | Safe local dependency and capability checks; no live provider/GitHub claims beyond discoverability |
| `oms stop` | Terminal, idempotent, non-destructive campaign drain |

Hidden `oms internal ...` remains for hooks, monitor, package lifecycle, and watchdog only. Hiding a command is not authorization.

### Skills

Human skills: `/oms-goal`, `/oms-status`, `/oms-stop`.
Internal skills: `oms-route`, `oms-worker`, `oms-verify`, `oms-recover`, `oms-team`.

Skills own judgment. TypeScript owns invariants and every state change or side effect through strict MCP/kernel validation.

### Runtime shape

```text
tmux session oms
├── conductor   interactive Claude; skills + strict OMS MCP tools only
├── observer    fixed event projection and wake delivery (no routing judgment)
├── watchdog    recovery-only checks every 15 minutes
└── workers     interactive Claude / native Codex Goal panes

provider hooks              immediate typed events
```

### State authority

- Journal under `.oms/runtime-v2/journal/<run_id>/` is the replay/evidence authority.
- Each record carries a monotonic per-run `sequence`, `previous_hash`, and `content_hash` that covers those chain fields plus operation metadata and payload.
- Filenames are zero-padded sequence numbers; replay is strictly by sequence and verifies the hash chain (not by timestamp).
- Idempotency uses a durable `ops/<operation_id>.json` index for O(1) lookup (no full journal scan per append).
- `run.json`, `tasks/*.json`, and `monitor.json` are CAS-updated derived views.
- Worker worktrees cannot read or write `.oms`.
- Only one active runtime version may mutate state. Until cutover, v2 remains inactive whenever v1 mutators are still reachable; the cutover action (above) is the exclusive switch to v2.

### Campaign semantics

- Goal acceptance fixes `single_goal` or `github_queue` for the run.
- Default deadline: until queue empty or 24 wall-clock hours, whichever first.
- Queue empty requires two consecutive successful empty polls after the last queue-affecting transition.
- Stop and deadline share one bounded drain path; worktrees and evidence are preserved.
- After host/tmux loss the watchdog records `needs_human` and never launches a provider; bare `oms` is required.

### Delivery and verification

- Deterministic verification binds one exact clean commit and has final authority.
- Exactly one fresh opposite-engine supplemental grade; graders cannot override a failed gate.
- Passing work may perform one non-force branch push and one draft PR.
- Merge, branch-protection changes, publish, tag, and release remain unavailable.

### Provider compatibility

Replace patch allowlists with binary-bound capability evidence, static discoverability probes, and first-use live handshakes. Unknown major versions and missing required behavior fail closed. `oms doctor` never claims discoverability equals runtime proof.

## Consequences

- v1 and v2 remain isolated writers until the atomic cutover; foundation phases must not grow the v1 guard surface as a substitute for cutover.
- Public 24-hour endurance claims require a fresh elapsed canary; implementation completion alone is insufficient.
- macOS and non-systemd hosts do not receive v0.2 automatic-recovery certification.
- Line-count reduction is a goal, never a reason to weaken crash/replay, verification, or delivery safeguards.

## Invariant matrix (test ownership)

| Invariant | Primary tests |
| --- | --- |
| Four-command public UX | `test/v2/public-ux.test.ts` |
| Goal source fixity, stop/deadline drain | `test/v2/run-state.test.ts` |
| Journal immutability, replay, bounded ingress | `test/v2/journal.test.ts` |
| Monitor timers, wake, watchdog recovery limits | `test/v2/monitor.test.ts` |
| One-ticket offline path through verify/grade/delivery intents | `test/v2/one-ticket.test.ts` |
| Existing worktree / verification / injection safety | retained v1 regression suite |
