Skip to content

feat: add cursor-agent (Composer 2.5) as a verified crew harness - #9

Merged
Quidge merged 3 commits into
mainfrom
fm/cursor-harness
Aug 4, 2026
Merged

feat: add cursor-agent (Composer 2.5) as a verified crew harness#9
Quidge merged 3 commits into
mainfrom
fm/cursor-harness

Conversation

@Quidge

@Quidge Quidge commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Intent

Integrate cursor-agent (Cursor Composer 2.5, non-fast) as a verified firstmate crew harness using Shape A: an interactive tmux-pane worker driven directly like the grok/kimi adapters (NOT ACP), with turn lifecycle from cursor's native beforeSubmitPrompt and stop user hooks. This is standard harness onboarding that slots into the existing adapter machinery; there is deliberately NO cursor-special dispatch or supervision path, and the change is additive with no behavior change for any existing harness.

Change sites and the deliberate decisions behind them (so review can tell intent from mistakes):

  • bin/fm-harness.sh: cursor detected by process ancestry on the cursor-agent command name only. There is deliberately no Layer-1 env marker because the launcher's CURSOR_INVOKED_AS is not confirmed exported to child/tool processes.
  • bin/fm-spawn.sh: launch template 'cursor-agent --force --trust --model ""'. Deliberately NO --effort flag (cursor encodes effort in the model id, like opencode/kimi; requested effort is recorded in meta only), and the template MUST end with cursor-agent as the shell's sole/last command with no trailing ';' so the pane's foreground command reports cursor-agent for liveness. Adds cursor to the spawn accept-lists and the --model flag list. Arms the busy contract behind the verification gate, writes a per-task registry entry and a gitignored .fm-cursor-turnend worktree pointer, and installs the global hook before pane creation (fail-closed).
  • bin/fm-cursor-turnend-hook.sh (new): idempotent install of firstmate-owned entries into the shared ~/.cursor/hooks.json for beforeSubmitPrompt (busy) and stop (idle/turn-end), plus a static guarded hook script and a private token registry. hooks.json is JSON with no comment markers, so firstmate entries are identified structurally by a command referencing the hook script, and the captain's own hooks and other keys are preserved; malformed/symlinked/non-object hooks.json is refused without a write. The installed hook is silent, always exits 0, and is guarded per task by matching the stdin payload workspace_roots[] (NOT cwd, because user hooks run from ~/.cursor) against the pointer+registry token. It dedupes stop by generation_id because an interrupted cursor turn fires stop twice (aborted then error) for one generation.
  • bin/fm-busy-lib.sh: new cursor-hook semantic busy source plus an OPEN fm_busy_cursor_verified gate (opened in this same change because the hook lifecycle was live-verified).
  • bin/fm-composer-lib.sh and bin/fm-tmux-lib.sh: handle cursor's novel dim prompt glyph U+2192 (an agent-glyph arm plus the empty-box geometry proof), so an idle cursor composer (the dim glyph and the 'Add a follow-up' placeholder, both SGR 2) classifies empty rather than as pending input.
  • bin/backends/tmux.sh: a cursor alive-set arm; deliberately NOT whitelisting bare node/MainThread (too broad). bin/fm-session-lock-lib.sh: cursor-agent added to the verified-harness identity lists as the full 'cursor-agent' name, not a bare 'cursor', so a firstmate fm-cursor-* script basename is never mistaken for the harness.
  • bin/fm-teardown.sh: removes the cursor pointer, registry entry, per-generation stop-dedupe dir, state token, and the .cursor/cli.json at every existing teardown site.
  • Attribution HARD GATE (AGENTS.md: never add an agent name as a commit co-author): cursor defaults attributeCommitsToAgent and attributePRsToAgent to true in the shared global ~/.cursor/cli-config.json, producing a Co-authored-by: Cursor trailer. Neutralized per worktree with a gitignored .cursor/cli.json that cursor reads by default and merges over the global (proven by cursor's own --disable-project-configs flag). This is deliberately NOT a destructive global edit and NOT a restore-on-teardown approach, because the global file is shared by concurrent workers and cursor rewrites it at runtime; the per-worktree override avoids that race and is removed with the worktree.
  • Docs: harness-adapters/SKILL.md records the verified cursor facts, docs/verification/runtime-backends.md records dated evidence pointing to data/cursor-verify/report.md, and AGENTS.md adds cursor to the verified-harness list (required for dispatchability). AGENTS.md size discipline was kept: pointers to reports/skill, not pasted evidence.

Deliberately OUT OF SCOPE (firstmate owns these after merge; do not flag as missing): config/crew-dispatch.json routing (that file is LOCAL and gitignored), and the downstream supervised cursor test-drive. Known limitation: the LIVE trailer-free-commit proof needs an interactive cursor worker a crewmate cannot launch in its own pane, so it is captured in that downstream test-drive; the neutralization mechanism itself is implemented and unit-tested.

Tests added mirror the grok/kimi adapters: new tests/fm-cursor-harness.test.sh (launch template, hook guard, busy lifecycle, stop double-fire dedupe, teardown, attribution cli.json, detection, session lock) plus cursor cases in tests/fm-composer-lib.test.sh, tests/fm-composer-ghost.test.sh, tests/fm-busy-adapter-wiring.test.sh, and tests/fm-tmux-agent-liveness.test.sh.

What Changed

  • Onboarded cursor-agent (Cursor Composer 2.5, non-fast) as an interactive tmux-pane crew harness driven like the grok/kimi adapters: added detection by cursor-agent process ancestry in fm-harness.sh, a cursor-agent --force --trust --model <m> "<brief>" launch template in fm-spawn.sh with cursor added to the spawn accept-lists, --model list, session-lock identity lists, and the tmux/backend alive-sets.
  • Added turn-lifecycle wiring via cursor's native hooks: a new fm-cursor-turnend-hook.sh idempotently installs firstmate-owned beforeSubmitPrompt (busy) and stop (idle) entries into the shared ~/.cursor/hooks.json (preserving other keys, refusing malformed/symlinked files), guarded per task by workspace_roots[] token matching and deduping the double stop-fire by generation_id; fm-busy-lib.sh gains the cursor-hook busy source behind an opened fm_busy_cursor_verified gate, and fm-composer-lib.sh/fm-tmux-lib.sh classify cursor's dim U+2192 prompt glyph as idle-empty.
  • Neutralized cursor's commit/PR co-author attribution with a gitignored per-worktree .cursor/cli.json override; fm-teardown.sh removes the cursor pointer, registry entry, stop-dedupe dir, state token, and cli.json. Documented the verified facts in the harness-adapters skill, runtime-backends.md, configuration.md, and AGENTS.md, and added tests/fm-cursor-harness.test.sh plus cursor cases across the composer, ghost, busy-wiring, and liveness test suites.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed
✅ **Rebase** - passed
✅ **Review** - completed
⚠️ **Test** - 1 info
✅ **Document** - passed
✅ **Lint** - passed
✅ **Push** - passed

Quidge added 3 commits August 4, 2026 22:18
Onboard cursor-agent as a directly-driven crew harness (grok/kimi Shape A:
interactive pane plus native beforeSubmitPrompt/stop user hooks), slotting into
the existing adapter machinery with no cursor-special path.

- fm-harness.sh: cursor process-ancestry detection.
- fm-spawn.sh: launch template (--force --trust --model, no effort flag, no
  trailing command so the pane reports cursor-agent), pre-pane hook install,
  busy-gen arming, the per-task registry entry and worktree pointer, and a
  per-worktree .cursor/cli.json that neutralizes commit/PR agent attribution
  without editing the shared global cli-config.json.
- fm-cursor-turnend-hook.sh (new): idempotent, foreign-preserving install of the
  firstmate-owned ~/.cursor/hooks.json entries plus a guarded hook that writes
  cursor-hook busy/idle events and dedupes the stop interrupt double-fire by
  generation_id.
- fm-busy-lib.sh: cursor-hook semantic source and the open fm_busy_cursor_verified gate.
- fm-composer-lib.sh and fm-tmux-lib.sh: the dim cursor composer glyph (U+2192).
- bin/backends/tmux.sh and fm-session-lock-lib.sh: cursor-agent liveness and identity.
- fm-teardown.sh: cursor pointer, registry entry, stop-dedupe dir, state token, and cli.json cleanup.
- harness-adapters/SKILL.md, docs/verification/runtime-backends.md, AGENTS.md:
  verified cursor facts and the verified-adapter list.

Tests: new tests/fm-cursor-harness.test.sh plus cursor cases in the composer,
busy-wiring, and tmux-liveness suites.
@Quidge
Quidge merged commit ff3532b into main Aug 4, 2026
13 checks passed
@Quidge
Quidge deleted the fm/cursor-harness branch August 4, 2026 23:29
Quidge added a commit that referenced this pull request Aug 5, 2026
…sor for crew-dispatch (#10)

* fix(cursor): repair non-functional attribution neutralization and register cursor for crew-dispatch

The freshly-landed cursor-agent adapter (PR #9) crashed at launch and could
not be routed via crew-dispatch. Two cursor-scoped, additive/corrective fixes:

Defect 1 (fatal): fm-spawn wrote a project .cursor/cli.json carrying
`{"version":1,"attribution":{...}}`. cursor-agent validates that project
config against a strict schema that accepts only `permissions`, so it rejected
`version`/`attribution` with "Unrecognized key(s)" and dropped the pane to a
bash prompt; the worker never started (the `attribution` block is only valid in
the GLOBAL ~/.cursor/cli-config.json, which cursor rewrites at runtime and
shares across workers). There is no CLI flag or env var to disable attribution.

Replace it with a per-task git commit-msg hook that strips cursor's
`Co-authored-by: Cursor <cursoragent@cursor.com>` trailer, reached only by the
cursor worker through an env-injected core.hooksPath (git resolves a linked
worktree's hooks from the shared common dir, so a per-worktree hooks dir would
not fire). The hook lives under state/, touches no cursor config, never races a
concurrent worker, preserves human co-authors, and is removed at teardown.

Defect 2 (minor): add cursor to the crew-dispatch verified-harness set in
fm-bootstrap so a config/crew-dispatch.json routing work to cursor validates
clean, and treat cursor like opencode/kimi in effort validation (it has no
effort flag). Scoped to crewmate/scout-facing lists only; the remote-secondmate
lists and secondmate-liveness gate correctly continue to omit cursor, which was
verified only as a local crewmate.

Tests: rework the cursor-harness attribution tests to cover the commit-msg hook
(trailer stripped, human co-authors and clean messages preserved) and the
core.hooksPath env wiring, and to assert the crash-inducing .cursor/cli.json is
no longer written; add crew-dispatch cases for cursor. Docs: correct the
attribution mechanism in harness-adapters and record the live crash/fix proof.

* no-mistakes: apply CI fixes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant