Messages and events
Common message envelope
Intercept request
{
"jsonrpc": "2.0",
"id": "evt_01JQ8Z2Y6YR0H8N7Q2M3X4V5W6",
"method": "hooks/intercept",
"params": {
"protocolVersion": "draft",
"event": {
"id": "evt_01JQ8Z2Y6YR0H8N7Q2M3X4V5W6",
"source": "urn:uuid:5b7de29e-a9e0-41a8-bf26-d94b05f0656d",
"harness": {"name": "example-harness", "version": "1.0.0"},
"type": "tool.before",
"time": "2026-08-24T08:51:14Z",
"session": {
"id": "sess_123",
"cwd": "/repo",
"workspaceRoots": [
"/repo"
]
},
"tool": {
"name": "Bash",
"kind": "shell",
"input": {
"command": "git push --force"
},
"origin": "native"
},
"call": {
"id": "call_456"
},
"path": "native"
},
"capabilities": {
"effects": [
"deny"
]
}
}
}
Subscriptions are harness-local configuration and are not transmitted as wire
identity. JSON-RPC request IDs correlate responses with pending requests; they
are not authorization claims. The JSON-RPC request id MUST equal
params.event.id. A retry of the same event MUST reuse both values.
Intercept response with no effect
{
"jsonrpc": "2.0",
"id": "evt_01JQ8Z2Y6YR0H8N7Q2M3X4V5W6",
"result": {
"protocolVersion": "draft",
"effects": []
}
}
An omitted result.effects or an empty effect list means only that this interceptor requests no change. Missing effects default to an empty list for composition; their wire presence is not rewritten. Neither form grants permission, overrides another interceptor, or bypasses the harness’s permission system.
Known effect values, pending candidates, injection state, and optional host-supplied session counters follow the typed payload contracts.
Intercept response denying the operation
{
"jsonrpc": "2.0",
"id": "evt_01JQ8Z2Y6YR0H8N7Q2M3X4V5W6",
"result": {
"protocolVersion": "draft",
"effects": [
{
"type": "deny",
"reason": "Force pushes are prohibited by organization policy.",
"code": "com.example.policy.force_push"
}
]
}
}
Observe notification
{
"jsonrpc": "2.0",
"method": "hooks/observe",
"params": {
"protocolVersion": "draft",
"event": {
"id": "evt_01JQ8Z7BEQW0X31TVPN5AZ1YJ6",
"source": "urn:uuid:5b7de29e-a9e0-41a8-bf26-d94b05f0656d",
"harness": {"name": "example-harness", "version": "1.0.0"},
"type": "tool.after",
"time": "2026-08-24T08:51:15Z",
"session": {
"id": "sess_123"
},
"tool": {
"name": "Bash",
"kind": "shell",
"input": {
"command": "git status"
},
"origin": "native"
},
"call": {
"id": "call_789"
},
"path": "native",
"items": [],
"outcome": "ok",
"execution": {
"status": "executed"
}
}
}
}
A notification has no JSON-RPC id and MUST NOT receive a JSON-RPC response.
Event model
Common event fields
| Field | Type | Requirement | Semantics |
| `id` | string | REQUIRED | Globally unique event identifier. Stable across retries. |
| `source` | string | REQUIRED | Stable, non-secret URI identifying the harness installation or runtime instance. |
| `type` | string | REQUIRED | Event type defined by this specification. |
| `time` | RFC 3339 string | REQUIRED | Time at which the harness created the event. |
| `harness` | object | REQUIRED | Originating agent harness identity: non-empty string `name` and `version`. |
| `session` | object | Event-specific | Session identity and optional execution context; required by session events. |
| `tool` | object | Tool events only | Tool-call identity and event-specific data. |
| `native` | object | OPTIONAL | Provider-specific fidelity payload. Disabled by default. |
| `extensions` | object | OPTIONAL | Namespaced extension data. |
An adapter MUST report the originating harness, not its own identity. The version
identifies the harness product version; it is not the AHP protocol version and
need not use semantic versioning. This metadata is not authentication or
capability negotiation. Backends MUST NOT infer capabilities from it. The field
is required for both interception and observation, including events received
without a preceding session.start.
Session object
| Field | Type | Requirement | Semantics |
| `id` | string | REQUIRED | Stable session identifier. |
| `cwd` | string | OPTIONAL | Current working directory at event creation. |
| `workspaceRoots` | string array | OPTIONAL | Workspace roots visible to the harness. |
| `model` | string | OPTIONAL | Harness-reported model identifier. |
| `agent` | object | OPTIONAL | Current subagent identity, if applicable. |
Tool events use call: {"id": "…"} for invocation identity, separate from the
tool descriptor. The call identity stays stable across the invocation’s
boundaries; adapters synthesize stable identity when native identity is absent.
Identical tool names and inputs alone do not distinguish repeated invocations.
path identifies the covered tool path.
The tool object requires name (preserved verbatim), parsed object input, and
origin (native or mcp). kind is an optional behavioral classification.
MCP origin requires a self-contained mcp descriptor; native origin forbids it.
See Execution payloads for MCP connection metadata,
explicit gaps, and credential restrictions.
tool.after requires outcome, execution, and items, in addition to call,
tool, and path. Result content uses content items rather than tool.output.
Failures use event-level error with required class and message when
outcome is error; there is no standard tool.error event. Other outcomes
forbid error. Denial uses outcome: "denied" and skipped/policy execution.
See Execution payloads for the complete event-specific
requirements and canonical content selection for inline
text parts and immutable non-text, non-JSON media attachments. Model-visible
content uses ordered canonical messages; the owning message supplies the role,
not a repeated role on each child part.
Native payload
The optional native object has this shape:
{
"provider": "claude-code",
"eventName": "PreToolUse",
"payload": {}
}
A harness MUST omit native unless native-payload delivery is enabled by local policy. A portable backend MUST function when native is absent. Native fields MUST NOT change the semantics of a core effect.
Extensions
AHP-EXT-001 — MUST. Extension keys use reverse-DNS names and core behavior does not depend on an extension unless that dependency is explicitly declared.
Extension keys MUST use reverse-DNS namespacing, such as com.example.policy or org.w3c.trace_context. Unnamespaced extension keys are invalid.
Extension values MAY contain any JSON value. Core implementations MUST preserve extensions when proxying an otherwise unchanged event, but they MUST NOT interpret unknown extensions.
A Trace Context extension might look like:
{
"extensions": {
"org.w3c.trace_context": {
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
"tracestate": "congo=t61rcWkgMzE"
}
}
}
This extension provides correlation only. Its presence does not make OpenTelemetry part of AHP conformance.