---
title: "live.agent-stream-state@2 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/live.agent-stream-state"
updated: null
---

# live.agent-stream-state@2 · OpenVibe.Services

HTML: [live.agent-stream-state@2 · OpenVibe.Services](https://openvibe.services/docs/contracts/live.agent-stream-state)

# LiveAgentStreamState `live.agent-stream-state@2`

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: 2.0.0
- Owner: live
- Visibility: first-party
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/live/agent-stream-state.v2.json`](/docs/contracts/live.agent-stream-state.json)

live.agent-stream-state@2 (the Agent Streams plan, S4): what the desk reads every ten seconds, GET /api/streams/agent/:id/state on OpenVibe.Live (with the publish ticket). VIEWERS is how many people watch. ENCODE says whether Live wants video: it is true while anyone watches or the stream is public, and while it is false the desk stops encoding and sends a snapshot every five seconds (PUT /api/streams/agent/:id/frame, JPEG or WebP, at most 200 KB, with the ticket). CURTAIN is the desk privacy screen (the reason is the owner only). ENDS\_AT is when the stream ends. LINES are the lines the agent said, oldest first, each with its id: the desk asks for the lines after the last id it spoke (the ?after parameter) and speaks them in the person own voice. VOICE is the voice the agent speaks in (the mode, the personality, the voice, the mute and, when the person allowance ran out, the pause message). The v2 shape adds LINES and VOICE to the v1 shape (both optional, so a v1 reader keeps working); the v1 fields keep their meaning. CAPTIONS\_URL is where the recording captions live: the WebVTT a VOD player can attach as a text track.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `viewers` | integer | yes |  | - `minimum 0` |
| `encode` | boolean | yes |  |  |
| `curtain` | object | yes |  | - `no other fields` |
| `curtain.up` | boolean | yes |  |  |
| `curtain.since` | string \| null |  |  | - `format date-time` |
| `curtain.reason` | enum |  | Why the curtain is up. The owner view only; a viewer never sees it. The password\_field reason is the one that silences the agent voice: a line spoken over a password field would put words where a secret is. | - `one of "password_field", "secret_file", "env_print", "manual", "site", "detector"` |
| `ends_at` | string \| null | yes |  | - `format date-time` |
| `lines` | array of object |  | The lines the agent said, oldest first. The desk asks for the lines after the last id it spoke. | - `items: no other fields` |
| `lines[].id` | integer | yes | The line id: the desk cursor. |  |
| `lines[].at` | string | yes |  | - `format date-time` |
| `lines[].text` | string | yes | The line, at most 220 characters. It passed the house text filters before it was written. | - `maxLength 220` |
| `lines[].kind` | enum | yes | What the line is about: a step that started, a step that failed, a result that landed, or an idle line. | - `one of "step", "failure", "result", "idle"` |
| `lines[].personality` | enum |  |  | - `one of "calm", "hype", "dry", "roast", null` |
| `lines[].voice` | string \| null |  | The Chat voice the line is to be spoken in. | - `maxLength 64` |
| `voice` | object |  |  | - `no other fields` |
| `voice.mode` | enum | yes | Watch is the quiet mode; narrated is the one where the agent says what it is doing. | - `one of "watch", "narrated"` |
| `voice.personality` | enum | yes |  | - `one of "calm", "hype", "dry", "roast"` |
| `voice.voice` | string \| null |  | The Chat voice the agent speaks in. Null is the site default. | - `maxLength 64` |
| `voice.muted` | boolean | yes | The kill switch: the owner or a channel moderator. It takes effect on the desk at once. |  |
| `voice.own` | boolean |  | The agent speaks in the person own cloned voice. Only with the person own consent. |  |
| `voice.own_consent_at` | string \| null |  | When the person consented to their own cloned voice. The owner view only. | - `format date-time` |
| `voice.paused` | string \| null |  | Why the voice is paused, or null: allowance\_spent when the person allowance ran out. |  |
| `voice.message` | string \| null |  | What the owner is told about the pause, for example voice paused: allowance spent. |  |
| `captions_url` | string \| null |  | Where the recording captions live: the WebVTT a VOD player can attach as a text track. The voice itself is mixed into the stream before the recording, so the recording keeps the voice; the captions are the track beside it. |  |

## Examples

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

 Valid: encoding

~~~~
{
  "viewers": 2,
  "encode": true,
  "curtain": {
    "up": false
  },
  "ends_at": null
}
~~~~

Valid: snapshot-mode

~~~~
{
  "viewers": 0,
  "encode": false,
  "curtain": {
    "up": true,
    "since": "2026-11-06T12:05:00.000Z",
    "reason": "password_field"
  },
  "ends_at": "2026-11-06T13:00:00.000Z"
}
~~~~

Valid: with-voice

~~~~
{
  "viewers": 3,
  "encode": true,
  "curtain": {
    "up": false,
    "since": null
  },
  "ends_at": "2026-11-06T18:00:00.000Z",
  "captions_url": "https://openvibe.live/api/streams/agent/ai_123456/captions.vtt",
  "lines": [
    {
      "id": 1,
      "at": "2026-11-06T17:00:00.000Z",
      "text": "The checkout tests are green.",
      "kind": "result",
      "personality": "calm",
      "voice": "gary"
    }
  ],
  "voice": {
    "mode": "narrated",
    "personality": "calm",
    "voice": "gary",
    "muted": false,
    "own": false,
    "own_consent_at": null,
    "paused": null,
    "message": null
  }
}
~~~~

Rejected: negative-viewers

~~~~
{
  "viewers": -1,
  "encode": false,
  "curtain": {
    "up": false
  },
  "ends_at": null
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('live.agent-stream-state@2', value);   // { valid, errors: [{ path, message }] }
~~~~
