Attention Request Contract (ARC)
Status: public draft, version 0.1.
ARC is an open, transport-neutral JSON contract for the moments when a software agent needs human attention. It standardizes how a producer creates, updates, and resolves a bounded attention request without defining how that event is transported, stored, routed, or displayed.
AgentNotify defines ARC and is its first reference implementation. The specification is independent of the AgentNotify product model: producers do not need to know about desktop toasts, SQLite, delivery providers, or the AgentNotify CLI.
Goals
- Let any agent request human attention without a provider-specific payload.
- Distinguish immutable delivery events from a stable unresolved condition.
- Preserve sender, session, project, working-directory, and correlation identity.
- Make transport retries and condition updates deterministic.
- Keep the core small enough for hooks, stdout adapters, HTTP clients, and message buses.
- Minimize sensitive data and leave presentation and routing policy to the consumer.
Non-goals in 0.1
ARC 0.1 does not define agent activity telemetry, model traces, tool inputs or outputs, attachments, authentication, discovery, network transport, broker storage, or outbound delivery. It also does not yet define structured human responses. AgentNotify will add response events only after its UI and agent callback boundary can implement and verify them end to end.
Event lifecycle
ARC 0.1 defines three event types:
| Event type | Meaning | Key rule |
|---|---|---|
request.created |
Open a new attention request | request.key is optional |
request.updated |
Replace the visible state of an active request | request.key is required |
request.resolved |
Close a request because attention is no longer needed | request.key is required |
A producer-assigned event_id identifies one immutable event. A request.key identifies a logical
condition that may span several events. They must not be treated as the same concept.
When request.created omits a key, a consumer derives an identity from sender.id and event_id.
Replaying that event returns the original result, including after it has been resolved. When a key
is supplied, the producer opts into the condition lifecycle: another created event with the same key
updates an active condition or creates a new condition after the old one is terminal.
request.updated never creates a missing condition. request.resolved is idempotent when the latest
matching condition is already resolved.
Envelope
Every ARC event is one JSON object:
| Field | Type | Required | Meaning |
|---|---|---|---|
arc_version |
string | yes | Exactly "0.1" |
event_id |
string | yes | Producer event identity, 1–512 characters |
event_type |
string | yes | One of the three lifecycle types |
occurred_at |
RFC 3339 timestamp | yes | When the source occurrence happened |
sender |
object | yes | Producing agent identity |
context |
object | no | Bounded execution context |
request |
object | yes | Attention condition and content |
extensions |
object | no | Namespaced x-<vendor> values |
Unknown core fields are rejected. Unknown correctly namespaced vendor extensions are ignored. This lets extensions evolve without allowing misspelled core fields to silently change behavior.
Sender
| Field | Required | Limit |
|---|---|---|
id |
yes | Stable producer identifier, 1–100 characters |
name |
no | Human-readable name, up to 160 characters |
version |
no | Producer version, up to 100 characters |
instance_id |
no | Current process/run identity, up to 100 characters |
Context
| Field | Meaning | Limit |
|---|---|---|
session_id |
Agent session/run identity | 100 characters |
correlation_id |
Producer correlation or turn identity | 200 characters |
project |
Human-readable project name | 200 characters |
cwd |
Working directory | 1,024 characters |
pid |
Local process ID | Non-negative integer |
Context values are correlation hints, not authentication claims. Consumers must not authorize an action solely because a producer supplied a project, process ID, or working directory.
Request
| Field | Required | Meaning |
|---|---|---|
key |
updates/resolution only | Stable condition identity, up to 100 characters |
kind |
created/updated | Portable attention reason |
title |
no | Optional display title, up to 200 characters |
message |
created/updated | Human-readable request, 1–4,000 characters |
priority |
no | low, normal, high, or critical |
Request kinds and AgentNotify defaults are:
| ARC kind | Meaning | AgentNotify type | Default priority |
|---|---|---|---|
information |
Useful state that deserves visibility | info |
normal |
question |
The agent needs an answer | input_required |
high |
permission |
The agent needs authorization | permission_required |
high |
blocked |
Work cannot progress | blocked |
high |
failure |
Work failed and needs review | error |
high |
completion |
Requested work completed | completed |
normal |
Consumers own presentation policy. They may group, defer, suppress, escalate, or route a request according to local configuration. Producers should choose the semantic kind and priority rather than trying to dictate colors, sounds, or delivery channels.
Example: create a permission request
{
"arc_version": "0.1",
"event_id": "evt_permission_018f",
"event_type": "request.created",
"occurred_at": "2026-08-26T01:15:00Z",
"sender": {
"id": "codex",
"name": "Codex",
"instance_id": "session-42"
},
"context": {
"session_id": "session-42",
"correlation_id": "turn-19",
"project": "agent-notify",
"cwd": "/work/agent-notify"
},
"request": {
"key": "release-approval",
"kind": "permission",
"title": "Release approval required",
"message": "May I run the release workflow?",
"priority": "high"
}
}
Example: update the condition
{
"arc_version": "0.1",
"event_id": "evt_permission_update_0190",
"event_type": "request.updated",
"occurred_at": "2026-08-26T01:17:00Z",
"sender": { "id": "codex", "name": "Codex" },
"request": {
"key": "release-approval",
"kind": "permission",
"message": "The build passed. May I publish the release?",
"priority": "high"
}
}
Example: resolve the condition
{
"arc_version": "0.1",
"event_id": "evt_permission_resolved_0191",
"event_type": "request.resolved",
"occurred_at": "2026-08-26T01:20:00Z",
"sender": { "id": "codex", "name": "Codex" },
"request": { "key": "release-approval" }
}
AgentNotify HTTP binding
AgentNotify accepts ARC at its existing local event endpoint:
POST http://127.0.0.1:47821/v1/events
Authorization: Bearer <local AgentNotify token>
Content-Type: application/json
The endpoint remains loopback-only, bearer-authenticated, limited to the configured 64 KiB request
body, and protected by the broker's POST rate limit. A newly persisted request returns 201 NotificationDto; an update, resolution, or idempotent replay returns 200 NotificationDto. Updating
or resolving a missing condition returns 404.
The HTTP binding is an AgentNotify implementation choice, not part of the ARC base contract. Other consumers may receive the same event through newline-delimited stdout, a local socket, a queue, or a different authenticated HTTP endpoint.
AgentNotify extension
AgentNotify recognizes one optional presentation hint:
{
"extensions": {
"x-agentnotify": {
"notification_type": "custom_type_id"
}
}
}
notification_type selects a configured AgentNotify type ID. Unknown fields inside
x-agentnotify are rejected. Other valid x-<vendor> extensions are ignored and are not copied
wholesale into notification metadata.
Idempotency and ordering
- Producers must not reuse an
event_idfor a different event. - Consumers should treat the same sender/event pair as a retry, not a new occurrence.
- Producers should send condition events in occurrence order.
- ARC 0.1 does not require globally ordered delivery or a distributed lock.
- A resolved keyed condition may later be created again as a new local lifecycle.
- An update targets only an active condition and must not silently create a replacement.
Security and privacy
- ARC does not authenticate a producer. Every transport must provide its own trust boundary.
- Do not include API tokens, provider credentials, private keys, full prompts, or tool output.
- Send the smallest human-readable message necessary for a decision.
- Treat
cwd, project, process, and sender identity as potentially sensitive metadata. - Consumers must bound payloads, validate every core field, and avoid executing request content.
- A process ID or working directory is never sufficient authority to activate or control a process.
AgentNotify never extends its local bearer token to an internet-facing API. Optional off-device routes apply their configured content policy only after the request is stored locally.
Schema and implementation
The normative machine-readable schema is
src/AgentNotify.Protocol/Schemas/arc-0.1.schema.json.
GitHub Pages publishes the same file at
schemas/arc-0.1.schema.json.
AgentNotify.Protocol contains the portable ARC models, schema, native API DTOs, enums, and shared
JSON rules. It has no WPF, ASP.NET, SQLite, or provider dependencies. Projection into AgentNotify's
local model remains in the API layer.
Versioning and conformance
ARC versions the wire contract with arc_version. Version 0.1 is a public draft and may change
before 1.0. Consumers must reject unsupported versions instead of guessing their meaning.
Use these implementation claims:
AgentNotify defines ARC 0.1 and implements its request creation, update, and resolution lifecycle.
This producer emits ARC 0.1 events.
A conforming ARC 0.1 producer emits schema-valid events and follows the identity and lifecycle rules above. A conforming consumer validates the envelope, implements all three event types, preserves idempotency, and does not treat context fields as authorization.