openvibers/openvibe.contracts Commits

Contracts 0.115.0: OpenVibe.Actor becomes a service: the task router, its capabilities and schemas (plan T17, ADR-044). (#62)

OP OpenVibers committed 8 Oct 2026 · b95db2042b · 66 files, +2834 −85

- manifests/services/actor.json (port 4950, openvibe.actor live, site position 23); the product points at it.
- actor.task.create / actor.task.read / actor.task.list / actor.agent.read.
- actor.task-create-request@1, actor.task-event@1, actor.task-list-result@1, actor.agent-list-result@1.
- platform.task@1 active, implemented by Actor; project_id optional and a new optional error { code, detail }.


Claude-Session: https://claude.ai/code/session_01K8AYn3FF3HE7CPfM262qdh

Co-authored-by: Claude Opus 5.5 <[email protected]>

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 4d335a13..e3bebd00 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,20 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.115.0 — 2026-10-08
++
++**OpenVibe.Actor becomes a service** (plan T17, ADR-044; owner direction 2026-10-08: Actor is OpenVibe's own general agent
++and the router for agent work, "like OpenRouter but for agents").
++- `manifests/services/actor.json`: `actor`, port 4950, `openvibe.actor` live with `publicSite: service`; site block
++ "Actor · Your agent for everything" (position 23). The product manifest now points at the service.
++- Capabilities (audience `openvibe.actor`): `actor.task.create` (POST /api/v1/tasks, cancel; sensitive),
++ `actor.task.read` (a task and its live stream), `actor.task.list`, and the public `actor.agent.read` (the agent
++ catalog with rate cards, and a dry-run route that explains which agent a task would go to).
++- Schemas: `actor.task-create-request@1`, `actor.task-event@1` (the SSE stream: state, output, end, resumable by
++ seq), `actor.task-list-result@1`, `actor.agent-list-result@1` (kind, trust, what it can do, rate card, availability).
++- `platform.task@1` is active and Actor implements it. Additive: `project_id` is optional (a person's own task has
++ none), and a new optional `error` { code, detail } says why a task failed.
++
## 0.114.0 — 2026-10-08
**Developer links point at OpenVibe.Services.** Manifest text only; no schema, capability or event changed. The
ddiff --git a/contracts/actor/agent-list-result.v1.json b/contracts/actor/agent-list-result.v1.json
nnew file mode 100644
iindex 00000000..936e0911
---- /dev/null
++++ b/contracts/actor/agent-list-result.v1.json
@@@ -0,0 +1,123 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/actor/agent-list-result.v1.json",
++ "title": "ActorAgentListResult",
++ "description": "actor.agent-list-result@1: GET /api/v1/agents (capability actor.agent.read): every agent system Actor can route a task to. KIND is the backend class (ADR-044): openvibe-runtime (OpenVibe's own agent, acting through OpenVibe services), agent-platform (another platform's agent, called through its API), open-model (an open model on OpenVibe's own hardware), codes (coding work through OpenVibe.Codes), node and run (a person's machine or OpenVibe.Run). TRUST is ADR-046's class; private mode takes only first-party ones. CAN are the task classes and tools it takes (task:<class>, tool:<name>). RATE_CARD is the published price, revised by review and never written by a model (SOURCE says where it comes from, VERIFIED_AT when a person last checked it). AVAILABLE says whether it can take a task now and REASON why not.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "agents"
++ ],
++ "properties": {
++ "agents": {
++ "type": "array",
++ "maxItems": 64,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "name",
++ "kind",
++ "provider",
++ "trust",
++ "can",
++ "rate_card",
++ "available"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "kind": {
++ "type": "string",
++ "enum": [
++ "openvibe-runtime",
++ "agent-platform",
++ "open-model",
++ "codes",
++ "node",
++ "run"
++ ]
++ },
++ "provider": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "model": {
++ "type": "string",
++ "maxLength": 120
++ },
++ "trust": {
++ "type": "string",
++ "enum": [
++ "first-party",
++ "user-owned",
++ "partner",
++ "community",
++ "external"
++ ]
++ },
++ "can": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "pattern": "^(task|tool):[a-z0-9_-]+$"
++ }
++ },
++ "rate_card": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "input_usd_per_mtok",
++ "output_usd_per_mtok",
++ "source",
++ "verified_at"
++ ],
++ "properties": {
++ "input_usd_per_mtok": {
++ "type": "number",
++ "minimum": 0
++ },
++ "output_usd_per_mtok": {
++ "type": "number",
++ "minimum": 0
++ },
++ "per_call_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "A fixed charge per tool call the platform bills (for example a web search)."
++ },
++ "source": {
++ "type": "string",
++ "format": "uri"
++ },
++ "verified_at": {
++ "type": "string",
++ "format": "date"
++ }
++ }
++ },
++ "typical_latency_ms": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "available": {
++ "type": "boolean"
++ },
++ "reason": {
++ "type": "string",
++ "maxLength": 300
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/actor/task-create-request.v1.json b/contracts/actor/task-create-request.v1.json
nnew file mode 100644
iindex 00000000..fc02ff7d
---- /dev/null
++++ b/contracts/actor/task-create-request.v1.json
@@@ -0,0 +1,67 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/actor/task-create-request.v1.json",
++ "title": "ActorTaskCreateRequest",
++ "description": "actor.task-create-request@1: the body of POST /api/v1/tasks on OpenVibe.Actor (capability actor.task.create), the creation subset of platform.task@1. TASK is the instruction in plain words. MODE is the planner objective (default balanced). BUDGET caps what this task may cost (per_task_usd) and what the caller's tasks may cost in one UTC day (per_day_usd); either may be left out for the person's free-allowance defaults, and a value above the person's tier is refused 422 actor.budget.over_tier, never silently lowered. AGENT pins one agent system by its id from GET /api/v1/agents (routing still checks that it can take the task). IDEMPOTENCY_KEY: a repeat from the same requester within 24 h with an identical body answers the first task (200), with a different body 409 actor.idempotency.conflict.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "task"
++ ],
++ "properties": {
++ "task": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 20000,
++ "pattern": "\\S",
++ "description": "The instruction in plain words."
++ },
++ "mode": {
++ "type": "string",
++ "enum": [
++ "cheapest",
++ "balanced",
++ "best",
++ "fastest",
++ "private"
++ ],
++ "default": "balanced"
++ },
++ "budget": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "per_task_usd": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 100
++ },
++ "per_day_usd": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000
++ }
++ }
++ },
++ "agent": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,63}$",
++ "description": "An agent system id from GET /api/v1/agents."
++ },
++ "idempotency_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 128,
++ "pattern": "^[A-Za-z0-9._:-]+$"
++ }
++ },
++ "examples": [
++ {
++ "task": "Find out who runs the mail for openvibe.network and whether its SPF and DMARC are set up properly.",
++ "mode": "cheapest",
++ "budget": {
++ "per_task_usd": 0.05
++ }
++ }
++ ]
++}
ddiff --git a/contracts/actor/task-event.v1.json b/contracts/actor/task-event.v1.json
nnew file mode 100644
iindex 00000000..1826c801
---- /dev/null
++++ b/contracts/actor/task-event.v1.json
@@@ -0,0 +1,110 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/actor/task-event.v1.json",
++ "title": "ActorTaskEvent",
++ "description": "actor.task-event@1: one event on a task's live stream (GET /api/v1/tasks/:id/events, text/event-stream; the SSE id is SEQ, so a reader resumes with Last-Event-ID). KIND is run.job-stream-event@1's three, as platform.task@1 progress names them: state (the task moved to STATE), output (one step of the agent's work: AGENT says what it is doing in TEXT, or calls TOOL with a short INPUT summary, or a tool answered), end (the task ended in STATE; nothing follows). Events are best-effort; the task record (GET /api/v1/tasks/:id) is the source of truth.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "task_id",
++ "seq",
++ "kind",
++ "at"
++ ],
++ "properties": {
++ "task_id": {
++ "type": "string",
++ "pattern": "^tsk_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "kind": {
++ "type": "string",
++ "enum": [
++ "state",
++ "output",
++ "end"
++ ]
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "state": {
++ "type": "string",
++ "enum": [
++ "queued",
++ "running",
++ "verifying",
++ "succeeded",
++ "failed",
++ "cancelled"
++ ]
++ },
++ "agent": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,63}$"
++ },
++ "step": {
++ "type": "string",
++ "enum": [
++ "plan",
++ "text",
++ "tool_call",
++ "tool_result",
++ "check",
++ "handoff"
++ ]
++ },
++ "text": {
++ "type": "string",
++ "maxLength": 8000
++ },
++ "tool": {
++ "type": "string",
++ "maxLength": 128
++ },
++ "input": {
++ "type": "string",
++ "maxLength": 2000
++ },
++ "is_error": {
++ "type": "boolean"
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "kind": {
++ "const": "output"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "step"
++ ]
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "kind": {
++ "enum": [
++ "state",
++ "end"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "state"
++ ]
++ }
++ }
++ ]
++}
ddiff --git a/contracts/actor/task-list-result.v1.json b/contracts/actor/task-list-result.v1.json
nnew file mode 100644
iindex 00000000..2b169d64
---- /dev/null
++++ b/contracts/actor/task-list-result.v1.json
@@@ -0,0 +1,28 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/actor/task-list-result.v1.json",
++ "title": "ActorTaskListResult",
++ "description": "actor.task-list-result@1: GET /api/v1/tasks (capability actor.task.list): the caller's latest tasks, newest first, and NEXT, the `before` cursor for the following page (null on the last page).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "tasks",
++ "next"
++ ],
++ "properties": {
++ "tasks": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "$ref": "../platform/task.v1.json"
++ }
++ },
++ "next": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128
++ }
++ }
++}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex e7013bf3..abf2f49e 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -5790,7 +5790,7 @@
"schema": "platform/task.v1.json",
"visibility": "public",
"compatibility": "backward",
-- "status": "planned",
++ "status": "active",
"adr": "ADR-044"
},
{
@@@ -6313,6 +6313,46 @@
"compatibility": "backward",
"status": "active",
"adr": "ADR-022"
++ },
++ {
++ "id": "actor.task-create-request",
++ "version": "1.0.0",
++ "owner": "actor",
++ "schema": "actor/task-create-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-044"
++ },
++ {
++ "id": "actor.task-event",
++ "version": "1.0.0",
++ "owner": "actor",
++ "schema": "actor/task-event.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-044"
++ },
++ {
++ "id": "actor.task-list-result",
++ "version": "1.0.0",
++ "owner": "actor",
++ "schema": "actor/task-list-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-044"
++ },
++ {
++ "id": "actor.agent-list-result",
++ "version": "1.0.0",
++ "owner": "actor",
++ "schema": "actor/agent-list-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-044"
}
]
}
ddiff --git a/contracts/platform/task.v1.json b/contracts/platform/task.v1.json
iindex 69e0ab5d..173d1a59 100644
---- a/contracts/platform/task.v1.json
++++ b/contracts/platform/task.v1.json
@@@ -2,13 +2,12 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/platform/task.v1.json",
"title": "Task",
-- "description": "platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.",
++ "description": "platform.task@1 (plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /api/v1/tasks (actor.task.create) creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered agent systems, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /api/v1/tasks/{id} (actor.task.read) reads the same resource. Progress streams as server-sent events from `progress.stream_url` (actor.task-event@1; the task's own counter in `progress.last_seq`); `cancel` records that a cancel was requested (POST /api/v1/tasks/{id}/cancel), `error` says why a task failed, and `webhooks` are the registered endpoints the router delivers each state change to. Owned by `network` like every other platform.* contract and implemented by OpenVibe.Actor (manifests/services/actor.json). This v1 contract may grow optional fields.",
"$comment": "Additive optional fields may extend this v1 contract. It is the task resource, not a device frame: Actor's link is HTTP/SSE where Run's is the Node WebSocket.",
"type": "object",
"additionalProperties": false,
"required": [
"id",
-- "project_id",
"requester",
"task",
"mode",
@@@ -25,7 +24,7 @@
"project_id": {
"type": "string",
"pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's."
++ "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's. Absent for a person's own task (one made signed in, without a project token): the person's free allowance and daily budget apply."
},
"requester": {
"description": "The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).",
@@@ -247,6 +246,34 @@
}
},
"description": "Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth."
++ },
++ "error": {
++ "anyOf": [
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "code",
++ "detail"
++ ],
++ "properties": {
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$",
++ "description": "A stable problem code, e.g. actor.budget.exceeded, actor.no_agent, actor.check.failed, actor.task.interrupted."
++ },
++ "detail": {
++ "type": "string",
++ "maxLength": 2000,
++ "description": "What happened, for a person."
++ }
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "Why the task failed, once it has; null otherwise. Never a credential and never raw provider output."
}
},
"allOf": [
ddiff --git a/fixtures/actor.agent-list-result/invalid/no-rate-card.json b/fixtures/actor.agent-list-result/invalid/no-rate-card.json
nnew file mode 100644
iindex 00000000..dcc84dbc
---- /dev/null
++++ b/fixtures/actor.agent-list-result/invalid/no-rate-card.json
@@@ -0,0 +1,19 @@
++{
++ "agents": [
++ {
++ "id": "openvibe-runtime",
++ "name": "OpenVibe runtime",
++ "kind": "openvibe-runtime",
++ "provider": "OpenVibe",
++ "model": "deepseek-chat",
++ "trust": "first-party",
++ "can": [
++ "task:answer",
++ "task:research",
++ "tool:openvibe-search",
++ "tool:openvibe-tools"
++ ],
++ "available": true
++ }
++ ]
++}
ddiff --git a/fixtures/actor.agent-list-result/invalid/unknown-kind.json b/fixtures/actor.agent-list-result/invalid/unknown-kind.json
nnew file mode 100644
iindex 00000000..f4d9a00a
---- /dev/null
++++ b/fixtures/actor.agent-list-result/invalid/unknown-kind.json
@@@ -0,0 +1,25 @@
++{
++ "agents": [
++ {
++ "id": "openvibe-runtime",
++ "name": "OpenVibe runtime",
++ "kind": "magic",
++ "provider": "OpenVibe",
++ "model": "deepseek-chat",
++ "trust": "first-party",
++ "can": [
++ "task:answer",
++ "task:research",
++ "tool:openvibe-search",
++ "tool:openvibe-tools"
++ ],
++ "rate_card": {
++ "input_usd_per_mtok": 0.27,
++ "output_usd_per_mtok": 1.1,
++ "source": "https://api-docs.deepseek.com/quick_start/pricing",
++ "verified_at": "2026-10-08"
++ },
++ "available": true
++ }
++ ]
++}
ddiff --git a/fixtures/actor.agent-list-result/valid/two.json b/fixtures/actor.agent-list-result/valid/two.json
nnew file mode 100644
iindex 00000000..47eb55e6
---- /dev/null
++++ b/fixtures/actor.agent-list-result/valid/two.json
@@@ -0,0 +1,47 @@
++{
++ "agents": [
++ {
++ "id": "openvibe-runtime",
++ "name": "OpenVibe runtime",
++ "kind": "openvibe-runtime",
++ "provider": "OpenVibe",
++ "model": "deepseek-chat",
++ "trust": "first-party",
++ "can": [
++ "task:answer",
++ "task:research",
++ "tool:openvibe-search",
++ "tool:openvibe-tools"
++ ],
++ "rate_card": {
++ "input_usd_per_mtok": 0.27,
++ "output_usd_per_mtok": 1.1,
++ "source": "https://api-docs.deepseek.com/quick_start/pricing",
++ "verified_at": "2026-10-08"
++ },
++ "available": true
++ },
++ {
++ "id": "openai-agent",
++ "name": "OpenAI agent",
++ "kind": "agent-platform",
++ "provider": "OpenAI",
++ "model": "gpt-5-mini",
++ "trust": "external",
++ "can": [
++ "task:answer",
++ "task:research",
++ "tool:web-search"
++ ],
++ "rate_card": {
++ "input_usd_per_mtok": 0.25,
++ "output_usd_per_mtok": 2,
++ "per_call_usd": 0.01,
++ "source": "https://openai.com/api/pricing/",
++ "verified_at": "2026-10-08"
++ },
++ "available": false,
++ "reason": "no OpenAI key configured"
++ }
++ ]
++}
ddiff --git a/fixtures/actor.task-create-request/invalid/blank-task.json b/fixtures/actor.task-create-request/invalid/blank-task.json
nnew file mode 100644
iindex 00000000..87494174
---- /dev/null
++++ b/fixtures/actor.task-create-request/invalid/blank-task.json
@@@ -0,0 +1,3 @@
++{
++ "task": " "
++}
ddiff --git a/fixtures/actor.task-create-request/invalid/negative-budget.json b/fixtures/actor.task-create-request/invalid/negative-budget.json
nnew file mode 100644
iindex 00000000..8ad0f899
---- /dev/null
++++ b/fixtures/actor.task-create-request/invalid/negative-budget.json
@@@ -0,0 +1,6 @@
++{
++ "task": "x",
++ "budget": {
++ "per_task_usd": -1
++ }
++}
ddiff --git a/fixtures/actor.task-create-request/invalid/unknown-mode.json b/fixtures/actor.task-create-request/invalid/unknown-mode.json
nnew file mode 100644
iindex 00000000..faf2b207
---- /dev/null
++++ b/fixtures/actor.task-create-request/invalid/unknown-mode.json
@@@ -0,0 +1,4 @@
++{
++ "task": "x",
++ "mode": "smartest"
++}
ddiff --git a/fixtures/actor.task-create-request/invalid/webhooks-not-here.json b/fixtures/actor.task-create-request/invalid/webhooks-not-here.json
nnew file mode 100644
iindex 00000000..445ae785
---- /dev/null
++++ b/fixtures/actor.task-create-request/invalid/webhooks-not-here.json
@@@ -0,0 +1,4 @@
++{
++ "task": "x",
++ "webhooks": []
++}
ddiff --git a/fixtures/actor.task-create-request/valid/full.json b/fixtures/actor.task-create-request/valid/full.json
nnew file mode 100644
iindex 00000000..eb61207d
---- /dev/null
++++ b/fixtures/actor.task-create-request/valid/full.json
@@@ -0,0 +1,10 @@
++{
++ "task": "Research five competitors of my shop and write a one-page brief with sources.",
++ "mode": "best",
++ "budget": {
++ "per_task_usd": 0.5,
++ "per_day_usd": 2
++ },
++ "agent": "openai-agent",
++ "idempotency_key": "brief-2026-10-08"
++}
ddiff --git a/fixtures/actor.task-create-request/valid/minimal.json b/fixtures/actor.task-create-request/valid/minimal.json
nnew file mode 100644
iindex 00000000..45749de8
---- /dev/null
++++ b/fixtures/actor.task-create-request/valid/minimal.json
@@@ -0,0 +1,3 @@
++{
++ "task": "What is the TTL of openvibe.network's MX record?"
++}
ddiff --git a/fixtures/actor.task-event/invalid/end-without-state.json b/fixtures/actor.task-event/invalid/end-without-state.json
nnew file mode 100644
iindex 00000000..08fbad7a
---- /dev/null
++++ b/fixtures/actor.task-event/invalid/end-without-state.json
@@@ -0,0 +1,6 @@
++{
++ "task_id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "seq": 9,
++ "kind": "end",
++ "at": "2026-10-08T19:00:09.000Z"
++}
ddiff --git a/fixtures/actor.task-event/invalid/output-without-step.json b/fixtures/actor.task-event/invalid/output-without-step.json
nnew file mode 100644
iindex 00000000..507bb177
---- /dev/null
++++ b/fixtures/actor.task-event/invalid/output-without-step.json
@@@ -0,0 +1,6 @@
++{
++ "task_id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "seq": 2,
++ "kind": "output",
++ "at": "2026-10-08T19:00:01.000Z"
++}
ddiff --git a/fixtures/actor.task-event/valid/end.json b/fixtures/actor.task-event/valid/end.json
nnew file mode 100644
iindex 00000000..244eca0c
---- /dev/null
++++ b/fixtures/actor.task-event/valid/end.json
@@@ -0,0 +1,7 @@
++{
++ "task_id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "seq": 9,
++ "kind": "end",
++ "at": "2026-10-08T19:00:09.000Z",
++ "state": "succeeded"
++}
ddiff --git a/fixtures/actor.task-event/valid/state.json b/fixtures/actor.task-event/valid/state.json
nnew file mode 100644
iindex 00000000..bed5231f
---- /dev/null
++++ b/fixtures/actor.task-event/valid/state.json
@@@ -0,0 +1,8 @@
++{
++ "task_id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "seq": 1,
++ "kind": "state",
++ "at": "2026-10-08T19:00:00.000Z",
++ "state": "running",
++ "agent": "openvibe-runtime"
++}
ddiff --git a/fixtures/actor.task-event/valid/tool-call.json b/fixtures/actor.task-event/valid/tool-call.json
nnew file mode 100644
iindex 00000000..20d7c015
---- /dev/null
++++ b/fixtures/actor.task-event/valid/tool-call.json
@@@ -0,0 +1,10 @@
++{
++ "task_id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "seq": 2,
++ "kind": "output",
++ "at": "2026-10-08T19:00:01.000Z",
++ "agent": "openvibe-runtime",
++ "step": "tool_call",
++ "tool": "tools.mx",
++ "input": "openvibe.network"
++}
ddiff --git a/fixtures/actor.task-list-result/invalid/no-next.json b/fixtures/actor.task-list-result/invalid/no-next.json
nnew file mode 100644
iindex 00000000..50ffbb9a
---- /dev/null
++++ b/fixtures/actor.task-list-result/invalid/no-next.json
@@@ -0,0 +1,3 @@
++{
++ "tasks": []
++}
ddiff --git a/fixtures/actor.task-list-result/valid/one-page.json b/fixtures/actor.task-list-result/valid/one-page.json
nnew file mode 100644
iindex 00000000..06031daf
---- /dev/null
++++ b/fixtures/actor.task-list-result/valid/one-page.json
@@@ -0,0 +1,56 @@
++{
++ "tasks": [
++ {
++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "requester": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ },
++ "task": "What is the TTL of openvibe.network's MX record?",
++ "mode": "cheapest",
++ "budget": {
++ "per_task_usd": 0.05,
++ "per_day_usd": 0.5
++ },
++ "state": "succeeded",
++ "created_at": "2026-10-08T19:00:00.000Z",
++ "finished_at": "2026-10-08T19:00:09.000Z",
++ "result": {
++ "answer": "300 seconds; the MX points at Cloudflare's email routing.",
++ "sources": [
++ "https://openvibe.tools/tool/mx"
++ ]
++ },
++ "cost": {
++ "usd": 0.0011,
++ "free_allowance_used": 0.0011
++ },
++ "explanation": [
++ {
++ "selected": "openvibe-runtime",
++ "objective": "cheapest",
++ "reasons": [
++ "the cheapest eligible agent"
++ ],
++ "candidates": [
++ {
++ "id": "openvibe-runtime",
++ "eligible": true,
++ "estimated_cost_usd": 0.001
++ },
++ {
++ "id": "openai-agent",
++ "eligible": true,
++ "estimated_cost_usd": 0.004
++ }
++ ],
++ "decided_at": "2026-10-08T19:00:00.100Z"
++ }
++ ],
++ "cancel": null,
++ "progress": null,
++ "error": null
++ }
++ ],
++ "next": null
++}
ddiff --git a/fixtures/platform.task/invalid/error-without-code.json b/fixtures/platform.task/invalid/error-without-code.json
nnew file mode 100644
iindex 00000000..dbd7e55f
---- /dev/null
++++ b/fixtures/platform.task/invalid/error-without-code.json
@@@ -0,0 +1,47 @@
++{
++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "requester": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ },
++ "task": "What is the TTL of openvibe.network's MX record?",
++ "mode": "cheapest",
++ "budget": {
++ "per_task_usd": 0.05,
++ "per_day_usd": 0.5
++ },
++ "state": "failed",
++ "created_at": "2026-10-08T19:00:00.000Z",
++ "finished_at": "2026-10-08T19:00:09.000Z",
++ "result": null,
++ "cost": {
++ "usd": 0
++ },
++ "explanation": [
++ {
++ "selected": "openvibe-runtime",
++ "objective": "cheapest",
++ "reasons": [
++ "the cheapest eligible agent"
++ ],
++ "candidates": [
++ {
++ "id": "openvibe-runtime",
++ "eligible": true,
++ "estimated_cost_usd": 0.001
++ },
++ {
++ "id": "openai-agent",
++ "eligible": true,
++ "estimated_cost_usd": 0.004
++ }
++ ],
++ "decided_at": "2026-10-08T19:00:00.100Z"
++ }
++ ],
++ "cancel": null,
++ "progress": null,
++ "error": {
++ "detail": "no code"
++ }
++}
ddiff --git a/fixtures/platform.task/valid/failed-with-error.json b/fixtures/platform.task/valid/failed-with-error.json
nnew file mode 100644
iindex 00000000..1970bfa6
---- /dev/null
++++ b/fixtures/platform.task/valid/failed-with-error.json
@@@ -0,0 +1,48 @@
++{
++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "requester": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ },
++ "task": "What is the TTL of openvibe.network's MX record?",
++ "mode": "cheapest",
++ "budget": {
++ "per_task_usd": 0.05,
++ "per_day_usd": 0.5
++ },
++ "state": "failed",
++ "created_at": "2026-10-08T19:00:00.000Z",
++ "finished_at": "2026-10-08T19:00:09.000Z",
++ "result": null,
++ "cost": {
++ "usd": 0
++ },
++ "explanation": [
++ {
++ "selected": "openvibe-runtime",
++ "objective": "cheapest",
++ "reasons": [
++ "the cheapest eligible agent"
++ ],
++ "candidates": [
++ {
++ "id": "openvibe-runtime",
++ "eligible": true,
++ "estimated_cost_usd": 0.001
++ },
++ {
++ "id": "openai-agent",
++ "eligible": true,
++ "estimated_cost_usd": 0.004
++ }
++ ],
++ "decided_at": "2026-10-08T19:00:00.100Z"
++ }
++ ],
++ "cancel": null,
++ "progress": null,
++ "error": {
++ "code": "actor.budget.exceeded",
++ "detail": "The cheapest agent that can take this task would cost about $0.40, over this task's $0.05 budget."
++ }
++}
ddiff --git a/fixtures/platform.task/valid/person-without-project.json b/fixtures/platform.task/valid/person-without-project.json
nnew file mode 100644
iindex 00000000..162646dd
---- /dev/null
++++ b/fixtures/platform.task/valid/person-without-project.json
@@@ -0,0 +1,51 @@
++{
++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "requester": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ },
++ "task": "What is the TTL of openvibe.network's MX record?",
++ "mode": "cheapest",
++ "budget": {
++ "per_task_usd": 0.05,
++ "per_day_usd": 0.5
++ },
++ "state": "succeeded",
++ "created_at": "2026-10-08T19:00:00.000Z",
++ "finished_at": "2026-10-08T19:00:09.000Z",
++ "result": {
++ "answer": "300 seconds; the MX points at Cloudflare's email routing.",
++ "sources": [
++ "https://openvibe.tools/tool/mx"
++ ]
++ },
++ "cost": {
++ "usd": 0.0011,
++ "free_allowance_used": 0.0011
++ },
++ "explanation": [
++ {
++ "selected": "openvibe-runtime",
++ "objective": "cheapest",
++ "reasons": [
++ "the cheapest eligible agent"
++ ],
++ "candidates": [
++ {
++ "id": "openvibe-runtime",
++ "eligible": true,
++ "estimated_cost_usd": 0.001
++ },
++ {
++ "id": "openai-agent",
++ "eligible": true,
++ "estimated_cost_usd": 0.004
++ }
++ ],
++ "decided_at": "2026-10-08T19:00:00.100Z"
++ }
++ ],
++ "cancel": null,
++ "progress": null,
++ "error": null
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 05266060..c28e6f61 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -66476,18 +66476,17 @@
"platform.task": {
"version": "1.0.0",
"owner": "network",
-- "status": "planned",
++ "status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/platform/task.v1.json",
"title": "Task",
-- "description": "platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.",
++ "description": "platform.task@1 (plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /api/v1/tasks (actor.task.create) creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered agent systems, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /api/v1/tasks/{id} (actor.task.read) reads the same resource. Progress streams as server-sent events from `progress.stream_url` (actor.task-event@1; the task's own counter in `progress.last_seq`); `cancel` records that a cancel was requested (POST /api/v1/tasks/{id}/cancel), `error` says why a task failed, and `webhooks` are the registered endpoints the router delivers each state change to. Owned by `network` like every other platform.* contract and implemented by OpenVibe.Actor (manifests/services/actor.json). This v1 contract may grow optional fields.",
"$comment": "Additive optional fields may extend this v1 contract. It is the task resource, not a device frame: Actor's link is HTTP/SSE where Run's is the Node WebSocket.",
"type": "object",
"additionalProperties": false,
"required": [
"id",
-- "project_id",
"requester",
"task",
"mode",
@@@ -66504,7 +66503,7 @@
"project_id": {
"type": "string",
"pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's."
++ "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's. Absent for a person's own task (one made signed in, without a project token): the person's free allowance and daily budget apply."
},
"requester": {
"description": "The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).",
@@@ -66726,6 +66725,34 @@
}
},
"description": "Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth."
++ },
++ "error": {
++ "anyOf": [
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "code",
++ "detail"
++ ],
++ "properties": {
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$",
++ "description": "A stable problem code, e.g. actor.budget.exceeded, actor.no_agent, actor.check.failed, actor.task.interrupted."
++ },
++ "detail": {
++ "type": "string",
++ "maxLength": 2000,
++ "description": "What happened, for a person."
++ }
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "Why the task failed, once it has; null otherwise. Never a credential and never raw provider output."
}
},
"allOf": [
@@@ -73102,6 +73129,354 @@
"description": "space.moderation.action v1 (ADR-022; OpenVibe.Space server/events.js moderationAction). A staff or space moderator action on someone else's forum content, or a space moderator assignment: deleting, locking or unlocking a thread, deleting a post, adding or removing a space moderator. Not a person acting on their own content. Written to Space's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the moderator. Never carries the content itself.",
"$ref": "../../common/moderation-action.v1.json"
}
++ },
++ "actor.task-create-request": {
++ "version": "1.0.0",
++ "owner": "actor",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/actor/task-create-request.v1.json",
++ "title": "ActorTaskCreateRequest",
++ "description": "actor.task-create-request@1: the body of POST /api/v1/tasks on OpenVibe.Actor (capability actor.task.create), the creation subset of platform.task@1. TASK is the instruction in plain words. MODE is the planner objective (default balanced). BUDGET caps what this task may cost (per_task_usd) and what the caller's tasks may cost in one UTC day (per_day_usd); either may be left out for the person's free-allowance defaults, and a value above the person's tier is refused 422 actor.budget.over_tier, never silently lowered. AGENT pins one agent system by its id from GET /api/v1/agents (routing still checks that it can take the task). IDEMPOTENCY_KEY: a repeat from the same requester within 24 h with an identical body answers the first task (200), with a different body 409 actor.idempotency.conflict.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "task"
++ ],
++ "properties": {
++ "task": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 20000,
++ "pattern": "\\S",
++ "description": "The instruction in plain words."
++ },
++ "mode": {
++ "type": "string",
++ "enum": [
++ "cheapest",
++ "balanced",
++ "best",
++ "fastest",
++ "private"
++ ],
++ "default": "balanced"
++ },
++ "budget": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "per_task_usd": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 100
++ },
++ "per_day_usd": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000
++ }
++ }
++ },
++ "agent": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,63}$",
++ "description": "An agent system id from GET /api/v1/agents."
++ },
++ "idempotency_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 128,
++ "pattern": "^[A-Za-z0-9._:-]+$"
++ }
++ },
++ "examples": [
++ {
++ "task": "Find out who runs the mail for openvibe.network and whether its SPF and DMARC are set up properly.",
++ "mode": "cheapest",
++ "budget": {
++ "per_task_usd": 0.05
++ }
++ }
++ ]
++ }
++ },
++ "actor.task-event": {
++ "version": "1.0.0",
++ "owner": "actor",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/actor/task-event.v1.json",
++ "title": "ActorTaskEvent",
++ "description": "actor.task-event@1: one event on a task's live stream (GET /api/v1/tasks/:id/events, text/event-stream; the SSE id is SEQ, so a reader resumes with Last-Event-ID). KIND is run.job-stream-event@1's three, as platform.task@1 progress names them: state (the task moved to STATE), output (one step of the agent's work: AGENT says what it is doing in TEXT, or calls TOOL with a short INPUT summary, or a tool answered), end (the task ended in STATE; nothing follows). Events are best-effort; the task record (GET /api/v1/tasks/:id) is the source of truth.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "task_id",
++ "seq",
++ "kind",
++ "at"
++ ],
++ "properties": {
++ "task_id": {
++ "type": "string",
++ "pattern": "^tsk_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "kind": {
++ "type": "string",
++ "enum": [
++ "state",
++ "output",
++ "end"
++ ]
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "state": {
++ "type": "string",
++ "enum": [
++ "queued",
++ "running",
++ "verifying",
++ "succeeded",
++ "failed",
++ "cancelled"
++ ]
++ },
++ "agent": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,63}$"
++ },
++ "step": {
++ "type": "string",
++ "enum": [
++ "plan",
++ "text",
++ "tool_call",
++ "tool_result",
++ "check",
++ "handoff"
++ ]
++ },
++ "text": {
++ "type": "string",
++ "maxLength": 8000
++ },
++ "tool": {
++ "type": "string",
++ "maxLength": 128
++ },
++ "input": {
++ "type": "string",
++ "maxLength": 2000
++ },
++ "is_error": {
++ "type": "boolean"
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "kind": {
++ "const": "output"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "step"
++ ]
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "kind": {
++ "enum": [
++ "state",
++ "end"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "state"
++ ]
++ }
++ }
++ ]
++ }
++ },
++ "actor.task-list-result": {
++ "version": "1.0.0",
++ "owner": "actor",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/actor/task-list-result.v1.json",
++ "title": "ActorTaskListResult",
++ "description": "actor.task-list-result@1: GET /api/v1/tasks (capability actor.task.list): the caller's latest tasks, newest first, and NEXT, the `before` cursor for the following page (null on the last page).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "tasks",
++ "next"
++ ],
++ "properties": {
++ "tasks": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "$ref": "../platform/task.v1.json"
++ }
++ },
++ "next": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128
++ }
++ }
++ }
++ },
++ "actor.agent-list-result": {
++ "version": "1.0.0",
++ "owner": "actor",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/actor/agent-list-result.v1.json",
++ "title": "ActorAgentListResult",
++ "description": "actor.agent-list-result@1: GET /api/v1/agents (capability actor.agent.read): every agent system Actor can route a task to. KIND is the backend class (ADR-044): openvibe-runtime (OpenVibe's own agent, acting through OpenVibe services), agent-platform (another platform's agent, called through its API), open-model (an open model on OpenVibe's own hardware), codes (coding work through OpenVibe.Codes), node and run (a person's machine or OpenVibe.Run). TRUST is ADR-046's class; private mode takes only first-party ones. CAN are the task classes and tools it takes (task:<class>, tool:<name>). RATE_CARD is the published price, revised by review and never written by a model (SOURCE says where it comes from, VERIFIED_AT when a person last checked it). AVAILABLE says whether it can take a task now and REASON why not.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "agents"
++ ],
++ "properties": {
++ "agents": {
++ "type": "array",
++ "maxItems": 64,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "name",
++ "kind",
++ "provider",
++ "trust",
++ "can",
++ "rate_card",
++ "available"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "kind": {
++ "type": "string",
++ "enum": [
++ "openvibe-runtime",
++ "agent-platform",
++ "open-model",
++ "codes",
++ "node",
++ "run"
++ ]
++ },
++ "provider": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "model": {
++ "type": "string",
++ "maxLength": 120
++ },
++ "trust": {
++ "type": "string",
++ "enum": [
++ "first-party",
++ "user-owned",
++ "partner",
++ "community",
++ "external"
++ ]
++ },
++ "can": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "pattern": "^(task|tool):[a-z0-9_-]+$"
++ }
++ },
++ "rate_card": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "input_usd_per_mtok",
++ "output_usd_per_mtok",
++ "source",
++ "verified_at"
++ ],
++ "properties": {
++ "input_usd_per_mtok": {
++ "type": "number",
++ "minimum": 0
++ },
++ "output_usd_per_mtok": {
++ "type": "number",
++ "minimum": 0
++ },
++ "per_call_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "A fixed charge per tool call the platform bills (for example a web search)."
++ },
++ "source": {
++ "type": "string",
++ "format": "uri"
++ },
++ "verified_at": {
++ "type": "string",
++ "format": "date"
++ }
++ }
++ },
++ "typical_latency_ms": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "available": {
++ "type": "boolean"
++ },
++ "reason": {
++ "type": "string",
++ "maxLength": 300
++ }
++ }
++ }
++ }
++ }
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
nnew file mode 100644
iindex 00000000..5cba4668
---- /dev/null
++++ b/generated/openapi/actor.json
@@@ -0,0 +1,1337 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Actor API",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://openvibe.actor"
++ }
++ ],
++ "paths": {
++ "/api/v1/agents": {
++ "get": {
++ "operationId": "get_api_v1_agents",
++ "summary": "The agent systems Actor routes to, with what each can do, its trust class, its rate card and whether it is available now (GET /api/v1/agents, actor.agent-list-result@1), and which one a task would go ",
++ "description": "**actor.agent.read** (public): The agent systems Actor routes to, with what each can do, its trust class, its rate card and whether it is available now (GET /api/v1/agents, actor.agent-list-result@1), and which one a task would go to in a mode, with the reason for every candidate (POST /api/v1/route { task, mode }, a platform.placement-result@1; nothing runs). Public; no token needed.",
++ "tags": [
++ "actor.agent"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.agent.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "actor-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.agent.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/actor.agent-list-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/route": {
++ "post": {
++ "operationId": "post_api_v1_route",
++ "summary": "The agent systems Actor routes to, with what each can do, its trust class, its rate card and whether it is available now (GET /api/v1/agents, actor.agent-list-result@1), and which one a task would go ",
++ "description": "**actor.agent.read** (public): The agent systems Actor routes to, with what each can do, its trust class, its rate card and whether it is available now (GET /api/v1/agents, actor.agent-list-result@1), and which one a task would go to in a mode, with the reason for every candidate (POST /api/v1/route { task, mode }, a platform.placement-result@1; nothing runs). Public; no token needed.",
++ "tags": [
++ "actor.agent"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.agent.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "actor-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.agent.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/actor.agent-list-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/tasks": {
++ "get": {
++ "operationId": "get_api_v1_tasks",
++ "summary": "List your latest tasks, newest first (GET /api/v1/tasks?limit=&before=): each as platform.task@1, with a cursor for the next page.",
++ "description": "**actor.task.list** (public): List your latest tasks, newest first (GET /api/v1/tasks?limit=&before=): each as platform.task@1, with a cursor for the next page.",
++ "tags": [
++ "actor.task"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.task.list"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "actor-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.task.list"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/actor.task-list-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_api_v1_tasks",
++ "summary": "Give OpenVibe.Actor a task: POST /api/v1/tasks with the task in plain words, a mode (cheapest, balanced, best, fastest, private) and a budget.",
++ "description": "**actor.task.create** (public): Give OpenVibe.Actor a task: POST /api/v1/tasks with the task in plain words, a mode (cheapest, balanced, best, fastest, private) and a budget. Actor picks the agent system for it with openvibe-sdk/placement, runs it, has a second model family check the result, and records the result, the cost to the cent and the explanation on the task (platform.task@1, answered 201 with state queued). POST /api/v1/tasks/:id/cancel stops a task that has not ended. A person's tasks are theirs; a token that names a project puts the task in that project. Each person has a daily free allowance; a task whose cheapest eligible agent would cost more than its budget fails with actor.budget.exceeded instead of running.",
++ "tags": [
++ "actor.task"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.task.create"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "actor-task",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.task.create"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/actor.task-create-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/platform.task.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/tasks/{id}": {
++ "get": {
++ "operationId": "get_api_v1_tasks_id",
++ "summary": "Read one of your tasks (platform.task@1: state, result, cost, explanation) with GET /api/v1/tasks/:id, and watch it live with GET /api/v1/tasks/:id/events: text/event-stream of actor.task-event@1 (sta",
++ "description": "**actor.task.read** (public): Read one of your tasks (platform.task@1: state, result, cost, explanation) with GET /api/v1/tasks/:id, and watch it live with GET /api/v1/tasks/:id/events: text/event-stream of actor.task-event@1 (state changes, the agent's steps, the end), resumable with Last-Event-ID. Only the task's requester (or its project's tokens) can read it.",
++ "tags": [
++ "actor.task"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.task.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "actor-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.task.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/platform.task.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/tasks/{id}/cancel": {
++ "post": {
++ "operationId": "post_api_v1_tasks_id_cancel",
++ "summary": "Give OpenVibe.Actor a task: POST /api/v1/tasks with the task in plain words, a mode (cheapest, balanced, best, fastest, private) and a budget.",
++ "description": "**actor.task.create** (public): Give OpenVibe.Actor a task: POST /api/v1/tasks with the task in plain words, a mode (cheapest, balanced, best, fastest, private) and a budget. Actor picks the agent system for it with openvibe-sdk/placement, runs it, has a second model family check the result, and records the result, the cost to the cent and the explanation on the task (platform.task@1, answered 201 with state queued). POST /api/v1/tasks/:id/cancel stops a task that has not ended. A person's tasks are theirs; a token that names a project puts the task in that project. Each person has a daily free allowance; a task whose cheapest eligible agent would cost more than its budget fails with actor.budget.exceeded instead of running.",
++ "tags": [
++ "actor.task"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.task.create"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "actor-task",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.task.create"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/actor.task-create-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/platform.task.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/tasks/{id}/events": {
++ "get": {
++ "operationId": "get_api_v1_tasks_id_events",
++ "summary": "Read one of your tasks (platform.task@1: state, result, cost, explanation) with GET /api/v1/tasks/:id, and watch it live with GET /api/v1/tasks/:id/events: text/event-stream of actor.task-event@1 (sta",
++ "description": "**actor.task.read** (public): Read one of your tasks (platform.task@1: state, result, cost, explanation) with GET /api/v1/tasks/:id, and watch it live with GET /api/v1/tasks/:id/events: text/event-stream of actor.task-event@1 (state changes, the agent's steps, the end), resumable with Last-Event-ID. Only the task's requester (or its project's tokens) can read it.",
++ "tags": [
++ "actor.task"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.task.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "actor-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.task.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/platform.task.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "actor.agent-list-result.v1": {
++ "title": "ActorAgentListResult",
++ "description": "actor.agent-list-result@1: GET /api/v1/agents (capability actor.agent.read): every agent system Actor can route a task to. KIND is the backend class (ADR-044): openvibe-runtime (OpenVibe's own agent, acting through OpenVibe services), agent-platform (another platform's agent, called through its API), open-model (an open model on OpenVibe's own hardware), codes (coding work through OpenVibe.Codes), node and run (a person's machine or OpenVibe.Run). TRUST is ADR-046's class; private mode takes only first-party ones. CAN are the task classes and tools it takes (task:<class>, tool:<name>). RATE_CARD is the published price, revised by review and never written by a model (SOURCE says where it comes from, VERIFIED_AT when a person last checked it). AVAILABLE says whether it can take a task now and REASON why not.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "agents"
++ ],
++ "properties": {
++ "agents": {
++ "type": "array",
++ "maxItems": 64,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "name",
++ "kind",
++ "provider",
++ "trust",
++ "can",
++ "rate_card",
++ "available"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "kind": {
++ "type": "string",
++ "enum": [
++ "openvibe-runtime",
++ "agent-platform",
++ "open-model",
++ "codes",
++ "node",
++ "run"
++ ]
++ },
++ "provider": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "model": {
++ "type": "string",
++ "maxLength": 120
++ },
++ "trust": {
++ "type": "string",
++ "enum": [
++ "first-party",
++ "user-owned",
++ "partner",
++ "community",
++ "external"
++ ]
++ },
++ "can": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "pattern": "^(task|tool):[a-z0-9_-]+$"
++ }
++ },
++ "rate_card": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "input_usd_per_mtok",
++ "output_usd_per_mtok",
++ "source",
++ "verified_at"
++ ],
++ "properties": {
++ "input_usd_per_mtok": {
++ "type": "number",
++ "minimum": 0
++ },
++ "output_usd_per_mtok": {
++ "type": "number",
++ "minimum": 0
++ },
++ "per_call_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "A fixed charge per tool call the platform bills (for example a web search)."
++ },
++ "source": {
++ "type": "string",
++ "format": "uri"
++ },
++ "verified_at": {
++ "type": "string",
++ "format": "date"
++ }
++ }
++ },
++ "typical_latency_ms": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "available": {
++ "type": "boolean"
++ },
++ "reason": {
++ "type": "string",
++ "maxLength": 300
++ }
++ }
++ }
++ }
++ }
++ },
++ "actor.task-create-request.v1": {
++ "title": "ActorTaskCreateRequest",
++ "description": "actor.task-create-request@1: the body of POST /api/v1/tasks on OpenVibe.Actor (capability actor.task.create), the creation subset of platform.task@1. TASK is the instruction in plain words. MODE is the planner objective (default balanced). BUDGET caps what this task may cost (per_task_usd) and what the caller's tasks may cost in one UTC day (per_day_usd); either may be left out for the person's free-allowance defaults, and a value above the person's tier is refused 422 actor.budget.over_tier, never silently lowered. AGENT pins one agent system by its id from GET /api/v1/agents (routing still checks that it can take the task). IDEMPOTENCY_KEY: a repeat from the same requester within 24 h with an identical body answers the first task (200), with a different body 409 actor.idempotency.conflict.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "task"
++ ],
++ "properties": {
++ "task": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 20000,
++ "pattern": "\\S",
++ "description": "The instruction in plain words."
++ },
++ "mode": {
++ "type": "string",
++ "enum": [
++ "cheapest",
++ "balanced",
++ "best",
++ "fastest",
++ "private"
++ ],
++ "default": "balanced"
++ },
++ "budget": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "per_task_usd": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 100
++ },
++ "per_day_usd": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000
++ }
++ }
++ },
++ "agent": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,63}$",
++ "description": "An agent system id from GET /api/v1/agents."
++ },
++ "idempotency_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 128,
++ "pattern": "^[A-Za-z0-9._:-]+$"
++ }
++ },
++ "examples": [
++ {
++ "task": "Find out who runs the mail for openvibe.network and whether its SPF and DMARC are set up properly.",
++ "mode": "cheapest",
++ "budget": {
++ "per_task_usd": 0.05
++ }
++ }
++ ]
++ },
++ "actor.task-list-result.v1": {
++ "title": "ActorTaskListResult",
++ "description": "actor.task-list-result@1: GET /api/v1/tasks (capability actor.task.list): the caller's latest tasks, newest first, and NEXT, the `before` cursor for the following page (null on the last page).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "tasks",
++ "next"
++ ],
++ "properties": {
++ "tasks": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "$ref": "#/components/schemas/platform.task.v1"
++ }
++ },
++ "next": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128
++ }
++ }
++ },
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
++ },
++ "platform.placement-result.v1": {
++ "title": "PlacementResult",
++ "description": "Where a workload was (or would be) placed and why: the chosen candidate, the reasons, and every candidate with its eligibility, estimated cost and latency (POST /api/v1/placement/explain; roadmap WS-Z9).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "selected",
++ "objective",
++ "candidates",
++ "decided_at"
++ ],
++ "properties": {
++ "selected": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "The id of the chosen candidate, or null when none qualified — every candidate was ineligible or none was offered — in which case `reasons` says why. openvibe-sdk/placement plan() returns null on the same condition."
++ },
++ "objective": {
++ "type": "string",
++ "enum": [
++ "cheapest",
++ "lowest-latency",
++ "balanced",
++ "private",
++ "local-only",
++ "first-party-only",
++ "high-reliability",
++ "correctness"
++ ]
++ },
++ "reasons": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ },
++ "candidates": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string"
++ },
++ "eligible": {
++ "type": "boolean"
++ },
++ "excluded_because": {
++ "type": "string"
++ },
++ "estimated_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "estimated_latency_ms": {
++ "type": "number",
++ "minimum": 0
++ },
++ "score": {
++ "type": "number"
++ },
++ "reasons": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ }
++ },
++ "required": [
++ "id",
++ "eligible"
++ ]
++ }
++ },
++ "plan_epoch": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "decided_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ },
++ "platform.task.v1": {
++ "title": "Task",
++ "description": "platform.task@1 (plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /api/v1/tasks (actor.task.create) creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered agent systems, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /api/v1/tasks/{id} (actor.task.read) reads the same resource. Progress streams as server-sent events from `progress.stream_url` (actor.task-event@1; the task's own counter in `progress.last_seq`); `cancel` records that a cancel was requested (POST /api/v1/tasks/{id}/cancel), `error` says why a task failed, and `webhooks` are the registered endpoints the router delivers each state change to. Owned by `network` like every other platform.* contract and implemented by OpenVibe.Actor (manifests/services/actor.json). This v1 contract may grow optional fields.",
++ "$comment": "Additive optional fields may extend this v1 contract. It is the task resource, not a device frame: Actor's link is HTTP/SSE where Run's is the Node WebSocket.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "requester",
++ "task",
++ "mode",
++ "budget",
++ "state",
++ "created_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^tsk_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Minted by the router (tsk_<ULID>): the idempotency key of the task and the stem of its usage readings, as platform.job@1's id is for a job. Never reused."
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's. Absent for a person's own task (one made signed in, without a project token): the person's free allowance and daily budget apply."
++ },
++ "requester": {
++ "description": "The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).",
++ "allOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ }
++ ]
++ },
++ "task": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 100000,
++ "description": "The instruction in plain words, exactly as the caller gave it. Not a backend command and not a model prompt: the planner turns it into one."
++ },
++ "mode": {
++ "type": "string",
++ "enum": [
++ "cheapest",
++ "balanced",
++ "best",
++ "fastest",
++ "private"
++ ],
++ "default": "balanced",
++ "description": "The plan's five modes, in contract spelling: Cheapest, Balanced (the default), Best, Fastest and Private are `cheapest`, `balanced`, `best`, `fastest` and `private`. A mode is the planner objective, never a fixed backend list (plan T17). `cheapest`: the lowest expected cost that still meets the task class's quality bar. `balanced` (the default): cost, quality and speed weighed together. `best`: the highest chance of a right answer first time. `fastest`: the shortest time to a checked result. `private`: only backends that keep the data on OpenVibe (first-party, and a user-owned node only when the task names it), per ADR-046."
++ },
++ "budget": {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "Hard limits the router never exceeds (plan T17: 'a hard limit per task and per day'). A task whose cheapest eligible backend would take it over `per_task_usd` ends failed (actor.budget.exceeded) rather than running; `per_day_usd` caps the project's tasks in one UTC day and a new task waits or fails, it never overruns.",
++ "required": [
++ "per_task_usd",
++ "per_day_usd"
++ ],
++ "properties": {
++ "per_task_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "per_day_usd": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ },
++ "state": {
++ "type": "string",
++ "enum": [
++ "queued",
++ "running",
++ "verifying",
++ "succeeded",
++ "failed",
++ "cancelled"
++ ],
++ "description": "queued: stored, waiting for the planner. running: a backend is working, or a cascade is between attempts. verifying: a result exists and a check (the task's own tests, a citation check, a postcondition) runs before it is delivered. succeeded, failed and cancelled are the end states; an end state never changes."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "finished_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When the task reached its end state; null before."
++ },
++ "result": {
++ "description": "The checked deliverable (any JSON, as a platform.job@1 `result` is): for a code task a diff or branch reference, for research a brief with citations, for an action a postcondition report. Never a credential and never raw sandbox output. Null (or absent) in every state but succeeded, which the allOf below enforces."
++ },
++ "cost": {
++ "anyOf": [
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "usd"
++ ],
++ "properties": {
++ "usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What this task was billed, to the cent, across every attempt it made."
++ },
++ "free_allowance_used": {
++ "type": "number",
++ "minimum": 0,
++ "description": "The part of `usd` covered by the project's free allowance; metered as platform.usage-sample@1."
++ }
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "Present once the task has run an attempt (also when it ends failed or cancelled); null while queued. One key, one bill (plan T17), through Billing (ADR-012)."
++ },
++ "explanation": {
++ "anyOf": [
++ {
++ "type": "array",
++ "minItems": 1,
++ "items": {
++ "$ref": "#/components/schemas/platform.placement-result.v1"
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "One platform.placement-result@1 per backend the planner tried, oldest first, an escalation appending a new one: which backend won, why and what else was considered, from openvibe-sdk/placement (ADR-046 §1). Null while queued."
++ },
++ "progress": {
++ "anyOf": [
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "stream_url",
++ "last_seq"
++ ],
++ "properties": {
++ "stream_url": {
++ "type": "string",
++ "format": "uri",
++ "description": "The text/event-stream endpoint that carries this task's progress. Each event mirrors platform.job-frame@1's job_stdout/state shape: a state change, an output chunk, or the end."
++ },
++ "last_seq": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The task's own event counter, from 0 before the first event; a reconnecting reader resumes after it, as a job frame's seq counts on one link."
++ },
++ "events": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "enum": [
++ "output",
++ "state",
++ "end"
++ ]
++ },
++ "description": "The event kinds the stream delivers, run.job-stream-event@1's own three."
++ }
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "Streaming progress (plan T17). Null before the router opens a stream and after the end."
++ },
++ "cancel": {
++ "anyOf": [
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "requested_at"
++ ],
++ "properties": {
++ "requested_at": {
++ "type": "string",
++ "format": "date-time",
++ "description": "When POST /v1/tasks/{id}/cancel was accepted. The running backend is stopped as its own contract allows; the state becomes cancelled and `finished_at` is set."
++ },
++ "requested_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ }
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "Cancellation (plan T17): null until a cancel is requested, then it records who asked and when; the task's outcome is `state`."
++ },
++ "webhooks": {
++ "type": "array",
++ "maxItems": 8,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "url",
++ "events"
++ ],
++ "properties": {
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https://",
++ "description": "The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused."
++ },
++ "events": {
++ "type": "array",
++ "minItems": 1,
++ "items": {
++ "type": "string",
++ "enum": [
++ "queued",
++ "running",
++ "verifying",
++ "succeeded",
++ "failed",
++ "cancelled"
++ ]
++ },
++ "description": "The state changes delivered; the same names as `state`."
++ },
++ "secret_ref": {
++ "type": "string",
++ "pattern": "^[A-Z0-9_]+$",
++ "description": "The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference."
++ }
++ }
++ },
++ "description": "Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth."
++ },
++ "error": {
++ "anyOf": [
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "code",
++ "detail"
++ ],
++ "properties": {
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$",
++ "description": "A stable problem code, e.g. actor.budget.exceeded, actor.no_agent, actor.check.failed, actor.task.interrupted."
++ },
++ "detail": {
++ "type": "string",
++ "maxLength": 2000,
++ "description": "What happened, for a person."
++ }
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "Why the task failed, once it has; null otherwise. Never a credential and never raw provider output."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "required": [
++ "state"
++ ],
++ "properties": {
++ "state": {
++ "const": "succeeded"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "result",
++ "cost",
++ "explanation"
++ ],
++ "properties": {
++ "result": {
++ "not": {
++ "type": "null"
++ }
++ },
++ "cost": {
++ "type": "object"
++ },
++ "explanation": {
++ "type": "array"
++ }
++ }
++ },
++ "else": {
++ "properties": {
++ "result": {
++ "type": "null"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "required": [
++ "state"
++ ],
++ "properties": {
++ "state": {
++ "const": "cancelled"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "cancel"
++ ]
++ }
++ }
++ ],
++ "examples": [
++ {
++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "requester": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ },
++ "task": "Research five competitors of my shop and write a one-page brief with sources.",
++ "mode": "balanced",
++ "budget": {
++ "per_task_usd": 5,
++ "per_day_usd": 50
++ },
++ "state": "queued",
++ "created_at": "2026-10-05T09:00:00.000Z",
++ "webhooks": [
++ {
++ "url": "https://example.com/hooks/actor",
++ "events": [
++ "succeeded",
++ "failed"
++ ],
++ "secret_ref": "ACTOR_WEBHOOK_SECRET"
++ }
++ ]
++ },
++ {
++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "requester": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ },
++ "task": "Fix the failing thumbnail test in OpenVibe.Media and open a PR.",
++ "mode": "best",
++ "budget": {
++ "per_task_usd": 5,
++ "per_day_usd": 50
++ },
++ "state": "running",
++ "created_at": "2026-10-05T09:00:00.000Z",
++ "progress": {
++ "stream_url": "https://openvibe.actor/v1/tasks/tsk_01JAB2C3D4E5F6G7H8J9K0MNPR/events",
++ "last_seq": 12,
++ "events": [
++ "output",
++ "state",
++ "end"
++ ]
++ },
++ "explanation": [
++ {
++ "selected": "openvibe-runtime",
++ "objective": "correctness",
++ "reasons": [
++ "the task is code; the Codes adapter exposes a code harness"
++ ],
++ "candidates": [
++ {
++ "id": "openvibe-runtime",
++ "eligible": true,
++ "estimated_cost_usd": 0.06,
++ "estimated_latency_ms": 180000
++ }
++ ],
++ "decided_at": "2026-10-05T09:00:01.000Z"
++ }
++ ]
++ },
++ {
++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "requester": {
++ "type": "agent",
++ "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT"
++ },
++ "task": "Check this week's outage report and summarize the causes.",
++ "mode": "cheapest",
++ "budget": {
++ "per_task_usd": 1,
++ "per_day_usd": 10
++ },
++ "state": "succeeded",
++ "created_at": "2026-10-05T09:00:00.000Z",
++ "finished_at": "2026-10-05T09:06:00.000Z",
++ "result": {
++ "brief": "Two outages; both were a saturated connection pool.",
++ "citations": [
++ "https://openvibe.status/incidents/41"
++ ]
++ },
++ "cost": {
++ "usd": 0.06,
++ "free_allowance_used": 0.06
++ },
++ "explanation": [
++ {
++ "selected": "open-model",
++ "objective": "cheapest",
++ "reasons": [
++ "research task; the checks are citations, met by the cheapest candidate"
++ ],
++ "candidates": [
++ {
++ "id": "open-model",
++ "eligible": true,
++ "estimated_cost_usd": 0.06,
++ "estimated_latency_ms": 420000
++ }
++ ],
++ "decided_at": "2026-10-05T09:00:01.000Z"
++ }
++ ],
++ "cancel": null,
++ "progress": null
++ }
++ ]
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 89db4cf9..0f4f01a9 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 303d547a..cee01e44 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex cebdf041..3d67f96e 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex c1b871e1..453c83e5 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex 00939363..35044da0 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex 2c1cfb59..df893b8b 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex 881b27ea..3ed5d1ca 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex 90581fd1..b0bac660 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 7d6bd232..381f5de4 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 1981f325..bce1ec6f 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex 5031cb93..7c39d682 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex 3fffcf9d..f09108fa 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -1,6 +1,13 @@
{
"_about": "Generated by scripts/generate.js (lib/openapi.js). Do not edit.",
"services": [
++ {
++ "service": "actor",
++ "name": "OpenVibe.Actor",
++ "origin": "https://openvibe.actor",
++ "operations": 7,
++ "capabilities": 4
++ },
{
"service": "ai",
"name": "OpenVibe.AI",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex f2eb60d5..c3db2e93 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex a7529dea..5e549afc 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex d3f172e3..aca55d2a 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex c9643cf7..b7b41372 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex dcae88be..d12ec91c 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.114.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 36dd8a89..6b4542b0 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex 55d6711f..c42f406d 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex 05a8c7ca..e38335e3 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/services.json b/generated/openapi/services.json
iindex db4b7088..146d1350 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex bbbf9abd..1182cf5e 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex 0165c3d7..837fa66c 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex b78abcd2..9e2a0fee 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex 5f19615f..06b9ec68 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex 73d1443d..68a04db2 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 6430cbde..4ee759ce 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/watch.json b/generated/openapi/watch.json
iindex f84ba0c8..1eaa5785 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex 57dc69e4..a352cef0 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.114.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.114.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.115.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex b71113dc..0dbb563f 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -55935,7 +55935,7 @@ export type JobFrame =
/** [email protected] (owner: network) */
/**
-- * platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.
++ * platform.task@1 (plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /api/v1/tasks (actor.task.create) creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered agent systems, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /api/v1/tasks/{id} (actor.task.read) reads the same resource. Progress streams as server-sent events from `progress.stream_url` (actor.task-event@1; the task's own counter in `progress.last_seq`); `cancel` records that a cancel was requested (POST /api/v1/tasks/{id}/cancel), `error` says why a task failed, and `webhooks` are the registered endpoints the router delivers each state change to. Owned by `network` like every other platform.* contract and implemented by OpenVibe.Actor (manifests/services/actor.json). This v1 contract may grow optional fields.
*/
export type Task = {
[k: string]: unknown | undefined;
@@@ -55945,9 +55945,9 @@ export type Task = {
*/
id: string;
/**
-- * The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's.
++ * The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's. Absent for a person's own task (one made signed in, without a project token): the person's free allowance and daily budget apply.
*/
-- project_id: string;
++ project_id?: string;
/**
* The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).
*/
@@@ -56733,6 +56733,19 @@ export type Task = {
secret_ref?: string;
}
];
++ /**
++ * Why the task failed, once it has; null otherwise. Never a credential and never raw provider output.
++ */
++ error?: {
++ /**
++ * A stable problem code, e.g. actor.budget.exceeded, actor.no_agent, actor.check.failed, actor.task.interrupted.
++ */
++ code: string;
++ /**
++ * What happened, for a person.
++ */
++ detail: string;
++ } | null;
};
/** [email protected] (owner: events) */
@@@ -58855,3 +58868,88 @@ export interface SpaceModerationActionPayload {
*/
details?: {};
}
++
++/** [email protected] (owner: actor) */
++/**
++ * actor.task-create-request@1: the body of POST /api/v1/tasks on OpenVibe.Actor (capability actor.task.create), the creation subset of platform.task@1. TASK is the instruction in plain words. MODE is the planner objective (default balanced). BUDGET caps what this task may cost (per_task_usd) and what the caller's tasks may cost in one UTC day (per_day_usd); either may be left out for the person's free-allowance defaults, and a value above the person's tier is refused 422 actor.budget.over_tier, never silently lowered. AGENT pins one agent system by its id from GET /api/v1/agents (routing still checks that it can take the task). IDEMPOTENCY_KEY: a repeat from the same requester within 24 h with an identical body answers the first task (200), with a different body 409 actor.idempotency.conflict.
++ */
++export interface ActorTaskCreateRequest {
++ /**
++ * The instruction in plain words.
++ */
++ task: string;
++ mode?: "cheapest" | "balanced" | "best" | "fastest" | "private";
++ budget?: {
++ per_task_usd?: number;
++ per_day_usd?: number;
++ };
++ /**
++ * An agent system id from GET /api/v1/agents.
++ */
++ agent?: string;
++ idempotency_key?: string;
++}
++
++/** [email protected] (owner: actor) */
++/**
++ * actor.task-event@1: one event on a task's live stream (GET /api/v1/tasks/:id/events, text/event-stream; the SSE id is SEQ, so a reader resumes with Last-Event-ID). KIND is run.job-stream-event@1's three, as platform.task@1 progress names them: state (the task moved to STATE), output (one step of the agent's work: AGENT says what it is doing in TEXT, or calls TOOL with a short INPUT summary, or a tool answered), end (the task ended in STATE; nothing follows). Events are best-effort; the task record (GET /api/v1/tasks/:id) is the source of truth.
++ */
++export type ActorTaskEvent = {
++ [k: string]: unknown | undefined;
++} & {
++ task_id: string;
++ seq: number;
++ kind: "state" | "output" | "end";
++ at: string;
++ state?: "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled";
++ agent?: string;
++ step?: "plan" | "text" | "tool_call" | "tool_result" | "check" | "handoff";
++ text?: string;
++ tool?: string;
++ input?: string;
++ is_error?: boolean;
++};
++
++/** [email protected] (owner: actor) */
++/**
++ * actor.task-list-result@1: GET /api/v1/tasks (capability actor.task.list): the caller's latest tasks, newest first, and NEXT, the `before` cursor for the following page (null on the last page).
++ */
++export interface ActorTaskListResult {
++ /**
++ * @maxItems 100
++ */
++ tasks: Task[];
++ next: string | null;
++}
++
++/** [email protected] (owner: actor) */
++/**
++ * actor.agent-list-result@1: GET /api/v1/agents (capability actor.agent.read): every agent system Actor can route a task to. KIND is the backend class (ADR-044): openvibe-runtime (OpenVibe's own agent, acting through OpenVibe services), agent-platform (another platform's agent, called through its API), open-model (an open model on OpenVibe's own hardware), codes (coding work through OpenVibe.Codes), node and run (a person's machine or OpenVibe.Run). TRUST is ADR-046's class; private mode takes only first-party ones. CAN are the task classes and tools it takes (task:<class>, tool:<name>). RATE_CARD is the published price, revised by review and never written by a model (SOURCE says where it comes from, VERIFIED_AT when a person last checked it). AVAILABLE says whether it can take a task now and REASON why not.
++ */
++export interface ActorAgentListResult {
++ /**
++ * @maxItems 64
++ */
++ agents: {
++ id: string;
++ name: string;
++ kind: "openvibe-runtime" | "agent-platform" | "open-model" | "codes" | "node" | "run";
++ provider: string;
++ model?: string;
++ trust: "first-party" | "user-owned" | "partner" | "community" | "external";
++ can: string[];
++ rate_card: {
++ input_usd_per_mtok: number;
++ output_usd_per_mtok: number;
++ /**
++ * A fixed charge per tool call the platform bills (for example a web search).
++ */
++ per_call_usd?: number;
++ source: string;
++ verified_at: string;
++ };
++ typical_latency_ms?: number;
++ available: boolean;
++ reason?: string;
++ }[];
++}
ddiff --git a/manifests/capabilities/actor.agent.read.json b/manifests/capabilities/actor.agent.read.json
nnew file mode 100644
iindex 00000000..42dfe6b8
---- /dev/null
++++ b/manifests/capabilities/actor.agent.read.json
@@@ -0,0 +1,22 @@
++{
++ "id": "actor.agent.read",
++ "version": "1.0",
++ "owner": "actor",
++ "status": "active",
++ "visibility": "public",
++ "description": "The agent systems Actor routes to, with what each can do, its trust class, its rate card and whether it is available now (GET /api/v1/agents, actor.agent-list-result@1), and which one a task would go to in a mode, with the reason for every candidate (POST /api/v1/route { task, mode }, a platform.placement-result@1; nothing runs). Public; no token needed.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "actor.agent-list-result@1",
++ "permissions": [
++ "actor:agents:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "actor-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/agents",
++ "POST /api/v1/route"
++ ]
++}
ddiff --git a/manifests/capabilities/actor.task.create.json b/manifests/capabilities/actor.task.create.json
nnew file mode 100644
iindex 00000000..6bcee119
---- /dev/null
++++ b/manifests/capabilities/actor.task.create.json
@@@ -0,0 +1,24 @@
++{
++ "id": "actor.task.create",
++ "version": "1.0",
++ "owner": "actor",
++ "status": "active",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "Give OpenVibe.Actor a task: POST /api/v1/tasks with the task in plain words, a mode (cheapest, balanced, best, fastest, private) and a budget. Actor picks the agent system for it with openvibe-sdk/placement, runs it, has a second model family check the result, and records the result, the cost to the cent and the explanation on the task (platform.task@1, answered 201 with state queued). POST /api/v1/tasks/:id/cancel stops a task that has not ended. A person's tasks are theirs; a token that names a project puts the task in that project. Each person has a daily free allowance; a task whose cheapest eligible agent would cost more than its budget fails with actor.budget.exceeded instead of running.",
++ "inputSchema": "actor.task-create-request@1",
++ "outputSchema": "platform.task@1",
++ "permissions": [
++ "actor:tasks:create"
++ ],
++ "resourceConstraints": [
++ "owner",
++ "project"
++ ],
++ "quotaClass": "actor-task",
++ "events": [],
++ "implementedBy": [
++ "POST /api/v1/tasks",
++ "POST /api/v1/tasks/:id/cancel"
++ ]
++}
ddiff --git a/manifests/capabilities/actor.task.list.json b/manifests/capabilities/actor.task.list.json
nnew file mode 100644
iindex 00000000..925f6668
---- /dev/null
++++ b/manifests/capabilities/actor.task.list.json
@@@ -0,0 +1,22 @@
++{
++ "id": "actor.task.list",
++ "version": "1.0",
++ "owner": "actor",
++ "status": "active",
++ "visibility": "public",
++ "description": "List your latest tasks, newest first (GET /api/v1/tasks?limit=&before=): each as platform.task@1, with a cursor for the next page.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "actor.task-list-result@1",
++ "permissions": [
++ "actor:tasks:read"
++ ],
++ "resourceConstraints": [
++ "owner",
++ "project"
++ ],
++ "quotaClass": "actor-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/tasks"
++ ]
++}
ddiff --git a/manifests/capabilities/actor.task.read.json b/manifests/capabilities/actor.task.read.json
nnew file mode 100644
iindex 00000000..2c117fde
---- /dev/null
++++ b/manifests/capabilities/actor.task.read.json
@@@ -0,0 +1,23 @@
++{
++ "id": "actor.task.read",
++ "version": "1.0",
++ "owner": "actor",
++ "status": "active",
++ "visibility": "public",
++ "description": "Read one of your tasks (platform.task@1: state, result, cost, explanation) with GET /api/v1/tasks/:id, and watch it live with GET /api/v1/tasks/:id/events: text/event-stream of actor.task-event@1 (state changes, the agent's steps, the end), resumable with Last-Event-ID. Only the task's requester (or its project's tokens) can read it.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "platform.task@1",
++ "permissions": [
++ "actor:tasks:read"
++ ],
++ "resourceConstraints": [
++ "owner",
++ "project"
++ ],
++ "quotaClass": "actor-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/tasks/:id",
++ "GET /api/v1/tasks/:id/events"
++ ]
++}
ddiff --git a/manifests/products/openvibe.actor.json b/manifests/products/openvibe.actor.json
iindex d44cb8a4..3a2fa74e 100644
---- a/manifests/products/openvibe.actor.json
++++ b/manifests/products/openvibe.actor.json
@@@ -60,7 +60,7 @@
],
"keywords": "ai agent router, openrouter for agents, ai agent that controls your computer, remote control computer with ai, self-hosted ai agent, computer use agent, ai command console, agent api, cloud ai agent, mcp server",
"vision": {
-- "badge": "being built",
++ "badge": "alpha",
"cta": [
"See the network",
"https://openvibe.network/#network",
@@@ -374,20 +374,8 @@
],
"building": "OpenVibe.Actor is being built now, on the network's accounts, AI router and event system that already run."
},
-- "launch": "the task router with its first agent adapters, then personal agents on OpenVibe.Network accounts",
++ "launch": "the task router: its own runtime on OpenVibe services, other platforms' agents, a private open model and a cross-family check; then Codes, personal agents and Node control",
"relationships": {
-- "noRepo": true,
-- "meanwhile": [
-- {
-- "label": "OpenVibe.Tools",
-- "url": "https://openvibe.tools",
-- "note": "Tools agents will call, usable by people today."
-- },
-- {
-- "label": "OpenVibe.Services",
-- "url": "https://openvibe.services",
-- "note": "The API and SDK agents are built on."
-- }
-- ]
++ "service": "actor"
}
}
ddiff --git a/manifests/services/actor.json b/manifests/services/actor.json
nnew file mode 100644
iindex 00000000..bafdd13b
---- /dev/null
++++ b/manifests/services/actor.json
@@@ -0,0 +1,81 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.115.0 <1.0.0"
++ },
++ "id": "actor",
++ "name": "OpenVibe.Actor",
++ "status": "alpha",
++ "repository": "OpenVibers/OpenVibe.Actor",
++ "domains": [
++ "openvibe.actor"
++ ],
++ "publicOrigin": "https://openvibe.actor",
++ "exposure": {
++ "state": "live",
++ "publicSite": "service"
++ },
++ "capabilities": [
++ "actor.task.create",
++ "actor.task.read",
++ "actor.task.list",
++ "actor.agent.read"
++ ],
++ "eventsProduced": [],
++ "eventsConsumed": [],
++ "namespacesOwned": [
++ "actor.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "endpoint": "/api/health",
++ "means": "the process is up and answers HTTP; it checks nothing else (the database and the agent backends are what ready checks)"
++ },
++ "shutdown": {
++ "signal": "SIGTERM",
++ "deadlineSeconds": 10,
++ "drains": [
++ "HTTP server closed",
++ "running tasks cancelled and recorded as failed (actor.task.interrupted)",
++ "Network key refresher stopped",
++ "store closed"
++ ]
++ },
++ "startupRecovery": {
++ "resumes": [
++ {
++ "kind": "jobs",
++ "what": "a task left queued, running or verifying by a stopped process is marked failed with actor.task.interrupted at boot; nothing is re-run without the person asking"
++ }
++ ]
++ },
++ "rollback": {
++ "conditions": [
++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
++ "manual: ovhost rollback <service> --to <sha>"
++ ],
++ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
++ "blockers": {
++ "none": "migration 0001 only creates Actor's own tables; an earlier release has none to read"
++ }
++ },
++ "contracts": {
++ "range": ">=0.115.0 <1.0.0"
++ },
++ "leases": {
++ "none": "no claims: a task runs in the process that accepted it"
++ }
++ },
++ "notes": "Port 4950, unit openvibe-actor.service. OpenVibe's own general agent and the router for agent work (plan T17, ADR-044): POST /api/v1/tasks takes a task in plain words, a mode and a budget; the planner (openvibe-sdk/placement) picks an agent system, runs it, has a second model family check the result and answers with the result, the cost and the explanation (platform.task@1). Agent systems: the OpenVibe runtime (a tool-using model that acts through OpenVibe services: Search, Tools), other platforms' agents (OpenAI's Responses agent with web search) and an open model on OpenVibe's own hardware for private mode; coding work goes to OpenVibe.Codes. Consumes OpenVibe.Network (SSO with PKCE S256 as OAuth client actor, JWKS for app and person tokens), OpenVibe.Search (public reads) and OpenVibe.Tools (tools.tool.run). Owns its tasks and their event logs; model provider keys are the operator's (BYO keys through OpenVibe.AI come next).",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "internalOrigin": "http://127.0.0.1:4950",
++ "site": {
++ "name": "Actor",
++ "icon": "actor",
++ "tagline": "Your agent for everything",
++ "what": "Give a task in plain words; Actor sends it to the best agent for it, its own or another platform's, checks the result and shows the cost and why.",
++ "legalProfile": "ugc",
++ "position": 23
++ }
++}
ddiff --git a/package-lock.json b/package-lock.json
iindex 265628ab..e73d2792 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.114.0",
++ "version": "0.115.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.114.0",
++ "version": "0.115.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 25e2faf5..225807b5 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.114.0",
++ "version": "0.115.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",