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

# agents.blueprint@1 · OpenVibe.Services

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

# AgentsBlueprint `agents.blueprint@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-044](/docs/adr/ADR-044)
- Schema: [`https://openvibe.network/contracts/agents/blueprint.v1.json`](/docs/contracts/agents.blueprint.json)

agents.blueprint@1 (agent-blueprints.md, 2026-11-12): an agent anyone can read, change, share and run, as one small document of ten parts. FACE is how it looks and sounds (Design), BRAIN which models it may use and what it may pay (Exchange), KNOWS its instructions, skills and knowledge, HANDS the tools it may use (allowed or ask first), PLACES where its work runs (your machines, machines shared with you, OpenVibe computers, a sandbox; Link), MEMORY what it keeps, RULES its approval level, budget and who may ask it, WHEN what starts it, MAKES what it answers with and whether other agents and apps may call it, TEAM a play whose roles are other blueprints. INPUTS are what an installer fills in ({{id}} in KNOWS and WHEN); NEEDS are what an installer must connect first. A blueprint carries no secret and grants nothing: it asks, and an install (agents.install@1) is the installer agreeing. A published version never changes; a new version that asks for more waits for each installer's yes. Money, passwords, deletions, new recipients and posting in a person's name always ask, whatever the level: no field lowers that floor. openvibe-sdk/agents/blueprint validates, fills, compiles and prices one.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `id` | string | yes | bp\_<ULID>, or a short stock id such as bp\_vibe. | - `pattern ^bp_[A-Za-z0-9_-]{3,40}$` |
| `version` | integer | yes | Counts up from 1. A published version never changes; a draft is the next number. | - `minimum 1`<br>- `maximum 999999` |
| `state` | enum | yes | retired: no new installs; existing ones keep running. | - `one of "draft", "published", "retired"` |
| `name` | string | yes |  | - `minLength 1`<br>- `maxLength 60` |
| `summary` | string | yes | What it does, in a sentence or two. | - `minLength 1`<br>- `maxLength 300` |
| `author` | object | yes |  | - `no other fields` |
| `author.type` | enum | yes |  | - `one of "user", "service"` |
| `author.id` | string | yes |  | - `minLength 1`<br>- `maxLength 160` |
| `visibility` | enum | yes | link: anyone with its address may read and install it. public: also listed on openvibe.design's gallery, every part readable. | - `one of "private", "link", "public"` |
| `forked_from` | string \| null |  | The version it was remixed from (bp\_…@N); the gallery credits it. | - `pattern ^bp_[A-Za-z0-9_-]{3,40}@[1-9][0-9]{0,5}$` |
| `licence` | string |  | An SPDX id (MIT, CC-BY-4.0, …) for remixes; absent means remixes need the author's yes. | - `minLength 1`<br>- `maxLength 60` |
| `tags` | array of string |  |  | - `maxItems 8`<br>- `unique items`<br>- `items: pattern ^[a-z][a-z0-9-]{0,40}$` |
| `inputs` | array of object |  | What an installer fills in. Each is used as {{id}} in KNOWS and WHEN. | - `maxItems 12`<br>- `items: no other fields` |
| `inputs[].id` | string | yes |  | - `pattern ^[a-z][a-z0-9_]{0,30}$` |
| `inputs[].type` | enum | yes |  | - `one of "text", "number", "boolean", "choice", "channel", "room", "repository", "machine", "url"` |
| `inputs[].label` | string | yes |  | - `minLength 1`<br>- `maxLength 80` |
| `inputs[].help` | string |  |  | - `minLength 1`<br>- `maxLength 300` |
| `inputs[].required` | boolean |  |  |  |
| `inputs[].default` | string \| number \| boolean |  |  |  |
| `inputs[].choices` | array of string |  |  | - `minItems 2`<br>- `maxItems 30`<br>- `items: minLength 1`<br>- `items: maxLength 80` |
| `needs` | array of object |  | What an installer must have connected; the install screen checks each and offers the way to connect it. | - `maxItems 12`<br>- `items: no other fields` |
| `needs[].kind` | enum | yes |  | - `one of "machine", "key", "plan", "app", "robot"` |
| `needs[].what` | string | yes | machine: gpu, desk, browser, linux, windows, macos, android; key and plan: a provider (gemini, openai, …); app: a Connect app; robot: a manifest id. | - `pattern ^[a-z0-9][a-z0-9_.-]{0,60}$` |
| `needs[].why` | string |  |  | - `minLength 1`<br>- `maxLength 200` |
| `face` | object |  | How it looks and sounds wherever it shows up: chat, a stream overlay, a robot, a run card. | - `no other fields` |
| `face.avatar` | string |  | An https image, a Media id, or a preset such as preset:researcher. | - `minLength 1`<br>- `maxLength 500` |
| `face.glyph` | string |  | A glyph from the registry, when it has no picture. | - `pattern ^[a-z][a-z0-9-]{0,40}$` |
| `face.hue` | integer |  |  | - `minimum 0`<br>- `maximum 359` |
| `face.voice` | string \| null |  | A TTS voice id, or null for none. | - `maxLength 80` |
| `face.tone` | string |  | How it talks, in words. | - `minLength 1`<br>- `maxLength 300` |
| `face.greeting` | string |  |  | - `minLength 1`<br>- `maxLength 500` |
| `face.elements` | array of string |  | Design elements it shows (a co-host card on the overlay, a reaction). | - `maxItems 8`<br>- `unique items`<br>- `items: minLength 1`<br>- `items: maxLength 160` |
| `brain` | object |  | Which models it may use. pick auto: the Dispatcher, cheapest that can, escalating only on evidence; size: the cheapest of that size; exact: MODEL only (a person's own pick always wins). | - `no other fields` |
| `brain.pick` | enum |  |  | - `one of "auto", "size", "exact"` |
| `brain.size` | enum |  |  | - `one of "small", "medium", "strong"` |
| `brain.model` | string |  | An Exchange offer or provider:model id, for pick exact. | - `minLength 1`<br>- `maxLength 160` |
| `brain.rungs` | array of enum |  | The ladder rungs it may use: the installer's machine, a signed-in plan, their key, the free allowance, credits. | - `minItems 1`<br>- `unique items`<br>- `items: one of "machine", "plan", "key", "allowance", "credits"` |
| `brain.max_usd_per_m` | number |  | The most it may pay per million tokens on any rung paid per token. | - `minimum 0`<br>- `maximum 10000` |
| `brain.caps` | array of enum |  |  | - `maxItems 8`<br>- `unique items`<br>- `items: one of "tools", "vision", "audio", "json", "thinking", "long-context"` |
| `brain.depth` | enum |  |  | - `one of "quick", "balanced", "deep"` |
| `brain.advisor` | object \| null |  | A stronger model it may consult at hard decisions, at most CONSULTS times a run. |  |
| `brain.decisions` | boolean |  | Yes/no, choose-one and rubric steps go to Decisions (POST /api/v1/decisions) instead of a model writing an answer. |  |
| `knows` | object | yes |  | - `no other fields` |
| `knows.instructions` | string | yes | Its instructions, in words. {{id}} is an input. | - `minLength 1`<br>- `maxLength 20000` |
| `knows.skills` | array of string |  | Skill ids (Actor's skills, Run's shared skills). | - `maxItems 32`<br>- `unique items`<br>- `items: minLength 1`<br>- `items: maxLength 160` |
| `knows.knowledge` | array of object |  |  | - `maxItems 32`<br>- `items: no other fields` |
| `knows.knowledge[].kind` | enum | yes |  | - `one of "file", "media", "wiki", "search", "repository", "url"` |
| `knows.knowledge[].ref` | string | yes |  | - `minLength 1`<br>- `maxLength 2000` |
| `knows.knowledge[].note` | string |  |  | - `minLength 1`<br>- `maxLength 300` |
| `knows.examples` | array of object |  | Asks with what a good answer does; Try it offers them and Bench grades with them. | - `maxItems 20`<br>- `items: no other fields` |
| `knows.examples[].ask` | string | yes |  | - `minLength 1`<br>- `maxLength 2000` |
| `knows.examples[].good` | string | yes |  | - `minLength 1`<br>- `maxLength 2000` |
| `hands` | object |  |  | - `no other fields` |
| `hands.tools` | array of object |  | ov:<product>.<tool> a product's tool on the one MCP endpoint; tools:<app> an openvibe.tools app; connect:<app> a Connect app; mcp:https://… the installer's own server; machine:files\|shell\|desk\|browser\|screen on its PLACE; robot:<command> a robot's manifest command; harness:<name> a coding harness for code work; agent:bp\_…\|agt\_… another agent as a tool. MODE ask: every use asks first. | - `maxItems 64`<br>- `items: no other fields` |
| `hands.tools[].tool` | string | yes |  | - `pattern ^(?:ov:[a-z][a-z0-9-]{0,40}(?:\.[a-z0-9_-]{1,60}){1,4}\|tools:[a-z0-9][a-z0-9-]{0,60}(?:\.[a-z0-9_-]{1,60}){0,3}\|connect:[a-z0-9][a-z0-9-]{0,60}(?:\.[a-z0-9_-]{1,60}){0,3}\|mcp:https://[^\s]{4,1800}\|machine:(?:files\|shell\|desk\|browser\|screen)\|robot:[a-z0-9_][a-z0-9_.-]{0,80}\|harness:(?:auto\|dsh\|codex\|claude-code\|opencode\|hermes\|gemini)\|agent:(?:bp_[A-Za-z0-9_-]{3,40}(?:@[1-9][0-9]{0,5})?\|agt_[A-Za-z0-9]{6,40}))$`<br>- `maxLength 1900` |
| `hands.tools[].mode` | enum | yes |  | - `one of "allow", "ask"` |
| `hands.tools[].note` | string |  |  | - `minLength 1`<br>- `maxLength 300` |
| `hands.search` | boolean |  | It may find more tools on the one MCP endpoint by search; each found tool asks first. |  |
| `places` | object |  |  | - `no other fields` |
| `places.order` | array of enum |  | Where its work may run, tried in order: none (it only talks and calls tools), own (the installer's machines through Link), shared (machines shared with the installer by a grant), openvibe (OpenVibe computers), sandbox (a throwaway one). A blueprint never names a machine; the install does. | - `minItems 1`<br>- `maxItems 5`<br>- `unique items`<br>- `items: one of "none", "own", "shared", "openvibe", "sandbox"` |
| `places.needs` | array of enum |  |  | - `maxItems 8`<br>- `unique items`<br>- `items: one of "gpu", "desk", "browser", "linux", "windows", "macos", "android", "robot"` |
| `memory` | object |  |  | - `no other fields` |
| `memory.scope` | enum | yes | person: one memory per installer; thread: per conversation; team: shared by a TEAM's roles. | - `one of "none", "person", "thread", "team"` |
| `memory.remember` | string |  | What is worth keeping, in words. | - `minLength 1`<br>- `maxLength 1000` |
| `memory.keep_days` | integer \| null |  |  | - `minimum 1`<br>- `maximum 3650` |
| `rules` | object |  |  | - `no other fields` |
| `rules.level` | enum |  | actor.agent@1's levels. The floor (money, passwords, deletions, new recipients, posting in a person's name) asks at every level. | - `one of "ask", "auto-edits", "approve-for-me", "full"` |
| `rules.budget` | object |  |  | - `no other fields` |
| `rules.budget.run_usd` | number |  |  | - `minimum 0`<br>- `maximum 10000` |
| `rules.budget.day_usd` | number |  |  | - `minimum 0`<br>- `maximum 10000` |
| `rules.budget.run_seconds` | number |  |  | - `minimum 1`<br>- `maximum 604800` |
| `rules.budget.run_steps` | integer |  |  | - `minimum 1`<br>- `maximum 10000` |
| `rules.who` | object |  | Who may start it besides its installer (agents.source@1's WHO). Another person's words start a run with a mandate; they are information, never permission. | - `no other fields` |
| `rules.who.may` | enum | yes |  | - `one of "owner", "list", "followers", "anyone"` |
| `when` | array of object |  | What starts it, besides being asked directly. KIND is agents.source@1's kinds plus manual and event. | - `maxItems 16`<br>- `items: no other fields` |
| `when[].kind` | enum | yes |  | - `one of "manual", "chat", "feed", "comments", "git", "composer", "notifications", "mail", "schedule", "goal", "event", "webhook", "machine"` |
| `when[].rule` | string |  | What counts, in words (a message that names it; a post that reports a bug). | - `minLength 1`<br>- `maxLength 1000` |
| `when[].where` | object |  |  | - `no other fields` |
| `when[].where.service` | string | yes |  | - `pattern ^[a-z][a-z0-9-]{0,40}$` |
| `when[].where.kind` | string |  |  | - `pattern ^[a-z][a-z0-9_-]{0,40}$` |
| `when[].where.id` | string |  | An id or an {{input}}. | - `minLength 1`<br>- `maxLength 200` |
| `when[].event` | string |  | An OpenVibe.Events type, for kind event. | - `pattern ^[a-z][a-z0-9]{0,40}(?:\.[a-z0-9_]{1,60}){1,4}$` |
| `when[].cron` | string |  | Five fields, for kind schedule. | - `pattern ^\S+(?: \S+){4}$` |
| `when[].tz` | string |  |  | - `minLength 1`<br>- `maxLength 60` |
| `makes` | object |  |  | - `no other fields` |
| `makes.answer` | array of string |  | What it answers with: text, a card of a registered type (card:clip), a file, a post, a notice, a pull request, a robot action. | - `maxItems 12`<br>- `unique items`<br>- `items: pattern ^(?:text\|file\|post\|notice\|pull-request\|robot\|card:[a-z][a-z0-9-]{0,40})$` |
| `makes.schema` | object \| null |  | The exact shape of its answer (a JSON Schema), when it has one. |  |
| `makes.serve` | object |  |  | - `no other fields` |
| `makes.serve.a2a` | boolean |  | Listed in Network's A2A directory, so other agents may ask it (behind its RULES.WHO). |  |
| `makes.serve.mcp` | boolean |  | Offered as a tool on the one MCP endpoint; needs SCHEMA and INPUT. |  |
| `makes.serve.input` | object \| null |  | The JSON Schema of what a caller sends, for mcp. |  |
| `team` | object \| null |  | A play (agents.play@1) whose roles are filled by other blueprints at fixed versions. Absent or null: it works alone. |  |
| `created_at` | string |  |  | - `format date-time` |
| `updated_at` | string |  |  | - `format date-time` |
| `published_at` | string \| null |  |  | - `format date-time` |

## Examples

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

 Valid: a-draft-with-a-grader

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PC",
  "version": 2,
  "state": "draft",
  "name": "Patch notes, checked",
  "summary": "Writes patch notes from merged pull requests and has another model check them against the diff.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "private",
  "forked_from": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PD@4",
  "inputs": [
    {
      "id": "repo",
      "type": "repository",
      "label": "Repository"
    },
    {
      "id": "style",
      "type": "choice",
      "label": "Style",
      "choices": [
        "short",
        "full"
      ],
      "default": "short"
    }
  ],
  "knows": {
    "instructions": "Write {{style}} patch notes for {{repo}} from the pull requests merged since the last release."
  },
  "team": {
    "play": "grader-loop",
    "roles": {
      "doer": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PE@1",
      "grader": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PF@3"
    }
  },
  "when": [
    {
      "kind": "schedule",
      "cron": "0 9 * * 1",
      "tz": "Europe/London"
    }
  ],
  "makes": {
    "answer": [
      "post",
      "pull-request"
    ]
  }
}
~~~~

Valid: a-robot-driver

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PH",
  "version": 1,
  "state": "published",
  "name": "Desk buddy",
  "summary": "Drives a small desk robot: looks at whoever talks, nods, and says hello.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "needs": [
    {
      "kind": "robot",
      "what": "looi",
      "why": "It drives a LOOI."
    }
  ],
  "brain": {
    "pick": "size",
    "size": "small",
    "caps": [
      "vision"
    ]
  },
  "knows": {
    "instructions": "Turn the head toward whoever is talking. Nod when someone says your name."
  },
  "hands": {
    "tools": [
      {
        "tool": "robot:head",
        "mode": "allow"
      },
      {
        "tool": "robot:drive",
        "mode": "ask"
      },
      {
        "tool": "machine:screen",
        "mode": "allow"
      }
    ]
  },
  "places": {
    "order": [
      "own"
    ],
    "needs": [
      "robot"
    ]
  },
  "when": [
    {
      "kind": "machine",
      "rule": "Someone starts talking near the robot."
    }
  ],
  "makes": {
    "answer": [
      "robot"
    ]
  },
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Valid: a-tool-for-other-agents

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PG",
  "version": 1,
  "state": "published",
  "name": "Recipe scaler",
  "summary": "Scales a recipe to a number of people and converts the units.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "link",
  "brain": {
    "pick": "size",
    "size": "small",
    "caps": [
      "json"
    ]
  },
  "knows": {
    "instructions": "Scale the recipe to the number of people asked for. Metric units unless asked."
  },
  "rules": {
    "level": "full",
    "budget": {
      "run_usd": 0.01
    }
  },
  "makes": {
    "answer": [
      "text"
    ],
    "schema": {
      "type": "object",
      "required": [
        "ingredients"
      ],
      "properties": {
        "ingredients": {
          "type": "array"
        }
      }
    },
    "serve": {
      "a2a": true,
      "mcp": true,
      "input": {
        "type": "object",
        "required": [
          "recipe",
          "people"
        ],
        "properties": {
          "recipe": {
            "type": "string"
          },
          "people": {
            "type": "integer"
          }
        }
      }
    }
  },
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Valid: clip-finder

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Valid: vibe

~~~~
{
  "id": "bp_vibe",
  "version": 1,
  "state": "published",
  "name": "Vibe",
  "summary": "The agent every person has: answers, finds things and does small jobs across OpenVibe.",
  "author": {
    "type": "service",
    "id": "actor"
  },
  "visibility": "public",
  "face": {
    "glyph": "sparkle",
    "hue": 210,
    "voice": "polly:Ruth",
    "tone": "Warm, plain words, short answers.",
    "greeting": "What should we do?"
  },
  "brain": {
    "pick": "auto",
    "depth": "balanced",
    "advisor": {
      "size": "strong",
      "consults": 3
    }
  },
  "knows": {
    "instructions": "You are Vibe, the person's own agent on OpenVibe. Answer in the shown format. Ask before anything the person did not ask for."
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:search.query",
        "mode": "allow"
      },
      {
        "tool": "harness:auto",
        "mode": "ask"
      }
    ],
    "search": true
  },
  "places": {
    "order": [
      "none",
      "own",
      "openvibe"
    ]
  },
  "memory": {
    "scope": "person"
  },
  "rules": {
    "level": "ask",
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "chat",
      "rule": "A message that names @Vibe."
    },
    {
      "kind": "composer"
    }
  ],
  "makes": {
    "answer": [
      "text",
      "card:link",
      "card:table",
      "file"
    ]
  },
  "published_at": "2026-11-12T00:00:00Z"
}
~~~~

Rejected: a-key-in-the-brain

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true,
    "api_key": "sk-not-a-real-key"
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: a-machine-by-name

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "desk-pc"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: a-schedule-without-a-time

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "schedule"
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: a-team-role-without-a-version

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": {
    "play": "grader-loop",
    "roles": {
      "doer": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PE"
    }
  },
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: a-tool-nobody-has

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      },
      {
        "tool": "shell:rm -rf",
        "mode": "allow"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: an-exact-pick-without-a-model

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "exact"
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: full-without-a-budget

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "full"
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: no-instructions

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: published-without-a-date

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "a2a": false,
      "mcp": false
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z"
}
~~~~

Rejected: served-as-a-tool-without-a-shape

~~~~
{
  "id": "bp_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
  "version": 3,
  "state": "published",
  "name": "Clip finder",
  "summary": "Watches your stream's chat and speech, and clips the moments people react to.",
  "author": {
    "type": "user",
    "id": "usr_01JD8C4QJ7Z3K9W2X5V6N1M0PB"
  },
  "visibility": "public",
  "forked_from": null,
  "licence": "MIT",
  "tags": [
    "live",
    "clips"
  ],
  "inputs": [
    {
      "id": "channel",
      "type": "channel",
      "label": "Your channel",
      "required": true
    }
  ],
  "face": {
    "avatar": "preset:scissors",
    "hue": 270,
    "voice": null,
    "tone": "Short and cheerful."
  },
  "brain": {
    "pick": "auto",
    "rungs": [
      "machine",
      "plan",
      "key",
      "allowance",
      "credits"
    ],
    "max_usd_per_m": 1,
    "caps": [
      "tools"
    ],
    "decisions": true
  },
  "knows": {
    "instructions": "You watch {{channel}}'s stream. A moment is worth a clip when chat reacts within ten seconds of it. Clip twenty to forty seconds around it and name it in five words.",
    "examples": [
      {
        "ask": "Chat floods with LOL after the streamer drops the cup.",
        "good": "One clip of the drop, named in five words, no post in chat."
      }
    ]
  },
  "hands": {
    "tools": [
      {
        "tool": "ov:live.transcript.read",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.read",
        "mode": "allow"
      },
      {
        "tool": "ov:media.clip.create",
        "mode": "allow"
      },
      {
        "tool": "ov:live.chat.post",
        "mode": "ask"
      }
    ],
    "search": false
  },
  "places": {
    "order": [
      "none"
    ]
  },
  "memory": {
    "scope": "person",
    "remember": "Which kinds of moments the streamer kept or deleted.",
    "keep_days": 30
  },
  "rules": {
    "level": "auto-edits",
    "budget": {
      "run_usd": 0.05,
      "day_usd": 0.5
    },
    "who": {
      "may": "owner"
    }
  },
  "when": [
    {
      "kind": "event",
      "event": "live.stream.started",
      "where": {
        "service": "live",
        "kind": "channel",
        "id": "{{channel}}"
      }
    }
  ],
  "makes": {
    "answer": [
      "card:clip"
    ],
    "serve": {
      "mcp": true
    }
  },
  "team": null,
  "created_at": "2026-11-10T09:00:00Z",
  "updated_at": "2026-11-12T21:00:00Z",
  "published_at": "2026-11-12T21:00:00Z"
}
~~~~

## Validate

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