Agent Event Protocol
Agent Event Protocol (AEP) is an open draft standard for normalizing AI coding agent activity and hook control responses.
One event language for many agents
Every coding agent emits hook payloads with slightly different shapes. Some send a model id. Some send a working directory. Some split tool input, command text, MCP server details, previews, and full output across several fields. Tools that consume those events end up with per-agent special cases that calcify quickly.
AEP defines one canonical envelope and a small event vocabulary, plus a standard hook response for control decisions. New agents plug in by emitting AEP directly, or by writing one declarative mapping file.
The public draft is available for review on GitHub. The current review draft is 0.1.
What AEP makes explicit
Most coding agents already expose a basic pre-tool hook. AEP is about the activity model around that hook: stable event identity, action correlation, catalog identity for tools and MCP servers, session and workspace context, execution outcomes, and control responses. Those are the pieces consumers need for approvals, audit trails, policy evaluation, and observability as agents move beyond one tool call at a time.
Choose a provider to see how its current hook surface maps into the same canonical needs. The AEP column is the shape consumers can target; the provider column shows where that data comes from today or where the source is still implicit. Top-level event fields stay top-level in the spec: no wrapper object is implied.
| Need | AEP shape | |
|---|---|---|
Event identity A consumer needs a stable event id, a canonical event type, and a timestamp before vendor-specific hook details. | idtypetime | missingPreToolUsetimestamp |
Agent identity Events need a stable producer identity so dashboards and policy rules can distinguish runtimes without guessing from hook names. | agent.slugagent.display_nameagent.version | agentagent_namemissing |
Session and workspace Policy and observability need to group activity by session, conversation, user, repository, and current directory. | session.idsession.conversation_idsession.turn_iduser.emailworkspace.cwdworkspace.roots | session_idconversation_idmissingmissingcwdmissing |
Correlation Approvals, denials, completions, failures, and audit records need to join back to the same action. | action.idresponse.request_event_idresponse.action_id | tool_use_idmissingtool_use_id |
Invocation A tool call, skill use, subagent spawn, or future action should have one place for its kind and arguments. | action.typeaction.inputtool.name | inferred: tool_calltool_inputtool_name |
Catalog identity Policy often cares whether a call is native, MCP, connector, extension, or custom, and which server supplied the tool. | tool.idtool.typetool.schemaserver.nameserver.url | missinginferred from tool_namemissingmcp__<server>__<tool>missing |
Control response Hook consumers need a standard way to allow, deny, ask, defer, block, or add context while preserving the native response shape. | control.behaviorcontrol.messagecontrol.updated_inputcontrol.additional_contextdecision.reason | permissionDecisionreasonupdatedInputadditionalContextreason |
Execution result Once an action runs, consumers need the final status, output, error, and execution duration without rewriting per-agent parsers. | action.statusaction.outputaction.errormetrics.duration_ms | statustool_responseerrormissing |
Beyond tools The protocol has to scale from tool approval into prompts, model activity, subagents, compaction, notifications, and workspace changes. | prompt.submittedmodel.thoughtcontext.compactedsubagent.startsession.end | UserPromptSubmitmissingPreCompactSubagentStartSessionEnd |
The basic tool request stays simple: a provider can emit action.requestedwith action.input and tool.name. The same envelope can then carry richer activity without adding a new one-off schema for each agent capability.
At a glance
The full canonical envelope. aep_version, id, type, time, and optional hook are top-level fields. Catalog nouns (tool, server, skill) are top-level peers; the action object is the verb that references them via discriminators.
Example AEP event
Snake_case JSON. Producers omit fields they don't provide rather than emitting null. Consumers tolerate omitted fields and ignore unknown extensions.x-* keys.
{
"aep_version": "0.1",
"id": "evt_01HXY7GZ3K8MZ9",
"type": "action.requested",
"time": "2026-05-09T12:00:00.000Z",
"hook": "PreToolUse",
"agent": {
"slug": "claude",
"display_name": "Claude Code",
"version": "1.4.2"
},
"session": {
"id": "sess_abc",
"conversation_id": "conv_8a1",
"permission_mode": "default"
},
"user": { "email": "user@example.com" },
"workspace": { "cwd": "/workspace/project" },
"model": { "id": "claude-sonnet-4-6", "provider": "anthropic" },
"action": {
"type": "tool_call",
"id": "call_xyz",
"input": { "command": "bun test" }
},
"tool": {
"id": "01HXY7QWPZ6PR0XEHZ3M2Q3WYZ",
"type": "native",
"name": "Bash",
"description": "Executes a given bash command and returns its output. The working directory persists between commands, but shell state does not.",
"version": "1.4.2",
"schema": {
"type": "object",
"required": ["command"],
"properties": {
"command": { "type": "string", "description": "The command to execute" },
"description": { "type": "string", "description": "5-10 word description of what the command does" },
"timeout": { "type": "number", "description": "Optional timeout in milliseconds (max 600000, default 120000)" },
"run_in_background": { "type": "boolean", "description": "Run command in background; use Read/Monitor for output" }
}
}
}
}Objects in detail
Every group in the envelope, expanded. All fields documented here are optional unless noted; unset fields SHOULD be omitted from the wire.
Event types
Twenty-one canonical types organized into five families. Past-participle for actions that completed (action.completed, prompt.submitted); bare noun for entities (model.thought, session.start).
Required fields below are in addition to the core envelope:aep_version, id, type, time, and agent. Parenthetical chips are conditional on the action kind or tool type.
Lifecycle
5 typesSession boundaries, agent process lifecycle, errors.
Prompts & responses
5 typesUser prompts, model thoughts, responses, streaming deltas, plans.
Actions
4 typesTool, skill, and subagent invocation lifecycle. action.type discriminates the kind.
Orchestration
4 typesSubagent lifecycle, context compaction, workspace state mutations.
Notifications
3 typesAgent notifications and interactive question/answer pairs.
Hook responses
Blocking hooks have two sides: an immutable AEP event sent into the hook, and a separate AEP response returned to the agent runtime. Apps can join both records by action.id to show one activity row.
{
"aep_version": "0.1",
"id": "rsp_01HXY7GZ3K8MZ9",
"request_event_id": "evt_01HXY7GZ3K8MZ9",
"action_id": "call_xyz",
"time": "2026-05-09T12:00:04.820Z",
"control": {
"behavior": "allow",
"message": "Matched allowlist rule shell.tests",
"updated_input": { "command": "bun test --filter api" }
},
"decision": {
"outcome": "approved",
"by": "auto-policy",
"reason": "matched allowlist rule shell.tests",
"response_time_ms": 4820
}
}| Behavior | Meaning |
|---|---|
allow | Approve or continue the requested action. |
deny | Reject the requested action and return feedback when supported. |
ask | Escalate to a human/user confirmation flow. |
defer | Pause gracefully so the action can be resumed later. |
block | Block the current result/stop and force another agent turn. |
continue | Explicitly allow the lifecycle to continue. |
stop | Stop the agent or interrupt the run when supported. |
abstain | Return no opinion and let the provider default behavior run. |
Approved flow
action.requested → response allow / approved → action.completed or action.failed.
Denied flow
action.requested → response deny / denied. action.denied may also be emitted for an audit stream.
Display styles
Producer hints for how a content/value field is intended to render. Consumers may ignore them and substitute their own.
| Style | Use for |
|---|---|
plain_text | Short strings, ids, names, and compact values. |
markdown | Prompts, thoughts, responses, plans, readable tool output. |
indented_json | Structured action input/output, server payloads, errors. |
key_value | Dynamic object fields when full JSON would hide useful detail. |
path | Working directories, project paths, file paths, config paths. |
url | External references, repositories, websites, MCP server URLs. |
image | Image attachments (screenshots, diagrams, charts). |
video | Video attachments (screen recordings, demos). |
audio | Audio attachments (voice clips, dictation). |
badge | Status, decision, type, category. |
duration | Milliseconds shown as readable durations. |
timestamp | Epoch or ISO timestamps shown in local time. |
Integrate your agent
Two paths, same wire format. Pick native AEP if you can change your hook output; pick a mapping if you ship an existing agent and don't want to.
Native AEP
Emit AEP envelopes directly from your hooks or SDK. Recommended for new agents. Consumers need zero per-agent code.
Mapping file
Keep your existing hook format. Write one YAML file describing how each of your events maps to AEP. The consumer translates at ingestion.
Mapping files
A mapping file says: for each of my hook events, this is the AEP type and these are the JSON Pointer paths to the source fields. Declarative, reviewable, swappable without a consumer rebuild.
schema_version: aep.mapping/v1
agent: cursor
display_name: Cursor
events:
- id: cursor.shell_approval
source_event: beforeShellExecution
when:
/event_name: beforeShellExecution
canonical_event: action.requested
fields:
action.id:
source: /tool_call_id
action.input:
source: /tool_input
display_style: indented_json
tool.name:
source: /tool_name
display_style: plain_text
workspace.cwd:
source: /cwd
display_style: path
content:
- type: prompt
text:
source: /prompt
style: markdownThe mapping format is part of the open standard. Draft provider mappings are available in the public repo for review; every consumer can write or adapt its own.
Versioning and conformance
AEP uses semver. The public review draft is 0.1; producers emit aep_version: "0.1". Breaking changes are still possible before 1.0, so consumers should pin to the draft version they implement.
The formal spec, examples, changelog, and draft provider mappings live in the Agent Event Protocol GitHub repo. Issues, discussions, and pull requests are open for review feedback.
A reference JSON Schema and conformance fixtures will ship alongside 1.0 final. The smoke test until then: a consumer that knows nothing about your agent should be able to render at least the title (from type and agent.slug) and tool name without per-agent code.
Agent Approve uses AEP to render every supported agent on iPhone and Apple Watch with one rendering layer. See the changelog for AEP draft updates and product changes.