AgentsBlueprint agents.blueprint@1

Generated at from openvibe-contracts v0.419.0 and openvibe-sdk v0.161.0.

Version
1.0.0
Owner
actor
Visibility
public
Status
active
Compatibility
backward
Decision
ADR-044
Schema
https://openvibe.network/contracts/agents/blueprint.v1.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

FieldTypeRequiredDescriptionConstraints
idstringyesbp_<ULID>, or a short stock id such as bp_vibe.
  • pattern ^bp_[A-Za-z0-9_-]{3,40}$
versionintegeryesCounts up from 1. A published version never changes; a draft is the next number.
  • minimum 1
  • maximum 999999
stateenumyesretired: no new installs; existing ones keep running.
  • one of "draft", "published", "retired"
namestringyes
  • minLength 1
  • maxLength 60
summarystringyesWhat it does, in a sentence or two.
  • minLength 1
  • maxLength 300
authorobjectyes
  • no other fields
author.typeenumyes
  • one of "user", "service"
author.idstringyes
  • minLength 1
  • maxLength 160
visibilityenumyeslink: 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_fromstring | nullThe 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}$
licencestringAn SPDX id (MIT, CC-BY-4.0, …) for remixes; absent means remixes need the author's yes.
  • minLength 1
  • maxLength 60
tagsarray of string
  • maxItems 8
  • unique items
  • items: pattern ^[a-z][a-z0-9-]{0,40}$
inputsarray of objectWhat an installer fills in. Each is used as {{id}} in KNOWS and WHEN.
  • maxItems 12
  • items: no other fields
inputs[].idstringyes
  • pattern ^[a-z][a-z0-9_]{0,30}$
inputs[].typeenumyes
  • one of "text", "number", "boolean", "choice", "channel", "room", "repository", "machine", "url"
inputs[].labelstringyes
  • minLength 1
  • maxLength 80
inputs[].helpstring
  • minLength 1
  • maxLength 300
inputs[].requiredboolean
inputs[].defaultstring | number | boolean
inputs[].choicesarray of string
  • minItems 2
  • maxItems 30
  • items: minLength 1
  • items: maxLength 80
needsarray of objectWhat an installer must have connected; the install screen checks each and offers the way to connect it.
  • maxItems 12
  • items: no other fields
needs[].kindenumyes
  • one of "machine", "key", "plan", "app", "robot"
needs[].whatstringyesmachine: 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[].whystring
  • minLength 1
  • maxLength 200
faceobjectHow it looks and sounds wherever it shows up: chat, a stream overlay, a robot, a run card.
  • no other fields
face.avatarstringAn https image, a Media id, or a preset such as preset:researcher.
  • minLength 1
  • maxLength 500
face.glyphstringA glyph from the registry, when it has no picture.
  • pattern ^[a-z][a-z0-9-]{0,40}$
face.hueinteger
  • minimum 0
  • maximum 359
face.voicestring | nullA TTS voice id, or null for none.
  • maxLength 80
face.tonestringHow it talks, in words.
  • minLength 1
  • maxLength 300
face.greetingstring
  • minLength 1
  • maxLength 500
face.elementsarray of stringDesign elements it shows (a co-host card on the overlay, a reaction).
  • maxItems 8
  • unique items
  • items: minLength 1
  • items: maxLength 160
brainobjectWhich 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.pickenum
  • one of "auto", "size", "exact"
brain.sizeenum
  • one of "small", "medium", "strong"
brain.modelstringAn Exchange offer or provider:model id, for pick exact.
  • minLength 1
  • maxLength 160
brain.rungsarray of enumThe ladder rungs it may use: the installer's machine, a signed-in plan, their key, the free allowance, credits.
  • minItems 1
  • unique items
  • items: one of "machine", "plan", "key", "allowance", "credits"
brain.max_usd_per_mnumberThe most it may pay per million tokens on any rung paid per token.
  • minimum 0
  • maximum 10000
brain.capsarray of enum
  • maxItems 8
  • unique items
  • items: one of "tools", "vision", "audio", "json", "thinking", "long-context"
brain.depthenum
  • one of "quick", "balanced", "deep"
brain.advisorobject | nullA stronger model it may consult at hard decisions, at most CONSULTS times a run.
brain.decisionsbooleanYes/no, choose-one and rubric steps go to Decisions (POST /api/v1/decisions) instead of a model writing an answer.
knowsobjectyes
  • no other fields
knows.instructionsstringyesIts instructions, in words. {{id}} is an input.
  • minLength 1
  • maxLength 20000
knows.skillsarray of stringSkill ids (Actor's skills, Run's shared skills).
  • maxItems 32
  • unique items
  • items: minLength 1
  • items: maxLength 160
knows.knowledgearray of object
  • maxItems 32
  • items: no other fields
knows.knowledge[].kindenumyes
  • one of "file", "media", "wiki", "search", "repository", "url"
knows.knowledge[].refstringyes
  • minLength 1
  • maxLength 2000
knows.knowledge[].notestring
  • minLength 1
  • maxLength 300
knows.examplesarray of objectAsks with what a good answer does; Try it offers them and Bench grades with them.
  • maxItems 20
  • items: no other fields
knows.examples[].askstringyes
  • minLength 1
  • maxLength 2000
knows.examples[].goodstringyes
  • minLength 1
  • maxLength 2000
handsobject
  • no other fields
hands.toolsarray of objectov:<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
  • items: no other fields
hands.tools[].toolstringyes
  • 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}))$
  • maxLength 1900
hands.tools[].modeenumyes
  • one of "allow", "ask"
hands.tools[].notestring
  • minLength 1
  • maxLength 300
hands.searchbooleanIt may find more tools on the one MCP endpoint by search; each found tool asks first.
placesobject
  • no other fields
places.orderarray of enumWhere 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
  • maxItems 5
  • unique items
  • items: one of "none", "own", "shared", "openvibe", "sandbox"
places.needsarray of enum
  • maxItems 8
  • unique items
  • items: one of "gpu", "desk", "browser", "linux", "windows", "macos", "android", "robot"
memoryobject
  • no other fields
memory.scopeenumyesperson: one memory per installer; thread: per conversation; team: shared by a TEAM's roles.
  • one of "none", "person", "thread", "team"
memory.rememberstringWhat is worth keeping, in words.
  • minLength 1
  • maxLength 1000
memory.keep_daysinteger | null
  • minimum 1
  • maximum 3650
rulesobject
  • no other fields
rules.levelenumactor.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.budgetobject
  • no other fields
rules.budget.run_usdnumber
  • minimum 0
  • maximum 10000
rules.budget.day_usdnumber
  • minimum 0
  • maximum 10000
rules.budget.run_secondsnumber
  • minimum 1
  • maximum 604800
rules.budget.run_stepsinteger
  • minimum 1
  • maximum 10000
rules.whoobjectWho 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.mayenumyes
  • one of "owner", "list", "followers", "anyone"
whenarray of objectWhat starts it, besides being asked directly. KIND is agents.source@1's kinds plus manual and event.
  • maxItems 16
  • items: no other fields
when[].kindenumyes
  • one of "manual", "chat", "feed", "comments", "git", "composer", "notifications", "mail", "schedule", "goal", "event", "webhook", "machine"
when[].rulestringWhat counts, in words (a message that names it; a post that reports a bug).
  • minLength 1
  • maxLength 1000
when[].whereobject
  • no other fields
when[].where.servicestringyes
  • pattern ^[a-z][a-z0-9-]{0,40}$
when[].where.kindstring
  • pattern ^[a-z][a-z0-9_-]{0,40}$
when[].where.idstringAn id or an {{input}}.
  • minLength 1
  • maxLength 200
when[].eventstringAn OpenVibe.Events type, for kind event.
  • pattern ^[a-z][a-z0-9]{0,40}(?:\.[a-z0-9_]{1,60}){1,4}$
when[].cronstringFive fields, for kind schedule.
  • pattern ^\S+(?: \S+){4}$
when[].tzstring
  • minLength 1
  • maxLength 60
makesobject
  • no other fields
makes.answerarray of stringWhat 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
  • unique items
  • items: pattern ^(?:text|file|post|notice|pull-request|robot|card:[a-z][a-z0-9-]{0,40})$
makes.schemaobject | nullThe exact shape of its answer (a JSON Schema), when it has one.
makes.serveobject
  • no other fields
makes.serve.a2abooleanListed in Network's A2A directory, so other agents may ask it (behind its RULES.WHO).
makes.serve.mcpbooleanOffered as a tool on the one MCP endpoint; needs SCHEMA and INPUT.
makes.serve.inputobject | nullThe JSON Schema of what a caller sends, for mcp.
teamobject | nullA play (agents.play@1) whose roles are filled by other blueprints at fixed versions. Absent or null: it works alone.
created_atstring
  • format date-time
updated_atstring
  • format date-time
published_atstring | 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 }] }