HTTP and A2A protocol¶
This page lists the vessel's HTTP endpoints, the A2A methods applications use, and the shape of a turn's request and
response. The A2A layer is served by a2a-sdk 1.2.2; field names follow A2A 1.0 in camelCase JSON.
Endpoints¶
| Method and path | Auth | Returns |
|---|---|---|
GET /health |
none | {"ok": true, "agents": {"<id>": <sandbox ready>}} |
GET /v1/agents |
yes | {"agents": [{"id", "name", "description", "card_url", "available"}]} for the caller's agents |
GET /agents/<id>/.well-known/agent-card.json |
none | The agent card |
GET /agents/<id>/manifest |
yes | The agent's manifest summary: agent fields and id, model name and max_steps, tool scopes, mounts, deadline, output settings, primitives schema version |
POST /agents/<id> |
yes | A2A JSON-RPC |
"Auth" means an app key with X-Vessel-User, or a token from a configured issuer; see
Identity, apps and issuers.
The agent card¶
{
"name": "field-guide",
"description": "Answers questions about the birds in a small sample field guide.",
"version": "0.1.0",
"supportedInterfaces": [{"url": "http://vessel:8700/agents/field-guide", "protocolBinding": "JSONRPC",
"protocolVersion": "1.0"}],
"capabilities": {"streaming": true},
"defaultInputModes": ["text/plain", "application/vnd.astralvessel.transcript+json"],
"defaultOutputModes": ["text/markdown", "application/vnd.astralvessel.transcript+json",
"application/vnd.astral.primitives+json", "image/png", "text/csv"],
"skills": [{"id": "field-guide", "name": "field-guide", "description": "...",
"tags": ["astralvessel", "scope:tools:execute"], "examples": ["..."]}],
"provider": {"organization": "AstralVessel", "url": "https://github.com/AstralDeep/AstralVessel"}
}
name and the skill id are the agent's deployment id; the skill name is the manifest's name. The interface URL
is public_url plus /agents/<id>. Image media types join defaultInputModes when the model accepts images; the
primitives type and file types join defaultOutputModes when the agent has them. The scope: tag is the strongest
scope among the agent's tools.
A2A methods¶
| Method | Use |
|---|---|
SendStreamingMessage |
Start a turn and stream its events as server-sent events |
SendMessage |
Start a turn and get the finished task in one response |
GetTask |
Read a task you own |
CancelTask |
Cancel a running turn you own |
Send A2A-Version: 1.0 with each request, and Accept: text/event-stream for streaming.
Sending a turn¶
curl -N http://vessel:8700/agents/field-guide \
-H "Authorization: Bearer $VESSEL_APP_KEY" -H "X-Vessel-User: alice" \
-H "A2A-Version: 1.0" -H "Accept: text/event-stream" -H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": "1", "method": "SendStreamingMessage", "params": {
"message": {"messageId": "m1", "role": "ROLE_USER", "contextId": "conv-1",
"parts": [{"text": "Which birds in the guide hunt fish?"}]},
"configuration": {"acceptedOutputModes": ["text/markdown",
"application/vnd.astralvessel.transcript+json"]}}}'
| Part of the message | Meaning |
|---|---|
A text part |
The user's message, at most 100,000 characters. |
A part with mediaType: application/vnd.astralvessel.transcript+json |
The history, as a transcript envelope: either raw (base64 of the JSON, faster for long histories) or data (the envelope as JSON). |
raw parts with an image media type |
Attached images: PNG, JPEG, GIF or WebP, at most 8, each at most 5 MB, for agents whose model accepts images. URL parts are refused. |
A data part shaped like {"method": "tools/call", "arguments": {"question": "..."}} |
A structured skill call, used when there is no text part. The question comes from question, message, query, text or prompt. |
contextId |
The conversation id, 1 to 128 characters from letters, digits and . _ : -. Generated when omitted. |
configuration.acceptedOutputModes |
List the transcript media type to receive the transcript; list file types to limit which files you receive. |
The whole request body may be at most 16,000,000 bytes.
A turn is rejected (task state TASK_STATE_REJECTED, with a message saying why) when the history is malformed or
holds a system message, an image is invalid or the agent does not accept images, there is no text, the text is too
long, or the conversation id is invalid.
What comes back¶
A streamed turn produces, in order:
- A
taskframe (stateTASK_STATE_SUBMITTED). statusUpdateframes in stateTASK_STATE_WORKING, each carrying one event as a data part of the status message, withmetadata["vessel.event"]set to the event type.artifactUpdateframes for thetranscriptartifact (if requested), appended chunk by chunk, the last withlastChunk: true, and onefileartifact per returned file.- An
artifactUpdatefor theanswerartifact: atext/markdownpart, plus a primitives part when components were attached. - A final
statusUpdatecarryingturn.finished, in the task's final state.
| Turn status | Final task state |
|---|---|
ok, step_limit |
TASK_STATE_COMPLETED |
cancelled |
TASK_STATE_CANCELED |
deadline, model_error |
TASK_STATE_FAILED |
Numbers inside data parts travel as JSON numbers through a protobuf Struct, so integers may arrive as 1.0. The
Python client converts whole floats back to integers.
Cancelling¶
The running turn stops, its tool calls and workspace are ended, and it still finishes with a turn.finished event
whose status is cancelled. Only the task's owner can cancel it.
What the vessel stores¶
Each agent has a table in a SQLite task store (/data/tasks.sqlite in the vessel_data volume). A stored task
keeps its latest status, the user's text and the answer; it never keeps streamed text pieces, the history sent in,
the transcript or files. Tasks are deleted task_ttl_hours after their last update. Tasks that were
running when the vessel stopped are marked failed when it starts again.
Errors¶
| HTTP status | JSON-RPC code | When |
|---|---|---|
| 401 | -32001 | Missing or invalid key or token, or a missing or invalid X-Vessel-User with an app key |
| 403 | -32003 | The caller may not call this agent |
| 503 | -32004 | The agent's sandbox is not ready |
| 413 | The request body is over 16,000,000 bytes (body {"error": "request too large"}) |
Errors from the A2A layer itself, such as invalid parameters or an unknown task, come back as standard JSON-RPC
errors with HTTP 200. The non-JSON-RPC endpoints return {"error": "..."} with the same statuses.