Cursor harness operations
This file is the Cursor-facing entry point for Yoke. It references the shared bootstrap contract and lists the safe command surface for Cursor sessions.
For the full project rules, read AGENTS.md — the harness-neutral shared doctrine file. Cursor loads AGENTS.md natively (including nested per-directory AGENTS.md), so everything there applies to Cursor sessions unless noted otherwise below. Yoke writes shared doctrine only to AGENTS.md. Project-specific harness content stays outside the managed block or in the harness’s native configuration. See native discovery for version floors and loading checks.
Each standing rule in AGENTS.md names a deep home under .yoke/docs/agents/rules/ carrying the reasoning, recovery paths, and worked failure modes behind it; AGENTS.md lists which file covers which operation. Read the one that governs an operation before performing it, and read an operation's own --help for its variants and flags.
The ## Simplify — three-axis doctrine section in AGENTS.md defines the shared reuse / quality / efficiency vocabulary; this file does not duplicate it. The doctrine is Yoke-owned and harness-neutral — do not treat any Claude-only built-in as a dependency.
Bootstrap
Cursor loads native AGENTS.md; its opening hook supplies orientation via
additional_context. The generated main_agent packet teaches role-needed
decisions and executable recipes, with explicit topic depth discoverable via
yoke packets render --help. harness_contract separately owns substrate
facts. See the packet reference.
.cursor/hooks.json is the primary project-policy hook owner in Cursor. Cursor may also list compatible entries discovered in the regular .claude/settings.json file when third-party config import is enabled; with matched Cursor process/payload provenance they defer only to a valid matching native owner and otherwise remain the compatibility path. For stop/sessionEnd the order is native project hook, machine-local backstop, then Claude compatibility, so one owner dispatches even after a worktree is deleted or a project config is missing. Project refresh and source-dev setup materialize both Cursor-scanned files and migrate legacy in-repo symlinks.
Repo-local skill discovery
Yoke skills live canonically in .agents/skills/yoke/. Cursor discovers that tree natively (measured on Cursor IDE 3.14+ and cursor-agent 2026.07+), so no .cursor/skills mirror is required for ordinary Yoke work. Skills surface in the / menu and via description-based invocation; .claude/skills/yoke is Claude’s required native discovery link to the same source; it contains no regular copies.
Surfaces
Cursor sessions run on two surfaces with different hook coverage:
| Surface | Discriminator | Hook coverage |
|---|---|---|
| IDE agent chat | CURSOR_INVOKED_AS unset in hook env |
Full event set, including beforeSubmitPrompt, stop, subagentStart/subagentStop, afterFileEdit |
cursor-agent CLI (-p non-interactive) |
CURSOR_INVOKED_AS set: the launcher exports the name it ran as (cursor-agent or agent) |
Lifecycle + tool events only; no prompt-submit, stop, or subagent lifecycle events |
Orientation and policy enforcement anchor on events both surfaces fire: sessionStart (orientation via additional_context) and beforeShellExecution/preToolUse (gates — a hook deny holds even under --force).
Identity
Cursor exports no session-id environment variable. Identity facts:
| Signal | Source | Purpose |
|---|---|---|
session_id / conversation_id |
every hook payload (stdin) | Session identity and native-resume conversation (identical values on the top-level session) |
CURSOR_TRANSCRIPT_PATH |
hook process env | Top-level (container) session recovery — points at the main session's transcript even inside subagent hooks |
parent_conversation_id |
subagentStart/subagentStop payloads |
Explicit subagent → container lineage |
CURSOR_CONVERSATION_ID |
agent shell env | The conversation that spawned a shell — a subagent shell carries its OWN id, so this is not a session id until mapped |
YOKE_EXECUTOR=cursor |
pinned in the generated hook command | Family attribution regardless of env inheritance |
model / model_id |
every hook payload | Model attribution — Cursor multiplexes providers, so the payload is the only truthful model source |
Yoke's container model applies: only the top-level session registers as a harness_sessions row; subagent activity (which arrives under per-subagent session ids) folds into that container. The same fold applies when Cursor remounts a chat onto a linked Yoke worktree (new conversation id under .worktrees/<lane>): the new conversation aliases to the session that holds the active work claim for that lane, via cursor-session-map, instead of minting competing authority.
Commands the agent runs in a shell resolve identity through that same container. Ambient identity never reads a Cursor host anchor — one cursor-agent pid may host multiple conversations — and CURSOR_CONVERSATION_ID alone names a conversation, not a registered session. So every hook records its conversation_id → container session pairing under <machine-home>/cursor-session-map/, and a shell resolves its own conversation id through that recording. Cursor CLI registration separately records the worker pid and start time as liveness-only evidence; a shared-pid contention marker identifies no session, while an uncontended record lets the relay prove a terminated worker dead. A conversation no hook has recorded resolves to nothing rather than to a guess.
The conversation map is established on the first client hook (preToolUse / beforeShellExecution / sessionStart), not only sessionStart. Stay-on-main with absolute .worktrees/<lane>/... paths is the supported posture (same conversation id). Switching back to main with a new conversation id and no transcript / parent / already-mapped alias is a new session — do not guess a unique live claim holder on that repo.
Do not root the Cursor chat on a Yoke worktree. Keep the agent on the main project checkout and use absolute worktree_path values for lane work. Do not call move_agent_to_root into .worktrees/.... Remounting triggers the conversation remap above; after merge removes the lane, Cursor's sticky cwd/root also leaves Shell failing with ENOENT on the deleted path. Pass an explicit live working_directory when needed. Yoke still owns worktree placement (cursor-agent -w / --worktree-base stay unused).
Stop/sessionEnd must still run when the project root is gone. Cursor project hooks spawn with cwd = project root; a deleted .worktrees/<lane> makes that spawn fail, so session-end cleanup never reaches Yoke. Lifecycle hook commands peel a missing worktree path to the parent checkout and emit Cursor-safe {} on allow. Yoke's machine-wide hooks in ~/.cursor/hooks.json (written by setup, refreshed on Cursor hook traffic) spawn from ~/.cursor, which always exists, so they still run there; while the project's .cursor/hooks.json is readable they step aside and the project hook runs instead. Outside any project they carry registration, prompt delivery, the Bash machine guards and mid-turn messages on postToolUse (Cursor's pre-tool hooks cannot carry context).
What Cursor does NOT own
Cursor is a harness adapter, not a replacement for Yoke core. Routing decisions, canonical telemetry, ownership truth, and safety enforcement remain Yoke-core responsibilities. Cursor hooks are enhancements and never the sole safety layer. Cursor-native features that overlap Yoke-owned mechanics stay unused by Yoke flows: cursor-agent's worktree flags (-w, --worktree-base) — Yoke owns worktree placement; Cloud Agent handoff (&) — Yoke sessions are local. Cursor does not reliably continue a model turn after a denied stop, so the manifest declares that limitation and Yoke allows Stop while recording live-claim work durably for recovery. Print mode (cursor-agent -p, every relay launch and resume) fires no stop at all; its turn ends with the process's sessionEnd, which the manifest declares as cursor-cli's turn_end_event, so that same recording runs there.
Approvals and the network sandbox
Cursor decides whether an agent command runs unprompted at three layers. Two are project files Yoke installs and keeps merged; the third is machine-level. Setup always selects Run Everything there, so commands run without asking even before a project exists.
| Layer | Where | Who owns it |
|---|---|---|
| Command approvals | .cursor/cli.json → permissions.allow |
Yoke installs and merges its region |
| Network sandbox | .cursor/sandbox.json → networkPolicy |
Yoke installs and merges its region |
| Approval / execution mode | ~/.cursor/cli-config.json → approvalMode, sandbox.mode |
Yoke setup always sets unattended values |
Recommended posture: zero prompts. Set Execution mode to Run Everything. That matches how Yoke machines already run every other harness, and the rationale is the same: Yoke owns enforcement through its PreToolUse hook chain and lint fleet, so a harness approval prompt is redundant friction rather than a safety layer. Hooks keep firing in this mode — a hook deny holds even under --force.
Confirm your chosen mode on the first session of a new machine: run one network-touching yoke read and check that it completes with no prompt and that hook telemetry recorded the call. yoke doctor reports the machine-level posture it can see (HC-harness-unattended-posture) and names the missing setting. The session check reads config for cursor-cli.
cursor-desktop reports HARNESS_POSTURE_CONFIRM_REQUIRED as a WARN:
Confirm Cursor Settings → Agents → Run Everything is on. Once you confirm,
onboarding may continue; the CLI file does not prove the desktop app’s mode.
Do not request full-network permission. Cursor treats an explicit required_permissions: full_network request as an escalation and prompts even for hosts the network policy already allows. Once .cursor/sandbox.json allows the control-plane origins, retry inside the sandbox instead — requesting the escalation causes the very per-command prompting these files exist to remove.
Explicit non-choices. The all permission tier is never the answer: it disables the sandbox wholesale, filesystem included, which is strictly broader than the zero-prompt posture needs. Yoke's installed regions default networkPolicy.default to deny and allow only this machine's configured control-plane and GitHub endpoints — resolved from machine config, so a self-hosted or differently-tenanted installation allows its own origins.
Both files are regular files, never symlinks (Cursor refuses project config paths containing symlinks). Yoke unions its entries in and never removes or reorders yours; yoke project install — or yoke dev setup in a Yoke source checkout — reapplies them idempotently, and HC-cursor-permission-config reports a missing or emptied region.
Operational cautions
.cursor/cli.jsonrequirespermissions.allow(an allow-less deny-only file aborts every run before the agent starts) — the same all-or-nothing failure class as Claude'ssettings.json.- Interactive
cursor-agentgates on a Workspace Trust prompt; automation passes--trust. Non-interactive-pruns do not prompt. - The IDE's bundled
cursor agentlauncher auto-installs thecursor-agentbinary when absent; install it deliberately during onboarding rather than as a side effect.
Lifecycle & Routing
The canonical lifecycle guide is .yoke/docs/workflows/lifecycle.md. For a live item, read yoke workflows item get PREFIX-N then yoke workflows version get WORKFLOW VERSION; the pinned definition is the source of truth for which executor owns the current stage. Frontier computation lives in .yoke/docs/strategy/charge-frontier.md. Yoke core derives Cursor's supported-path set server-side from the shared registry plus any limitations declared in the Cursor manifest; the adapter does not self-report capabilities.