COG-9: Agent Messaging
Status: Draft (Work in Progress)
Version: 0.1
Created: 2026-01-29
Authors: Mike Anderson
This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback.
This standard specifies the messaging protocol for delivering messages to Jobs and Agents on the Covia Grid. It defines how clients, users, and other agents submit messages to a running process via a per-job message queue, and how those messages are processed into state transitions.
Purpose
Jobs on the Covia Grid represent running processes — from short-lived operation invocations to long-lived interactive agents. Many of these processes need to receive input after creation:
- User interaction: A human sends messages to a conversational AI agent
- Agent-to-agent communication: One agent sends data or instructions to another
- Authorisation responses: A client provides credentials or approval requested by a paused job
- External notifications: A system signals that an out-of-band action has completed
COG-8 defines the job lifecycle and state chain. This specification defines how messages are delivered to jobs and how they trigger state transitions. It also defines compatibility mappings to the A2A and MCP protocols.
Terminology
See COG-1: Architecture for definitions of Grid terminology. Additional terms used in this specification:
- Message: An arbitrary JSON value submitted to a job's message queue for processing
- Message Queue: An ordered, per-job buffer of unprocessed messages
- Transition Function: The Operation asset that processes messages and produces new state records
Specification
Message Format
A message is an arbitrary JSON value submitted to a job for processing.
Messages SHOULD be JSON objects. The following top-level fields are RECOMMENDED for interoperability, but not required:
| Field | Type | Description |
|---|---|---|
role | string | Origin of the message: "user", "agent", or "system" |
parts | array | Content parts, each with a type field (e.g. "text", "data", "file") |
from | string | Sender identity (DID or venue URL) for agent-to-agent messages |
messageId | string | Optional client-assigned identifier for deduplication or correlation |
Venues and transition functions MUST accept arbitrary JSON as message content. The interpretation of message content is the responsibility of the transition function, not the messaging layer.
Example: User Text Message
{
"role": "user",
"parts": [
{"type": "text", "text": "What is the capital of France?"}
]
}
Example: Agent-to-Agent Message
{
"role": "agent",
"from": "did:web:other-venue.covia.ai",
"parts": [
{"type": "text", "text": "Here are the results you requested."},
{"type": "data", "data": {"count": 42, "items": ["a", "b", "c"]}}
]
}
Example: Authorisation Response
{
"role": "system",
"type": "auth/complete",
"credentials": {"apiKey": "sk-..."}
}
Example: Simple Input
For simple interactions, a plain JSON value is sufficient:
{"prompt": "Hello, how are you?"}
Message Delivery Endpoint
Messages are delivered to a job via:
POST /api/v1/jobs/{id}
Content-Type: application/json
<message body>
Response
The venue MUST return one of the following:
| Status | Condition | Response Body |
|---|---|---|
| 202 Accepted | Message enqueued for processing | {"id": "<job-id>", "status": "<current-status>", "queued": true} |
| 404 Not Found | Job ID does not exist | Error object |
| 409 Conflict | Job is in a terminal state | {"id": "<job-id>", "status": "<terminal-status>", "error": "Job has finished"} |
| 429 Too Many Requests | Message queue is full or rate limit exceeded | Error object with Retry-After header |
The 202 Accepted response indicates the message has been received and queued. It does NOT indicate that the message has been processed or that a state transition has occurred. Clients MUST use job observation (polling or SSE) to observe the resulting state changes.
Behaviour Requirements
A venue MUST:
- Accept messages for jobs in any non-terminal state (
PENDING,STARTED,PAUSED,INPUT_REQUIRED,AUTH_REQUIRED) - Enqueue the message and return 202 before processing begins
- Preserve message ordering within a single connection
A venue SHOULD:
- Accept messages while the job is actively processing (status
STARTED), queuing them for later processing - Include the current job status in the 202 response for client convenience
A venue MAY:
- Impose a maximum queue depth per job and return 429 when exceeded
- Impose rate limits on message submission and return 429 when exceeded
- Assign a server-side message ID if the client did not provide a
messageId
Message Queue
Each job maintains an ordered message queue. Messages are processed sequentially by the job's transition function.
The queue is the key mechanism that decouples message submission from message processing. A job may be in any non-terminal state when a message arrives — including STARTED (actively processing a previous message). The queue holds the message until the job is ready to process it.
Ordering
Messages within a single job's queue MUST be processed in the order they were accepted (FIFO).
When messages arrive from multiple sources concurrently, the venue determines the interleaving order. Venues SHOULD use arrival time at the venue as the ordering criterion.
Processing
When a message is dequeued for processing:
- The job transitions to
STARTED - The transition function is invoked with the current state and the message as input
- The transition function produces a new state (which may itself be
INPUT_REQUIRED,COMPLETE,FAILED, etc.) - A new state record is appended to the job's chain (see COG-8)
- If the new state is non-terminal and there are remaining messages in the queue, the next message is dequeued and processing continues from step 1
The state record produced by processing a message SHOULD include a reference to the message that triggered it, enabling audit trail reconstruction.
When Messages Are Processed
Messages are dequeued and processed when the job is ready to accept input. This occurs:
- When the job enters an interactive state (
INPUT_REQUIRED,AUTH_REQUIRED,PAUSED) and messages are already waiting in the queue - When a new message is submitted and the job is already in an interactive state with an empty queue
If the job is actively processing (STARTED), newly submitted messages wait in the queue. The job will process them after the current transition completes and the job returns to an interactive state.
This means a client can submit a message to a job in any non-terminal state:
| Job Status | Message Behaviour |
|---|---|
PENDING | Queued; processed when job starts and is ready for input |
STARTED | Queued; processed after current transition completes |
INPUT_REQUIRED / AUTH_REQUIRED / PAUSED | Queued; immediately dequeued and processed |
| Terminal | Rejected with 409 Conflict |
Queue Draining
If a job reaches a terminal state while messages remain in the queue, those messages are discarded. The venue MAY log discarded messages for debugging.
If a job transitions to an interactive state (INPUT_REQUIRED, AUTH_REQUIRED, PAUSED) while messages remain in the queue, the next queued message is immediately dequeued and processing continues. This enables pipelining, where a client submits multiple messages without waiting for intermediate responses.
State Record Linkage
When a message triggers a state transition, the resulting state record SHOULD include a trigger field referencing the message:
{
"status": "INPUT_REQUIRED",
"prev": "0xabc...",
"output": {"response": "The capital of France is Paris."},
"trigger": {
"messageId": "msg-001",
"role": "user"
},
"updated": 1769683717710
}
This enables clients to correlate responses with the messages that produced them, which is essential for multi-source messaging where responses may arrive out of order relative to submissions.
Agent Interaction Patterns
Conversational Agent
The most common pattern: a user exchanges messages with an LLM-backed agent.
Client Venue
│ │
│ POST /api/v1/invoke │
│ {"operation": "llm-agent", │
│ "input": {"system": "..."}} │
│ ─────────────────────────────────▶ │
│ │ State(0): PENDING
│ 201 Created │ State(1): INPUT_REQUIRED
│ {"id": "0x456..."} │ (agent ready for first message)
│ ◀───────────────────────────────── │
│ │
│ POST /api/v1/jobs/0x456... │
│ {"role": "user", │
│ "parts": [{"text": "Hello"}]} │
│ ─────────────────────────────────▶ │
│ │
│ 202 Accepted │ Message queued
│ ◀───────────────────────────────── │ State(2): STARTED (LLM processing)
│ │ State(3): INPUT_REQUIRED
│ GET /api/v1/jobs/0x456... │ (response ready, awaiting next)
│ ─────────────────────────────────▶ │
│ │
│ 200 OK │
│ {"status": "INPUT_REQUIRED", │
│ "output": {"response": "Hi!"}} │
│ ◀───────────────────────────────── │
│ │
│ POST /api/v1/jobs/0x456... │
│ {"role": "user", │
│ "parts": [{"text": "Bye"}]} │
│ ─────────────────────────────────▶ │
│ │
│ 202 Accepted │ State(4): STARTED
│ ◀───────────────────────────────── │ State(5): COMPLETE
│ │ (agent chose to terminate)
Agent-to-Agent
Two agents on different venues communicate by posting messages to each other's job queues.
Venue A Venue B
│ │
│ Agent A's transition function │
│ decides to contact Agent B │
│ │
│ POST /api/v1/jobs/{agent-b-id} │
│ {"role": "agent", │
│ "from": "did:web:venue-a...", │
│ "parts": [{"text": "Request"}]} │
│ ─────────────────────────────────▶ │
│ │
│ 202 Accepted │ Agent B processes message
│ ◀───────────────────────────────── │ Agent B's transition function
│ │ posts reply to Agent A
│ │
│ POST /api/v1/jobs/{agent-a-id} │
│ ◀───────────────────────────────── │
│ │
│ 202 Accepted │
│ ─────────────────────────────────▶ │
Cross-venue agent messaging uses the same endpoint and format. The sending venue's adapter makes an HTTP POST to the target venue's API. No special federation protocol is needed — the messaging API is the federation protocol.
Pipelining
A client may submit multiple messages without waiting for responses:
Client Venue
│ │
│ POST /api/v1/jobs/{id} │
│ {"role": "user", ...msg1...} │
│ ─────────────────────────────────▶ │
│ 202 Accepted │
│ ◀───────────────────────────────── │
│ │
│ POST /api/v1/jobs/{id} │
│ {"role": "user", ...msg2...} │
│ ─────────────────────────────────▶ │
│ 202 Accepted │
│ ◀───────────────────────────────── │
│ │
│ POST /api/v1/jobs/{id} │
│ {"role": "user", ...msg3...} │
│ ─────────────────────────────────▶ │
│ 202 Accepted │
│ ◀───────────────────────────────── │
│ │ Messages processed in order:
│ │ msg1 → State(N+1)
│ │ msg2 → State(N+2)
│ │ msg3 → State(N+3)
This is useful for batch input, data streaming, or pre-loading context into an agent before it begins processing.
Synchronous and Asynchronous Interaction
The message queue supports both synchronous and asynchronous interaction patterns on the same job:
Asynchronous (Native)
The native Covia pattern: the client submits messages via POST /api/v1/jobs/{id} and observes state changes via polling or SSE. The client is in full control of the interaction rhythm and can submit messages at any time.
This is the pattern used by:
- Conversational UIs
- A2A agent-to-agent communication
- Orchestrator workflows
- Any client that wants real-time observation
Synchronous (Bridge)
Some protocols require a synchronous request/response cycle — the caller blocks until a final result is available. The most prominent example is MCP's tools/call, which expects a single response containing the tool output.
Synchronous bridges work by:
- Creating a job (or sending a message to an existing one)
- Waiting for the job to reach a terminal state (
COMPLETE,FAILED, etc.) - Returning the terminal state's output or error as the response
The bridge does not need to know whether the job is one-shot or multi-turn internally. A multi-turn job that requires intermediate input (e.g. via MCP elicitation) goes through its full interactive cycle — the bridge mediates each INPUT_REQUIRED → input → STARTED transition — and the synchronous caller only sees the final result.
This means the same job can be driven by a synchronous bridge (MCP tools/call) or an asynchronous client (A2A message/send + SSE) interchangeably. The job's behaviour is identical in both cases; only the client-side interaction pattern differs.
Protocol Compatibility
A2A (Agent-to-Agent Protocol)
The Covia messaging model is designed for compatibility with the A2A protocol. The mapping is:
| A2A | Covia | Notes |
|---|---|---|
| Task | Job | Both are stateful processes with lifecycle states |
message/send (no taskId) | POST /api/v1/invoke | Creates a new job/task |
message/send (with taskId) | POST /api/v1/jobs/{id} | Sends message to existing job |
message/sendStreaming | POST /api/v1/jobs/{id} + GET /jobs/{id}/sse | Send message, then subscribe to SSE for streaming response |
| Task states: submitted, working | PENDING, STARTED | |
| Task state: input-required | INPUT_REQUIRED | |
| Task states: completed, failed, cancelled, rejected | COMPLETE, FAILED, CANCELLED, REJECTED | |
| Message with role + parts | Message with role + parts | Same structure by design |
| Artifacts (in task output) | Job output / asset references |
A2A Message Mapping
An A2A message/send request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"type": "text", "text": "Hello"}]
},
"configuration": {
"taskId": "0x456..."
}
}
}
Maps to a Covia message delivery:
POST /api/v1/jobs/0x456...
Content-Type: application/json
{
"role": "user",
"parts": [{"type": "text", "text": "Hello"}]
}
The A2A bridge layer on the venue (see A2A.java) translates between JSON-RPC and the REST endpoint, extracting the taskId from the configuration and the message body from params.message.
A2A Ordering Guarantee
A2A requires: "All implementations MUST deliver events in the order they were generated." The per-job FIFO queue satisfies this requirement.
MCP (Model Context Protocol)
MCP uses a different interaction model — servers expose tools to clients, and clients invoke them. However, several MCP features map onto the Covia messaging model:
| MCP | Covia | Notes |
|---|---|---|
tools/call | POST /api/v1/invoke | Invoke an operation, creating a job |
elicitation/create (form mode) | Job enters INPUT_REQUIRED; response = message to job | Server asks client for input; client responds with data |
elicitation/create (URL mode) | Job enters AUTH_REQUIRED | Server directs user to external URL for auth |
notifications/elicitation/complete | Message to job confirming auth complete | Fire-and-forget notification |
notifications/cancelled | PUT /api/v1/jobs/{id}/cancel | Cancellation |
| Progress notifications | SSE events on job state chain | Each state record = progress update |
MCP Elicitation as Job Messaging
When a Covia venue acts as an MCP server and a tool call requires user input:
- The venue creates a job for the tool call
- The job transitions to
INPUT_REQUIREDwith amessagedescribing what's needed - The MCP bridge sends an
elicitation/createrequest to the MCP client - The MCP client presents the form to the user
- The user's response is translated into a message posted to the job queue
- The job processes the message and continues
MCP Client MCP Bridge Job Queue
│ │ │
│ tools/call │ │
│ ──────────────────────▶│ │
│ │ invoke → Job created │
│ │ Job: INPUT_REQUIRED │
│ │ │
│ elicitation/create │ │
│ ◀──────────────────────│ │
│ │ │
│ User provides input │ │
│ ──────────────────────▶│ │
│ │ POST /jobs/{id} │
│ │ ─────────────────────▶│
│ │ 202 Accepted │
│ │ ◀─────────────────────│
│ │ │ Process → COMPLETE
│ tools/call result │ │
│ ◀──────────────────────│ │
MCP Notifications as Messages
MCP's notifications/elicitation/complete is a fire-and-forget notification (no response expected). This maps directly to the 202 Accepted semantics of the Covia messaging endpoint — the sender does not wait for processing.
MCP Tasks (November 2025)
The November 2025 MCP specification introduced Tasks as a server-side abstraction for tracking work. MCP Tasks align with Covia Jobs:
- Both represent asynchronous work with lifecycle states
- Both support progress tracking and cancellation
- MCP's
tasks/cancelmaps toPUT /api/v1/jobs/{id}/cancel
As the MCP Tasks specification matures, deeper integration may be defined. The Covia messaging model is designed to be forward-compatible with task-based MCP interactions.
Security Considerations
Message Authentication
For agent-to-agent messaging across venues, the receiving venue SHOULD:
- Verify the sender's identity (e.g. via DID resolution or mutual TLS)
- Apply access control policies to determine whether the sender is authorised to message the target job
- Log the sender's identity in the state record for audit purposes
For user messages, the venue SHOULD:
- Apply the same authentication used for other API endpoints
- Associate the message with the authenticated user identity
Message Size Limits
Venues SHOULD impose maximum message sizes to prevent resource exhaustion. A recommended default is 1 MB per message.
Venues MUST return 413 Payload Too Large if a message exceeds the configured limit.
Queue Depth Limits
Venues SHOULD impose maximum queue depths per job to prevent memory exhaustion from clients that submit messages faster than the transition function can process them.
When the queue is full, the venue MUST return 429 Too Many Requests with a Retry-After header.
Sensitive Content
Messages may contain sensitive data (credentials, personal information, proprietary data). Venues SHOULD:
- Apply the same data protection policies as for job inputs and outputs (see COG-8)
- Not log message content at debug level in production environments
- Support encryption of message content at rest
For credential delivery (e.g. API keys in response to AUTH_REQUIRED), clients SHOULD use secure transport (HTTPS) and venues SHOULD NOT persist credentials in the state chain. The transition function SHOULD extract and store credentials separately, recording only a reference (e.g. "API key provided") in the state record.
Denial of Service
The messaging endpoint is a potential target for denial-of-service attacks. Venues SHOULD:
- Rate-limit message submissions per client
- Rate-limit message submissions per job
- Impose connection limits
- Reject messages from unauthenticated clients for non-public jobs
Implementation Notes
Relationship to Job Creation
The POST /api/v1/invoke endpoint (COG-7/COG-8) creates a new job with initial input. The POST /api/v1/jobs/{id} endpoint (this specification) sends subsequent messages to an existing job. Together they form the complete interaction lifecycle:
| Action | Endpoint | COG |
|---|---|---|
| Create job with initial input | POST /api/v1/invoke | COG-7, COG-8 |
| Send message to existing job | POST /api/v1/jobs/{id} | COG-9 (this spec) |
| Observe job state | GET /api/v1/jobs/{id} | COG-8 |
| Stream job events | GET /api/v1/jobs/{id}/sse | COG-8 |
| View job history | GET /api/v1/jobs/{id}/history | COG-8 |
| Pause job | PUT /api/v1/jobs/{id}/pause | COG-8 |
| Resume paused job | PUT /api/v1/jobs/{id}/resume | COG-8 |
| Cancel job | PUT /api/v1/jobs/{id}/cancel | COG-8 |
| Delete job | PUT /api/v1/jobs/{id}/delete | COG-8 |
Transition Function Contract
A transition function that supports messaging MUST:
- Accept arbitrary JSON as the message input
- Return a new state value (which becomes the next state record in the chain)
- Handle unknown message formats gracefully (e.g. by returning an error state or ignoring unrecognised fields)
A transition function SHOULD:
- Be idempotent where possible (processing the same message twice produces the same result)
- Complete within a reasonable time to avoid blocking the queue
- Produce meaningful state records that capture the essence of each interaction
SSE Integration
When a message is processed and produces a new state record, the venue MUST emit an SSE event to any active subscribers for that job (see COG-8: Job Observation). This provides real-time feedback to clients that submit a message via POST and observe via SSE concurrently.
Related Specifications
- COG-1: Architecture - Overall Grid architecture and terminology
- COG-2: Decentralised ID - Agent and venue identification
- COG-7: Operations - Operation definitions and invocation
- COG-8: Jobs - Job lifecycle, state chains, and agent model
- COG-11: Agent Lifecycle - Stateful agent creation, run loop, and transition functions
- A2A Protocol - Agent-to-Agent protocol specification
- MCP Specification - Model Context Protocol specification