Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/391-lineage-only-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@agent-bundle/runtime": patch
"agent-bundle": patch
---

Make `request.lineage` the only identity-adjacent surface — parent conversation, root, and the parent-of-subagent chain — and stop reading operator identity anywhere: the Cursor `workspaceOpen` event-route validator no longer inspects `user_email` (the field passes through inside `native` untouched), and `actor` is documented as the HTTP-authenticated MCP client only. Bind a never-seen Cursor conversation to a pending `subagentStart` only when exactly one is pending in the same `workspace_roots`, and undo a blind binding (re-rooting anything started beneath it) when that conversation later carries a root-only event such as `beforeSubmitPrompt`, so a chat tab whose prompt predates the registry resolves as a root instead of another conversation's subagent. (#444)
4 changes: 2 additions & 2 deletions docs/audits/2026-09-02-agent-plugins-cursor-ide-proof.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,6 @@ are not provided (§9.1), and a plugin-relative `./` `command` is resolved
against the workspace folder instead of the plugin root (§7.2.1). Full
table, log excerpts, and 1440×900 captures:
[`2026-09-03-agent-plugins-cursor-ide-proof.md`](./2026-09-03-agent-plugins-cursor-ide-proof.md);
ready-to-submit vendor report:
[`2026-09-03-cursor-agent-plugins-feedback.md`](./2026-09-03-cursor-agent-plugins-feedback.md).
internal observations record (not submitted to Cursor, by maintainer decision):
[`2026-09-03-cursor-agent-plugins-observations.md`](./2026-09-03-cursor-agent-plugins-observations.md).
No capability row moved to `supported`.
5 changes: 3 additions & 2 deletions docs/audits/2026-09-03-agent-plugins-cursor-ide-proof.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,9 @@ correct; only the §7.2.1/§9 path contract is missing.
`mcp.evidence`) gain a dated 2026-09-03 line for the re-verification and the
three additional observations; no row moves to `supported` because nothing
was fixed.
- The vendor feedback that #426 tracks is written up ready to submit in
[`2026-09-03-cursor-agent-plugins-feedback.md`](./2026-09-03-cursor-agent-plugins-feedback.md).
- The observations #426 tracks are recorded as internal reference in
[`2026-09-03-cursor-agent-plugins-observations.md`](./2026-09-03-cursor-agent-plugins-observations.md);
the maintainer chose not to submit them to Cursor.
- The "re-run when a Cursor release notes placeholder expansion" item stays
time-gated on a Cursor release; the harness above (`/tmp/w426/iso-setup.sh`
shape: five probes + the emitted pack, reload, read the three log surfaces)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
# Cursor product feedback — Agent Plugins 1.0.0 `${PLUGIN_ROOT}` expansion (ready to submit)

Prepared 2026-09-03 for #426. Everything below was observed on the current
installed stable build and is reproducible with the plugin in "Minimal
repro" (one directory, four files — three for items 1–5 plus `mcp/launch.sh`
for item 6 — no dependencies beyond `node`). The maintainer submits
it; this repository cannot post on their behalf.

**Where to submit (pick one, template-compatible):**

- Bug report (preferred — every item below is a MUST violation with a
deterministic repro): <https://forum.cursor.com/c/support/bug-report/6> →
"New Topic" (direct link
<https://forum.cursor.com/new-topic?category=support/bug-report>). The
forum's bug template asks for product area, description, steps, expected
behaviour, screenshots, and the About-dialog version block; all of it is
filled in below. Reporting guidance: <https://cursor.com/help/troubleshooting/reporting-bugs>.
- Feature request, if triaged as "Agent Plugins §9 support" rather than a
defect: <https://forum.cursor.com/c/ideas/feature-requests/5>.
- Email fallback named by the reporting guide: `hi@cursor.com`.

Attach the two screenshots from
# Cursor observations — Agent Plugins 1.0.0 `${PLUGIN_ROOT}` expansion

Recorded 2026-09-03 for #426 as internal reference. Everything below was
observed on the current installed stable build and is reproducible with the
plugin in "Minimal repro" (one directory, four files — three for items 1–5
plus `mcp/launch.sh` for item 6 — no dependencies beyond `node`).

Maintainer decision (2026-09-03): this is **not** submitted to Cursor. The
record exists so the `portable` capability table and any future re-check of a
newer Cursor build have a precise baseline to compare against; the `portable`
rows stay `degraded` until a build is observed that expands the placeholders.

Screenshots of the two states (spec shape failing, proprietary placeholder
connected) are in
`docs/assets/agent-plugins-cursor-proof/2026-09-03-plugin-detail-spec-shape-error.png`
and `…/2026-09-03-plugin-detail-cursor-placeholder-connected.png`.

---

## Bug report body
## Observed behaviour

**Where does the bug appear:** Cursor IDE → MCP & tools (plugin loader for
**Where it appears:** Cursor IDE → MCP & tools (plugin loader for
Agent Plugins packages placed in `~/.cursor/plugins/local`).

**Version block (Menu → About Cursor):** Cursor 3.18.25, Linux x64 (deb,
Expand Down Expand Up @@ -163,7 +155,8 @@ starts, the marker shows `argv[1]` and `PROBE_ROOT` expanded to
mapping of `${PLUGIN_ROOT}`/`${PLUGIN_DATA}` (and the §9.1 variables, the
default `cwd`, and `./` command resolution) onto the pipeline that already
serves `${CURSOR_PLUGIN_ROOT}` when the package is an Agent Plugins package
(`$schema` under `https://agent-plugins.org/schemas/`).
(`$schema` under `https://agent-plugins.org/schemas/`) — this is what a
future build would need to do for the `portable` rows to move.

### Why it matters

Expand Down
16 changes: 9 additions & 7 deletions docs/audits/2026-09-03-cursor-plugin-hooks-registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,17 +220,19 @@ Decision implemented in this change:
and runs the emitted `install.mjs --mode marketplace` twice (`Staged`, then
`Already staged` with the same commit) and Doctor's `AB7324` guidance.

## 7. Product feedback recorded for Cursor
## 7. Documentation discrepancies recorded on #407 (internal; not submitted to Cursor)

Text filed as the #407 discrepancy note (URL: https://cursor.com/docs/hooks):
"The hooks documentation lists working directories for project, user,
Recorded as the #407 discrepancy note against https://cursor.com/docs/hooks;
by maintainer decision (2026-09-03) no feedback is submitted to Cursor, so
this stays an internal observation:
the hooks documentation lists working directories for project, user,
enterprise, and team hooks but not for plugin-delivered hooks; observed on
3.18.25 they run from the plugin root with `${CURSOR_PLUGIN_ROOT}` expanded.
On 3.16.21 (remote server build) plugin-declared `preToolUse`/`postToolUse`/
`stop` hooks did not execute while the same commands in `~/.cursor/hooks.json`
did; the CLI changelog of August 11, 2026 records a fix for plugin hooks in
the CLI. Please document the plugin hook working directory and the minimum
build that delivers plugin hooks for tool events, and expose a
the CLI. Undocumented as of this build: the plugin hook working directory, the
minimum build that delivers plugin hooks for tool events, and any
non-interactive `cursor-agent plugin marketplace add` for local repositories
(`file://`) so packs can be marketplace-installed without the Customize
folder picker."
(`file://`), so packs cannot be marketplace-installed without the Customize
folder picker.
86 changes: 68 additions & 18 deletions docs/audits/2026-09-03-host-lineage-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,28 +112,30 @@ from the rollout filename in `transcript_path`):

### Cursor 3.18.25 (desktop)

| Event (native) | `conversation_id`* | `session_id`* | `generation_id` | `subagent_id` / `tool_call_id` | `parent_conversation_id` | `is_parallel_worker` | `user_email` | Other |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| beforeSubmitPrompt | root | = conversation_id | per generation | — | — | — | yes | `prompt`, `attachments`, `model`, `model_id`, `composer_mode`, `cursor_version`, `workspace_roots`, `transcript_path: null` |
| preToolUse / postToolUse (root) | root | = conversation_id | per generation | `tool_use_id` (`call-<uuid>-<n>\nfc_<id>_<k>` for model tools, plain uuid for `Shell` and `MCP:*`) | — | — | yes | `tool_name` (`Read`, `Grep`, `Shell`, `Write`, `Task`, `MCP:dump`, `MCP:probe`), `tool_input`, `tool_output` (JSON string, Post), `duration` (Post), `cwd: ""` on `Shell`, `model: ""` on `Task` |
| preToolUse / postToolUse (inside a subagent) | **a new conversation id** (`bf617dfd-…`) | = that new id | new | tool_use_id | **absent** | **absent** | yes | **nothing in the payload names the parent** |
| preToolUse / postToolUse (inside the nested agent) | another new id (`46efda32-…`) | = id | new | | absent | absent | yes | |
| subagentStart | **the parent's** conversation id | = parent | **equals the conversation id** (not a generation) | `subagent_id` = `tool_call_id` = the parent's `Task` `tool_use_id` (a two-line composite) | = conversation_id | `false` | yes | `subagent_type`, `subagent_model`, `task` (full prompt), parent `transcript_path`; **the child's conversation id is not included** |
| subagentStop | parent's | = parent | = conversation id | `subagent_id` | = conversation_id | — | yes | `status`, `duration_ms`, `message_count`, `tool_call_count`, `loop_count`, `task`, `description`, `agent_transcript_path: null` |
| stop | root | = root | root generation | — | — | — | yes | `status`, `loop_count`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens`, root `transcript_path` |
| Event (native) | `conversation_id`* | `session_id`* | `generation_id` | `subagent_id` / `tool_call_id` | `parent_conversation_id` | `is_parallel_worker` | Other |
| --- | --- | --- | --- | --- | --- | --- | --- |
| beforeSubmitPrompt | root | = conversation_id | per generation | — | — | — | `prompt`, `attachments`, `model`, `model_id`, `composer_mode`, `cursor_version`, `workspace_roots`, `transcript_path: null` |
| preToolUse / postToolUse (root) | root | = conversation_id | per generation | `tool_use_id` (`call-<uuid>-<n>\nfc_<id>_<k>` for model tools, plain uuid for `Shell` and `MCP:*`) | — | — | `tool_name` (`Read`, `Grep`, `Shell`, `Write`, `Task`, `MCP:dump`, `MCP:probe`), `tool_input`, `tool_output` (JSON string, Post), `duration` (Post), `cwd: ""` on `Shell`, `model: ""` on `Task` |
| preToolUse / postToolUse (inside a subagent) | **a new conversation id** (`bf617dfd-…`) | = that new id | new | tool_use_id | **absent** | **absent** | **nothing in the payload names the parent** |
| preToolUse / postToolUse (inside the nested agent) | another new id (`46efda32-…`) | = id | new | | absent | absent | |
| subagentStart | **the parent's** conversation id | = parent | **equals the conversation id** (not a generation) | `subagent_id` = `tool_call_id` = the parent's `Task` `tool_use_id` (a two-line composite) | = conversation_id | `false` | `subagent_type`, `subagent_model`, `task` (full prompt), parent `transcript_path`; **the child's conversation id is not included** |
| subagentStop | parent's | = parent | = conversation id | `subagent_id` | = conversation_id | — | `status`, `duration_ms`, `message_count`, `tool_call_count`, `loop_count`, `task`, `description`, `agent_transcript_path: null` |
| stop | root | = root | root generation | — | — | — | `status`, `loop_count`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens`, root `transcript_path` |

Not delivered to the plugin in this run: `sessionStart` (never fired for the
plugin; the sibling raw-hooks probe on the same build saw none either),
`workspaceOpen` (fired to user-level hooks only), `sessionEnd` (nothing arrived
when the window was closed with the agent idle), `postToolUseFailure`,
`preCompact`. `preToolUse` was delivered twice for some `Read`/`Grep` calls
with the same `tool_use_id` (pairs 3/4, 9/10, … in the fixture).
`preCompact`. §9 below shows that on a real desktop `workspaceOpen` and
`sessionEnd` *do* reach plugin-scoped hooks and only `sessionStart` never
does. `preToolUse` was delivered twice for some `Read`/`Grep` calls with the
same `tool_use_id` (pairs 3/4, 9/10, … in the fixture).

Payload excerpt (subagentStart — the only place the parent link exists, and it
does not name the child conversation):

```json
{"conversation_id":"b60ae0c1-2f85-4c4d-b3e5-b512f9b06e4c","generation_id":"b60ae0c1-2f85-4c4d-b3e5-b512f9b06e4c","model":"default","subagent_id":"call-2ec9530d-b502-4c4f-8a6e-63f0bf7ebc9a-29\nfc_49466487-df47-9fb4-8b10-079ee845fb97_0","subagent_type":"general-purpose","task":"…","parent_conversation_id":"b60ae0c1-2f85-4c4d-b3e5-b512f9b06e4c","tool_call_id":"call-2ec9530d-…\nfc_49466487-…_0","subagent_model":"default","is_parallel_worker":false,"session_id":"b60ae0c1-2f85-4c4d-b3e5-b512f9b06e4c","hook_event_name":"subagentStart","cursor_version":"3.18.25","workspace_roots":["/tmp/host-test/cursor-workspace"],"user_email":"probe@example.invalid","transcript_path":"…/agent-transcripts/b60ae0c1-…/b60ae0c1-….jsonl"}
{"conversation_id":"b60ae0c1-2f85-4c4d-b3e5-b512f9b06e4c","generation_id":"b60ae0c1-2f85-4c4d-b3e5-b512f9b06e4c","model":"default","subagent_id":"call-2ec9530d-b502-4c4f-8a6e-63f0bf7ebc9a-29\nfc_49466487-df47-9fb4-8b10-079ee845fb97_0","subagent_type":"general-purpose","task":"…","parent_conversation_id":"b60ae0c1-2f85-4c4d-b3e5-b512f9b06e4c","tool_call_id":"call-2ec9530d-…\nfc_49466487-…_0","subagent_model":"default","is_parallel_worker":false,"session_id":"b60ae0c1-2f85-4c4d-b3e5-b512f9b06e4c","hook_event_name":"subagentStart","cursor_version":"3.18.25","workspace_roots":["/tmp/host-test/cursor-workspace"],"user_email":"","transcript_path":"…/agent-transcripts/b60ae0c1-…/b60ae0c1-….jsonl"}
```

## 2. Which id a subagent's own events carry
Expand Down Expand Up @@ -208,9 +210,11 @@ gives shell commands the agent runs `CURSOR_CONVERSATION_ID`/`CURSOR_REQUEST_ID`
| Codex | Yes — `session_id` is the root thread on every event, and `_meta.x-codex-turn-metadata.session_id` on MCP calls | Yes on MCP calls (`parent_thread_id`); on hooks only through the registry (or the parent rollout at `SubagentStop`) |
| Cursor | Only through the registry — a child's payload carries neither root nor parent | Only through the registry (ordering-bound) |

**Actor principal facts** (for #391): Cursor delivers `user_email` on every hook
payload and `CURSOR_USER_EMAIL` in the hook environment; Claude and Codex
deliver no user identity to hooks or MCP servers.
**Operator identity is out of scope.** The maintainer decided on 2026-09-03
(#391, closed as not planned) that agent-bundle will not derive or surface who
the human behind a host session is; the fields some hosts send for that are
passed through inside `native` unread, and `request.lineage` — parent session,
root, and the parent-of-subagent chain — is the only identity-adjacent surface.

## 6. Framework consequences landed with this audit

Expand All @@ -228,10 +232,11 @@ deliver no user identity to hooks or MCP servers.

| Gap | Host | Evidence | Status |
| --- | --- | --- | --- |
| No parent id on `SubagentStart`; child events carry no parent | Claude, Codex | §1, §2 | Inferred from the open spawn tool call; filed as a host request |
| Child conversation id absent from `subagentStart`; child events carry no parent/root | Cursor | §1, §2 | Ordering-bound in the registry; ambiguous for parallel workers; filed |
| No parent id on `SubagentStart`; child events carry no parent | Claude, Codex | §1, §2 | Inferred from the newest unclaimed spawn call under the same root; refused when two parents have unclaimed spawns; filed as #422 / #423 |
| Child conversation id absent from `subagentStart`; child events carry no parent/root | Cursor | §1, §2 | Bound by elimination in the registry (single pending start per workspace); refused while ambiguous for parallel workers; filed as #424 |
| `_meta` carries no conversation/tool-call id | Cursor | §3 | Hook-correlated only; filed |
| `sessionStart`, `workspaceOpen`, `sessionEnd` not delivered to plugin hooks | Cursor | §1 | Recorded; owned by the Cursor installer/emitter lane for follow-up |
| `sessionStart` never dispatched on the desktop (`workspaceOpen`/`sessionEnd` are) | Cursor | §1, §9 | Host-side (#424 gap 4); lineage never depends on it to establish a root |
| Roots first seen on a tool hook (Cursor restart or plugin load mid-conversation) | Cursor | §9 | Ours: workspace-scoped child binding plus correction (subtree re-rooted) when a bound conversation later carries a root-only event (`beforeSubmitPrompt`, `stop`, `sessionEnd`, `preCompact`) |
| Cursor CLI not exercised | Cursor | table above | Needs a signed-in `cursor-agent`; not attempted on the operator's account |
| ~~Claude session used a scripted model~~ | Claude | §8 | Closed 2026-09-03: two live-model sessions replace the stand-in fixture; every stand-in claim held, see §8 |
| Claude `PostToolUse(Agent).tool_response.agentId` not consumed by the registry | Claude | §1, §2 | The registry claims the newest unclaimed spawn under the root at `SubagentStart` and marks same-parent sibling cohorts `siblingsUncertain`; the parent's `Agent` PostToolUse could later firm those up. Not needed for either live run (spawns were sequential); left as an improvement |
Expand Down Expand Up @@ -263,3 +268,48 @@ No lineage or projection code change was needed: the registry replay test
(`packages/rsc-runtime/tests/lineage-registry.test.ts`) now runs against both
live fixtures and passes unchanged apart from asserting the new
`tool_response.agentId` host fact.

## 9. Cursor desktop hooks-service evidence (added 2026-09-03)

Cursor desktop keeps a per-window hooks log
(`~/.config/Cursor/logs/<launch>/<window>/output_*/cursor.hooks.workspaceId-*.log`)
that prints `Hook step requested: <event>` for **every** step before it looks
up declared hooks — 58,717 `preToolUse` steps appear with no hook declared for
them — so an absent step is non-dispatch, not a registration problem. The
retained logs on the maintainer's machine (cursor_version 3.14.7 for
2026-08-14 → 2026-08-25, 3.18.25 on 2026-09-03; 89,219 steps; 35
conversations; a local plugin declaring `sessionStart`, `sessionEnd`,
`workspaceOpen`, `stop`, `postToolUse`, `preCompact`, `afterFileEdit`,
`afterShellExecution`) show:

| Step | Requested | Delivered to the plugin-scoped hook (`from claude-plugin config`) |
| --- | --- | --- |
| `preToolUse` | 58,717 | n/a (not declared) |
| `postToolUse` | 29,308 | yes |
| `afterShellExecution` | 887 | yes |
| `afterFileEdit` | 270 | yes |
| `workspaceOpen` | 12 | yes — sessionless envelope |
| `stop` | 8 | yes |
| `beforeSubmitPrompt` | 7 | yes — `prompt`, `attachments[]`, `composer_mode`, `generation_id` |
| `sessionEnd` | 6 | yes — `reason: window_close`, `final_status: none`, `duration_ms`, `is_background_agent: false`, `session_id` = `conversation_id`, `generation_id: ""`, `transcript_path: null` |
| `preCompact` | 4 | yes |
| **`sessionStart`** | **0** | — |

Consequences for lineage:

- `workspaceOpen` and `sessionEnd` are confirmed plugin-scoped deliveries on
the desktop; the §1 run missed them because the Xvfb CLI session never
closed a window. Only `sessionStart` remains a host gap (#424 gap 4).
- Only 7 of the 35 conversations ever produced `beforeSubmitPrompt`; 28 were
first seen on `postToolUse`/`preToolUse`/`afterShellExecution` (the log
starts mid-conversation after a Cursor restart, or the plugin loaded
mid-conversation). `transcript_path` is `null` on 98.5 % of desktop events,
so it cannot mark roots either. A registry that binds the next unseen
conversation to a lone pending `subagentStart` can therefore mis-bind a
second chat tab; the registry now scopes binding to the same
`workspace_roots` and undoes a binding when the bound conversation later
receives `beforeSubmitPrompt`, which a subagent never does.
- No `Task` tool call appears in these desktop logs, so desktop
`subagentStart`/`subagentStop` delivery is unobserved here; the §1 CLI
capture remains the evidence for the subagent families.

Loading
Loading