Task and Response
Every message/send produces a Task. All three Caoliao skills are synchronous queries, so by the time the request returns the Task has finished and the result is in the same response.
Lifecycle
submitted → working → (artifact-update) → completed
└→ failedSince the service does not stream, you never observe the intermediate states — the response you receive already carries completed or failed in status.state.
Successful response
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"kind": "task",
"id": "9332c282-1fa7-4c42-96ac-58e4e2f88f4c",
"contextId": "e9c10667-e79b-4c99-b05f-0cb237067127",
"status": {
"state": "completed",
"timestamp": "2026-08-12T17:14:13.272Z"
},
"history": [
{
"kind": "message",
"role": "user",
"messageId": "m1",
"parts": [{ "kind": "text", "text": "What is this QR code? https://qr71.cn/okDISU/qssTnoy" }]
}
],
"artifacts": [
{
"artifactId": "6915a6bd-d63c-42e6-b629-8aed3bb06236",
"name": "entity",
"parts": [
{ "kind": "data", "data": { "...": "ObjectCard v2" } },
{ "kind": "text", "text": "..." }
]
}
]
}
}| Field | Description |
|---|---|
id | Task id, used for tasks/get |
contextId | Conversation context id, shared by Tasks in the same exchange |
status.state | completed or failed |
history | Messages exchanged in this interaction |
artifacts | The result. Caoliao returns exactly one |
The two artifact parts
Every artifact contains two parts:
- DataPart (
kind: "data") — structured output for programs. The shape depends on the skill, see Skills - TextPart (
kind: "text") — a one-line summary you can put straight into a conversation without summarizing again
The artifact's name tells you what came back: entity, records or actions.
Note: summary text is written in Chinese.
Looking up a finished Task
curl -X POST https://a2a.objqr.com/a2a/jsonrpc \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": "2",
"method": "tasks/get",
"params": { "id": "9332c282-1fa7-4c42-96ac-58e4e2f88f4c" }
}'Returns the same Task structure.
Important: Task records live in the service process's memory, so a restart loses them and
tasks/getwill no longer find them. Do not treattasks/getas durable storage — persist anything you need to keep when you first receive it.
Failed response
A failure is not a JSON-RPC level error. You still get result, with status.state set to failed and the reason in status.message:
{
"jsonrpc": "2.0",
"id": "9",
"result": {
"kind": "task",
"id": "d091e105-9767-472a-b7e7-5951541cab15",
"status": {
"state": "failed",
"timestamp": "2026-08-12T17:23:56.789Z",
"message": {
"kind": "message",
"role": "agent",
"parts": [{ "kind": "text", "text": "查询失败:码不存在或无法解析" }]
}
}
}
}The failure text above reads "query failed: code does not exist or cannot be resolved" — status messages are returned in Chinese.
So check result.status.state to decide success or failure — not the HTTP status code, and not the presence of an error field.
"Code does not exist" and "code exists but is not public" return the same result. This is deliberate: distinguishing them would allow enumeration probing. For a caller, both mean "no readable public entity".
Cancellation
The protocol supports tasks/cancel, but all three Caoliao skills are synchronous queries that have already finished by the time you get a response, so there is no window in which cancelling has any effect.
Known limitations
| Limitation | Detail |
|---|---|
| No streaming | capabilities.streaming is false |
| No push notifications | capabilities.pushNotifications is false |
| No state-transition history | capabilities.stateTransitionHistory is false |
| Tasks are not persisted | tasks/get finds nothing after a restart |
| Read-only | No writes; actions are completed by a person via humanUrl |
| Single organization | One call reads one organization's data |