Skip to content

feat(lineage): request.lineage is the only identity-adjacent surface; scoped, self-correcting Cursor child binding - #444

Merged
ScriptedAlchemy merged 4 commits into
mainfrom
chore/lineage-only-identity
Sep 3, 2026
Merged

feat(lineage): request.lineage is the only identity-adjacent surface; scoped, self-correcting Cursor child binding#444
ScriptedAlchemy merged 4 commits into
mainfrom
chore/lineage-only-identity

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

Maintainer decision (2026-09-03): agent-bundle will not surface operator/user identity. #391 (actor principal for event scopes) is closed as not planned; the only identity-adjacent question the framework answers is lineage — who a request's parent conversation is, what its root is, and, for subagents, the parent-of-subagent chain — on request.lineage.

Identity removal

  • packages/agent-bundle/src/events/projection.ts: the Cursor workspaceOpen validator no longer inspects user_email (the only framework code that read it). The field passes through inside native untouched; the test now proves the validator ignores any value there.
  • hook-contract.ts lifecycle-replay fixture drops user_email: null (tests updated).
  • Docs (docs/entry-conventions.md, routes/public.ts, website mcp.mdx en/zh): actor is the HTTP-authenticated MCP client and nothing more; request.lineage is documented as the answer to "who is my parent / root / are we a subagent"; there is deliberately no operator identity axis.
  • AgentRequestContext is unchanged (no principal slot ever existed on main; actor stays as the HTTP authInfo.clientId axis that Add recipient-aware notices and capability-gated delivery #99 recipient matching uses). No request-store version bump is needed.

Lineage quality (#422 / #423 / #424)

Re-examined with the captures plus new host evidence: Cursor desktop's own hooks-service logs (~/.config/Cursor/logs/*/cursor.hooks.*.log, 89,219 steps, 35 conversations, 3.14.7 + 3.18.25). Findings recorded in docs/audits/2026-09-03-host-lineage-matrix.md §8:

Registry changes (@agent-bundle/runtime minor, lineage/state.ts + registry.ts):

  • Workspace-scoped binding: a never-seen Cursor conversation binds only when exactly one start is pending in the same workspace_roots (digest stored on nodes, never the paths). Two windows sharing one durable registry never bind each other's children; a payload without roots keeps the single-pending rule.
  • Self-correction: when a blind-bound conversation later receives beforeSubmitPrompt (subagents never do), childUnbound returns the child to pending and the conversation becomes the root it is; an already-stopped mis-bound child stays a finished node.
  • Five new registry tests; existing survivor-binding test (two pending → one stops → bind) still covers parallel workers.

Capability tables (Claude/Codex/Cursor) now name the host trackers in each degraded lineage reason and carry the desktop evidence. docs/audits/2026-09-03-host-lineage-observations.md records each host gap as an internal reference (lineage-only); by maintainer decision nothing is submitted to any vendor, and the former Cursor ${PLUGIN_ROOT} report is likewise kept as 2026-09-03-cursor-agent-plugins-observations.md.

Tracker bookkeeping (done)

#391 closed (not planned); #424 re-titled and re-scoped (identity fields stripped, ask 4 narrowed to sessionStart); #422/#423 given an explicit lineage-only scope and precise refusal semantics; dated pointer comments on #233, #223, #107, #189, #99.

Verification

  • pnpm typecheck green; pnpm build green (publint passed); rslint clean on touched files.
  • Unit: lineage-registry, agent-request, event-project, adapter-capability-states — 118 passed. Route-unit: lifecycle-replay, event-project — 19 passed. Integration: hooks.test.ts — 27 passed.

Closes nothing automatically; #422/#423/#424 stay open as host-integration trackers.

@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6bea637

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@agent-bundle/runtime Patch
agent-bundle Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-03T20:41:06.949483Z 0d4154f Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 3, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@444
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@444
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@444

commit: 6bea637

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bed5afc5b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +472 to +473
if (host === 'cursor' && CURSOR_USER_ONLY_EVENTS.has(event) && carrier.conversation !== undefined) {
await correctMisboundChild(carrier.conversation, observedAt, keys);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Correct misbindings before processing every root-only event

When an existing Cursor chat is blindly bound to another root's pending child and is then closed before submitting another prompt, its session/end is not corrected by this prompt-only gate. Because observeSessionEnd runs first, it follows the erroneous child node's root and stops and retires the unrelated parent session and all its descendants. Apply correction before retirement for session/end and any other event that only a root conversation can emit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c11d97. The correction now runs before the event dispatch switch and fires on every root-only Cursor event (CURSOR_ROOT_EVENTS: prompt/submit, stop, session/end, compact/*), not just prompt/submit, so a session/end on a misbound chat retires that chat and never the parent it was filed under. Test: "corrects a blind binding before session/end so the misbound chat retires itself, not the parent it was filed under" asserts the original root, its pending child and pendingChildren are untouched and the real child still binds afterwards.

Comment on lines +171 to +174
const { [conversation]: _moved, ...rest } = state.nodes;
return {
...state,
nodes: { ...rest, [subagentId]: { ...bound, id: subagentId } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-root descendants when undoing a blind binding

If the misbound conversation starts a subagent before its next prompt, childUnbound moves only the conversation node back to the original pending ID. The newly started descendants still reference the corrected conversation as their parent but retain the unrelated root and inflated depth; their pending children can then remain ambiguous with the restored pending child, and session retirement targets the wrong tree. Re-root or otherwise reconcile nodes and correlation state created beneath the conversation when correcting it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c11d97. childUnbound now walks the remaining nodes and re-roots every descendant of the corrected conversation (root → the conversation, depth rebased by the misbound depth), and rewrites root on openCalls/pendingSpawns belonging to that subtree. Test: "re-roots what the misbound conversation started beneath it when the binding is undone" covers a grandchild bound beneath the misbound chat, an open MCP call resolving with the corrected root, and session/end on the corrected root retiring only its own subtree. Pending children beneath the corrected conversation stay pending alongside the restored one; if both are live in one workspace the next never-seen conversation is refused as ambiguous rather than guessed.

Comment thread .changeset/391-lineage-only-identity.md Outdated

`request.lineage` is the only identity-adjacent surface: who a request's parent conversation is, what its root is, and — for subagents — the parent-of-subagent chain. The framework no longer reads, validates, or surfaces operator identity anywhere: the Cursor `workspaceOpen` envelope validator stops inspecting `user_email` (the field still passes through inside `native` untouched), the dev-playground lifecycle fixture drops it, and the docs describe `actor` as the HTTP-authenticated MCP client and nothing more (#391, closed as not planned).

Cursor child binding in the lineage registry is now scoped and self-correcting: a never-seen conversation on a tool hook binds to a pending `subagentStart` only when exactly one is pending in the same `workspace_roots` (nodes record a digest of the roots, never the paths), so two windows sharing one durable registry never bind each other's children; and a blind binding is undone when the bound conversation later receives `beforeSubmitPrompt`, which a subagent never does, so a chat tab whose prompt predates the registry becomes the root it is while the pending child waits for its real conversation. The `LineageNode` schema gains an optional `workspace` field and the journal gains a `childUnbound` event. Capability tables cite the host trackers (#422/#423/#424) and the Cursor desktop hooks-service evidence (`workspaceOpen` and `sessionEnd` reach plugin-scoped hooks; `sessionStart` is never dispatched on the desktop).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rewrite the changeset as the required imperative summary

The changeset ends with implementation and evidence details rather than the required PR-number suffix, and neither paragraph is an imperative user-facing summary. Condense it into the behavior users receive, name the affected public surface, and end it with (#<PR>) as required.

AGENTS.md reference: AGENTS.md:L57-L58

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewritten in 6c11d97 as one imperative user-facing summary naming request.lineage, the Cursor workspaceOpen validator / user_email, actor, and the binding rule, ending with (#444). Also corrected the bump: both packages are patch — the runtime change is additive (optional workspace node field, new childUnbound event), and AGENTS.md reserves minor for breaking pre-1.0.

@ScriptedAlchemy
ScriptedAlchemy force-pushed the chore/lineage-only-identity branch from 7bed5af to 6c11d97 Compare September 3, 2026 20:30
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c11d9739c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +460 to +461
if (host === 'cursor' && CURSOR_ROOT_EVENTS.has(event) && carrier.conversation !== undefined) {
await correctMisboundChild(carrier.conversation, observedAt, keys);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve lineage for corrected session-end events

When a blind-bound conversation's first root-only event is session/end, this call dispatches childUnbound, which removes state.nodes[conversation]; observeSessionEnd then retires its descendants, and the later event !== 'session/end' guard skips recreating the proven root. Consequently observe() returns id-not-resolvable for this session-end event, unlike session-end events for roots already known to the registry and despite docs/entry-conventions.md stating that correction makes the conversation a root. Preserve an ephemeral lineage result or materialize the root as stopped before retiring it.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0d4154f. correctMisboundChild now dispatches nodeStarted for the proven root right after childUnbound, carrying the bound node's generation, workspace and first-seen time, so the correcting event resolves against a real root node like any known root's would. For session/end that root is then stopped and retired by observeSessionEnd, leaving a stopped depth-0 node that retention prunes — same shape as a known root's session end. The session/end test now asserts observe() returns { conversation: 'other-root', depth: 0, root: 'other-root' } and the node is materialized as stopped with its original startedAt.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 0d4154f869

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ScriptedAlchemy and others added 4 commits September 3, 2026 20:46
…e; scope and self-correct Cursor child binding

Maintainer decision (2026-09-03, #391 closed as not planned): agent-bundle does
not surface operator/user identity. Remove the only framework code that read
it (the Cursor workspaceOpen `user_email` validation; the field passes through
inside `native` unread), drop it from the dev-playground lifecycle fixture, and
rewrite the docs so `actor` is the HTTP-authenticated MCP client and nothing
more, while `request.lineage` answers "who is my parent / root / are we a
subagent".

Lineage registry (Cursor): bind a never-seen conversation to a pending
subagentStart only when exactly one is pending in the same workspace_roots
(nodes carry a digest, never the paths), and undo a blind binding when the
bound conversation later receives beforeSubmitPrompt, which a subagent never
does. Cursor desktop hooks-service logs show 28 of 35 conversations first seen
on a tool hook and sessionStart never dispatched, so roots cannot rely on it.

Capability tables cite #422/#423/#424 and the desktop evidence (workspaceOpen
and sessionEnd do reach plugin-scoped hooks); the lineage matrix gains §8 and
drops the identity paragraph; vendor feedback drafts added.
…ndor feedback

Maintainer decision (2026-09-03): nothing is submitted to Cursor, Anthropic,
or OpenAI. The two "ready to submit" feedback drafts become observation
records that describe each host gap and what a future build would need to
carry for the lineage/portable capability rows to move; the audits,
capability evidence lines, and #424 wording follow ("gap", not "ask").

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ScriptedAlchemy
ScriptedAlchemy force-pushed the chore/lineage-only-identity branch from 0d4154f to 6bea637 Compare September 3, 2026 21:29
@ScriptedAlchemy
ScriptedAlchemy merged commit 2841419 into main Sep 3, 2026
13 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the chore/lineage-only-identity branch September 3, 2026 22:00
ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
Maintainer decision (2026-09-03): no feedback is submitted to any host
vendor, and the repo keeps no vendor-addressed write-ups. Delete the two
documents #444 had renamed from `*-feedback.md` to `*-observations.md`,
drop the vendor-addressed "documentation discrepancies" section from the
hooks-registration audit, and repoint the two IDE-proof notes that linked
the deleted files. Engineering audits (lineage matrix, hooks-registration
evidence, IDE proofs) stay.
ScriptedAlchemy added a commit that referenced this pull request Sep 4, 2026
…host-test, mcp-app, skills-starter (#471)

* chore(examples/host-test): record request.lineage through the public context member

request.lineage is a first-class Observed<AgentLineage> on AgentRequestContext
since #444, so the probe reads it directly instead of casting for an optional
member and records it on every line (available or unavailable with reason).
renderLineage types the serialized value against the public AgentLineage and
Observed types. The route-unit suite asserts a mounted lineage is recorded and
rendered.

* chore(examples/skills-starter): type outcome graders against agent-bundle/eval

Both graders declare themselves as the public EvalGraderFunction instead of
restating the grader context and outcome shapes by hand. The README names the
build output directory the CLI actually writes (artifact/, not dist/).

* chore(examples/mcp-app): type the outcome grader against agent-bundle/eval

The status grader declares itself as the public EvalGraderFunction instead of
restating the grader context and outcome shapes. The README's mcp run example
reuses the build output the CLI actually writes (--artifact artifact).
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