@machinemetrics/agent-event-protocol
v0.1.1
Published
Agent Event Protocol implementation
Readme
@machinemetrics/agent-event-protocol
A transport-agnostic event protocol for streaming agent responses. Defines the schema and utilities for real-time communication between AI agents and clients.
Installation
npm install @machinemetrics/agent-event-protocolDesign Principles
- Simple - Easy to understand and implement
- Flexible - Supports future features without breaking changes
- Type-safe - Strong contracts between client and server
- Observable - Easy to monitor and debug
- Transport-agnostic - Same events work over SSE, NDJSON, WebSocket, NATS, etc.
Protocol Overview
The protocol defines a streaming event schema for agent responses. All events are self-identifying JSON payloads with common fields:
event- Event type identifierrequestId- Correlates all events to the originating requesttimestamp- ISO 8601 timestamp for observability
Message Flow
┌─────────┐ ┌─────────┐
│ Client │ │ Server │
└────┬────┘ └────┬────┘
│ │
│ POST /v1/agents/{agentName}/stream │
│ { messages, threadId?, context? } │
├─────────────────────────────────────────────>│
│ │
│ metadata: {threadId, requestId, agentId} │
│<─────────────────────────────────────────────┤
│ │
│ text: {blockId, stage: "start"} │
│<─────────────────────────────────────────────┤
│ │
│ text: {blockId, stage: "delta", delta} │
│<─────────────────────────────────────────────┤
│ │
│ text: {blockId, stage: "stop"} │
│<─────────────────────────────────────────────┤
│ │
│ done: {content, usage, finishReason} │
│<─────────────────────────────────────────────┤
│ │Client Action Flow
When the agent needs client-side action, the stream pauses and resumes via a new request:
┌─────────┐ ┌─────────┐
│ Client │ │ Server │
└────┬────┘ └────┬────┘
│ │
│ POST /stream { messages, threadId? } │
├─────────────────────────────────────────────>│
│ │
│ continuation: {actionId, kind, payload} │
│<─────────────────────────────────────────────┤
│ │
│ done: {finishReason: "action_required"} │
│<─────────────────────────────────────────────┤
│ │
│ (client handles action based on kind) │
│ │
│ POST /stream { messages, threadId, │
│ context: { actionResults: [...] }} │
├─────────────────────────────────────────────>│
│ │
│ (agent continues with result) │
│ │Event Types
The protocol defines 9 event types covering the complete lifecycle of an agent response.
| Event | Description |
|-------|-------------|
| metadata | Stream initialization with threadId, requestId, agentId |
| text | Streaming text output with stage lifecycle |
| thinking | Model reasoning/thinking content (extended thinking) |
| tool_call | Tool invocation with streaming arguments |
| tool_result | Tool execution result (status and summary only) |
| done | Stream complete with full content array |
| error | Error condition with code and retry guidance |
| aborted | Client cancellation acknowledgment |
| continuation | Pause for client action (tools, approval, input) |
Terminal Events
Each request must end with exactly one terminal event: done, error, or aborted.
Event Lifecycle (Stage Pattern)
Content events (text, thinking, tool_call, tool_result) use a consistent stage pattern:
| Stage | Description |
|-------|-------------|
| start | Block begins, includes blockIndex for ordering |
| delta | Content chunk, includes deltaIndex for ordering within block |
| stop | Block complete, includes final data for that block type |
Request Schema
type AgentRequest = {
messages: InputMessage[]; // New user input
threadId?: string; // Resume existing thread
context?: {
actionResults?: ActionResult[]; // Results from continuation events
[key: string]: unknown; // Additional passthrough data
};
};Only messages is required. The server loads thread history from storage via threadId.
Content Blocks
User Content Blocks (in requests):
text- Plain text input
Assistant Content Blocks (in responses):
text- Text outputthinking- Model reasoningtool_use- Tool invocation with argstool_result- Tool result status and summary (no full payloads)
Error Handling
Error Codes
| Code | Description |
|------|-------------|
| auth_failed | Authentication failed |
| thread_not_found | Thread does not exist |
| thread_access_denied | Thread ownership violation |
| invalid_message_format | Invalid request format |
| message_too_large | Message exceeds size limit |
| rate_limit_exceeded | Rate limit hit |
| stream_timeout | Stream timed out |
| agent_error | Agent execution error |
| tool_error | Tool execution error |
| internal_error | Internal server error |
| invalid_action_id | Action ID not found or already resolved |
HTTP Status Mapping
| Status | Error Codes |
|--------|-------------|
| 400 | invalid_message_format, invalid_action_id |
| 401 | auth_failed |
| 403 | thread_access_denied |
| 404 | thread_not_found |
| 413 | message_too_large |
| 429 | rate_limit_exceeded |
| 500 | internal_error |
| 504 | stream_timeout |
Retry Guidance
retryable: true- Client can retry with backoffretryable: false- Don't retry, user action required
SSE Transport
Server-Sent Events is the default transport implementation.
Wire Format
Content-Type: text/event-stream
Connection: keep-alive
Cache-Control: no-cache
event: metadata
data: {"event":"metadata","requestId":"req-1","threadId":"th-1","agentId":"agent","timestamp":"..."}
event: text
data: {"event":"text","requestId":"req-1","messageId":"msg-1","blockId":"b0","stage":"start","blockIndex":0,"timestamp":"..."}
event: text
data: {"event":"text","requestId":"req-1","messageId":"msg-1","blockId":"b0","stage":"delta","delta":"Hello","deltaIndex":0,"timestamp":"..."}
event: done
data: {"event":"done","requestId":"req-1","messageId":"msg-1","threadId":"th-1","content":[...],"finishReason":"stop","timestamp":"..."}Keepalive
Servers should send SSE comment lines (: heartbeat\n\n) every 15 seconds to prevent proxy timeouts.
Utilities
import { encodeSSE, parseSSEChunk } from '@machinemetrics/agent-event-protocol';
// Server: encode events for streaming
const sseData = encodeSSE('text', { event: 'text', ... });
// Client: parse incoming chunks
const { events, remainder } = parseSSEChunk(chunk);Extensibility
Adding new events is backward compatible:
- Clients should ignore unknown event types
- New optional fields can be added to existing events
- No version bump required for additive changes
Breaking changes require new endpoint version:
/v1/agents/{agentName}/stream → Current
/v2/agents/{agentName}/stream → Breaking changesExports
import {
// Core types
InputMessage,
AgentRequest,
AgentEvent,
ActionResult,
UserContentBlock,
AssistantContentBlock,
EventStage,
MetadataEvent,
TextEvent,
ThinkingEvent,
ToolCallEvent,
ToolResultEvent,
DoneEvent,
ErrorEvent,
AbortedEvent,
ContinuationEvent,
// Schemas (Zod validators)
AgentRequestSchema,
AgentEventSchema,
// ... all event schemas
// SSE utilities
encodeSSE,
parseSSEChunk,
ParsedEvent,
ParseResult,
// Constants
PROTOCOL_VERSION,
EVENT_TYPES,
} from '@machinemetrics/agent-event-protocol';Development
pnpm install
pnpm build # Build the package (ESM + CJS)
pnpm test # Run tests
pnpm typecheck # Type check