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
8 changes: 8 additions & 0 deletions .changeset/promote-failure-compact-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"agent-bundle": minor
---

Promote canonical `tool/failure`, `compact/before`, and `compact/after`
event-route families across Claude Code, Codex, Cursor, and composite plugin
artifacts with evidence-pinned envelope validation and fail-closed result
projection.
10 changes: 10 additions & 0 deletions examples/rsc-agent-runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ Host/Origin allowlists mitigate DNS rebinding and cross-origin requests, but the
| `prompt/submit` | `beforeSubmitPrompt` (deny) | `UserPromptSubmit` (deny + context) | `UserPromptSubmit` (deny + context) |
| `tool/before` | Supported | `PreToolUse` | `PreToolUse` |
| `tool/after` | Supported | `PostToolUse` | `PostToolUse` |
| `tool/failure` | `postToolUseFailure` (observe-only) | `PostToolUseFailure` (context) | Unavailable |
| `compact/before` | `preCompact` (observe-only; native `user_message` not modeled) | `PreCompact` (deny) | `PreCompact` (observe-only) |
| `compact/after` | Unavailable | `PostCompact` (observe-only) | `PostCompact` (observe-only; no summary field) |
| `stop` | Supported | `Stop` | `Stop` |
| `agent/start` | `subagentStart` | `SubagentStart` | `SubagentStart` |
| `agent/stop` | `subagentStop` | `SubagentStop` | `SubagentStop` |
Expand All @@ -257,6 +260,13 @@ observe-only. `prompt/submit` maps canonical deny to each host's blocking
contract; Claude Code and Codex also accept `Agent.Context`, while Cursor has
no context or prompt-rewrite channel. Cursor cloud agents do not expose
`sessionEnd`.
`tool/failure`, `compact/before`, and `compact/after` are also event-route-only
families. Claude Code alone maps `tool/failure` context and
`compact/before` deny; every other result effect fails closed. Cursor's
`preCompact.user_message` is user-facing rather than agent context, and the
Codex compact schemas do not pin event-level semantics for their common output
fields, so neither channel is projected. Codex has no tool-failure event,
Cursor has no post-compaction event, and Codex `PostCompact` carries no summary.

## Extension-author guide

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@
"eventRoutes": {
"agent/start": { "nativeEvent": "SubagentStart", "state": "supported" },
"agent/stop": { "nativeEvent": "SubagentStop", "state": "supported" },
"compact/after": { "nativeEvent": "PostCompact", "state": "supported" },
"compact/before": { "nativeEvent": "PreCompact", "state": "supported" },
"prompt/submit": { "nativeEvent": "UserPromptSubmit", "state": "supported" },
"session/end": { "nativeEvent": "SessionEnd", "state": "supported" },
"session/start": { "nativeEvent": "SessionStart", "state": "supported" },
"stop": { "nativeEvent": "Stop", "state": "supported" },
"tool/after": { "nativeEvent": "PostToolUse", "state": "supported" },
"tool/before": { "nativeEvent": "PreToolUse", "state": "supported" },
"tool/failure": { "nativeEvent": "PostToolUseFailure", "state": "supported" },
"workspace/open": {
"reason": "The pinned Claude Code 2.1.250 hooks contract has no workspace-open event.",
"state": "unavailable"
Expand Down Expand Up @@ -631,6 +634,10 @@
"2026-09-02: live Claude Code 2.1.257 non-interactive capture recorded UserPromptSubmit with session_id, transcript_path, cwd, prompt_id, permission_mode, hook_event_name, and prompt; the scrubbed envelope is tests/fixtures/events/claude-user-prompt-submit.json.",
"retrieved 2026-09-02: https://code.claude.com/docs/en/hooks documents SessionEnd input as the common hook envelope plus reason, with matcher filtering by reason; SessionEnd has no decision control, discards JSON output, and does not honor exit code 2 as a block.",
"2026-09-02: live Claude Code 2.1.257 non-interactive capture recorded SessionEnd with session_id, transcript_path, cwd, prompt_id, hook_event_name, and reason; the scrubbed envelope is tests/fixtures/events/claude-session-end.json.",
"retrieved 2026-09-02: https://code.claude.com/docs/en/hooks documents PostToolUseFailure input as tool_name, tool_input, tool_use_id, error, optional is_interrupt, and optional duration_ms. It can add hookSpecificOutput.additionalContext after the failure but cannot deny or rewrite the completed tool call.",
"2026-09-02: live Claude Code 2.1.257 non-interactive captures recorded PostToolUseFailure for failed Write, Read, and Bash calls; the scrubbed Bash envelope is tests/fixtures/events/claude-post-tool-use-failure.json.",
"retrieved 2026-09-02: https://code.claude.com/docs/en/hooks documents PreCompact input as trigger plus nullable custom_instructions and allows decision:block with reason, while discarding continue and systemMessage. PostCompact adds compact_summary, has no decision control, and discards continue and systemMessage.",
"2026-09-02: live Claude Code 2.1.257 non-interactive captures recorded PreCompact and PostCompact for manual compaction; scrubbed envelopes are tests/fixtures/events/claude-pre-compact.json and tests/fixtures/events/claude-post-compact.json.",
"The hooks reference states prompt_id requires Claude Code 2.1.196 or later; the pinned 2.1.250 release covers that input field without a version bump.",
"2026-09-01: https://code.claude.com/docs/en/plugins-reference documents an agents/ component with name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, and isolation: worktree; #100 stage 2 defers the agents component per the G5 narrowing in #107, so no agents capability row is published until a later stage admits it.",
"2026-09-01: https://code.claude.com/docs/en/plugins \"Ship default settings with your plugin\": a plugin may include settings.json at the plugin root \"to apply default configuration when the plugin is enabled. Currently, only the `agent` and `subagentStatusLine` keys are supported.\" The file-locations table of https://code.claude.com/docs/en/plugins-reference repeats the same bound: \"Settings | settings.json | Default configuration applied when the plugin is enabled. Only the agent and subagentStatusLine keys are supported\".",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@
"eventRoutes": {
"agent/start": { "nativeEvent": "SubagentStart", "state": "supported" },
"agent/stop": { "nativeEvent": "SubagentStop", "state": "supported" },
"compact/after": { "nativeEvent": "PostCompact", "state": "supported" },
"compact/before": { "nativeEvent": "PreCompact", "state": "supported" },
Comment on lines +28 to +29

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 Advance revisions for adapters gaining compact routes

These mappings change the Codex adapter's generated hook artifacts, but codex.ts still reports the parent's adapterRevision: '1.5.0'; likewise, the composite plugin adapter now emits compact/before hooks while retaining 1.20.0. Because artifact freshness in matchesTargetMetadata relies on these revisions, artifacts produced before and after this behavior change are indistinguishable by adapter provenance. Increment both adapter revisions and update their metadata expectations.

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 on main in #364 (merge 305161a).

"prompt/submit": { "nativeEvent": "UserPromptSubmit", "state": "supported" },
"session/end": { "nativeEvent": "SessionEnd", "state": "supported" },
"session/start": { "nativeEvent": "SessionStart", "state": "supported" },
"stop": { "nativeEvent": "Stop", "state": "supported" },
"tool/after": { "nativeEvent": "PostToolUse", "state": "supported" },
"tool/before": { "nativeEvent": "PreToolUse", "state": "supported" },
"tool/failure": {
"reason": "retrieved 2026-09-02: the complete rust-v0.147.0 generated hook schema directory and native event inventory contain no tool-failure event.",
"state": "unavailable"
},
"workspace/open": {
"reason": "The pinned Codex 0.147.0 hooks contract has no workspace-open event.",
"state": "unavailable"
Expand Down Expand Up @@ -257,6 +263,10 @@
"liveRevision": "codex-cli 0.147.0 app-server protocol",
"matchingRelativePaths": [],
"missingPinnedRelativePaths": [
"post-compact.command.input.schema.json",
"post-compact.command.output.schema.json",
"pre-compact.command.input.schema.json",
"pre-compact.command.output.schema.json",
"session-end.command.input.schema.json",
"subagent-start.command.input.schema.json",
"subagent-start.command.output.schema.json",
Expand All @@ -266,6 +276,10 @@
"user-prompt-submit.command.output.schema.json"
],
"pinnedRepositorySha256": {
"post-compact.command.input.schema.json": "d5cecd14bd2ca18605ba8209108f76291f886ffe3cb4762d70e712c148836f31",
"post-compact.command.output.schema.json": "811b7ae2a4b277cd51c9df989f347e494fa981d01e346bc2a757506e97734882",
"pre-compact.command.input.schema.json": "5728b5da4c63e1e07f2d53ac8b2adc18306cd3dc8b01b556fc422e75b32a8734",
"pre-compact.command.output.schema.json": "98ba0dbf0848d8283cfe48a85122f4d9122baae1b457f50e239504864900247e",
"session-end.command.input.schema.json": "99bf6e75091525b96926dd85a0adf6589dea93fe0d181394027fabda8139402e",
"subagent-start.command.input.schema.json": "e1cacc5cd92217e96e327cf182038fa93099d194c3107439b4dad4b806d414cc",
"subagent-start.command.output.schema.json": "531f7a457ad8430de82388319ff2bf030fd3a1dc0e9a0d4078447bc30948448b",
Expand Down Expand Up @@ -300,7 +314,11 @@
"retrieved 2026-09-02: https://github.com/openai/codex/blob/rust-v0.147.0/codex-rs/hooks/schema/generated/user-prompt-submit.command.input.schema.json pins UserPromptSubmit input (sha256 e6b923bc519896197c44c4fc267a9d115cef24ac418dde9c27db699f4e3b65fd).",
"retrieved 2026-09-02: https://github.com/openai/codex/blob/rust-v0.147.0/codex-rs/hooks/schema/generated/user-prompt-submit.command.output.schema.json pins decision:block plus reason and hookSpecificOutput.additionalContext, with no prompt-rewrite field (sha256 5e290303db710f3ccc12f4a2744e8586e7749b3ca2b6bf9f57781ed75bf17b2b).",
"retrieved 2026-09-02: https://github.com/openai/codex/blob/rust-v0.147.0/codex-rs/hooks/schema/generated/session-end.command.input.schema.json pins observe-only SessionEnd input with reason const other (sha256 23b1b69f92fa8ac29f8319478984b5aa5aaf09e5ca355ce90aa010452937e41c); the same generated directory has no session-end.command.output.schema.json (GitHub contents API returned 404), so no output is projected.",
"2026-09-02: live codex-cli 0.147.0 non-interactive capture recorded UserPromptSubmit and SessionEnd; the scrubbed envelopes are tests/fixtures/events/codex-user-prompt-submit.json and tests/fixtures/events/codex-session-end.json."
"2026-09-02: live codex-cli 0.147.0 non-interactive capture recorded UserPromptSubmit and SessionEnd; the scrubbed envelopes are tests/fixtures/events/codex-user-prompt-submit.json and tests/fixtures/events/codex-session-end.json.",
"retrieved 2026-09-02: the complete rust-v0.147.0 generated hook schema directory and hooks/src/lib.rs inventory contain no tool-failure event, so tool/failure is unavailable rather than inferred from PostToolUse.",
"retrieved 2026-09-02: rust-v0.147.0 pre-compact.command.input.schema.json pins PreCompact trigger, model, turn_id, session, cwd, and nullable transcript input (sha256 065f0ae3cd628ac9af8c0cf9bd1d5a673bcbd5ea1d7dcdc0c6437f34dd0189d9); its output schema accepts only common continue, stopReason, suppressOutput, and systemMessage fields (sha256 c392f3054ae6750f427d4dec07380fd67e8c58a7939a35d5c69bfa070c7ca032).",
"retrieved 2026-09-02: rust-v0.147.0 post-compact.command.input.schema.json pins PostCompact with the same input fields and no compact summary field (sha256 4a4b3f3022c939a15ab12e95f5c5c17b18bb20f74fe962ae0a51b2a3e76e63f9); its output schema accepts only the same common fields (sha256 48355bfcb568259cf396beb6ade2ac32827f50bf6a3c20b395c337dce184cbed).",
"2026-09-02: the generated compact output schemas do not document event-level runtime effects for continue:false or the other common fields, so Agent Bundle exposes no canonical compact result channel until runtime evidence pins those semantics."
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"eventRoutes": {
"agent/start": { "nativeEvent": "subagentStart", "state": "supported" },
"agent/stop": { "nativeEvent": "subagentStop", "state": "supported" },
"compact/after": {
"reason": "retrieved 2026-09-02: the complete https://cursor.com/docs/hooks event inventory documents no postCompact event.",
"state": "unavailable"
},
"compact/before": { "nativeEvent": "preCompact", "state": "supported" },
"prompt/submit": { "nativeEvent": "beforeSubmitPrompt", "state": "supported" },
"session/end": {
"availability": {
Expand All @@ -41,6 +46,7 @@
"stop": { "nativeEvent": "stop", "state": "supported" },
"tool/after": { "nativeEvent": "postToolUse", "state": "supported" },
"tool/before": { "nativeEvent": "preToolUse", "state": "supported" },
"tool/failure": { "nativeEvent": "postToolUseFailure", "state": "supported" },
"workspace/open": { "nativeEvent": "workspaceOpen", "state": "supported" }
},
"matchers": {
Expand Down Expand Up @@ -108,6 +114,10 @@
"retrieved 2026-09-02: https://cursor.com/docs/hooks documents beforeSubmitPrompt input as prompt plus file/rule attachments, matcher evaluation against the literal UserPromptSubmit, and output as continue plus optional user_message. continue:false blocks submission; no additional-context or prompt-rewrite channel is documented.",
"retrieved 2026-09-02: https://cursor.com/docs/hooks documents sessionEnd input as session_id, reason, duration_ms, is_background_agent, final_status, and optional error_message. It is fire-and-forget; output is logged but unused. The same source states cloud agents have no sessionEnd because they lack an editor-lifetime session boundary.",
"2026-09-02: local cursor-agent 2026.08.31-4057e58 capture was attempted with trusted project hooks, but the binary required Cursor authentication unavailable in the capture environment; the pinned official contract is the evidence source for beforeSubmitPrompt and sessionEnd.",
"retrieved 2026-09-02: https://cursor.com/docs/hooks documents postToolUseFailure input as tool_name, tool_input, tool_use_id, cwd, error_message, failure_type, duration, and is_interrupt, with no supported output fields. Unlike Claude PostToolUseFailure, failure_type includes permission_denied.",
"retrieved 2026-09-02: https://cursor.com/docs/hooks documents preCompact as observational and unable to block or modify compaction. Its optional user_message is user-facing rather than agent context, so the canonical Agent.Result and Agent.Context vocabulary exposes no output channel for it.",
"retrieved 2026-09-02: the complete https://cursor.com/docs/hooks Agent and cloud event inventories document no postCompact event, so compact/after is unavailable rather than inferred from preCompact.",
"2026-09-02: local cursor-agent 2026.08.31-4057e58 capture attempts for postToolUseFailure and preCompact could not dispatch because the binary required unavailable authentication; the pinned official contract is the evidence source.",
"2026-09-01: https://cursor.com/docs/plugins and https://prod.cursor.com/docs/reference/plugins document agents as a full Cursor Plugin component alongside rules and commands; #100 stage 2 defers the agents component per the G5 narrowing in #107, so no agents capability row is published until a later stage admits it."
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
"eventRoutes": {
"agent/start": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"agent/stop": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"compact/after": {
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native PostCompact equivalent (https://agent-plugins.org/).",
"state": "unavailable"
},
"compact/before": {
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native PreCompact/preCompact equivalent (https://agent-plugins.org/).",
"state": "unavailable"
},
"prompt/submit": {
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native UserPromptSubmit/beforeSubmitPrompt equivalent (https://agent-plugins.org/).",
"state": "unavailable"
Expand All @@ -24,6 +32,10 @@
"stop": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"tool/after": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"tool/before": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"tool/failure": {
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native PostToolUseFailure/postToolUseFailure equivalent (https://agent-plugins.org/).",
"state": "unavailable"
},
"workspace/open": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" }
},
"host": "portable",
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/adapters/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ const hookContract = Object.freeze({
wrapperSource: (entry) => nativeHookWrapperSource(entry, 'Claude'),
} satisfies TargetHookContract);
const metadata = Object.freeze({
adapterRevision: '1.20.0',
adapterRevision: '1.21.0',
observedVersion: capabilityTable.observedCliVersion,
schemas: schemaDescriptorsFrom(schemaProvenance, schemaProvenance.observedCliVersion),
});
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/adapters/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export const cursorManifest = (
});

const metadata = Object.freeze({
adapterRevision: '1.6.0',
adapterRevision: '1.7.0',
observedVersion: capabilityTable.observedCliVersion,
schemas: schemaDescriptorsFrom(schemaProvenance, schemaProvenance.observedCliVersion),
});
Expand Down
52 changes: 52 additions & 0 deletions packages/agent-bundle/src/adapters/hook-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,55 @@ export const createNativeEventStarter = (
? { model: 'default', turn_id: 'lifecycle-replay-turn' }
: {}),
});
case 'tool/failure':
return deepFreeze(target === 'cursor'
? {
...base,
cwd: '/tmp',
duration: 0,
error_message: 'Lifecycle replay tool failure.',
failure_type: 'error',
is_interrupt: false,
tool_input: toolInput,
tool_name: toolName,
tool_use_id: 'lifecycle-replay-tool',
}
: {
...base,
duration_ms: 0,
error: 'Lifecycle replay tool failure.',
is_interrupt: false,
tool_input: toolInput,
tool_name: toolName,
tool_use_id: 'lifecycle-replay-tool',
});
case 'compact/before':
return deepFreeze(target === 'cursor'
? {
...base,
context_tokens: 0,
context_usage_percent: 0,
context_window_size: 1,
is_first_compaction: true,
message_count: 0,
messages_to_compact: 0,
trigger: 'manual',
}
: {
...base,
trigger: 'manual',
...(target === 'codex'
? { model: 'default', turn_id: 'lifecycle-replay-turn' }
: { custom_instructions: null }),
});
case 'compact/after':
return deepFreeze({
...base,
trigger: 'manual',
...(target === 'codex'
? { model: 'default', turn_id: 'lifecycle-replay-turn' }
: { compact_summary: 'Lifecycle replay compact summary.' }),
});
case 'tool/before':
return deepFreeze({
...base,
Expand Down Expand Up @@ -322,6 +371,9 @@ const canonicalEventOrder: readonly NormalizedHookEvent[] = [
'agentStop',
'workspaceOpen',
'sessionEnd',
'toolFailure',
'compactBefore',
'compactAfter',
];

export const canonicalHookEventFor = (event: string): CanonicalHookEvent | undefined =>
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/adapters/portable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const schemaValidator = createAdapterValidator();
const validatePlugin = schemaValidator.compile(pluginSchema);
const validateMcp = schemaValidator.compile(mcpSchema);
const metadata = Object.freeze({
adapterRevision: '1.4.0',
adapterRevision: '1.5.0',
observedVersion: capabilityTable.observedSpecificationVersion,
schemas: schemaDescriptorsFrom(schemaProvenance, schemaProvenance.version),
});
Expand Down
Loading
Loading