Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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, cursor, opencode, pi, and grok.

## 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`, `cursor`, `opencode`, `pi`, or `grok`.
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
46 changes: 44 additions & 2 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, cursor, opencode, pi, and grok.
user-invocable: false
metadata:
internal: true
Expand Down Expand Up @@ -52,7 +52,7 @@ 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.
`cursor`, `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.
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.

Expand All @@ -72,6 +72,7 @@ Full mechanics, scoping, dated commands, payloads, and fail-open evidence live i

- `claude`: verified native `SessionStart` stdout injection; `.claude/settings.json` matches `startup`, `resume`, and `clear`, but not `compact`.
- `codex`: verified on 0.144.4; `.codex/hooks.json` receives `source=startup`, and wrapper stdout reaches model context.
- `cursor`: `.cursor/hooks.json` registers the wrapper on the bundle-confirmed `sessionStart` event, but Cursor Agent 2026.07.16-899851b executes no hooks at all for accounts without Cursor's server-side hooks rollout, so this adapter is documented fail-open and the emitted supervision block plus AGENTS.md section 3 remain the working enforcement.
- `opencode`: verified on 1.17.18; `session.created` plus `client.session.promptAsync` starts the nudge turn in the TUI, while `opencode run` remains fail-open headless.
- `pi`: verified native `session_start`; the existing primary extension handles `startup`, `new`, and `resume` and uses `pi.sendMessage` to inject context without racing a positional launch prompt.
- `grok`: the 0.2.103 project `SessionStart` event fires with `source=new`, but stdout does not reach model context; the tracked project hook remains fail-open, and a global token-guarded fallback requires a captain decision.
Expand All @@ -81,6 +82,7 @@ Full mechanics, scoping, dated commands, payloads, and fail-open evidence live i
At session start, `bin/fm-session-start.sh` prints exactly one watcher supervision block for the detected primary harness.
Do not substitute another harness's wait shape when resuming supervision.
Claude and Grok use tracked background-notify cycles around `bin/fm-watch-arm.sh`.
Cursor uses a tracked background Shell task 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.
Expand All @@ -105,6 +107,7 @@ The supported launch-profile flags below are verified locally; each row records
|---|---|---|---|
| claude | `--model <model>` | `--effort <low\|medium\|high\|xhigh\|max>` | Verified on Claude Code 2.1.196. |
| codex | `--model <model>` | `-c 'model_reasoning_effort="<low\|medium\|high\|xhigh>"'` | Verified on codex-cli 0.142.1. The installed binary schema contains `model_reasoning_effort`, the active config uses it, and the bundled model catalog advertises only low/medium/high/xhigh. `max` is omitted. |
| cursor | `--model <model>` | none | Verified on Cursor Agent 2026.07.16-899851b (2026-07-18). `--help` documents quoted bracket model overrides such as `claude-opus-4-8[context=1m,effort=high,fast=false]`, but there is no standalone effort flag, so firstmate records and omits the separate effort axis and leaves any effort choice inside the caller's model string. |
| 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. |
Expand All @@ -119,6 +122,7 @@ Natural language is acceptable if uncertain.

- claude: `/<skill>`, for example `/no-mistakes`.
- codex: `$<skill>`, for example `$no-mistakes`; `/<skill>` is claude-only and codex rejects it as "Unrecognized command".
- cursor: natural language; no dedicated skill invocation syntax was verified.
- 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.
- 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.
Expand Down Expand Up @@ -184,6 +188,44 @@ The tracked hook anchors to `pwd -P`, verifies that root is firstmate-shaped and
Codex's primary watcher protocol is `bin/fm-watch-checkpoint.sh --seconds "${FM_CODEX_WATCH_CHECKPOINT:-180}"`, not `bin/fm-watch-arm.sh`.
The checkpoint is deliberately foreground and bounded so Codex regains control regularly to process user messages and queued wakes.

## cursor (CLI VERIFIED 2026-07-18, Cursor Agent 2026.07.16-899851b; first verified 2026-07-14 on 2026.07.09-a3815c0)

Cursor Agent's local CLI command is `cursor-agent`.
The installer also symlinks a generic `~/.local/bin/agent` to the same launcher; firstmate always launches and matches the specific `cursor-agent` name, never `agent`.
Firstmate launches workers with `--force`, `--approve-mcps`, an explicit `--workspace`, and the positional brief.
For model and effort behavior, see the [launch-profile-axes table](#launch-profile-axes).

| Fact | Value |
|---|---|
| Busy-pane signature | The composer footer shows an end-anchored `ctrl+c to stop` exactly while a turn is running (2026.07.16); older 2026.07.09 builds showed a braille spinner followed by `Running <N> tokens`. The shared busy regex matches both. |
| Exit command | `/quit` (plus one Enter to accept its slash popup) exits cleanly at idle; two rapid `Ctrl+C` presses at idle also exit, but a delayed second press misses the confirmation window. |
| Interrupt | Single `Ctrl+C` mid-turn cancels the active turn (the tool call reports `Cancelled`) and returns to an idle composer; the interrupted prompt is not restored into the composer on 2026.07.16. |
| Trust dialog | First interactive launch in a new workspace shows `Workspace Trust Required`; press `a` (`Trust this workspace`) to proceed, and trust persists per path. Print mode needs `--trust` instead. |
| Autonomy | `--force` (`--yolo` is only its alias and is avoided because Firstmate's unrelated project setting already uses that word). |
| Resume | `cursor-agent --continue` resumes the most recent session for the workspace; `--resume [chatId]` is also available. |
| Skill invocation | Natural language; no dedicated syntax was verified. |
| Submit shape | The first Enter after typed text may leave the prompt in the composer; `fm_tmux_submit_core`'s cursor-row-verified retry sends the genuine second Enter, so `fm-send` steering works unchanged. |
| Non-TTY launch | Without a TTY, `cursor-agent` falls back to print mode and fails with `No prompt provided for print mode`; never pipe its interactive launch. |

`cursor-agent` is a Node launcher whose process arguments retain the exact `cursor-agent` executable path.
On macOS a sole-column `ps -o comm=` reports that argv[0] path directly, and on the `node` `pane_current_command` path the tmux liveness probe requires the exact `cursor-agent` argv[0] basename; neither ever matches a generic process named `agent`.
`cursor-agent` exports `CURSOR_AGENT=1` and `CURSOR_INVOKED_AS=cursor-agent` to its tool subprocesses (verified 2026-07-18), but detection deliberately stays ancestry-based: inherited environment survives nested launches (a cursor tool shell inside a Claude session still carries `CLAUDECODE=1`), so an env shortcut would misattribute nested harnesses.

Hook execution is server-gated: on 2026-07-18, Cursor Agent 2026.07.16-899851b executed no project, user, Claude-compat, or plugin hooks in print or interactive mode for an individual Pro account, while the 2026-07-14 evidence on 2026.07.09-a3815c0 recorded project `preToolUse` and `stop` hooks firing for a different account.
Every Cursor hook integration is therefore strictly fail-open: the wiring is tracked and correct per the installed bundle's hook contract, and supervision never depends on a hook firing.
`docs/turnend-guard.md` owns the dated evidence for both readings.

Cursor workers receive a correctly structured task plugin through `--plugin-dir`, plus one additive user-level fallback stop hook that is token-scoped to Firstmate task worktrees and no-ops elsewhere.
It never overwrites a target project's Cursor hooks, and `bin/fm-cursor-lib.sh` owns its atomic install, the lock against concurrent spawns and teardowns, and the deterministic removal of the shared hook artifacts when the last Cursor task's token is gone.
When hooks do not execute, worker turn-end signals simply never arrive and the watcher's pane-based staleness path supervises the worker.

The primary checkout's `.cursor/hooks.json` registers a `sessionStart` nudge, `preToolUse` seatbelts, and a passive `stop` hook.
The stop adapter returns one `followup_message` when the shared predicate blocks and uses Cursor's `loop_count` plus `loop_limit: 1` to prevent recursion.
Cursor's primary watcher protocol is a tracked background Shell task running `bin/fm-watch-arm.sh`; task completion is the wake, and it works with or without hook execution.

Desktop Agents Window hook loading is documented by Cursor's shared project-hook contract but was not exercised here.
Before treating that entry point as fully verified, run `bin/fm-session-start.sh` from the desktop Agents Window and confirm the tool subprocess has a `cursor-agent` ancestor or another Cursor-specific marker.

## opencode (VERIFIED 2026-06-11, v1.15.7-1.17.6)

| Fact | Value |
Expand Down
34 changes: 34 additions & 0 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"version": 1,
"hooks": {
"sessionStart": [
{
"command": "\"$CURSOR_PROJECT_DIR\"/bin/fm-sessionstart-nudge.sh",
"timeout": 10,
"failClosed": false
}
],
"preToolUse": [
{
"command": "\"$CURSOR_PROJECT_DIR\"/bin/fm-arm-pretool-check.sh",
"matcher": "Shell",
"timeout": 10,
"failClosed": true
},
{
"command": "\"$CURSOR_PROJECT_DIR\"/bin/fm-cd-pretool-check.sh",
"matcher": "Shell",
"timeout": 10,
"failClosed": true
}
],
"stop": [
{
"command": "\"$CURSOR_PROJECT_DIR\"/bin/fm-turnend-guard-cursor.sh",
"timeout": 30,
"loop_limit": 1,
"failClosed": false
}
]
}
}
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ state/ volatile runtime signals; gitignored
<id>.status appended by crewmates: "<state>: <note>" wake-event lines, not current-state truth
<id>.turn-ended touched by turn-end hooks
<id>.grok-turnend-token firstmate-owned grok hook registry token for the task; removed by teardown
<id>.cursor-turnend-token firstmate-owned Cursor hook registry token for the task; teardown removes it and clears the shared user-level hook after the last Cursor task (bin/fm-cursor-lib.sh)
<id>.cursor-plugin/ generated per-task Cursor plugin passed via --plugin-dir; removed by teardown
<id>.meta written by fm-spawn: window=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; kind=secondmate also records home= and projects=; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and GitHub's pr_head= when available; fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
<id>.check.sh authenticated slow poll; the watcher dispatches validated PR data and the byte-identified X shim through trusted repository scripts, runs registered custom checks from hash-validated private snapshots, and rejects every other state check without execution
<id>.check-trust private content binding created by fm-check-register.sh for an intentional custom check
Expand Down Expand Up @@ -150,7 +152,7 @@ A silent bootstrap section needs no action; for any printed actionable diagnosti
## 4. Harness and runtime dispatch

Load `harness-adapters` before every spawn or recovery and before trust handling, skill invocation, interrupt, exit, resume, or adapter verification.
The verified harnesses are `claude`, `codex`, `opencode`, `pi`, and `grok`; never dispatch on an unverified adapter.
The verified harnesses are `claude`, `codex`, `cursor`, `opencode`, `pi`, and `grok`; never dispatch on an unverified adapter.
If configured harness data names an unverified adapter, report it and fall back only to a verified adapter rather than launching it.

`docs/configuration.md` owns dispatch-profile and runtime-backend schemas, `bin/fm-dispatch-select.sh` owns selector mechanics, `bin/fm-harness.sh` owns static resolution, and `bin/fm-spawn.sh` owns launch flags and fail-closed validation.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Full detail on every feature lives in [docs/architecture.md](docs/architecture.m

### Requirements

- A verified agent harness: Claude Code, Grok, Pi, Codex, or OpenCode.
- A verified agent harness: Claude Code, Grok, Pi, Codex, Cursor, or OpenCode.
- Git and the GitHub CLI, authenticated through `gh auth login`.
- tmux, for the reference session backend.

Expand All @@ -71,7 +71,7 @@ Claude Code and Grok use background-notify wake cycles; Pi uses its tracked prim
All three have verified turn-end guard paths when launched with their documented setup.
Pick whichever one matches your subscription and workflow.

Codex and OpenCode are also verified and supported as primary harnesses; Codex uses bounded foreground checkpoints, and OpenCode uses a TUI plugin, so both carry more harness-specific supervision tradeoffs than the three co-primaries.
Codex, Cursor, and OpenCode are also verified and supported as primary harnesses; Codex uses bounded foreground checkpoints, Cursor uses a tracked background Shell task, and OpenCode uses a TUI plugin, so all three carry more harness-specific supervision tradeoffs than the three co-primaries.

### Install and launch

Expand Down Expand Up @@ -192,7 +192,7 @@ Firstmate's skills live in two separate places with different audiences:
- [docs/cmux-backend.md](docs/cmux-backend.md) - setup guide for the experimental cmux backend, plus its verification notes and known gaps.
- [docs/codex-app-backend.md](docs/codex-app-backend.md) - Codex App backend boundary, evidence, and rollout contract.
- [docs/turnend-guard.md](docs/turnend-guard.md) - the primary session's structural "no turn ends blind" backstop: verified per-harness hook mechanisms, scoping, loop safety, and fail-open tradeoffs.
- [docs/supervision-protocols/](docs/supervision-protocols/) - rendered primary-harness watcher protocols for Claude, Codex, OpenCode, Pi, Grok, and unknown harness fallback.
- [docs/supervision-protocols/](docs/supervision-protocols/) - rendered primary-harness watcher protocols for Claude, Codex, Cursor, OpenCode, Pi, Grok, and unknown harness fallback.
- [docs/scripts.md](docs/scripts.md) - the `bin/` toolbelt reference.
- [`AGENTS.md`](AGENTS.md) - the distro's always-loaded operating contract and routing index for conditional procedures.
- [CONTRIBUTING.md](CONTRIBUTING.md) - how to contribute, including the dev/test commands.
Expand Down
Loading