Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
3611347
feat(omp): add Oh My Pi harness adapter
LoneExile Jul 10, 2026
c5e00b6
fix(lock): detect omp harness via bun interpreter, drop basename -zsh…
LoneExile Jul 10, 2026
52ccbfb
docs(omp): mark harness adapter verified after live run
LoneExile Jul 10, 2026
0b83b7d
test(omp): add live-e2e regression, backing the verified adapter
LoneExile Jul 10, 2026
af5819f
test(omp): isolate harness-detecting tests from ambient OMPCODE (exte…
LoneExile Jul 10, 2026
0aba296
no-mistakes(review): clean omp-ext on teardown, anchor omp lock regex
LoneExile Jul 11, 2026
ca3f36d
no-mistakes(review): add omp to bootstrap harness allowlists
LoneExile Jul 11, 2026
50b01b3
no-mistakes(review): add omp to AGENTS.md verified adapter list
LoneExile Jul 11, 2026
abc5227
chore(gate): run the no-mistakes test step in a hermetic env
LoneExile Jul 11, 2026
49f3877
fix(omp): port cd-guard seatbelt to OMP turnend-guard extension
LoneExile Jul 12, 2026
a5c27aa
no-mistakes(document): sync harness-list docs for new omp adapter
LoneExile Jul 11, 2026
0f13226
no-mistakes(document): add omp to three stale harness enumerations
LoneExile Jul 13, 2026
bc8f477
test(omp): cover omp launch templates in agent-secrets suite
LoneExile Jul 17, 2026
5713500
no-mistakes(review): fail omp typecheck test on tsc error; sync guard…
LoneExile Jul 17, 2026
614e865
no-mistakes(document): sync guard transport and tmux liveness docs fo…
LoneExile Jul 17, 2026
28be797
fix(harness): match omp/pi launched-form args in detect_own fallback
LoneExile Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/afk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ explicit word - the daemon just batches the notification.

The daemon prefixes every injection with `FM_INJECT_MARK` (U+2063 INVISIBLE SEPARATOR), which has no normal keyboard keystroke and survives terminal transport as UTF-8 text.
This is how firstmate tells a daemon escalation apart from a real message in the same pane.
The marker travels with the message text; it does not rely on harness-level typed-vs-injected detection, which is not portable across claude, codex, opencode, pi, and grok.
The marker travels with the message text; it does not rely on harness-level typed-vs-injected detection, which is not portable across claude, codex, opencode, pi, grok, and omp.

## Busy-guard and composer guard

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/firstmate-orca/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ It does not replace `AGENTS.md`, `docs/orca-backend.md`, or `harness-adapters`.

Orca is a runtime backend, not an agent harness.
The runtime backend owns the task endpoint and, for Orca, the task worktree.
The harness is the agent process launched inside that endpoint, such as `claude`, `codex`, `opencode`, `pi`, or `grok`.
The harness is the agent process launched inside that endpoint, such as `claude`, `codex`, `opencode`, `pi`, `grok`, or `omp`.
Load `harness-adapters` for harness-specific launch, interrupt, resume, trust-dialog, and skill-invocation facts.

Implementation details, metadata fields, teardown guarantees, limitations, and smoke evidence live in `docs/orca-backend.md`.
Expand Down
37 changes: 34 additions & 3 deletions .agents/skills/harness-adapters/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: harness-adapters
description: Agent-only reference for firstmate harness operations. Use before spawning or recovering a crewmate or secondmate, handling a trust dialog, sending a harness-specific skill invocation, interrupting or exiting an agent, resuming an exited agent, or verifying a new harness adapter. Contains verified facts for claude, codex, opencode, pi, and grok.
description: Agent-only reference for firstmate harness operations. Use before spawning or recovering a crewmate or secondmate, handling a trust dialog, sending a harness-specific skill invocation, interrupting or exiting an agent, resuming an exited agent, or verifying a new harness adapter. Contains verified facts for claude, codex, opencode, pi, grok, and omp.
user-invocable: false
metadata:
internal: true
Expand Down Expand Up @@ -44,6 +44,7 @@ If the captain asks for a new harness, propose verifying it first: spawn a trivi
On `unknown`, ask the captain instead of guessing.
A captain override always beats detection.
When verifying a new adapter, record its env marker and command name in `bin/fm-harness.sh`.
The `omp` (Oh My Pi) adapter's env marker is `OMPCODE=1`, set by omp for its child/tool processes; `bin/fm-harness.sh` checks it BEFORE the claude `CLAUDECODE=1` marker because omp sets BOTH, so omp is never misdetected as claude. Ancestry fallback also matches an `omp` command name or a `bun`/`node`/`python` process whose args reference omp.

For stuck recovery, the target window's harness is recorded as `harness=` in `state/<id>.meta`.
Use that value for interrupt, exit, resume, and skill-invocation facts.
Expand All @@ -52,15 +53,15 @@ Use that value for interrupt, exit, resume, and skill-invocation facts.

Every verified primary harness has an empirically validated hook path for the "no turn ends blind" guard.
`claude` and `codex` block directly through Stop hooks that preserve exit status 2 and stderr from `bin/fm-turnend-guard.sh`.
`opencode`, `pi`, and `grok` expose passive lifecycle callbacks for this purpose, so their tracked primary adapters force one bounded follow-up or resume when the shared predicate blocks.
`opencode`, `pi`, `grok`, and `omp` expose passive lifecycle callbacks for this purpose, so their tracked primary adapters force one bounded follow-up or resume when the shared predicate blocks.
The exact hook files, commands, validation transcripts, scoping rules, and fail-open tradeoffs are owned by `docs/turnend-guard.md`.
When changing any primary turn-end hook, validate the real harness behavior in a scratch project or throwaway home before trusting it, then update that doc and the relevant concise fact below.

## Primary pre-arm (PreToolUse) seatbelt

Every verified primary harness also has a wired PreToolUse-equivalent hook that denies a watcher-arm anti-pattern (shell `&`, truncating pipe, bundling, broad `pkill -f fm-watch`) before it runs.
`claude` and `codex` block directly through PreToolUse hooks; `grok` blocks the same way but requires every `$VAR` reference in its hook `command` string to carry an inline `:-default` or it fails to launch the hook entirely.
`opencode` and `pi` block by throwing from `tool.execute.before` / returning `{block: true}` from `tool_call`.
`opencode`, `pi`, and `omp` block by throwing from `tool.execute.before` / returning `{block: true}` from `tool_call`.
The exact hook files, commands, output-shaping quirks (Claude Code only honors the deny when stdout is empty), and validation transcripts are owned by `docs/arm-pretool-check.md`.
When changing any primary PreToolUse hook, validate the real harness behavior in a scratch project before trusting it, then update that doc.

Expand All @@ -72,6 +73,7 @@ Claude and Grok use tracked background-notify cycles around `bin/fm-watch-arm.sh
Codex uses bounded foreground checkpoints through `bin/fm-watch-checkpoint.sh` because Codex cannot reason while a foreground tool call is running.
OpenCode uses `.opencode/plugins/fm-primary-watch-arm.js`, which coordinates with the turn-end guard plugin and wakes the TUI with `client.session.promptAsync`.
Pi uses the tracked `.pi/extensions/fm-primary-turnend-guard.ts` plus the tracked `.pi/extensions/fm-primary-pi-watch.ts`, both project-local extensions Pi auto-discovers once trusted.
omp (a Pi fork) uses the tracked `.omp/extensions/fm-primary-turnend-guard.ts` plus the tracked `.omp/extensions/fm-primary-omp-watch.ts`, both project-local extensions omp auto-discovers once trusted (omp scans `.omp/extensions/`, never `.pi/`).
When changing any primary watcher adapter, update `docs/supervision-protocols/`, `docs/turnend-guard.md` if a shared idle or turn-end hook changed, and the relevant concise fact below.

## Launch profile axes
Expand All @@ -96,6 +98,7 @@ The supported launch-profile flags below are verified locally; each row records
| grok | `--model <model>` | `--reasoning-effort <low\|medium\|high>` | Verified on grok 0.2.99 (2026-07-13). `--effort` is an alias, but firstmate's profile axis is reasoning effort. As of 0.2.99 the ceiling is `high`; both `xhigh` and `max` are rejected with `use one of: high, medium, low`, so firstmate omits them. |
| pi | `--model <model>` | `--thinking <low\|medium\|high\|xhigh\|max>` | Verified 2026-07-13 on Pi 0.80.6. `pi --help` advertises `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`; `pi --print --model openai-codex/gpt-5.6-sol --thinking max 'Reply with exactly OK.'` completed successfully. |
| opencode | `--model <provider/model>` | none for firstmate's interactive launch | Verified on opencode 1.17.6. `opencode run` has `--variant`, but firstmate launches the interactive `opencode --prompt` path, which has no verified effort flag. |
| omp | `--model <model>` | `--thinking <low\|medium\|high\|xhigh>` | Verified live 2026-07-10 (omp v16.3.15). omp is a Pi fork: `--thinking` also accepts `off\|minimal\|auto` but not `max` (omit it), `--auto-approve` grants autonomy, `-e/--extension` loads the turn-end/watch supervisors. |

When a requested effort value is outside the harness-specific accepted set, `fm-spawn` records the requested `effort=` in meta but emits no effort flag for that harness.
This preserves launch success instead of passing a known-bad value.
Expand All @@ -109,6 +112,7 @@ Natural language is acceptable if uncertain.
- codex: `$<skill>`, for example `$no-mistakes`; `/<skill>` is claude-only and codex rejects it as "Unrecognized command".
- opencode: no separate verified skill invocation beyond normal slash-command behavior; use natural language if the exact skill command is uncertain.
- pi: no separate verified skill invocation beyond normal command behavior; use natural language if the exact skill command is uncertain.
- omp: `/no-mistakes` (omp supports slash-command skills, like claude); no separate verified invocation quirk, use natural language if uncertain. The skill must be discoverable by omp (a scope omp reads, e.g. `~/.omp/agent/skills/` or the project `.claude/skills/`).
- grok: `/<skill>`, for example `/no-mistakes` (same form as claude). Verified end to end: grok discovers the user-level `no-mistakes` skill, `/no-mistakes` invokes it, and grok drives a real `no-mistakes axi run`. Like codex's `$`/`/` popups, typing `/<skill>` opens grok's slash-autocomplete, so a too-fast Enter selects the popup entry instead of sending, and for an argument-taking command (like `/no-mistakes`'s optional task-first argument) that first Enter only expands the popup selection into an argument-hint placeholder rather than submitting - a genuine second Enter is required (see the grok section below for the 2026-07-03 incident and fix). `fm_tmux_submit_core`'s retried Enter (used by `fm-send` on the tmux backend) already handles this correctly by reading the cursor row; the herdr backend needed a dedicated fix (`fm_backend_herdr_composer_state`, docs/herdr-backend.md) because its prior delta-based verification false-positived on that same popup-close content change.

## claude (VERIFIED)
Expand Down Expand Up @@ -275,3 +279,30 @@ The adapter therefore runs the shared predicate and, when it returns 2, forces o
It does not pass `--permission-mode`, so the passive hook cannot escalate the primary session's tool permissions.
Project-local Grok hooks require folder trust, verified with launch-time `--trust`; if the primary firstmate checkout is not trusted for Grok hooks, this primary guard fails open and `fm-guard.sh` remains the next-command alarm.
Grok's primary watcher protocol is Claude-shaped background-notify around `bin/fm-watch-arm.sh`; the passive Stop hook is only a backstop for blind turn ends.

## omp (VERIFIED 2026-07-10, omp v16.3.15)

omp (Oh My Pi, https://omp.sh) is a Pi fork with a Pi-compatible extension API (`turn_end` / `tool_call` events, `{block:true}` from `tool_call`, `pi.sendUserMessage(..., { deliverAs: "followUp" })`). Ported from the pi adapter and live-verified 2026-07-10 (see the validation record below): detection, extension load, the turn-end guard + watcher re-arm loop, and crew dispatch on omp are all confirmed on a real session. A few narrow items remain unexercised and are flagged inline (seatbelt deny, tmux-backend busy signature, exit/interrupt keys).

| Fact | Value |
|---|---|
| Env marker | `OMPCODE=1` (omp also sets `CLAUDECODE=1`; `fm-harness.sh` checks `OMPCODE` first) |
| Busy-pane signature | herdr backend: native busy-state verified working 2026-07-10. tmux backend: `Working...`/`Working…` / `esc to interrupt` regex still PENDING a tmux-backend run; override with `FM_BUSY_REGEX` / `FM_COMPOSER_IDLE_RE` |
| Exit command | `/quit` - VERIFIED 2026-07-10 by `tests/fm-omp-primary-live-e2e.test.sh` (clean `OMP_EXIT=0`; watcher + arm children reaped on exit) |
| Interrupt | single Escape (inherited from pi; not exercised by the e2e) |
| Skill invocation | `/<skill>` (e.g. `/no-mistakes`) |
| Autonomy | `--auto-approve` (omp HAS an approval system, unlike pi; fm-spawn passes it for crewmates - verified live 2026-07-10: two crewmates ran unattended and shipped PRs) |

omp is claude-compatible (sets `CLAUDECODE=1`) but does NOT implement Claude Code's `.claude/settings.json` `Stop`/`PreToolUse` event-hook contract - it uses its own `.omp/extensions/` runtime instead. That is exactly why detection must resolve `omp`, not `claude`: a claude-detected omp session would install `.claude/settings.json` Stop/PreToolUse hooks that omp never fires, silently disabling supervision.

**Primary-session guard (VERIFIED live 2026-07-10).**
The primary's turn-end guard AND PreToolUse seatbelt both live in `.omp/extensions/fm-primary-turnend-guard.ts`. It listens for `turn_end` because OMP has no `agent_settled` event (Pi 0.80.5-only). The `guardFollowupActive` one-shot skip suppresses the guard on its OWN injected follow-up turn, but - unlike pi's per-logical-run `agent_settled` - it re-nags on each subsequent blind turn until supervision is armed (the live e2e saw 3 injections before the model armed the watcher). On block it `await`s `pi.sendUserMessage(..., { deliverAs: "followUp" })` when `bin/fm-turnend-guard.sh` returns 2. The seatbelt returns `{ block: true }` from the `tool_call` handler when `bin/fm-arm-pretool-check.sh` denies a bash command.

**Primary watcher (VERIFIED live 2026-07-10).**
`.omp/extensions/fm-primary-omp-watch.ts` registers the `fm_watch_arm_omp` tool (primary path, called instead of a foreground bash arm) plus the `/fm-watch-arm-omp` command as a human fallback (the command notifies via `ctx.ui.notify`). Arming spawns `bin/fm-watch-arm.sh --restart` attached to the live omp process and sends a follow-up wake when the child exits with an actionable reason; a one-shot `process.once("exit")` listener (mirroring Pi #397) plus `session_shutdown` stop the arm child on exit. `bin/fm-session-start.sh` reports when the running omp session has not loaded both extensions (markers `state/.omp-turnend-extension-loaded` and `state/.omp-watch-extension-loaded`). Both are project-local `.omp/extensions/*.ts` files omp auto-discovers once the project is trusted (approve trust once per clone, or launch with `-e` as the trust-free fallback). The tool schema uses `pi.zod.object({})` (OMP-canonical) rather than Pi's typebox `Type.Object({})`, and OMP's ToolDefinition has no `promptSnippet`/`promptGuidelines` fields.

**Live validation record, 2026-07-10 (omp v16.3.15, herdr backend).**
A fresh `omp` in the firstmate home became the first mate (root `AGENTS.md` loaded via the `agents-md` provider), `bin/fm-harness.sh` and `bin/fm-lock.sh` both detected `omp`, and both `.omp/extensions/` loaded (markers written). It dispatched two crewmates (`fm-webull-broker-w7`, `fm-finnhub-free-f3`) - each a real omp session in its own treehouse worktree, running autonomously under `--auto-approve` - which shipped two green PRs. The turn-end guard fired on a real multi-turn primary ("TURN WOULD END BLIND ... 2 task(s) in flight, but no live watcher holds this home lock") and the primary re-armed the watcher (alive) instead of ending blind.

**Automated live E2E, 2026-07-10 (omp v16.4.0, tmux).**
`tests/fm-omp-primary-live-e2e.test.sh` (opt-in, `FM_OMP_LIVE_E2E=1`; mirrors `fm-pi-primary-live-e2e.test.sh`) passes: it launches omp on a private tmux socket with the tracked extensions via `-e`, drives bash/read turns, triggers the turn-end guard, arms `fm_watch_arm_omp`, delivers a watcher wake, drains + re-arms, and asserts one-or-more guard injections (omp re-nags per blind turn), NO foreground `bin/fm-watch-arm.sh` arm, a live re-armed watcher pid, and a clean `/quit` (`OMP_EXIT=0`) that reaps both the watcher and arm children. It uses the default (already-authed) agent dir because a fresh `PI_CODING_AGENT_DIR` triggers omp's blocking first-run setup wizard. Still not exercised anywhere: a seatbelt `{block:true}` deny of an arm anti-pattern, the tmux busy-footer regex (`FM_BUSY_REGEX`), and the interrupt (Escape) key.
5 changes: 4 additions & 1 deletion .no-mistakes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ disable_project_settings: true
# iterate every tests/*.test.sh, run each, and fail the step if any one exits
# non-zero (an agent-driven test step has crashed the daemon). The e2e tests need
# tmux on PATH, which the firstmate environment provides.
# Each test runs with ambient harness/backend markers stripped
# (OMPCODE/CLAUDECODE/HERDR_ENV/TMUX) so the gate matches CI's clean env even when
# the daemon or push originates from inside an omp or herdr session.
commands:
lint: 'bin/fm-lint.sh'
test: 'command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"'
test: 'command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; env -u OMPCODE -u CLAUDECODE -u HERDR_ENV -u TMUX bash "$t" || rc=1; done; exit "$rc"'

# Keep test evidence out of this repo; it stays in a temp dir instead.
test:
Expand Down
Loading