---
title: "actor.canvas@1 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/actor.canvas"
updated: null
---

# actor.canvas@1 · OpenVibe.Services

HTML: [actor.canvas@1 · OpenVibe.Services](https://openvibe.services/docs/contracts/actor.canvas)

# ActorCanvas `actor.canvas@1`

Generated at 2026-11-13 06:25:48 UTC from [`openvibe-contracts v0.419.0`](https://github.com/OpenVibers/OpenVibe.Contracts/tree/v0.419.0) and [`openvibe-sdk v0.161.0`](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.161.0).

- Version: 1.0.0
- Owner: actor
- Visibility: public
- Status: active
- Compatibility: backward
- Decision: [ADR-059](/docs/adr/ADR-059)
- Schema: [`https://openvibe.network/contracts/actor/canvas.v1.json`](/docs/contracts/actor.canvas.json)

actor.canvas@1: an agent's live panel beside the conversation (ADR-059) — a board of a trip, a table of prices, a checklist of what is left. It is DATA: a list of blocks drawn by one renderer (OpenVibe.Shared ov-canvas.js) wherever it is shown (Chat, Surf, the Dock, Actor). There is no HTML, no style, no script and no frame: text is plain and the renderer escapes it; an image is only a picture on openvibe.media; a link opens in a new tab with its host shown. A canvas acts only through the person: a button sends its label to the agent as the person's own message (action ask), and ticking a checklist item tells the agent which item changed; the agent's next steps go through its level and approvals like any other. VERSION only grows: a viewer ignores an older one, and the agent replaces the whole document, never part of it.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `canvas` | const | yes |  | - `= "actor.canvas@1"` |
| `id` | string | yes |  | - `pattern ^cnv_[0-9A-HJKMNP-TV-Z]{26}$` |
| `title` | string | yes |  | - `minLength 1`<br>- `maxLength 120` |
| `version` | integer | yes | Grows with every update; a viewer ignores an older version. | - `minimum 1` |
| `agent_id` | string |  | The agent that draws it. | - `pattern ^agt_[0-9A-HJKMNP-TV-Z]{26}$` |
| `thread` | string |  | The conversation it sits beside (Actor's task or chat id). | - `maxLength 64` |
| `updated_at` | string | yes |  | - `format date-time` |
| `blocks` | array of one of | yes |  | - `maxItems 60` |

## Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

 Valid: minimal

~~~~
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Notes",
  "version": 1,
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": []
}
~~~~

Valid: trip

~~~~
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "heading",
      "text": "Lisbon, 14–17 November",
      "level": 1
    },
    {
      "type": "metric",
      "label": "Flights and hotel",
      "value": "€612",
      "note": "two people, booked nothing yet"
    },
    {
      "type": "checklist",
      "id": "todo",
      "items": [
        {
          "id": "flights",
          "text": "Pick the flights",
          "done": true
        },
        {
          "id": "hotel",
          "text": "Choose a hotel near Alfama",
          "done": false
        }
      ]
    },
    {
      "type": "table",
      "id": "hotels",
      "columns": [
        "Hotel",
        "Night",
        "Walk to the river"
      ],
      "rows": [
        [
          "Casa A",
          118,
          "6 min"
        ],
        [
          "Hotel B",
          96,
          "14 min"
        ]
      ]
    },
    {
      "type": "chart",
      "id": "prices",
      "kind": "line",
      "unit": "€",
      "series": [
        {
          "name": "Flights",
          "points": [
            {
              "x": "Mon",
              "y": 240
            },
            {
              "x": "Tue",
              "y": 212
            },
            {
              "x": "Wed",
              "y": 199
            }
          ]
        }
      ]
    },
    {
      "type": "image",
      "src": "https://openvibe.media/t/lisbon.jpg",
      "alt": "The river at dusk"
    },
    {
      "type": "link",
      "href": "https://openvibe.video/watch?v=AbCdEfGhIjK",
      "text": "The walking tour video"
    },
    {
      "type": "text",
      "text": "Casa A is closer to the river; Hotel B is cheaper.\nBoth have free cancellation until Friday."
    },
    {
      "type": "button",
      "label": "Book Casa A",
      "action": "ask"
    }
  ]
}
~~~~

Rejected: button-runs

~~~~
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "button",
      "label": "Delete everything",
      "action": "run"
    }
  ]
}
~~~~

Rejected: foreign-image

~~~~
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "image",
      "src": "https://tracker.example/p.png",
      "alt": "x"
    }
  ]
}
~~~~

Rejected: html-block

~~~~
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "html",
      "html": "<script>alert(1)</script>"
    }
  ]
}
~~~~

Rejected: http-link

~~~~
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "link",
      "href": "javascript:alert(1)",
      "text": "x"
    }
  ]
}
~~~~

Rejected: too-many-blocks

~~~~
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    }
  ]
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('actor.canvas@1', value);   // { valid, errors: [{ path, message }] }
~~~~
