Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9451e97
fix(orca): validate uuid::path worktree identity against recorded wor…
mdc2122 Sep 16, 2026
fc1a538
feat(calm): ship Calm for OMP as a linked user-scope plugin
mdc2122 Sep 16, 2026
e7bedc1
no-mistakes(review): Abort installer on failed legacy retirement; add…
mdc2122 Sep 16, 2026
24d161d
no-mistakes(test): Corrected OMP uninstall guidance and verified inst…
mdc2122 Sep 16, 2026
c334637
no-mistakes(test): Corrected OMP uninstall documentation and installe…
mdc2122 Sep 16, 2026
4793164
no-mistakes(document): Document global Calm support and uninstall beh…
mdc2122 Sep 16, 2026
2315b42
feat(calm): standalone global fm-calm-omp plugin install.
mdc2122 Sep 16, 2026
1efcb09
fix(orca): validate uuid::path worktree identity against recorded wor…
mdc2122 Sep 16, 2026
647fa04
fix(harness): detect omp's bun-script launcher shape (omp >= 18.1.22)
mdc2122 Sep 16, 2026
73c9348
fix(calm): paint calm-omp boat one yellow over all-blue water
mdc2122 Sep 16, 2026
158f7e9
fix(calm): restore original ASCII two-color boat sprite in calm-omp
mdc2122 Sep 16, 2026
5f25974
fix(calm): paint calm-omp water cyan so it reads blue in Apple Terminal
mdc2122 Sep 16, 2026
3fc0ae6
fix(calm): restore exports lost in cyan-water edit
mdc2122 Sep 16, 2026
c5f6906
docs: incident summary - stale read-only loop from omp bun-launcher s…
mdc2122 Sep 16, 2026
8fd2fc7
fix: prevent composer clears from clobbering captain input (#5)
mdc2122 Sep 16, 2026
4f3c76f
feat: automatically merge green yolo pull requests (#6)
mdc2122 Sep 17, 2026
ce19de2
fix(docs): correct pi model-discovery contract for unauthenticated pr…
mdc2122 Sep 17, 2026
bde5e95
no-mistakes(document): Align catalog-omission blocking rules with pi …
mdc2122 Sep 17, 2026
33abf4b
no-mistakes(document): Reviewed model discovery documentation
mdc2122 Sep 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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Treat model and provider knowledge as current discovery, not a permanent namespa
Use the selected tool reference's authoritative surface in the current authenticated environment because availability changes by version, account, and configuration.

For an unfamiliar namespace, establish support and provider identity from that harness's CLI help, model listing, or current documentation.
An account-reaching listing that omits a model is concrete unsupported evidence; block the candidate and quote it.
An account-reaching listing that omits a model is concrete unsupported evidence only when the tool reference records that omission as authoritative; block the candidate and quote it then, and treat an omission the reference records as a hiding rather than a proof as disclosed uncertainty instead.
An unreachable surface establishes nothing; report uncertainty instead of a verdict.

For a matched profile array, return to `quota-array-dispatch` only after establishing every candidate's harness support, provider relationship, and uncertainty.
4 changes: 2 additions & 2 deletions .agents/skills/harness-adapters/references/harness/muse.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ The router owns Muse's task-kind boundary.
| Models | `--model <model>`; only provider `meta`. |
| Busy | Durable session event log folded by `../../../bin/fm-busy-lib.sh`; no hook or plugin writer, arming, or seeded busy record. |
| Exit | `/exit`, one Enter; prints `To continue this session, run muse resume <session-uuid>`. |
| Interrupt | Single Escape records `terminal: cancelled` and restores bright prompt text, so control follows with `Ctrl+U`; the legacy typed key path uses the same clear table. |
| Interrupt | Single Escape records `terminal: cancelled` and restores the cancelled prompt as bright text only when the composer was empty at cancel time - fresh typed input survives the interrupt untouched (`../../../../../docs/verification/muse.md`); both interrupt planes follow with `Ctrl+C` only after proving the composer holds the restored prompt, never the captain's typing (`../../../../../docs/agent-control.md`). |
| Skill | `/<skill>`, the Claude or Grok form. |
| Resume | `muse resume --last` or `muse resume <session-uuid>`; bare `muse resume` opens a picker. |
| Autonomy | `--yolo` disables approval and sandbox and trusts the workspace. |
| Trust | Dialog `Do you trust this workspace?`, choice `1 Trust and continue` preselected for Enter; `--yolo` suppresses it, which fresh task paths require. |
| Marker | None; identity comes from anchored `muse-bin-*` ancestry, which `../../../bin/fm-harness.sh` keeps a retained foreign marker from overriding, while `MUSE_CURRENT_SESSION_LOG` is a path rather than identity and its export to tools is unverified. |
| Composer | Bordered `⟩`, truecolor `38;2;90;160;255`, luminance about 149.9 and narrowly above ghost threshold 128; typed text is `38;2;204;211;219`, about 209.8, with no observed placeholder or ghost. |
| Composer | Bordered `⟩`, truecolor `38;2;90;160;255`, luminance about 149.9 and narrowly above ghost threshold 128; typed text is `38;2;204;211;219`, about 209.8, with no observed placeholder or ghost. muse 1.3.0 instead renders `❯` framed by rules; `../../../../../docs/verification/muse.md` owns both version records. |
| Effort | `--reasoning-effort`, default `high`, accepts `none\|minimal\|low\|medium\|high\|xhigh\|ultra`; shared values expose low through xhigh, explicit captain `max` maps to `ultra`, and `none` or `minimal` remain unreachable. |

## Credential preflight
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/harness-adapters/references/harness/pi.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Verified on 2026-07-27 with Pi and Pi-signed 0.82.0 unless a fact gives another
| Skill invocation | No separate verified form beyond normal command behavior; use natural language when the exact command is uncertain. |
| Model flag | `--model <model>`. |
| Effort flag | `--thinking <low\|medium\|high\|xhigh\|max>`; both identities expose the same levels and completed the same model-qualified max-thinking smoke. |
| Model discovery | Run the selected executable as `<executable> --list-models [search]`; Pi's installed `docs/models.md` owns how built-in, extension-registered, and custom provider/model entries reach that list. |
| Model discovery | Run the selected executable as `<executable> --list-models [search]`; the listing hides every provider with no configured auth, including extension-registered providers whose models `--model` still resolves (the run then fails only at the auth check), so an empty listing is not proof a model is unresolvable. Verified 2026-09-17 on 0.85.1. A `No models match pattern "<id>"` startup warning names a stale `enabledModels` pin in `~/.pi/agent/settings.json` and does not fail the launch. Pi's installed `docs/models.md` owns how built-in, extension-registered, and custom provider/model entries reach that list. |

Native Codex sessions may request `ultra` through the native extension flag described by `../../../bin/fm-spawn.sh`; it is separate from Pi's thinking levels.
Pi has no permission system, so workers are always autonomous.
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/quota-array-dispatch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Deterministic shell must never map a model to a provider, a provider to a creden
You establish those relations yourself, in the open, from the candidate's own authoritative catalog (`harness-adapters` owns the per-harness discovery surface) plus the one intake snapshot.

Confirm the catalog lists the candidate's model and record the provider family it reports.
A model the catalog does not list is concrete contradictory evidence: block that candidate and quote the catalog result.
A model the catalog does not list is concrete contradictory evidence only when the harness's discovery surface makes that omission authoritative (the per-harness reference owns whether its listing can hide a resolvable model); an omission it can produce for a resolvable model is disclosed uncertainty, not a block.
Apply quota at the granularity the vendor actually supplies.
A provider-level or `all_models`/`all_products` scope bounds every model you established in that family, including one with no window of its own.
A named-model or named-product scope is an additional bound for that model alone.
Expand Down
5 changes: 2 additions & 3 deletions .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,9 @@ Run `bin/fm-teardown.sh <id>` for `kind=secondmate` only when the captain or mai

The safety check is the secondmate's own home.
Teardown refuses while its `state/*.meta` contains in-flight work.
Non-forced retirement also refuses while any parent pending-reply for that id is still unresolved.
A remote route delegates the in-flight guard to its configured host and additionally refuses while the primary has a pending handoff outbox.
A remote route delegates the same guard to its configured host and additionally refuses while the primary has a pending handoff outbox or unresolved routed reply.
SSH exit 255 preserves the route and local records because remote completion is unknown.
When retirement proceeds, teardown kills the direct endpoint, removes every parent pending-reply record for that id including resolved leftovers and its delivery confirmation, removes the `data/secondmates.md` route, clears the main home metadata, and removes the retired secondmate home.
When safe, teardown kills the direct endpoint, removes the `data/secondmates.md` route, clears the main home metadata, and removes the retired secondmate home.
An endpoint close that could not be made stops the retirement before any record naming that endpoint is removed, so a cleanup never reports success for an agent that may still be live with nothing left on disk naming it.
`--force` overrides that stop only for the retiring secondmate's own endpoint, never for a child endpoint inside forced cleanup, and a forced continue still names the endpoint you must then reconcile yourself; [`docs/verification/runtime-backends.md`](../../../docs/verification/runtime-backends.md) "Endpoint close" owns what each backend can prove about its own close.
Removing a leased home releases its durable treehouse lease via `treehouse return`, so the pool slot is freed for reuse rather than left leased forever.
Expand Down
1 change: 1 addition & 0 deletions .tmp-pi-home-18012/auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions .tmp-pi-home-18012/models-store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions .tmp-pi-home-21668/auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions .tmp-pi-home-21668/models-store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions .tmp-pi-home-70253/auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions .tmp-pi-home-70253/models-store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions .tmp-pi-home-70418/auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions .tmp-pi-home-70418/models-store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions .tmp-pi-home-70418/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ README.md public overview and development notes
.claude/skills symlink to .agents/skills for claude compatibility
.claude/mods/ Claude Code mods (function-hooks plugins), committed; Calm's module may load through CLAUDE_CODE_ENABLE_FUNCTION_HOOKS or tengu_plugin_hooks_modules, but activates only when CLAUDE_CODE_ENABLE_FUNCTION_HOOKS is exactly "1" and is otherwise a complete no-op (docs/calm.md)
skills/ standalone public installer-facing skills, committed; not loaded by firstmate
extensions/ OMP plugin packages linked into the user scope by their bin/ installers, committed (docs/calm.md)
bin/ helper scripts, committed; read each script's header before first use
.env optional Relay pairing token (presence-gates section 14), mail-plane credentials (schema: docs/configuration.md "Mail plane"), and typed dispatch resolution key TYPESAFE_API_KEY (presence-gates bin/fm-dispatch-resolve.sh; docs/configuration.md "Typed dispatch resolution"); LOCAL, gitignored
config/crew-harness crewmate harness override; LOCAL, gitignored; absent or "default" = same as firstmate. Inherited as the literal file: a concrete primary adapter value also controls a secondmate home's own crewmates (section 4)
Expand Down Expand Up @@ -351,6 +352,7 @@ The path's worker, automated gates, and captain approval remain authoritative:

Delivery mode and `yolo` are orthogonal.
`yolo` governs merge authority only: with it off, the captain approves every PR merge and every local-only landing; with it on, firstmate merges green, in-scope work itself.
For a PR-based task the armed merge poll applies that authority itself: when the poll finds the PR still open and the task records `yolo=on`, `bin/fm-watch.sh` runs `bin/fm-pr-merge.sh` directly, so a green mergeable PR lands without waiting for a firstmate turn.
Never merge a red PR under either setting unless a current explicit captain instruction names the single GitHub check waived through `fm-pr-merge.sh --allow-red`; that attended-only waiver still requires every other check green.
Destructive, irreversible, and security-sensitive merges still escalate.
Without a current explicit captain instruction that states the concrete merge, the green default stands, and standing `yolo` cannot authorize a red merge; section 1 owns when such an instruction overrides a Firstmate-written standing rule within its exact scope.
Expand Down
10 changes: 10 additions & 0 deletions bin/backends/cmux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,16 @@ fm_backend_cmux_composer_state() { # <target> [expected-label] -> empty|pending
printf '%s' "$verdict"
}

# fm_backend_cmux_composer_content: the composer's normalized text content for
# callers that must compare content rather than classify it (fm-send.sh's
# post-interrupt clear proof). Same capture and descriptor as the verdict
# adapter above.
fm_backend_cmux_composer_content() { # <target> [expected-label]
local cap
cap=$(fm_backend_cmux_composer_capture "$1" "${2:-}") || return 1
fm_composer_extract_selected_content "$(fm_backend_cmux_composer_caps)" "$cap"
}

# fm_backend_cmux_send_text_submit: type <text> into <target> once (raw,
# unsubmitted, via send_literal), then drive the shared verify-and-retry-Enter
# loop (bin/fm-composer-lib.sh: fm_composer_submit_retry_core) against the
Expand Down
17 changes: 17 additions & 0 deletions bin/backends/herdr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3082,6 +3082,23 @@ fm_backend_herdr_composer_state() { # <target> -> empty|pending|pending-unprove
printf '%s' "$verdict"
}

# fm_backend_herdr_composer_content: the composer's normalized text content,
# mirroring the capture fallback of fm_backend_herdr_composer_state above, for
# callers that must compare content rather than classify it (fm-send.sh's
# post-interrupt clear proof).
fm_backend_herdr_composer_content() { # <target>
local target=$1 cap caps
fm_backend_herdr_parse_target "$target" || return 1
if cap=$(fm_backend_herdr_capture_ansi "$target" "$FM_COMPOSER_CAPTURE_LINES" 2>/dev/null); then
caps=$(printf 'styled=1\ncursor=0\nidentity=1\nrows=%s' "$FM_COMPOSER_CAPTURE_LINES")
elif cap=$(fm_backend_herdr_capture "$target" "$FM_COMPOSER_CAPTURE_LINES"); then
caps=$(printf 'styled=0\ncursor=0\nidentity=1\nrows=%s' "$FM_COMPOSER_CAPTURE_LINES")
else
return 1
fi
fm_composer_extract_selected_content "$caps" "$cap"
}

# fm_backend_herdr_rendered_busy_state: busy|idle|unknown from the pane's
# RENDERED busy footer, the same delivery-only signal bin/fm-tmux-lib.sh's
# fm_pane_busy_state reads, scanning the same 40-line tail folded to its last
Expand Down
11 changes: 8 additions & 3 deletions bin/backends/tmux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,10 @@ fm_backend_tmux_foreground_comms() { # <target>
done
}

# The foreground group's full command lines. Needed because a node-bundle
# harness carries its identity in argv[1] rather than in its command name or
# argv[0]; bin/fm-gemini-lib.sh owns what counts as evidence inside one.
# The foreground group's full command lines. Needed because some harnesses
# carry their identity in argv[1] rather than in their command name or argv[0]:
# gemini's node bundle (bin/fm-gemini-lib.sh owns what counts as evidence) and
# omp's bun launcher (fm_omp_args_are_omp in bin/fm-session-lock-lib.sh).
fm_backend_tmux_foreground_args() { # <target>
local target=$1 tty pid pgid tpgid comm args
tty=$(tmux display-message -p -t "$target" '#{pane_tty}' 2>/dev/null) || return 0
Expand Down Expand Up @@ -381,6 +382,10 @@ EOF
printf 'alive'
return 0
fi
if fm_omp_args_are_omp "$name"; then
printf 'alive'
return 0
fi
done <<EOF
$(fm_backend_tmux_foreground_args "$target")
EOF
Expand Down
14 changes: 11 additions & 3 deletions bin/fm-agent-process-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ fm_agent_process_classify_name() { # <path> [argv0] -> agent|shell|other
muse|muse-bin-*) printf 'agent' ;;
# omp (Oh My Pi) is anchored for the same reason as muse: its live process
# name is the bare word `omp` (verified, omp 18.1.11) and a glob would claim
# unrelated commands such as ompd or comp.
# unrelated commands such as ompd or comp. Since 18.1.22 omp also ships as
# a bun script (comm bun), which no name pattern can own; that shape is
# recognized from its argv by fm_omp_args_are_omp in classify below.
*claude*|*codex*|*opencode*|*grok*|*kimi*|*rovo*|pi|pi-signed|pi-launcher|Pi|omp) printf 'agent' ;;
# agy (Antigravity CLI) is anchored for the same reason as muse and omp: its
# live process name is the bare word `agy` (verified, agy 1.2.0: a Go-compiled
Expand Down Expand Up @@ -79,8 +81,10 @@ fm_agent_process_classify_name() { # <path> [argv0] -> agent|shell|other
# on Linux the exec name, on macOS argv[0] truncated to 16 bytes.
# <argv0> argv[0] as the process reports it - a bare name or an install
# path, whichever the launcher used (empty when unknown).
# <args> the flattened command line, read only for the node-bundle
# harnesses whose identity sits in argv[1] (bin/fm-gemini-lib.sh).
# <args> the flattened command line, read only for the harnesses whose
# identity sits in argv[1]: gemini's node bundle
# (bin/fm-gemini-lib.sh) and omp's bun launcher
# (fm_omp_args_are_omp in bin/fm-session-lock-lib.sh).
# [pid] when given, lets the Gemini rule read argv boundaries from the
# live process instead of the flattened line.
fm_agent_process_classify() { # <name> <argv0> <args> [pid] -> agent|shell|other
Expand All @@ -103,6 +107,10 @@ fm_agent_process_classify() { # <name> <argv0> <args> [pid] -> agent|shell|othe
printf 'agent'
return 0
fi
if [ -n "$args" ] && fm_omp_args_are_omp "$args"; then
printf 'agent'
return 0
fi
if [ "$by_name" = shell ] && [ "$by_argv0" = shell ]; then
printf 'shell'
else
Expand Down
58 changes: 42 additions & 16 deletions bin/fm-backend.sh
Original file line number Diff line number Diff line change
Expand Up @@ -387,24 +387,30 @@ fm_backend_endpoint_atom_valid() { # <value>
''|*[!A-Za-z0-9._@%+-]*) return 1 ;;
esac
}

# An Orca worktree id is the composite `<orca id>::<absolute worktree path>`
# that Orca itself returns, so the `:` and `/` characters every real value
# carries make the simple-atom check reject it. Firstmate hands the id back to
# Orca opaquely and resolves it through Orca before removing anything, so this
# proves only the shape that can name one worktree: both halves of the first
# `::` split present, and the path half absolute.
fm_backend_orca_worktree_id_valid() { # <value>
case "$1" in
*$'\n'*|*$'\r'*|*$'\t'*) return 1 ;;
*::*) ;;
*) return 1 ;;
# fm_backend_orca_worktree_id_valid: validate an Orca composite worktree
# identity of the form <uuid>::<absolute-path> against the meta's recorded
# worktree. The shared atom allowlist rejects ':' and '/', so Orca's
# path-qualified ids need this dedicated check. The uuid half names the Orca
# repo (shared by every worktree of that repo), so only the path half
# distinguishes tasks: it must equal the recorded worktree exactly.
fm_backend_orca_worktree_id_valid() { # <worktree-id> <worktree>
local worktree_id=$1 worktree=$2 uuid_part rest path_part
[ -n "$worktree_id" ] && [ -n "$worktree" ] || return 1
case "$worktree_id" in
*$'\n'*|*$'\r'*) return 1 ;;
esac
[ -n "${1%%::*}" ] || return 1
case "${1#*::}" in
/*) ;;
case "$worktree_id" in
*::*)
uuid_part=${worktree_id%%::*}
rest=${worktree_id#*::}
;;
*) return 1 ;;
esac
path_part=$rest
case "$path_part" in /*) ;; *) return 1 ;; esac
case "$uuid_part" in ????????-????-????-????-????????????) ;; *) return 1 ;; esac
case "$uuid_part" in *[!0-9a-fA-F-]*) return 1 ;; esac
[ "$path_part" = "$worktree" ] || return 1
}

fm_backend_validate_task_endpoint() { # <meta-file> <task-id>
Expand Down Expand Up @@ -527,7 +533,7 @@ fm_backend_validate_task_endpoint() { # <meta-file> <task-id>
}
if [ "$window" != "fm-$id" ] \
|| ! fm_backend_endpoint_atom_valid "$terminal" \
|| ! fm_backend_orca_worktree_id_valid "$worktree_id"; then
|| ! fm_backend_orca_worktree_id_valid "$worktree_id" "$worktree"; then
echo "REFUSED: Orca endpoint metadata for task $id is malformed or inconsistent; preserving task state." >&2
return 1
fi
Expand Down Expand Up @@ -852,6 +858,26 @@ fm_backend_composer_state() { # <backend> <target> [expected-label] -> empty|pe
esac
}

# fm_backend_composer_content: the composer's normalized text content - what a
# human typed or the harness restored - for callers that must compare content,
# not just classify it (fm-send.sh's post-interrupt clear proof). Same thin-
# adapter rule as the verdict: capture plus a capability descriptor fed to the
# one shared extractor (bin/fm-composer-lib.sh,
# fm_composer_extract_selected_content). Fails when the backend cannot capture
# or no composer shape is provable.
fm_backend_composer_content() { # <backend> <target> [expected-label]
local backend=$1
shift
fm_backend_source "$backend" || return 1
case "$backend" in
tmux) fm_tmux_composer_content "$@" ;;
herdr) fm_backend_herdr_composer_content "$@" ;;
cmux) fm_backend_cmux_composer_content "$@" ;;
zellij) fm_backend_zellij_composer_content "$@" ;;
*) return 1 ;;
esac
}

# fm_backend_target_exists: cheap, READ-ONLY existence check - does the
# recorded TARGET endpoint still exist on BACKEND? Never starts a server or
# session: for herdr this deliberately queries the pane directly instead of
Expand Down
Loading