openvibers/openvibe.contracts Commits

OpenVibe Link: the new name in descriptions and docs (#356)

OP OpenVibers committed 5 days ago · 6b94ac8048 · 62 files, +420 −420

OpenVibe Node is now OpenVibe Link. Pages, emails, notifications, docs, READMEs, error messages and tool descriptions say OpenVibe Link (or Link), and installer links point at openvibe.link. Code identifiers, the openvibe-node command, paths, contract ids and the repository name are unchanged.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 45b4f836..aef2c4c8 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -45,7 +45,7 @@
ordinary text resource (`.js`, at most 64 KiB) in the same integrity hash. `requires.kinds` gains `skill.run`.
The v2 listing pins each code resource's Media id and sha256 and lets a scan finding name the tool it is about.
Code never runs on an OpenVibe server, never outside a sandbox and never with a secret: it runs as a Run
-- `function` job on a Node that declared the skill runner. `actor.skill@1` and `actor.skill-listing@1` are
++ `function` job on a Link that declared the skill runner. `actor.skill@1` and `actor.skill-listing@1` are
unchanged and remain what a prompt-only skill publishes.
## 0.342.0 — 2026-11-07
@@@ -272,7 +272,7 @@
## 0.312.0 — 2026-11-07
- Add the Codes skills-hub records (the Compute plan §5, E6). `codes.skill@1` is one skill, rule, MCP server, command or hook: its owner, kind, stable slug, title, summary, the four sharing audiences (`private`, `people`, `link`, `public`), the harnesses it is written for, the newest version and that version's body. A `private`, `people` or `link` record is never in the gallery; only `public` is. The body is untrusted Markdown and the page renders it through the markup gate.
--- Add `codes.skill-version@1`: one immutable version of a record, keyed by `skill_id` and a growing `version`, with the body, summary, tools, audience, author and the note that says why it was written. A rollback copies the chosen version into a new one rather than rewriting history, so a Node that synced version 3 can still name it.
++- Add `codes.skill-version@1`: one immutable version of a record, keyed by `skill_id` and a growing `version`, with the body, summary, tools, audience, author and the note that says why it was written. A rollback copies the chosen version into a new one rather than rewriting history, so a Link that synced version 3 can still name it.
- Valid and invalid fixtures cover a public skill, a private rule, a people-shared MCP record, versions 1 and 4, and the bad ids, kinds, audiences, tools, bodies and authors.
## 0.311.0 — 2026-11-07
@@@ -577,7 +577,7 @@
and none is served or granted.
- **ADR-057's review** decides v0: the WebSocket fallback through Cloudflare inside Connect (no UDP, no DNS-only
record), names `<label>--<user>.openvibe.website` (Space names never contain "--"), a share-scoped connect key until
-- the Node plugin, the gate's host-only signed cookie, modes owner/named and staff-first signin, no public shares
++ the Link plugin, the gate's host-only signed cookie, modes owner/named and staff-first signin, no public shares
until CON-9 and CON-21.
## 0.278.0 — 2026-11-02
@@@ -593,8 +593,8 @@
may add `tools` (up to 64 OpenAI Chat Completions function definitions) and `tool_choice` (`auto` | `none`);
messages may be `role: tool` with `tool_call_id`, and an assistant message may carry the `tool_calls` the model
asked for in an earlier answer. The answer's data adds `message.tool_calls` and `finish_reason`. The machine passes
-- the tools to its model server and hands back the calls; it never runs one. A machine whose Node can pass them says
-- `ai.tools: true` in its capabilities (Node v0.36.0), so a caller sends tools only to such a machine. Older commands
++ the tools to its model server and hands back the calls; it never runs one. A machine whose Link can pass them says
++ `ai.tools: true` in its capabilities (Link v0.36.0), so a caller sends tools only to such a machine. Older commands
validate as before. Fixtures: `ai-chat-tools` (valid), `ai-tool-without-call-id` and `ai-tool-bad-name` (invalid).
## 0.276.0 — 2026-11-02
@@@ -911,7 +911,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
- **`bot.device-message@1`'s heartbeat gains an optional `fleet`** (Codes C9c): what the machine's coding agents have
left — each agent's id, whether it is installed and signed in, and each limit window's label, used and limit and
-- when it frees. OpenVibe.Node reads it with OpenVibe.Codes' own logins probe as the machine's user and reports labels,
++ when it frees. OpenVibe Link reads it with OpenVibe.Codes' own logins probe as the machine's user and reports labels,
numbers and times only (no key, token or account name); absent means no reading yet, never "nothing installed".
Additive: no field changed or removed, and a heartbeat without `fleet` validates unchanged.
- **`run.job-create-request@1` gains an optional `node`** (Codes C9c): the device id (`dev_…`) of the job's own
@@@ -1287,8 +1287,8 @@ the notes were in the tag and commit messages (`git tag -n1`).
idempotency key `run:<session>:<minute>`).
- **Desk leases on Bot**: `bot.desk-lease@1` and `bot.desk-lease-create-request@1`, with the capability
**`bot.desk.session`** (first-party, sensitive): Run leases one named desk for one session on the person's own machine
-- (where they let agents use it) or on a desk host whose owner offers it to other people and whose OpenVibe.Node runs
-- every desk in its own sandbox (Node v0.32.0, `desk_isolation: sandbox`); only that session's commands reach the desk,
++ (where they let agents use it) or on a desk host whose owner offers it to other people and whose OpenVibe Link runs
++ every desk in its own sandbox (Link v0.32.0, `desk_isolation: sandbox`); only that session's commands reach the desk,
never the machine itself.
- Fixtures for each, and `test/platform-run.test.js` holds the two sides together (ids, desk names, sizes, kinds).
@@@ -1308,7 +1308,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
- **Any of your own machines** (AI on your own machines). A node credential's `machine`, in `ai.credential-put@1` and
`ai.credential@1`, may be **`any`**: whichever of the person's machines is on, lets their AI use its models and offers
the model.
-- - The fastest goes first, by the speed OpenVibe.Bot reports (`computer.ai_speed`, OpenVibe.Node v0.29.0).
++ - The fastest goes first, by the speed OpenVibe.Bot reports (`computer.ai_speed`, OpenVibe Link v0.29.0).
- The next one is tried when one is busy or away.
- It is never anyone else's machine, and never another provider.
- Fixtures: a valid `any`, and an invalid machine that is neither a `dev_` id nor `any`.
@@@ -1327,7 +1327,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
- **AI on your own machines** (the roadmap's user-owned GPU route; `OpenVibe_AI_On_Your_Machines_Plan_2026-10-27.md`).
`bot.command@1` gains kind `ai`: one chat completion (`model`, `messages`, `max_tokens`, `temperature`) on the
-- person's own machine. OpenVibe.Node v0.27.0's `ai` plugin relays it to a local model server (Ollama, llama.cpp,
++ person's own machine. OpenVibe Link v0.27.0's `ai` plugin relays it to a local model server (Ollama, llama.cpp,
LM Studio) and answers `{ model, message, usage? }`.
- New capability **`bot.machine.ai`** (first-party, sensitive, owned by `bot`). A service, OpenVibe.AI, runs a model on a
person's own machine for that person, where they turned on "Let my AI use this computer's models". It never places
@@@ -1336,7 +1336,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.218.0 — 2026-10-27
--- **Programs on a desk** (the Desks plan's desk templates; OpenVibe.Node v0.26.0). `bot.command@1`'s `exec` takes:
++- **Programs on a desk** (the Desks plan's desk templates; OpenVibe Link v0.26.0). `bot.command@1`'s `exec` takes:
- `desk`: the program runs in that desk's session (its display, session bus and temp folder), so a program with
windows opens on the desk.
- `detach`: on a whole desk only. It starts a program that keeps running, such as an office suite, a terminal or an
@@@ -1347,7 +1347,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.217.0 — 2026-10-27
--- **Kept desk profiles** (the Desks plan's step D7, on one machine; OpenVibe.Node v0.24.0). On `start`, kind `desk`
++- **Kept desk profiles** (the Desks plan's step D7, on one machine; OpenVibe Link v0.24.0). On `start`, kind `desk`
takes `profile`: a browser profile kept on the machine by that name.
- The desk's browser starts from that profile, with its cookies and site storage, so a person signs in once, on any
desk (an agent's "Needs you" sign-in carries over to the next task).
@@@ -1358,7 +1358,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.216.0 — 2026-10-27
--- **Browser desks** (the Desks plan's step D2; OpenVibe.Node v0.23.0). Kind `desk` takes `kind` on start:
++- **Browser desks** (the Desks plan's step D2; OpenVibe Link v0.23.0). Kind `desk` takes `kind` on start:
- `desktop` (the default) is a whole screen: a display, a window manager and a browser shown on it. It needs Linux
with Xvfb.
- `browser` is a browser alone, with no display, at about half the cost. Its screen, mouse and keyboard, and its live
@@@ -1368,7 +1368,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.215.0 — 2026-10-27
--- **Several desks per machine** (the Desks plan's step D1, `OpenVibe_Agent_Desks_Plan_2026-10-27.md`; OpenVibe.Node
++- **Several desks per machine** (the Desks plan's step D1, `OpenVibe_Agent_Desks_Plan_2026-10-27.md`; OpenVibe Link
v0.19.0). `bot.command@1`'s `desk` on `screen`, `input` and `browser` takes a named desk, `desk-<name>` (lower-case
letters, digits and dashes, up to 24 after `desk-`), besides `main` and `private`. Kind `desk` takes `desk` (which
desk; `private` by default) and the action `list` (the desks that run). Each desk is a separate computer for its
@@@ -1932,7 +1932,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.182.0 — 2026-10-15
--- **The live screen** (plan T14, OpenVibe.Node v0.12.0): `bot.device-message@1` commands take the kind `screen_stream`.
++- **The live screen** (plan T14, OpenVibe Link v0.12.0): `bot.device-message@1` commands take the kind `screen_stream`.
Its value is `{ action: 'start', whip_url, publish_key, seconds?, fps? }` or `{ action: 'stop' }`. It shows the machine's
screen live to its owner on the machine's own OpenRestream stream: playback private, no recording, never mirrored to
Live.
@@@ -2040,7 +2040,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.174.0 — 2026-10-14
--- **`browser`, a computer kind** (plan T14/T17 next step; OpenVibe.Node's computer plugin 0.5.0). `bot.command@1` takes
++- **`browser`, a computer kind** (plan T14/T17 next step; OpenVibe Link's computer plugin 0.5.0). `bot.command@1` takes
`{ action, url, selector, text, key, max_width }`:
- `open` an http(s) `url`;
- `click` a CSS `selector` or the button or link showing `text`;
@@@ -2098,7 +2098,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
`{ action, x, y, width, height, dy, text, keys }`. move, click, double_click and right_click go to a point, scroll
by `dy` clicks, type text into whatever has focus, or press one key or chord (`keys`: letters, digits, `+`, `-` and
`_` only, for example `ctrl+s`). Coordinates are in the space of the screen picture the caller last saw (`width` and
-- `height`, the `screen` kind's answer); the Node scales them to the real screen. The machine's level decides: refused
++ `height`, the `screen` kind's answer); the Link scales them to the real screen. The machine's level decides: refused
at observe, approved one by one at ask. `bot.computer.control` lists it for a person's agent; the device frame
lists it.
- Fixtures: valid `input-click` and `input-key`, invalid `input-key-shell`. Additive.
@@@ -2143,7 +2143,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.166.0 — 2026-10-14
--- **Computer control (plan T14/T17, OpenVibe.Node's `computer` plugin, Node#41).** `bot.command@1` takes the kinds
++- **Computer control (plan T14/T17, OpenVibe Link's `computer` plugin, Node#41).** `bot.command@1` takes the kinds
`exec` (argv, cwd, timeout_s, stdin), `fs` (op list|stat|read|write|mkdir, path, offset, length, content_b64, mode) and
`screen` (max_width), each with its value's shape. `bot.command-result@1` and the device `ack` of
`bot.device-message@1` gain `data`: the machine's answer. The device `command` frame lists button, point and the
@@@ -2202,7 +2202,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.160.0 — 2026-10-14
- **`bot.robot-profile@1`: `mapping.driver` may be `plugin`** (plan T15 "community-submitted profiles and drivers"): the
-- robot is driven by a third-party OpenVibe Node plugin, named in `mapping.plugin`, that its owner installs on their
++ robot is driven by a third-party OpenVibe Link plugin, named in `mapping.plugin`, that its owner installs on their
Node. A community driver no longer needs a contract release of its own (as `looi` did); OpenVibe.Bot reviews and signs
community profiles before robots can use them. New valid fixture `community-plugin.json`. Additive only: every
existing profile stays valid.
@@@ -2229,7 +2229,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
- **`bot.robot-profile@1`: `mapping.driver` may be `phone`** (plan T15 "an Android phone as the robot's brain"). A
phone joins OpenVibe.Bot as a device from its own browser, with nothing to install: it pairs with a one-time code
(or a QR code), publishes its camera over WHIP, speaks, shows a face or text, and reports its battery and tilt. The
-- driver runs in the page (OpenVibe.Bot `public/phone-device.js`), not as a Node plugin. Additive only: every
++ driver runs in the page (OpenVibe.Bot `public/phone-device.js`), not as a Link plugin. Additive only: every
existing profile stays valid.
## 0.156.0 — 2026-10-13
@@@ -2392,7 +2392,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.137.0 — 2026-10-11
- `bot.robot-profile@1`: `mapping.driver` may be `looi`, the community's Generic LOOI driver (a Bluetooth bridge
-- plugin for the OpenVibe Node; OpenVibe.Node `docs/hardware-looi.md`). OpenVibe.Bot ships the matching `looi` profile.
++ plugin for the OpenVibe Link; OpenVibe Link `docs/hardware-looi.md`). OpenVibe.Bot ships the matching `looi` profile.
- Service manifests name their PostgreSQL databases (no service declares SQLite any more).
## 0.136.1 — 2026-10-10
@@@ -2750,8 +2750,8 @@ openvibe.codes now answers those paths with permanent redirects.
## 0.112.0 — 2026-10-07
**Robots driven through the relay plugin.** `bot.robot-profile@1` is 1.1.0: `mapping.driver` gains `relay`, the
--OpenVibe.Node plugin that forwards `button` and `point` commands to an owner's own script in OpenVibe.Live's
--hardware-client messages (OpenVibe.Node #33; plan T15 R9 steps 4-5). Bot's catalogue profile `relay.generic` uses it
++OpenVibe Link plugin that forwards `button` and `point` commands to an owner's own script in OpenVibe.Live's
++hardware-client messages (OpenVibe Link #33; plan T15 R9 steps 4-5). Bot's catalogue profile `relay.generic` uses it
for the robots converted from Live's stream controls. Additive: every existing profile stays valid.
## 0.111.0 — 2026-10-07
@@@ -3021,10 +3021,10 @@ depends on this one; there is no consumer yet. Additive.
## 0.97.0 — 2026-10-04
--**The job contract matches the Node again, and Bot's dispatch answer has a contract** (plan T14 step 6 follow-up).
++**The job contract matches the Link again, and Bot's dispatch answer has a contract** (plan T14 step 6 follow-up).
- `platform.job@1`: `artifact` is required for class `code` as well as `function` again — the allOf `if` names both
classes and the `artifact` description says so — and the invalid fixture `code-without-artifact` is back.
-- OpenVibe.Node already refuses a code job without an exact-version artifact (its `internal/protocol/protocol.go`
++ OpenVibe Link already refuses a code job without an exact-version artifact (its `internal/protocol/protocol.go`
`JobNoArtifact`), and `run.job-create-request@1` already required it; b88849c had dropped the `code` case from
this contract alone. A correction to match the implementations, not a new rule.
- `bot.job-dispatch-result@1` (new, owner bot, ADR-034): what OpenVibe.Bot answers OpenVibe.Run's `bot.job.dispatch`
@@@ -3037,7 +3037,7 @@ depends on this one; there is no consumer yet. Additive.
## 0.96.0 — 2026-10-04
--**OpenVibe.Bot's job dispatch for OpenVibe.Run** (plan T14 step 6, R1c): Run hands a job to a paired Node through Bot.
++**OpenVibe.Bot's job dispatch for OpenVibe.Run** (plan T14 step 6, R1c): Run hands a job to a paired Link through Bot.
Additive, no breaking changes. One new internal, sensitive capability owned by bot, added to
`manifests/services/bot.json`: `bot.job.dispatch` (permission `bot:jobs`, resource constraint `project`, input
`platform.job@1`, events `run.job.*`). Service-to-service only (Run's service token); never granted to an app or mod and
@@@ -3108,7 +3108,7 @@ test `test/usage-topics.test.js`: only network, codes and zone may list a `*.usa
**OpenVibe.Bot's profiles, device protocol, operator commands, pairing and events** (plan T15 R7; ADR-043). The Bot
manifest and its capabilities came in 0.85.0; this adds the contracts Bot's code already speaks, taken from Bot
--`155accc` and OpenVibe.Node `1ebf1a7`. Additive: every contract id is new.
++`155accc` and OpenVibe Link `1ebf1a7`. Additive: every contract id is new.
- `bot.robot-profile@1` (public): a profile as `server/profiles/*.json` ships it and `GET /api/v1/profiles` serves it
(null vendor, kind, description, variants and camera; defaulted limits; `halt` always in `commands`). The per-kind
`commands` schema is Bot's (`drive`/`ptz` axes, `actuator` number, rgb, tone and bool, `say`, `display`); capability
@@@ -3224,7 +3224,7 @@ planned (Run's repository does not exist yet) and additive.
adaptive fabric" (Proposed, for the owner) ties `platform.resource-offer@1`, `workload-requirements@1`,
`placement-result@1`, `placement-plan@1`, `provider-state@1`, `rate-card@1`, `cost-snapshot@1`, `telemetry-sample@1`
and `usage-sample@1` to one placer (`openvibe-sdk/placement`), names the five trust classes, the `worker:<class>`
--capabilities a Node advertises (`worker:function`, `worker:code`, …) and the plane rule: the data plane keeps running
++capabilities a Link advertises (`worker:function`, `worker:code`, …) and the plane rule: the data plane keeps running
on the last valid signed plan (`signature`, `epoch`, `expires_at`, checked by `placement.verifyPlan`). It records that
`user-owned` is **not** in the default trust set: a user-owned offer is eligible only for a workload whose requirements
name `user-owned`, so the SDK's default trust list stays the other four (this replaces the 0.87.0 note that the SDK
@@@ -3358,7 +3358,7 @@ path.
all `planned`, with fixtures. `contracts.zones.checkObjectZone` also checks that a zone's OVRN and Media namespace
name its own project and id. Additive; no active contract changes.
--**Reserved worker capability names for OpenVibe.Node** (plan T14 groundwork, additive): a Node advertises its runtimes
++**Reserved worker capability names for OpenVibe Link** (plan T14 groundwork, additive): a Link advertises its runtimes
as `worker:` capabilities in `platform.resource-offer@1`. The names are reserved in the contract now, not only by the
`capabilities` pattern: `platform.resource-offer@1` gains `$defs.reservedWorkerCapabilities`, the six names
`worker:function`, `worker:code`, `worker:browser`, `worker:linux`, `worker:desktop` and `worker:gpu` — one per
@@@ -3383,7 +3383,7 @@ as `worker:` capabilities in `platform.resource-offer@1`. The names are reserved
`task:edit`, `task:review`, `task:browse`, `task:run`, `task:test`, `task:plan`, `harness:mcp`, `harness:resume`,
`harness:host-access`, `harness:long-autonomy`. New valid fixture `harness-task-routing`. Additive.
--**Run jobs and their metering** (plan T14 lane G, follow-up 1 of the Node gap audit):
++**Run jobs and their metering** (plan T14 lane G, follow-up 1 of the Link gap audit):
- New `platform.runtime-class@1`: the classes `function`, `code`, `browser`, `linux`, `desktop` and `gpu`, names
only; `function` is the first one implemented.
- New `platform.job@1`: `id` (`job_<ULID>`, the idempotency and ack key), `class`, `artifact` {`name`, `version`}
ddiff --git a/contracts/actor/skill.v2.json b/contracts/actor/skill.v2.json
iindex 346469fc..05dae1a1 100644
---- a/contracts/actor/skill.v2.json
++++ b/contracts/actor/skill.v2.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/actor/skill.v2.json",
"title": "ActorSkillV2",
-- "description": "actor.skill@2: a skill, the document an agent loads to do one kind of job well (ADR-058, ADR-060). It keeps every field and meaning of actor.skill@1 and adds a way for the skill to bring CODE that runs in a sandbox: RUNS is prompt (as @1, text and instructions only) or sandbox. A sandbox skill carries its code as ordinary TEXT resources (a .js path with media_type text/javascript, at most 64 KiB each, in the integrity hash); TOOLS (1 to 5, only when runs is sandbox) say what an agent may call: a name, a description the agent reads, language javascript (the only one in v1), an ENTRY naming one of the skill's own .js resources, ARGS as a JSON Schema subset (an object whose properties are string, number, integer, boolean or an array of those; enum, maxLength, and pattern at most 200 characters; no $ref and no nested object), NET (deny by default, openvibe-only, public) and LIMITS (wall_ms at most 30000, default 10000; mem_bytes at most 268435456, default 134217728). A sandbox skill's requires.kinds names skill.run; a prompt skill declares no tools. A skill GRANTS NOTHING: requires only declares; the agent's level and approvals decide every step (actor approvals, HO1), and a step of a kind the skill did not declare is a scan finding (actor.skill-listing@2). Code never runs on an OpenVibe server and never sees a secret: it runs as a Run job of class function on a Node that declared the skill runner, with the network its tool declared and the exact bytes the registry scanned. The INTEGRITY block is a sha256 over the canonical JSON of the document: it shows the file was not edited after it was written and says nothing about who wrote it, so the AUTHOR is a claim; the registry's listing is the only place a publisher is vouched for. The canonical JSON is the document's own fields, in this order: skill, slug, name, description, version, instructions, resources, tools, requires, runs, license, author; an optional field that is absent or empty is left out; resources are [{ path, media_type, text }] in the document's order; tools are [{ name, description, language, entry, args, net, limits }] in the document's order, with an absent net or limits (and each absent limits field) left out and args as the object the document wrote; requires is { kinds, apps, hosts } with empty lists left out; author is { subject } or { subject, name }; the value is the compact JSON of that object (no spaces). CREATED_AT is not hashed.",
++ "description": "actor.skill@2: a skill, the document an agent loads to do one kind of job well (ADR-058, ADR-060). It keeps every field and meaning of actor.skill@1 and adds a way for the skill to bring CODE that runs in a sandbox: RUNS is prompt (as @1, text and instructions only) or sandbox. A sandbox skill carries its code as ordinary TEXT resources (a .js path with media_type text/javascript, at most 64 KiB each, in the integrity hash); TOOLS (1 to 5, only when runs is sandbox) say what an agent may call: a name, a description the agent reads, language javascript (the only one in v1), an ENTRY naming one of the skill's own .js resources, ARGS as a JSON Schema subset (an object whose properties are string, number, integer, boolean or an array of those; enum, maxLength, and pattern at most 200 characters; no $ref and no nested object), NET (deny by default, openvibe-only, public) and LIMITS (wall_ms at most 30000, default 10000; mem_bytes at most 268435456, default 134217728). A sandbox skill's requires.kinds names skill.run; a prompt skill declares no tools. A skill GRANTS NOTHING: requires only declares; the agent's level and approvals decide every step (actor approvals, HO1), and a step of a kind the skill did not declare is a scan finding (actor.skill-listing@2). Code never runs on an OpenVibe server and never sees a secret: it runs as a Run job of class function on a Link that declared the skill runner, with the network its tool declared and the exact bytes the registry scanned. The INTEGRITY block is a sha256 over the canonical JSON of the document: it shows the file was not edited after it was written and says nothing about who wrote it, so the AUTHOR is a claim; the registry's listing is the only place a publisher is vouched for. The canonical JSON is the document's own fields, in this order: skill, slug, name, description, version, instructions, resources, tools, requires, runs, license, author; an optional field that is absent or empty is left out; resources are [{ path, media_type, text }] in the document's order; tools are [{ name, description, language, entry, args, net, limits }] in the document's order, with an absent net or limits (and each absent limits field) left out and args as the object the document wrote; requires is { kinds, apps, hosts } with empty lists left out; author is { subject } or { subject, name }; the value is the compact JSON of that object (no spaces). CREATED_AT is not hashed.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/ai/credential-put.v1.json b/contracts/ai/credential-put.v1.json
iindex aa03501b..dddb2415 100644
---- a/contracts/ai/credential-put.v1.json
++++ b/contracts/ai/credential-put.v1.json
@@@ -15,12 +15,12 @@
"anthropic",
"node"
],
-- "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Node and OpenVibe.Bot (no key, no base_url, no cost)."
++ "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Link and OpenVibe.Bot (no key, no base_url, no cost)."
},
"machine": {
"type": "string",
"pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
},
"base_url": {
"type": "string",
ddiff --git a/contracts/ai/credential-put.v2.json b/contracts/ai/credential-put.v2.json
iindex a45e5ea1..3134fdba 100644
---- a/contracts/ai/credential-put.v2.json
++++ b/contracts/ai/credential-put.v2.json
@@@ -42,7 +42,7 @@
"machine": {
"type": "string",
"pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model, or `any` of their own."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model, or `any` of their own."
},
"models": {
"type": "object",
ddiff --git a/contracts/ai/memory-export.v1.json b/contracts/ai/memory-export.v1.json
iindex 2a716716..dd5ff319 100644
---- a/contracts/ai/memory-export.v1.json
++++ b/contracts/ai/memory-export.v1.json
@@@ -101,7 +101,7 @@
"null"
],
"maxLength": 100,
-- "description": "The Node machine id the item came from, when it came from one."
++ "description": "The Link machine id the item came from, when it came from one."
},
"seen_at": {
"type": "string",
ddiff --git a/contracts/ai/memory-item.v1.json b/contracts/ai/memory-item.v1.json
iindex e37fc648..de7352a2 100644
---- a/contracts/ai/memory-item.v1.json
++++ b/contracts/ai/memory-item.v1.json
@@@ -102,7 +102,7 @@
"null"
],
"maxLength": 100,
-- "description": "The Node machine id it came from, when it came from one."
++ "description": "The Link machine id it came from, when it came from one."
},
"no_looks": {
"type": "boolean",
ddiff --git a/contracts/ai/offer.v1.json b/contracts/ai/offer.v1.json
iindex fe0937f6..703e80c3 100644
---- a/contracts/ai/offer.v1.json
++++ b/contracts/ai/offer.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/ai/offer.v1.json",
"title": "AiOffer",
-- "description": "ai.offer@1: one community AI offer, a person's machine (OpenVibe.Node's ai plugin, \"Offer to the network\") serving its local models to requests that opt in to community capacity. OpenVibe.Bot reports these fields (POST /internal/ai/offers/report on Network, network.resource.report; a report with any other field is refused whole), and OpenVibe.AI reads them from Network (GET /internal/ai/offers, network.registry.read, { offers, generated_at, count }). Network adds `pool` (ai.offer-pool@1) to the head's offer when the machine is the head of an approved pool (bot.machine-pool@1), and leaves a pool member's own offer out of the list while it is in a pool. For a pool the hours, audience, price, readiness, idle-only and verification apply to the whole pool: it is ready only while every member has approved, renewed its readiness in the last few seconds, and is still the owner's. Nothing here is public: AI shows Exchange a hashed offer id and no owner or machine id.",
++ "description": "ai.offer@1: one community AI offer, a person's machine (OpenVibe Link's ai plugin, \"Offer to the network\") serving its local models to requests that opt in to community capacity. OpenVibe.Bot reports these fields (POST /internal/ai/offers/report on Network, network.resource.report; a report with any other field is refused whole), and OpenVibe.AI reads them from Network (GET /internal/ai/offers, network.registry.read, { offers, generated_at, count }). Network adds `pool` (ai.offer-pool@1) to the head's offer when the machine is the head of an approved pool (bot.machine-pool@1), and leaves a pool member's own offer out of the list while it is in a pool. For a pool the hours, audience, price, readiness, idle-only and verification apply to the whole pool: it is ready only while every member has approved, renewed its readiness in the last few seconds, and is still the owner's. Nothing here is public: AI shows Exchange a hashed offer id and no owner or machine id.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/bot/command.v1.json b/contracts/bot/command.v1.json
iindex 1787dc69..727f47e2 100644
---- a/contracts/bot/command.v1.json
++++ b/contracts/bot/command.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/command.v1.json",
"title": "BotCommand",
-- "description": "bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9). The computer kinds (exec, fs, screen, input, browser; OpenVibe.Node's computer plugin, Node docs/computer.md) are taken only for a person's own machine, from its owner or an agent the owner let use it (bot.computer.control); the machine's owner sets on the machine what they may do, and the answer comes back in bot.command-result@1's data. probe (a read-only check of something on the machine's own network) is the one computer kind OpenVibe.Watch may send, for owners who let their watches use the computer. input (added in 0.171.0) moves the mouse, clicks, scrolls, types or presses keys on the machine's screen, at coordinates in the space of the screen picture the caller last saw (its width and height; the Node scales them to the real screen): refused at level observe, approved one by one at ask. browser (added in 0.174.0) drives a Chromium-family browser the agent uses on the machine, in a profile of its own: open an http(s) URL, click a CSS selector or the button or link showing a text, fill a field, press a named key, go back, read the page, take a screenshot, or close it. read, screenshot and close run at every level; the rest act (refused at observe, approved one by one at ask), and below level full open refuses an address on the machine's own network. desk (added in 0.214.0; plan track K, Vibe's desk) starts, stops or reports a private desk on the machine: a display of its own (Xvfb, a window manager, its own session and browser profile) where an agent works without touching the person's screen, which the person can watch and take over; screen, input and browser take desk: private to act there instead of on the person's screen (main, the default). Starting and stopping a desk act (refused at observe, approved one by one at ask); status runs at every level.",
++ "description": "bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9). The computer kinds (exec, fs, screen, input, browser; OpenVibe Link's computer plugin, Link docs/computer.md) are taken only for a person's own machine, from its owner or an agent the owner let use it (bot.computer.control); the machine's owner sets on the machine what they may do, and the answer comes back in bot.command-result@1's data. probe (a read-only check of something on the machine's own network) is the one computer kind OpenVibe.Watch may send, for owners who let their watches use the computer. input (added in 0.171.0) moves the mouse, clicks, scrolls, types or presses keys on the machine's screen, at coordinates in the space of the screen picture the caller last saw (its width and height; the Link scales them to the real screen): refused at level observe, approved one by one at ask. browser (added in 0.174.0) drives a Chromium-family browser the agent uses on the machine, in a profile of its own: open an http(s) URL, click a CSS selector or the button or link showing a text, fill a field, press a named key, go back, read the page, take a screenshot, or close it. read, screenshot and close run at every level; the rest act (refused at observe, approved one by one at ask), and below level full open refuses an address on the machine's own network. desk (added in 0.214.0; plan track K, Vibe's desk) starts, stops or reports a private desk on the machine: a display of its own (Xvfb, a window manager, its own session and browser profile) where an agent works without touching the person's screen, which the person can watch and take over; screen, input and browser take desk: private to act there instead of on the person's screen (main, the default). Starting and stopping a desk act (refused at observe, approved one by one at ask); status runs at every level.",
"type": "object",
"required": [
"type",
@@@ -190,11 +190,11 @@
"desk": {
"type": "string",
"pattern": "^(main|private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which session it runs in (Node v0.26.0): the person's own (main, the default), or a desk's (private or desk-<name>): the desk's display, session bus and temp folder, so a program with windows opens on that desk; start the desk first"
++ "description": "which session it runs in (Link v0.26.0): the person's own (main, the default), or a desk's (private or desk-<name>): the desk's display, session bus and temp folder, so a program with windows opens on that desk; start the desk first"
},
"detach": {
"type": "boolean",
-- "description": "on a whole desk only (Node v0.26.0): start a program that keeps running (an office suite, a terminal, an editor) and answer at once with its pid; it runs until the desk stops. Never on the person's own session or a browser desk"
++ "description": "on a whole desk only (Link v0.26.0): start a program that keeps running (an office suite, a terminal, an editor) and answer at once with its pid; it runs until the desk stops. Never on the person's own session or a browser desk"
}
}
}
@@@ -295,7 +295,7 @@
"desk": {
"type": "string",
"pattern": "^(main|private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Node v0.19.0: one per agent task); start the desk first"
++ "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Link v0.19.0: one per agent task); start the desk first"
}
}
}
@@@ -422,7 +422,7 @@
"desk": {
"type": "string",
"pattern": "^(main|private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Node v0.19.0: one per agent task); start the desk first"
++ "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Link v0.19.0: one per agent task); start the desk first"
}
}
}
@@@ -510,7 +510,7 @@
"desk": {
"type": "string",
"pattern": "^(main|private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Node v0.19.0: one per agent task); start the desk first"
++ "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Link v0.19.0: one per agent task); start the desk first"
}
},
"allOf": [
@@@ -627,24 +627,24 @@
"status",
"list"
],
-- "description": "start a desk (a no-op when it runs), stop it, report it, or list the desks that run (list, Node v0.19.0)"
++ "description": "start a desk (a no-op when it runs), stop it, report it, or list the desks that run (list, Link v0.19.0)"
},
"desk": {
"type": "string",
"pattern": "^(private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which desk (Node v0.19.0): private (the default, the first desk) or desk-<name>; each desk has its own display, browser profile, downloads and temp folder, and a machine runs at most its max_desks at once"
++ "description": "which desk (Link v0.19.0): private (the default, the first desk) or desk-<name>; each desk has its own display, browser profile, downloads and temp folder, and a machine runs at most its max_desks at once"
},
"kind": {
"enum": [
"desktop",
"browser"
],
-- "description": "start only (Node v0.23.0, Desks plan D2): what the desk is. desktop (the default) is a whole screen: a display, a window manager and a browser shown on it (Linux with Xvfb). browser is a browser alone with no display (Linux or macOS with Chromium), about half the cost: its screen, mouse and keyboard and its live view are the page's own. A machine says which it runs in its computer capabilities (desk.kinds)"
++ "description": "start only (Link v0.23.0, Desks plan D2): what the desk is. desktop (the default) is a whole screen: a display, a window manager and a browser shown on it (Linux with Xvfb). browser is a browser alone with no display (Linux or macOS with Chromium), about half the cost: its screen, mouse and keyboard and its live view are the page's own. A machine says which it runs in its computer capabilities (desk.kinds)"
},
"profile": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{0,23}$",
-- "description": "start only, a named desk (Node v0.24.0): a browser profile kept on the machine by that name. The desk's browser starts from it (its cookies and site storage: the person signs in once, on any desk) and the desk saves it back when it stops (the last desk to stop wins); caches are never kept. The private desk keeps its own profile. A machine that keeps profiles says so in its computer capabilities (desk.profiles)"
++ "description": "start only, a named desk (Link v0.24.0): a browser profile kept on the machine by that name. The desk's browser starts from it (its cookies and site storage: the person signs in once, on any desk) and the desk saves it back when it stops (the last desk to stop wins); caches are never kept. The private desk keeps its own profile. A machine that keeps profiles says so in its computer capabilities (desk.profiles)"
},
"width": {
"type": "integer",
@@@ -686,7 +686,7 @@
"messages"
],
"additionalProperties": false,
-- "description": "one chat completion on the person's own machine (OpenVibe.Node v0.27.0's ai plugin, relayed to a local OpenAI-compatible model server: Ollama, llama.cpp, LM Studio); the answer's data is { model, message: { role, content, tool_calls? }, finish_reason?, usage? }. It reads nothing on the machine and changes nothing there. TOOLS (since 0.277.0, Node v0.36.0's ai plugin; a machine that can pass them says so with ai.tools: true in its capabilities) are the functions the model may ask for: the machine passes them to its model server and hands back the calls the model asked for in message.tool_calls; it never runs one. The caller runs them and sends the next command with the assistant's message (its tool_calls) and one tool message per call (TOOL_CALL_ID)",
++ "description": "one chat completion on the person's own machine (OpenVibe Link v0.27.0's ai plugin, relayed to a local OpenAI-compatible model server: Ollama, llama.cpp, LM Studio); the answer's data is { model, message: { role, content, tool_calls? }, finish_reason?, usage? }. It reads nothing on the machine and changes nothing there. TOOLS (since 0.277.0, Link v0.36.0's ai plugin; a machine that can pass them says so with ai.tools: true in its capabilities) are the functions the model may ask for: the machine passes them to its model server and hands back the calls the model asked for in message.tool_calls; it never runs one. The caller runs them and sends the next command with the assistant's message (its tool_calls) and one tool message per call (TOOL_CALL_ID)",
"properties": {
"model": {
"type": "string",
ddiff --git a/contracts/bot/desk-lease.v1.json b/contracts/bot/desk-lease.v1.json
iindex e6fb8fd7..e2d44191 100644
---- a/contracts/bot/desk-lease.v1.json
++++ b/contracts/bot/desk-lease.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/desk-lease.v1.json",
"title": "BotDeskLease",
-- "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
++ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe Link runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/bot/device-message.v1.json b/contracts/bot/device-message.v1.json
iindex 8e1656a0..ecc9afe6 100644
---- a/contracts/bot/device-message.v1.json
++++ b/contracts/bot/device-message.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/device-message.v1.json",
"title": "BotDeviceMessage",
-- "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).",
++ "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe Link internal/protocol/protocol.go (the Go structs of the device agent).",
"oneOf": [
{
"description": "Server to device, once authenticated.",
@@@ -190,7 +190,7 @@
},
"value": {
"type": "object",
-- "description": "The command's value. screen_stream (OpenVibe.Node v0.12.0): {action: 'start', whip_url, publish_key, seconds? (30-1800, default 600), fps? (1-5, default 2)} or {action: 'stop'}: the machine's screen shown live on its own private OpenRestream stream. Bot alone fills in whip_url and publish_key (the machine's stream, playback private, never recorded); no operator, agent or service sends this kind."
++ "description": "The command's value. screen_stream (OpenVibe Link v0.12.0): {action: 'start', whip_url, publish_key, seconds? (30-1800, default 600), fps? (1-5, default 2)} or {action: 'stop'}: the machine's screen shown live on its own private OpenRestream stream. Bot alone fills in whip_url and publish_key (the machine's stream, playback private, never recorded); no operator, agent or service sends this kind."
},
"deadline_ms": {
"type": "integer",
@@@ -225,7 +225,7 @@
},
"target": {
"type": "string",
-- "description": "OpenVibe.Node only: the driver to send it to."
++ "description": "OpenVibe Link only: the driver to send it to."
}
}
},
@@@ -521,7 +521,7 @@
}
},
{
-- "description": "Device to server: what the agent runs; OpenVibe.Node sends drivers as objects, older agents as names.",
++ "description": "Device to server: what the agent runs; OpenVibe Link sends drivers as objects, older agents as names.",
"type": "object",
"required": [
"v",
@@@ -756,12 +756,12 @@
},
"data": {
"type": "object",
-- "description": "OpenVibe.Node: a computer command's answer (exec, fs, screen), at most 512 KB of JSON."
++ "description": "OpenVibe Link: a computer command's answer (exec, fs, screen), at most 512 KB of JSON."
}
}
},
{
-- "description": "Device to server: the command was refused, with OpenVibe.Node's fault code (bad_value, estopped, local_stop, expired, …).",
++ "description": "Device to server: the command was refused, with OpenVibe Link's fault code (bad_value, estopped, local_stop, expired, …).",
"type": "object",
"required": [
"v",
@@@ -835,7 +835,7 @@
"fleet": {
"type": "array",
"maxItems": 16,
-- "description": "What this machine's coding agents have left (Codes C9c): each agent's id, whether it is installed and signed in, and each limit window's label, what it has used of its limit, and when it frees (RFC 3339 or empty when the agent's login file does not say). OpenVibe.Node reads it with the OpenVibe.Codes runner's own logins probe as the machine's own user and reports labels, numbers and times only: no key, token or account name. Absent means the machine has no reading (no OpenVibe.Codes runner, or its probe has not answered yet), never that nothing is installed.",
++ "description": "What this machine's coding agents have left (Codes C9c): each agent's id, whether it is installed and signed in, and each limit window's label, what it has used of its limit, and when it frees (RFC 3339 or empty when the agent's login file does not say). OpenVibe Link reads it with the OpenVibe.Codes runner's own logins probe as the machine's own user and reports labels, numbers and times only: no key, token or account name. Absent means the machine has no reading (no OpenVibe.Codes runner, or its probe has not answered yet), never that nothing is installed.",
"items": {
"type": "object",
"required": ["agent", "installed", "signed_in"],
ddiff --git a/contracts/bot/job-dispatch-result.v1.json b/contracts/bot/job-dispatch-result.v1.json
iindex 7f71040d..01c5fd4d 100644
---- a/contracts/bot/job-dispatch-result.v1.json
++++ b/contracts/bot/job-dispatch-result.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/job-dispatch-result.v1.json",
"title": "BotJobDispatchResult",
-- "description": "bot.job-dispatch-result@1: what OpenVibe.Bot answers OpenVibe.Run's bot.job.dispatch calls (server/api/v1.js and server/jobs/index.js). POST /api/v1/jobs is 201 { job, sent }: `job` is Bot's run_jobs record and `sent` says whether the `job` frame went out on the device link now (false while the paired Node is away; it is resent on every reconnect until the Node acks it). POST /api/v1/jobs/:id/cancel is 200 { job, sent } with the same shape: `sent` says whether a `job_cancel` frame went out. GET /api/v1/jobs/:id is 200 { job, stdout }: the record plus the last 1 MiB of the job's captured stdout, or null when the Node sent none. The record's `state` follows the run_jobs lifecycle queued -> placed (the Node acked) -> running (job_started) -> one end state, and its `job` field is the platform.job@1 body Run dispatched, stored and resent verbatim. A refused dispatch answers a problem instead (422 bot.invalid_job, 404 bot.device_not_found, 409 bot.class_unadvertised, 409 bot.job_id_reused), never this contract.",
++ "description": "bot.job-dispatch-result@1: what OpenVibe.Bot answers OpenVibe.Run's bot.job.dispatch calls (server/api/v1.js and server/jobs/index.js). POST /api/v1/jobs is 201 { job, sent }: `job` is Bot's run_jobs record and `sent` says whether the `job` frame went out on the device link now (false while the paired Link is away; it is resent on every reconnect until the Link acks it). POST /api/v1/jobs/:id/cancel is 200 { job, sent } with the same shape: `sent` says whether a `job_cancel` frame went out. GET /api/v1/jobs/:id is 200 { job, stdout }: the record plus the last 1 MiB of the job's captured stdout, or null when the Link sent none. The record's `state` follows the run_jobs lifecycle queued -> placed (the Link acked) -> running (job_started) -> one end state, and its `job` field is the platform.job@1 body Run dispatched, stored and resent verbatim. A refused dispatch answers a problem instead (422 bot.invalid_job, 404 bot.device_not_found, 409 bot.class_unadvertised, 409 bot.job_id_reused), never this contract.",
"oneOf": [
{ "$ref": "#/$defs/dispatched" },
{ "$ref": "#/$defs/state" }
@@@ -43,7 +43,7 @@
"node_id": {
"type": "string",
"pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The paired device the job was sent to; a Node never reads another device's job."
++ "description": "The paired device the job was sent to; a Link never reads another device's job."
},
"class": {
"$ref": "../platform/runtime-class.v1.json"
@@@ -58,7 +58,7 @@
"cancelled",
"expired"
],
-- "description": "queued (stored; sent and resent until acked), placed (the Node acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl)."
++ "description": "queued (stored; sent and resent until acked), placed (the Link acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl)."
},
"project_id": {
"type": "string",
@@@ -91,7 +91,7 @@
"null"
],
"maxLength": 64,
-- "description": "The Node's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable."
++ "description": "The Link's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable."
},
"sent_at": {
"type": [
@@@ -107,7 +107,7 @@
"null"
],
"minimum": 0,
-- "description": "The Node's job_started started_ms: the anchor of every usage second; null until it started."
++ "description": "The Link's job_started started_ms: the anchor of every usage second; null until it started."
},
"finished_at": {
"type": [
@@@ -230,7 +230,7 @@
"state": {
"type": "object",
"additionalProperties": false,
-- "description": "The answer of GET /api/v1/jobs/:id (200): the row and the job's held stdout (null when the Node sent none).",
++ "description": "The answer of GET /api/v1/jobs/:id (200): the row and the job's held stdout (null when the Link sent none).",
"required": [
"job",
"stdout"
ddiff --git a/contracts/bot/machine-pool.v1.json b/contracts/bot/machine-pool.v1.json
iindex 21d8ab29..b744c456 100644
---- a/contracts/bot/machine-pool.v1.json
++++ b/contracts/bot/machine-pool.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/machine-pool.v1.json",
"title": "BotMachinePool",
-- "description": "bot.machine-pool@1 (the pooled machines design, 2026-11-06): several of one owner's machines on the same local network that serve one model together through llama.cpp RPC, `rpc-server` on the members and `llama-server --rpc` on the head. OpenVibe.Network holds the record: the owner proposes it on /machines (POST /api/v1/machine-pools, two to eight of their own machines and an optional head; the default head has the most free VRAM), and each machine reads it with its own node token (GET /api/v1/node/self/pools/:id, network.node.self.manage) and approves it locally (POST /api/v1/node/self/pools/:id/approve with { certificate }). A machine's `certificate` is null until it approves on that machine: the Node generates an Ed25519 key there, keeps the private key, and uploads only its self-signed public certificate, which the other members pin for the pool's private link. A pool with any null certificate never serves. The record never carries a private key or a prompt. What a schema cannot say, Network and Node check at run time: the members are distinct and the head is one of them; every member has the pool's owner, today, through Bot; every member was seen from the same public address with a private address in the same subnet; the head's direct link probe to each member passes; each machine's certificate is its own; and any change of membership, owner, head or key revokes the pool, so all machines approve a new one. A machine that leaves (removed, signed out, a different owner) drops its link and its key at once.",
++ "description": "bot.machine-pool@1 (the pooled machines design, 2026-11-06): several of one owner's machines on the same local network that serve one model together through llama.cpp RPC, `rpc-server` on the members and `llama-server --rpc` on the head. OpenVibe.Network holds the record: the owner proposes it on /machines (POST /api/v1/machine-pools, two to eight of their own machines and an optional head; the default head has the most free VRAM), and each machine reads it with its own node token (GET /api/v1/node/self/pools/:id, network.node.self.manage) and approves it locally (POST /api/v1/node/self/pools/:id/approve with { certificate }). A machine's `certificate` is null until it approves on that machine: the Link generates an Ed25519 key there, keeps the private key, and uploads only its self-signed public certificate, which the other members pin for the pool's private link. A pool with any null certificate never serves. The record never carries a private key or a prompt. What a schema cannot say, Network and Node check at run time: the members are distinct and the head is one of them; every member has the pool's owner, today, through Bot; every member was seen from the same public address with a private address in the same subnet; the head's direct link probe to each member passes; each machine's certificate is its own; and any change of membership, owner, head or key revokes the pool, so all machines approve a new one. A machine that leaves (removed, signed out, a different owner) drops its link and its key at once.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/browser-provider/job-create-request.v1.json b/contracts/browser-provider/job-create-request.v1.json
iindex 94d6780d..e2f11b97 100644
---- a/contracts/browser-provider/job-create-request.v1.json
++++ b/contracts/browser-provider/job-create-request.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/browser-provider/job-create-request.v1.json",
"title": "BrowserProviderJobCreateRequest",
-- "description": "browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.",
++ "description": "browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/codes/skill-version.v1.json b/contracts/codes/skill-version.v1.json
iindex 2b9eb5d9..59e0653c 100644
---- a/contracts/codes/skill-version.v1.json
++++ b/contracts/codes/skill-version.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/codes/skill-version.v1.json",
"title": "CodesSkillVersion",
-- "description": "codes.skill-version@1: one immutable version of a codes.skill@1 record in the OpenVibe.Codes skills hub. Every save writes a new version; VERSION starts at 1 and only grows, and SKILL_ID plus VERSION is the key. BODY and SUMMARY are the content at that version, TOOLS and AUDIENCE the settings it had, and NOTE says why it was written (created, edited, published, or rolled back to N). A rollback never rewrites an old row: it copies the chosen version into a new one, so the history of what every machine synced stays exact and the Node's state can name the version it wrote.",
++ "description": "codes.skill-version@1: one immutable version of a codes.skill@1 record in the OpenVibe.Codes skills hub. Every save writes a new version; VERSION starts at 1 and only grows, and SKILL_ID plus VERSION is the key. BODY and SUMMARY are the content at that version, TOOLS and AUDIENCE the settings it had, and NOTE says why it was written (created, edited, published, or rolled back to N). A rollback never rewrites an old row: it copies the chosen version into a new one, so the history of what every machine synced stays exact and the Link's state can name the version it wrote.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/codes/skill.v1.json b/contracts/codes/skill.v1.json
iindex fbf38301..65af21d8 100644
---- a/contracts/codes/skill.v1.json
++++ b/contracts/codes/skill.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/codes/skill.v1.json",
"title": "CodesSkill",
-- "description": "codes.skill@1: one skill, rule, MCP server, command or hook in the OpenVibe.Codes hub (the Compute plan §5, E6). KIND says what it is: a skill is a SKILL.md folder the agent tools load, a rule is text for the tools' AGENTS.md/CLAUDE.md files, an mcp record is one MCP server definition, and command/hook are a slash command and a hook. SLUG is the folder name (lowercase letters, digits and hyphens) and is stable within one owner. VERSION is the newest record in codes.skill-version@1; BODY is that version's content. AUDIENCE is the four sharing audiences of openvibe-sdk/share: private (the owner only), people (the owner and the members listed in codes_skill_members), link (anyone with the link, not in the gallery) and public (the gallery, search and agents). A private, people or link record is never in the gallery. OFFICIAL marks OpenVibe's own skills. TOOLS names the harnesses the skill is written for; an empty list means every tool the Node knows. The body is untrusted text: the page renders it through the markup gate (escape first, then a small Markdown subset), never as HTML.",
++ "description": "codes.skill@1: one skill, rule, MCP server, command or hook in the OpenVibe.Codes hub (the Compute plan §5, E6). KIND says what it is: a skill is a SKILL.md folder the agent tools load, a rule is text for the tools' AGENTS.md/CLAUDE.md files, an mcp record is one MCP server definition, and command/hook are a slash command and a hook. SLUG is the folder name (lowercase letters, digits and hyphens) and is stable within one owner. VERSION is the newest record in codes.skill-version@1; BODY is that version's content. AUDIENCE is the four sharing audiences of openvibe-sdk/share: private (the owner only), people (the owner and the members listed in codes_skill_members), link (anyone with the link, not in the gallery) and public (the gallery, search and agents). A private, people or link record is never in the gallery. OFFICIAL marks OpenVibe's own skills. TOOLS names the harnesses the skill is written for; an empty list means every tool the Link knows. The body is untrusted text: the page renders it through the markup gate (escape first, then a small Markdown subset), never as HTML.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/connect/device.v1.json b/contracts/connect/device.v1.json
iindex 03a65e92..a3fd4ced 100644
---- a/contracts/connect/device.v1.json
++++ b/contracts/connect/device.v1.json
@@@ -123,7 +123,7 @@
"type": "string",
"minLength": 1,
"maxLength": 40,
-- "description": "The Node release running on the device."
++ "description": "The Link release running on the device."
},
"addresses": {
"type": "array",
ddiff --git a/contracts/events/hook.v1.json b/contracts/events/hook.v1.json
iindex 3d332b0c..62e81d1d 100644
---- a/contracts/events/hook.v1.json
++++ b/contracts/events/hook.v1.json
@@@ -156,7 +156,7 @@
"type": "integer",
"minimum": 1,
"maximum": 3600,
-- "description": "How far a signed timestamp may drift before the Node refuses it; 300 is the default."
++ "description": "How far a signed timestamp may drift before the Link refuses it; 300 is the default."
}
}
},
ddiff --git a/contracts/live/agent-stream-create-request.v1.json b/contracts/live/agent-stream-create-request.v1.json
iindex 98ad0bc6..e821f9f1 100644
---- a/contracts/live/agent-stream-create-request.v1.json
++++ b/contracts/live/agent-stream-create-request.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/live/agent-stream-create-request.v1.json",
"title": "LiveAgentStreamCreateRequest",
-- "description": "live.agent-stream-create-request@1 (the Agent Streams plan, S1): the body of POST /api/streams/agent on OpenVibe.Live (capability live.agent_stream.publish). It answers 201 with the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1): { stream, publish }. The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds live.agent_stream.publish for the audience openvibe.live; Network grants it to exactly those principals. Never a developer-app token, never another person's stream. TITLE is the card's title, TASK says what the agent is doing and where that task lives, VISIBILITY says who may watch, and ALLOW_SITES is the optional list of hosts a browser desk may navigate to (a desk that leaves the list raises the curtain).",
++ "description": "live.agent-stream-create-request@1 (the Agent Streams plan, S1): the body of POST /api/streams/agent on OpenVibe.Live (capability live.agent_stream.publish). It answers 201 with the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1): { stream, publish }. The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds live.agent_stream.publish for the audience openvibe.live; Network grants it to exactly those principals. Never a developer-app token, never another person's stream. TITLE is the card's title, TASK says what the agent is doing and where that task lives, VISIBILITY says who may watch, and ALLOW_SITES is the optional list of hosts a browser desk may navigate to (a desk that leaves the list raises the curtain).",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/network/machine-roles.v1.json b/contracts/network/machine-roles.v1.json
iindex 1976501d..ff60a849 100644
---- a/contracts/network/machine-roles.v1.json
++++ b/contracts/network/machine-roles.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/network/machine-roles.v1.json",
"title": "NetworkMachineRoles",
-- "description": "network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).",
++ "description": "network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Link turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).",
"type": "object",
"required": ["roles"],
"additionalProperties": false,
ddiff --git a/contracts/network/machine.v1.json b/contracts/network/machine.v1.json
iindex 4ecf5bd2..687a0c19 100644
---- a/contracts/network/machine.v1.json
++++ b/contracts/network/machine.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/network/machine.v1.json",
"title": "NetworkMachine",
-- "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
++ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Link install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
"type": "object",
"required": ["id", "kind", "online", "roles", "running"],
"additionalProperties": false,
@@@ -22,10 +22,10 @@
"enum": ["onboard", "bridge", "server"],
"description": "What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs)."
},
-- "os": { "type": "string", "maxLength": 40, "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in." },
++ "os": { "type": "string", "maxLength": 40, "description": "The operating system the Link reports (linux, darwin, windows). Absent until the machine has checked in." },
"online": { "type": "boolean", "description": "Whether the machine is connected to Bot right now." },
"last_seen_at": { "type": "string", "format": "date-time", "description": "When the machine was last seen. Absent before its first check-in." },
-- "node_version": { "type": "string", "maxLength": 40, "description": "The OpenVibe Node version the machine reports. Absent until it has checked in." },
++ "node_version": { "type": "string", "maxLength": 40, "description": "The OpenVibe Link version the machine reports. Absent until it has checked in." },
"roles": {
"type": "array",
"uniqueItems": true,
ddiff --git a/contracts/platform/job-frame.v1.json b/contracts/platform/job-frame.v1.json
iindex 912aab66..2e789e51 100644
---- a/contracts/platform/job-frame.v1.json
++++ b/contracts/platform/job-frame.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/platform/job-frame.v1.json",
"title": "JobFrame",
-- "description": "platform.job-frame@1: one job lifecycle frame on the device control link (plan T14 Run; ADR-034 proposed). The link is OpenVibe.Node's WebSocket to OpenVibe.Bot (Node docs/protocol.md section 2); Fabric may terminate it later. Each frame is one JSON object in one text message, with the link's envelope at the top level: `v` (1), `seq` (per-direction counter from 1 on each connection), `ts` (sender's clock, Unix ms) and `type`. Server to device: `job`, `job_cancel`, `job_exit_ack`. Device to server: `job_started`, `job_stdout`, `job_usage`, `job_exit`. The link's existing `ack`/`nack` frames, keyed by the job id, accept or refuse a `job`; a worker that runs jobs advertises its classes in `status.capabilities`. A running `function` job is metered per wall-clock second into platform.usage-sample@1 readings: see `job_usage` and `job_exit`.",
++ "description": "platform.job-frame@1: one job lifecycle frame on the device control link (plan T14 Run; ADR-034 proposed). The link is OpenVibe Link's WebSocket to OpenVibe.Bot (Link docs/protocol.md section 2); Fabric may terminate it later. Each frame is one JSON object in one text message, with the link's envelope at the top level: `v` (1), `seq` (per-direction counter from 1 on each connection), `ts` (sender's clock, Unix ms) and `type`. Server to device: `job`, `job_cancel`, `job_exit_ack`. Device to server: `job_started`, `job_stdout`, `job_usage`, `job_exit`. The link's existing `ack`/`nack` frames, keyed by the job id, accept or refuse a `job`; a worker that runs jobs advertises its classes in `status.capabilities`. A running `function` job is metered per wall-clock second into platform.usage-sample@1 readings: see `job_usage` and `job_exit`.",
"$comment": "Additive: new frame types and optional fields may extend this v1 contract; a receiver ignores frame types it does not know.",
"oneOf": [
{
ddiff --git a/contracts/platform/job.v1.json b/contracts/platform/job.v1.json
iindex 1cbc8ab6..812bb69a 100644
---- a/contracts/platform/job.v1.json
++++ b/contracts/platform/job.v1.json
@@@ -17,7 +17,7 @@
"id": {
"type": "string",
"pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Node command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`)."
++ "description": "Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Link command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`)."
},
"class": {
"$ref": "runtime-class.v1.json"
@@@ -87,7 +87,7 @@
"openvibe-only"
],
"default": "deny",
-- "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe.Node's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
++ "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe Link's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
},
"inputs": {
"type": "array",
ddiff --git a/contracts/platform/resource-offer.v1.json b/contracts/platform/resource-offer.v1.json
iindex 51dfe3d0..764cc3a9 100644
---- a/contracts/platform/resource-offer.v1.json
++++ b/contracts/platform/resource-offer.v1.json
@@@ -71,7 +71,7 @@
"type": "string",
"pattern": "^[a-z][a-z0-9-]*:[a-z0-9.-]+$"
},
-- "description": "e.g. node:http, worker:browser, worker:ffmpeg, worker:ai-gpu, events:gateway, events:durable, object:r2. Kind harness: task:edit, task:review, task:browse, task:run, task:test and task:plan for the task kinds it takes (its detail's task_capabilities), and harness:mcp, harness:resume, harness:host-access and harness:long-autonomy for its detail's capabilities that are true. The `worker:<class>` names that map to platform.runtime-class@1 values are reserved for OpenVibe.Node's job worker, one per class: worker:function, worker:code, worker:browser, worker:linux, worker:desktop and worker:gpu ($defs.reservedWorkerCapabilities). A node advertises the name of each class it runs (Node's status.capabilities.worker); every other `worker:` name (worker:ffmpeg, worker:ai-gpu) is an ordinary capability name. Beside them, a kind harness names `runtime:<class>` for each platform.runtime-class@1 class it can run (runtime:function, runtime:code, runtime:browser, runtime:linux, runtime:desktop, runtime:gpu); these are ordinary capability names, not reserved, and OpenVibe.Codes already advertises the classes it offers."
++ "description": "e.g. node:http, worker:browser, worker:ffmpeg, worker:ai-gpu, events:gateway, events:durable, object:r2. Kind harness: task:edit, task:review, task:browse, task:run, task:test and task:plan for the task kinds it takes (its detail's task_capabilities), and harness:mcp, harness:resume, harness:host-access and harness:long-autonomy for its detail's capabilities that are true. The `worker:<class>` names that map to platform.runtime-class@1 values are reserved for OpenVibe Link's job worker, one per class: worker:function, worker:code, worker:browser, worker:linux, worker:desktop and worker:gpu ($defs.reservedWorkerCapabilities). A node advertises the name of each class it runs (Node's status.capabilities.worker); every other `worker:` name (worker:ffmpeg, worker:ai-gpu) is an ordinary capability name. Beside them, a kind harness names `runtime:<class>` for each platform.runtime-class@1 class it can run (runtime:function, runtime:code, runtime:browser, runtime:linux, runtime:desktop, runtime:gpu); these are ordinary capability names, not reserved, and OpenVibe.Codes already advertises the classes it offers."
},
"capacity": {
"type": "object",
@@@ -409,7 +409,7 @@
],
"$defs": {
"reservedWorkerCapabilities": {
-- "description": "The reserved Fabric capability names of OpenVibe.Node's job worker, one per platform.runtime-class@1 value: `worker:` + the class. These six names are reserved in this contract — the `capabilities` pattern alone accepted any `worker:` name — and a node advertises only the classes it actually runs, in class order.",
++ "description": "The reserved Fabric capability names of OpenVibe Link's job worker, one per platform.runtime-class@1 value: `worker:` + the class. These six names are reserved in this contract — the `capabilities` pattern alone accepted any `worker:` name — and a node advertises only the classes it actually runs, in class order.",
"type": "string",
"enum": [
"worker:function",
ddiff --git a/contracts/platform/runtime-class.v1.json b/contracts/platform/runtime-class.v1.json
iindex 78a280cb..620f1e61 100644
---- a/contracts/platform/runtime-class.v1.json
++++ b/contracts/platform/runtime-class.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/platform/runtime-class.v1.json",
"title": "RuntimeClass",
-- "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe.Node's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
++ "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe Link's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
"$comment": "Additive: a class may be added to v1; none is removed, renamed or reordered.",
"type": "string",
"oneOf": [
ddiff --git a/contracts/platform/task.v1.json b/contracts/platform/task.v1.json
iindex 08c28142..fb87e3e7 100644
---- a/contracts/platform/task.v1.json
++++ b/contracts/platform/task.v1.json
@@@ -3,7 +3,7 @@
"$id": "https://openvibe.network/contracts/platform/task.v1.json",
"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 (actor.task-webhook@1, signed with `webhook_secret`, which only the creating answer carries). Owned by `network` like every other platform.* contract and implemented by OpenVibe.Actor (manifests/services/actor.json). This v1 contract may grow optional fields. A-1 adds AGENT_ID (the agent contact the task runs as; absent means the person's Vibe), LEVEL (the thread's own approval level), EFFECTIVE_LEVEL (max of the agent level and the person's own inherited line) and AGENT_LEVEL (the agent contact's own level), so a reader never has to guess why a step did or did not ask.",
-- "$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.",
++ "$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 Link WebSocket.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/run/job-create-request.v1.json b/contracts/run/job-create-request.v1.json
iindex 3cf10ec6..30f524ed 100644
---- a/contracts/run/job-create-request.v1.json
++++ b/contracts/run/job-create-request.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/run/job-create-request.v1.json",
"title": "RunJobCreateRequest",
-- "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).",
++ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -101,7 +101,7 @@
"type": "string",
"enum": ["none", "public", "openvibe-only"],
"default": "none",
-- "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
++ "description": "OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
},
"idempotency_key": {
"type": "string",
ddiff --git a/contracts/run/session.v1.json b/contracts/run/session.v1.json
iindex 1dbd7e6b..2b4196cd 100644
---- a/contracts/run/session.v1.json
++++ b/contracts/run/session.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/run/session.v1.json",
"title": "RunSession",
-- "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
++ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe Link runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/sandbox-provider/job-create-request.v1.json b/contracts/sandbox-provider/job-create-request.v1.json
iindex 34ea8bb1..d5a3a4ac 100644
---- a/contracts/sandbox-provider/job-create-request.v1.json
++++ b/contracts/sandbox-provider/job-create-request.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/sandbox-provider/job-create-request.v1.json",
"title": "SandboxProviderJobCreateRequest",
-- "description": "sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is code or linux, the platform.runtime-class@1 classes a sandbox hosts. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is sandbox-provider.job-create-result@1.",
++ "description": "sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is code or linux, the platform.runtime-class@1 classes a sandbox hosts. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is sandbox-provider.job-create-result@1.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/contracts/website/share-connect-request.v1.json b/contracts/website/share-connect-request.v1.json
iindex a3ccd408..e0b352ed 100644
---- a/contracts/website/share-connect-request.v1.json
++++ b/contracts/website/share-connect-request.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/website/share-connect-request.v1.json",
"title": "WebsiteShareConnectRequest",
-- "description": "website.share-connect-request@1: what a Node presents to open the tunnel for one share (POST /api/v1/shares/:id/tunnel, capability website.share.connect). The Node proves its Network node principal with a client-credentials token whose audience is openvibe.website, and this short-lived, single-use tunnel_token minted by Connect binds the connection to one share. The edge checks both; neither alone opens the route. The token is presented once and never stored, logged or returned.",
++ "description": "website.share-connect-request@1: what a Link presents to open the tunnel for one share (POST /api/v1/shares/:id/tunnel, capability website.share.connect). The Link proves its Network node principal with a client-credentials token whose audience is openvibe.website, and this short-lived, single-use tunnel_token minted by Connect binds the connection to one share. The edge checks both; neither alone opens the route. The token is presented once and never stored, logged or returned.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -32,17 +32,17 @@
"http2-ws"
],
"default": "quic",
-- "description": "The data-plane transport the Node is trying. quic is ALPN ovconnect/1; http2-ws is the 443 fallback when UDP is blocked."
++ "description": "The data-plane transport the Link is trying. quic is ALPN ovconnect/1; http2-ws is the 443 fallback when UDP is blocked."
},
"node_version": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$",
-- "description": "The Node release, for posture and diagnostics; never used for authorization."
++ "description": "The Link release, for posture and diagnostics; never used for authorization."
},
"region": {
"type": "string",
"maxLength": 40,
-- "description": "The edge region the Node is dialing; the edge refuses a region it does not serve."
++ "description": "The edge region the Link is dialing; the edge refuses a region it does not serve."
}
}
}
ddiff --git a/contracts/website/share-create-request.v1.json b/contracts/website/share-create-request.v1.json
iindex f4c1f36b..fd2190e0 100644
---- a/contracts/website/share-create-request.v1.json
++++ b/contracts/website/share-create-request.v1.json
@@@ -29,7 +29,7 @@
"type": "string",
"minLength": 3,
"maxLength": 300,
-- "description": "The local port the Node exposes, e.g. 127.0.0.1:3000; never an arbitrary internet host."
++ "description": "The local port the Link exposes, e.g. 127.0.0.1:3000; never an arbitrary internet host."
},
"policy": {
"allOf": [
ddiff --git a/contracts/website/share.v1.json b/contracts/website/share.v1.json
iindex 3612a2ce..a44aafd5 100644
---- a/contracts/website/share.v1.json
++++ b/contracts/website/share.v1.json
@@@ -71,7 +71,7 @@
"type": "string",
"minLength": 3,
"maxLength": 300,
-- "description": "The local target the Node may forward to, e.g. 127.0.0.1:3000, tcp://127.0.0.1:25565 or udp://127.0.0.1:19132. Loopback by default; a LAN target is allowed only when the owner enabled it locally."
++ "description": "The local target the Link may forward to, e.g. 127.0.0.1:3000, tcp://127.0.0.1:25565 or udp://127.0.0.1:19132. Loopback by default; a LAN target is allowed only when the owner enabled it locally."
},
"status": {
"enum": [
ddiff --git a/docs/adr/ADR-036-run-execution-authority.md b/docs/adr/ADR-036-run-execution-authority.md
iindex f340800d..6e3484ce 100644
---- a/docs/adr/ADR-036-run-execution-authority.md
++++ b/docs/adr/ADR-036-run-execution-authority.md
@@@ -32,9 +32,9 @@ isolation belongs to Stage C) and ADR-046 (offers, requirements, placement, sign
`platform.runtime-offer.v1.json` is the execution-capacity offer (its `kind` is `container`/`process`/`wasm`/`vm`/`function`,
with `limits`, `price`, `availability` and `constraints`); `platform.resource-offer.v1.json` carries the
capabilities, trust, latency and health the placer filters and ranks on.
--- **The first executor exists.** OpenVibe.Node's job worker runs `platform.job@1` jobs and emits per-second
++- **The first executor exists.** OpenVibe Link's job worker runs `platform.job@1` jobs and emits per-second
`job_usage`, folded into `platform.usage-sample@1` readings keyed `run:<job id>:<n>` (`test/platform-run.test.js`;
-- OpenVibe.Node `docs/protocol.md` and `docs/worker.md`). The Node advertises `worker:function` (and `worker:code`)
++ OpenVibe Link `docs/protocol.md` and `docs/worker.md`). The Link advertises `worker:function` (and `worker:code`)
## Decision
@@@ -63,7 +63,7 @@ isolation belongs to Stage C) and ADR-046 (offers, requirements, placement, sign
### 3. Provider adapters are Fabric offers, not code paths in Run
--The adapters the plan names (T14, lines 567–569) — OpenVibe official worker, OpenVibe Node worker, self-hosted Run
++The adapters the plan names (T14, lines 567–569) — OpenVibe official worker, OpenVibe Link worker, self-hosted Run
worker, managed browser providers, external sandbox providers, external GPU providers — are **offers**, not branches
inside Run. Each reports:
@@@ -77,8 +77,8 @@ Run states `platform.workload-requirements@1` and reads the `platform.placement-
Run writes no placer of its own (ADR-046: "A service states requirements and reads results; it does not rank
offers."). The plan's §2.1.11 cross-reference is the Fabric section as written in ADR-046/T1, not a separate scheme.
--**Node descriptor → offers.** When Run turns an OpenVibe.Node into Fabric offers it applies the mapping the Node
--protocol already fixes (OpenVibe.Node `docs/protocol.md:310–321`):
++**Node descriptor → offers.** When Run turns an OpenVibe Link into Fabric offers it applies the mapping the Node
++protocol already fixes (OpenVibe Link `docs/protocol.md:310–321`):
| Node descriptor field | Fabric offer | Example |
| --- | --- | --- |
@@@ -105,7 +105,7 @@ Every job, on every adapter, is held to the same guarantees (plan T14, lines 572
- **TTL, idle suspend, cancel, snapshot/resume** — a job has a lifetime from creation (wait plus run); an idle
environment may be suspended and later resumed; `job_cancel` kills the process group and a job cancelled before it
starts never starts.
--- **Streaming stdout** — stdout is streamed as chunks (`job_stdout` on the Node link; `run.job-stream-event@1` and a
++- **Streaming stdout** — stdout is streamed as chunks (`job_stdout` on the Link link; `run.job-stream-event@1` and a
stream ticket over the API), best effort, bounded.
- **Artifacts via Media** — inputs are Media objects pinned by sha256; outputs are Media objects. Run never fetches a
caller-chosen URL.
@@@ -121,7 +121,7 @@ Every job, on every adapter, is held to the same guarantees (plan T14, lines 572
- One execution authority and one placement vocabulary. A new runtime class or provider is a new offer (and, for a
class, a reserved capability name), not new code in Run.
- Host and Run can share a cell's machines without sharing authority; deleting either leaves the other's rows alone.
--- The Node job worker is the first execution worker Run places; its per-second frames are already the metering
++- The Link job worker is the first execution worker Run places; its per-second frames are already the metering
contract Billing consumes.
- The `run.*` contracts are the authority's API; they stay PLANNED until the owner unblocks the service.
@@@ -129,7 +129,7 @@ Every job, on every adapter, is held to the same guarantees (plan T14, lines 572
1. **Isolation mechanism.** The plan requires more than a container for hostile arbitrary code but names no
mechanism. Micro-VM (Firecracker/Kata), a userspace kernel (gVisor) and namespace/seccomp sandboxes are all open;
-- the Node worker's current `function` isolation is namespaces plus a seccomp allowlist.
++ the Link worker's current `function` isolation is namespaces plus a seccomp allowlist.
2. **Snapshot/resume backend.** Whether a resumed environment's state lives in Media, a Node-local disk, or a
provider's own snapshot is open.
3. **Idle-suspend policy.** Who decides to suspend — Run, the node's local policy, or a placement objective — is
@@@ -143,7 +143,7 @@ Every job, on every adapter, is held to the same guarantees (plan T14, lines 572
- No OpenVibe.Run repository exists on `origin/main`; `run` is a placeholder service and `openvibe.run` has no
repository. Every `run.*` contract is PLANNED and nothing serves them.
--- Only `function` (and, per the worker's own docs, `code`) exists as a worker class, in OpenVibe.Node; `browser`,
++- Only `function` (and, per the worker's own docs, `code`) exists as a worker class, in OpenVibe Link; `browser`,
`linux`, `desktop` and `gpu` are reserved names with no worker.
- Cells are a single host (`cell = wnam-1`) today; O24's second physical cell, the WireGuard mesh and geo placement
are not built (plan T14, lines 580–581).
ddiff --git a/docs/adr/ADR-044-actor-runtime.md b/docs/adr/ADR-044-actor-runtime.md
iindex bb8caeae..83bfab14 100644
---- a/docs/adr/ADR-044-actor-runtime.md
++++ b/docs/adr/ADR-044-actor-runtime.md
@@@ -33,7 +33,7 @@ levels and the local kill switch). For the owner's review.
`platform.telemetry-sample@1` and `platform.usage-sample@1` are the cost and signal vocabulary (ADR-046 §§2–3);
`platform.runtime-class@1`, `platform.job@1` and `platform.job-frame@1` are Run's execution shape (T14); the
`agent` subject is already in `identity/subject-ref@1`.
--- **Actor's backend classes** are fixed by the plan: "agent platform, OpenVibe runtime, **Codes**, **Node**, **Run**.
++- **Actor's backend classes** are fixed by the plan: "agent platform, OpenVibe runtime, **Codes**, **Link**, **Run**.
Coding work is exposed to Actor **through Codes** — one coding-harness router in the estate, not two."
## Decision
@@@ -49,7 +49,7 @@ the `progress` descriptor, the `cancel` record and the registered `webhooks`.
- **`POST /v1/tasks` takes the task's creation subset** — `task`, `mode`, `budget`, and optionally `webhooks`. The
router mints `id`, sets `project_id` and `requester` from the token, and answers `state: queued`.
- **It is the resource, not a device frame.** Run's `platform.job@1` is handed to a worker inside a
-- `platform.job-frame@1` link message because the link is OpenVibe.Node's WebSocket. Actor's link is HTTP and
++ `platform.job-frame@1` link message because the link is OpenVibe Link's WebSocket. Actor's link is HTTP and
server-sent events, so the frame vocabulary is not reused as a message type; `progress` and `explanation` carry
what the frames carry for a job. This is the shape, and whether Actor later needs an explicit stream-event contract
of its own (as `run.job-stream-event@1` is Run's) is left to the router's implementation.
@@@ -111,7 +111,7 @@ ADR-046 §4's five classes apply unchanged: `first-party`, `user-owned`, `partne
`user-owned` eligible only when the task names it. `private` mode narrows the eligible set to backends that keep the
data on OpenVibe — `first-party`, and a `user-owned` node only when the deployment allows it — which the plan
describes as "only agents that keep your data on OpenVibe: its own runtime and open models on its hardware". Local
--execution on a person's own machine goes through OpenVibe.Node under ADR-043's control levels (Observe / Ask /
++execution on a person's own machine goes through OpenVibe Link under ADR-043's control levels (Observe / Ask /
Trusted / Full control), a local indicator and a kill switch the owner holds; Node's local policy always overrides a
cloud command (plan T14).
@@@ -121,7 +121,7 @@ cloud command (plan T14).
(`output`, `state`, `end`, the same three as `run.job-stream-event@1`). The stream is best-effort, as a job's stdout
is; the task record is the source of truth.
- **Cancel** is `cancel`: `POST /v1/tasks/{id}/cancel` records who asked and when, the running backend is stopped as
-- its own contract allows (a Run job through `run.job.cancel`, a Node task through its link, an external platform
++ its own contract allows (a Run job through `run.job.cancel`, a Link task through its link, an external platform
through its API), and the task ends `cancelled` with `finished_at` set. Cancel is not a promise that money already
spent is returned.
- **Webhooks** are registered at creation: an HTTPS URL, the state changes to deliver, and an optional `secret_ref`
ddiff --git a/docs/adr/ADR-045-node-universal-runtime.md b/docs/adr/ADR-045-node-universal-runtime.md
iindex 9e3cec68..654aa371 100644
---- a/docs/adr/ADR-045-node-universal-runtime.md
++++ b/docs/adr/ADR-045-node-universal-runtime.md
@@@ -1,4 +1,4 @@
--# ADR-045: The Node is OpenVibe's universal runtime
++# ADR-045: The Link is OpenVibe's universal runtime
**Status:** Proposed 2026-10-05 (plan §8: *"ADR-045 (Node; **revise: Node is the universal runtime, not a
Bot/Actor helper**)"*; plan T14, lines 575–579). For the owner's review. Builds on ADR-043 (Bot — devices, pairing,
@@@ -12,7 +12,7 @@ link.
- **The plan revises ADR-045.** Plan §8 (line 986) calls ADR-045 written but to be revised: *"Node is the universal
runtime, not a Bot/Actor helper"*. `origin/main` has no ADR-045 file in any repository, so the revision is unstated
in the contract repository.
--- **What the plan says the Node is.** Plan T14, lines 575–579: *"**Node runtime** (ADR-045 revised: Node is the
++- **What the plan says the Link is.** Plan T14, lines 575–579: *"**Node runtime** (ADR-045 revised: Node is the
universal runtime, not a Bot/Actor helper). **Core built on `origin/main`:** one signed binary, pairing, control
link, WHIP publisher, plugin runtime, the Adeept and Cozmo plugins, installer and CI. Still to add for its other
roles: computer control (terminal, files, screen, input, browser) under control levels, local kill switch and
@@@ -23,7 +23,7 @@ link.
capabilities (T14); Bot binds robot resources to Node capabilities (T15); Actor binds computer control (T17); Run
binds execution workers (T14); Media binds cache/storage (T4). Bot keeps robots, robot profiles, operators, command
leases and physical safety — not universal device identity."*
--- **What the code already does** (OpenVibe.Node, `origin/main`): one Go static binary per platform — pairing
++- **What the code already does** (OpenVibe Link, `origin/main`): one Go static binary per platform — pairing
(device credential or a Network node principal), one outbound WebSocket to `wss://openvibe.bot/device`, heartbeats
and a deadman, owner limits clamped before any driver sees a value, a persisted e-stop and a local kill switch, a
plugin supervisor, WHIP video publishing (pion), and the system service (systemd, launchd, Windows). Plugins are
@@@ -33,7 +33,7 @@ link.
reserved `worker:<class>` names it runs; Run maps that descriptor to Fabric offers
(`docs/protocol.md:303–321`).
- **The capability names are already agreed.** ADR-046 §5 reserves `worker:function`, `worker:code`,
-- `worker:browser`, `worker:linux`, `worker:desktop` and `worker:gpu` for the Node job worker, one per
++ `worker:browser`, `worker:linux`, `worker:desktop` and `worker:gpu` for the Link job worker, one per
`platform.runtime-class@1` class, in `platform.resource-offer@1.$defs.reservedWorkerCapabilities`.
- **The control levels are plan wording.** Plan lines 745–747: *"each Node has a control level set on the device
(Observe / Ask / Trusted / Full control), a local indicator, a local kill switch, optional container/VM
@@@ -47,7 +47,7 @@ link.
### 1. One Node, the universal runtime
--The Node is OpenVibe's universal runtime — one agent for a person's computers, servers, Raspberry Pis, phones and
++The Link is OpenVibe's universal runtime — one agent for a person's computers, servers, Raspberry Pis, phones and
robots. It is **not** a Bot helper or an Actor helper: Bot, Actor, Run and Media are all clients of the same Node,
and each binds the capabilities it needs. ADR-043 remains the robot/device half (robots, robot profiles, operators,
command leases and physical safety); this ADR is the runtime half. The one split this record draws inside ADR-043 is
@@@ -58,37 +58,37 @@ stands.
### 2. One release binary, one pairing, one control link, one local policy
- The core is one checksummed release binary per platform with one pairing path and one outbound control link.
-- A role is **a capability the Node advertises plus local configuration**, never a second agent or a second install.
--- The Node is a Network principal (its node/device principal and pairing credential are Network's), and its resource
++ A role is **a capability the Link advertises plus local configuration**, never a second agent or a second install.
++- The Link is a Network principal (its node/device principal and pairing credential are Network's), and its resource
is named by OVRN (ADR-048). Network owns identity and pairing; Bot keeps the robot binding and the control link
(amending ADR-043's credential paragraph); Node presents capabilities.
### 3. Execution worker (Run's need)
--- The Node advertises one `worker:<class>` capability per `platform.runtime-class@1` class it runs, per ADR-046 §5,
++- The Link advertises one `worker:<class>` capability per `platform.runtime-class@1` class it runs, per ADR-046 §5,
and refuses every class it does not advertise. `function` (the first class implemented) and `code` are the classes
the worker declares; `browser`, `linux`, `desktop` and `gpu` are reserved names.
--- Run places jobs on the Node by converting its descriptor into `platform.resource-offer@1` /
-- `platform.runtime-offer@1` (OpenVibe.Node `docs/protocol.md:310–321`) and calling `openvibe-sdk/placement` `plan()`.
-- The Node runs only locally declared artifacts, at an exact version, for the class they declare; nothing is fetched
++- Run places jobs on the Link by converting its descriptor into `platform.resource-offer@1` /
++ `platform.runtime-offer@1` (OpenVibe Link `docs/protocol.md:310–321`) and calling `openvibe-sdk/placement` `plan()`.
++ The Link runs only locally declared artifacts, at an exact version, for the class they declare; nothing is fetched
and no path comes from the server.
- Metering, TTL, cancel, limits, egress and stdout streaming are the job worker's own behaviour (`docs/worker.md`),
and are the Run guarantees ADR-036 states.
### 4. Computer control (Actor's need, T17)
--- The Node exposes terminal, files, screen, input and browser control to an authorised client under **control levels
++- The Link exposes terminal, files, screen, input and browser control to an authorised client under **control levels
set on the device** (the plan names Observe / Ask / Trusted / Full control), with a local indicator, a **local kill
switch**, optional container/VM confinement, replayable transcripts and file changes, and local secrets used
through handles.
- This control is off by default and approved per app (plan line 713); Actor reaches a person's own devices only
-- through the Node under a delegated grant.
++ through the Link under a delegated grant.
### 5. Cache/storage/delivery (Media's need, T4)
--- The Node is a storage/delivery **candidate**: it may offer `media.cache`, `storage.hot` and prepaid disk and
++- The Link is a storage/delivery **candidate**: it may offer `media.cache`, `storage.hot` and prepaid disk and
bandwidth as Fabric offers, so placement can use unused capacity on already-paid nodes before buying CDN traffic.
--- This makes the Node an offer holder, not a second byte authority: Media keeps the object catalog, the location
++- This makes the Link an offer holder, not a second byte authority: Media keeps the object catalog, the location
policy and the only deletion path (ADR-031/ADR-046).
### 6. Local policy always overrides cloud commands
@@@ -98,7 +98,7 @@ stands.
policy.
- The egress rule is the model the other roles follow: a job asking for `public` or `openvibe-only` is placed only on
a node whose `worker.egress` allows it, and a worker whose host policy is stricter runs the stricter one
-- (`run.job-create-request@1`; OpenVibe.Node `docs/protocol.md`).
++ (`run.job-create-request@1`; OpenVibe Link `docs/protocol.md`).
## Alternatives considered
@@@ -109,28 +109,28 @@ stands.
roles.
- **Cloud-authoritative control levels:** rejected. The device enforces local policy and the kill switch; generalising
ADR-043's device-authoritative safety is the point of the runtime half.
--- **The Node as a second resource authority:** rejected. Products own their resource rows; the Node presents
++- **The Link as a second resource authority:** rejected. Products own their resource rows; the Link presents
capabilities and local policy (plan lines 321–324, ADR-048).
## Consequences
- One device surface for every product; a new role is a capability plus local configuration, not a new agent.
- Local policy is the security boundary: no cloud command can enable a role, widen egress or clear the stop latch.
--- The Node class work (`worker:<class>`, ADR-046 §5) and the future Actor and storage/delivery capabilities follow
++- The Link class work (`worker:<class>`, ADR-046 §5) and the future Actor and storage/delivery capabilities follow
these semantics, so they need no separate device model.
--- Bot keeps robots, robot profiles, operators, command leases and physical safety (ADR-043); the Node keeps execution,
++- Bot keeps robots, robot profiles, operators, command leases and physical safety (ADR-043); the Link keeps execution,
computer control and cache/storage/delivery.
## Open questions for the owner
1. **Control levels.** Whether the set is exactly Observe / Ask / Trusted / Full control, how each maps to a local
indicator and to recording, and the capability names they use, is plan wording; the contract is open.
--2. **One principal or many.** Whether a Node serving several products/projects is one Network principal with scoped
++2. **One principal or many.** Whether a Link serving several products/projects is one Network principal with scoped
grants or one principal per project is Network's decision and is open (ADR-048 OVRN).
--3. **Cache/storage contracts.** Which offer contracts carry the Node's cache/storage role — the existing
++3. **Cache/storage contracts.** Which offer contracts carry the Link's cache/storage role — the existing
`platform.storage-offer@1` / `platform.delivery-offer@1` or a Node-specific one — and whether placement policy
stays Media's, is open.
--4. **Other users' workloads.** Whether a Node may run other users' jobs or serve other users' computer control, or
++4. **Other users' workloads.** Whether a Link may run other users' jobs or serve other users' computer control, or
only its owner's, mirrors ADR-046's `user-owned` question and is open.
## What this ADR does not claim
@@@ -142,5 +142,5 @@ stands.
the four levels.
- No Actor, Run or Media consumer binds these Node capabilities on `origin/main`; Run is a placeholder service
(ADR-036).
--- The Node worker is reached over the Bot control link today; it is not yet an offer producer, because the Run that
++- The Link worker is reached over the Bot control link today; it is not yet an offer producer, because the Run that
would convert its descriptor is not built.
ddiff --git a/docs/adr/ADR-046-universal-adaptive-fabric.md b/docs/adr/ADR-046-universal-adaptive-fabric.md
iindex edf6052e..590a31f4 100644
---- a/docs/adr/ADR-046-universal-adaptive-fabric.md
++++ b/docs/adr/ADR-046-universal-adaptive-fabric.md
@@@ -63,7 +63,7 @@ to run the work:
| Class | Meaning |
| --- | --- |
| `first-party` | Hosts and accounts the OpenVibe network operates itself (its own servers and provider accounts). |
--| `user-owned` | A person's own node or machine (an OpenVibe.Node they run), offered for their own workloads. |
++| `user-owned` | A person's own node or machine (an OpenVibe Link they run), offered for their own workloads. |
| `partner` | An organisation under a written agreement with the network, bound by its data and uptime terms. |
| `community` | Capacity volunteered by community members, without a contract; best effort, no private data. |
| `external` | A third-party provider used through its public API under its own terms (a cloud or AI vendor). |
@@@ -73,12 +73,12 @@ to run the work:
for a workload whose requirements list `"user-owned"` explicitly. Adding the class to the contracts therefore widens
no existing caller's eligible set, and the SDK must not add it to its default list.
--### 5. Worker capability names a Node advertises
++### 5. Worker capability names a Link advertises
--An OpenVibe.Node that runs jobs advertises one `worker:<class>` capability in its `resource-offer.capabilities` for
++An OpenVibe Link that runs jobs advertises one `worker:<class>` capability in its `resource-offer.capabilities` for
each `platform.runtime-class@1` class it runs: `worker:function` (a declared function artifact) and `worker:code`
(submitted code in a sandbox) today, and `worker:browser`, `worker:linux`, `worker:desktop` and `worker:gpu` as Node
--gains them. These six names are reserved for the Node job worker (`$defs.reservedWorkerCapabilities` in
++gains them. These six names are reserved for the Link job worker (`$defs.reservedWorkerCapabilities` in
`platform.resource-offer@1`); every other `worker:` name (`worker:ffmpeg`, `worker:ai-gpu`) is an ordinary capability.
A requirement asks for a class by listing the same name in its `capabilities`.
@@@ -104,7 +104,7 @@ placement result, telemetry and usage samples for every run. Other services adop
- One vocabulary and one placer for every placed workload; a new kind of capacity is a new offer, not new code.
- Routing survives the control plane: plans are signed, epoch-ordered and verified where they are used.
- A person can run their own work on their own machine without that machine joining anyone else's default pool.
--- The SDK placer, the T2 resource registry and OpenVibe.Node must agree on the trust rule; this ADR is that rule.
++- The SDK placer, the T2 resource registry and OpenVibe Link must agree on the trust rule; this ADR is that rule.
## Open questions for the owner
ddiff --git a/docs/adr/ADR-057-website-connect.md b/docs/adr/ADR-057-website-connect.md
iindex 86eff78c..1ae8caba 100644
---- a/docs/adr/ADR-057-website-connect.md
++++ b/docs/adr/ADR-057-website-connect.md
@@@ -2,7 +2,7 @@
**Status:** Accepted 2026-11-02 with the OQ-13 review below, which decides what v0 ships (proposed 2026-10-29, CON-1; plan 2026-10-28 §5.9b; research
`research-2026-10-29/website-connect.md` §3.3, §4.3, §4.4 and §4.12). For the owner's review. Builds on ADR-053
--(the Website/Host boundary), ADR-045 (the Node is the universal runtime), ADR-042 (the events fabric), ADR-048 (one
++(the Website/Host boundary), ADR-045 (the Link is the universal runtime), ADR-042 (the events fabric), ADR-048 (one
authority per resource) and ADR-056 (project secrets: write-only, used by name).
## Context and evidence
@@@ -18,7 +18,7 @@ authority per resource) and ADR-056 (project secrets: write-only, used by name).
verdict is one path per pillar: build a focused relay on `quic-go` for Share, adopt headscale + `tsnet` with a
tenant boundary for Connect, build the policy/abuse layer on top of Cloudflare and Network identity, and extend
Events for hooks. §3.3 and §3.4 name what to build and what not to reuse.
--- **The data plane is Go; the control plane is Node.** The Node agent is Go (ADR-045). QUIC, HTTP/3, datagrams,
++- **The data plane is Go; the control plane is Node.** The Link agent is Go (ADR-045). QUIC, HTTP/3, datagrams,
half-close and backpressure are native there and awkward in Node.js. The control API can stay Node/Express, like
every other network service. The edge is a separate, exposed, bandwidth-heavy tier and must not share a host with
the control plane (research §4.1).
@@@ -45,7 +45,7 @@ authority per resource) and ADR-056 (project secrets: write-only, used by name).
## Decision
--1. **Share is a focused Go edge relay, `openvibe-edge`, plus a Node `connect` core in OpenVibe.Node.** The relay is
++1. **Share is a focused Go edge relay, `openvibe-edge`, plus a Link `connect` core in OpenVibe Link.** The relay is
built on `quic-go` (MIT): one QUIC connection per Node, ALPN `ovconnect/1`, TLS 1.3, streams for HTTP/WebSocket/TCP,
datagrams for UDP, and an HTTP/2 + WebSocket fallback on 443 for networks that block UDP. It routes by SNI/Host to a
`share_id` from Connect's name registry, never from a user-controlled header, checks the signed policy, and then
@@@ -67,7 +67,7 @@ authority per resource) and ADR-056 (project secrets: write-only, used by name).
custom control server on every platform; for v1 the phone reaches services through Share in a browser, and Connect
is for PCs, NAS and servers. DERP is self-hosted (`derper`) so relay traffic stays on OpenVibe infrastructure;
Live's TURN is for WebRTC and is not a WireGuard relay.
--4. **Identity and the tunnel handshake use two credentials, both checked.** The Node presents a Network
++4. **Identity and the tunnel handshake use two credentials, both checked.** The Link presents a Network
client-credentials token whose subject is its node principal (`node:nod_…`) and whose audience is
`openvibe.website`; the request body is `website.share-connect-request@1` with the `share_id`, the same `node_id`
and a short-lived, single-use, share-scoped `tunnel_token` minted by Connect. The edge validates the token against
@@@ -77,7 +77,7 @@ authority per resource) and ADR-056 (project secrets: write-only, used by name).
never stored, logged, audited or published in an event. OIDC at the share host uses Authorization Code + PKCE
against Network; the session is a host-only `__Host-ov_session` (Secure, Path `=`, no `Domain`) carrying
`share_id`, `sub` and `policy_version`.
-- `POST /api/v1/shares` returns only `website.share@1`; its response has no tunnel token. The Node obtains the
++ `POST /api/v1/shares` returns only `website.share@1`; its response has no tunnel token. The Link obtains the
short-lived, single-use token through a separate authenticated credential exchange before connecting. CON-5
defines that exchange's endpoint, request and response schemas, authorization, and delivery to the Node.
5. **Revocation is immediate and has one owner per trigger.** `DELETE /api/v1/shares/:id` writes
@@@ -88,7 +88,7 @@ authority per resource) and ADR-056 (project secrets: write-only, used by name).
6. **Policy is enforced at the edge, authored and audited by Connect, and fronted by Cloudflare.** Cloudflare
terminates TLS for HTTP/WS and applies DDoS/WAF/bot rules; the edge performs share lookup, auth, the
`website.policy@1` decision (public, signin, named, password, one_time, expiry, IP/CIDR, country, rate limits, bot
-- check, WAF preset, posture), request size/timeouts and audit; the Node restricts the local target to the owner's
++ check, WAF preset, posture), request size/timeouts and audit; the Link restricts the local target to the owner's
chosen loopback/LAN port and never proxies arbitrary hosts. Connect owns ownership, policy CRUD, abuse review,
quotas and revocation. Passwords are Argon2id hashes stored beside the policy and never in the contract; audit
entries never carry bodies, headers, cookies, tokens, query strings or passwords. Public shares require a
@@@ -101,7 +101,7 @@ authority per resource) and ADR-056 (project secrets: write-only, used by name).
the hook's own share host (`https://<share>/__ov/hook/<id>`), never an arbitrary URL, and mints a `whsec_…` secret
shown once. `hooks.openvibe.website/h/<id>` is a thin ingress: size cap, rate limit, optional provider signature
check, raw body stored at rest by reference, and an event published; Events stores the event, signs every attempt
-- with v2, retries for up to seven days while the laptop is offline, dead-letters and replays. The Node verifies the
++ with v2, retries for up to seven days while the laptop is offline, dead-letters and replays. The Link verifies the
signature and 300-second timestamp tolerance, dedupes on `event_id`, acks 2xx and may buffer locally; a 503 asks
Events to retry. Events produces `events.hook.received|delivered|dead`; Connect writes none of them. No smee or
Hookdeck is adopted.
@@@ -129,12 +129,12 @@ authority per resource) and ADR-056 (project secrets: write-only, used by name).
|---|---|---|
| Share record, name registry, policy, tunnel token, inspector metadata, audit | OpenVibe.Connect (Node control API, `ov_connect`) | the edge keeps active session state and a signed policy cache only |
| Public QUIC/HTTP/WS/SSE/TCP/UDP ingress, routing, edge policy check, rate limits | `openvibe-edge` (Go, `quic-go`) | it does not own users, shares, policies, audit or hooks |
--| Node tunnel, local forwarding, `ov share`, `tsnet` | OpenVibe.Node (`internal/connect`) | it does not decide policy or open arbitrary hosts |
++| Node tunnel, local forwarding, `ov share`, `tsnet` | OpenVibe Link (`internal/connect`) | it does not decide policy or open arbitrary hosts |
| Account, node principal, OIDC, grants, revocation event | OpenVibe.Network | Connect never mints identity |
| DNS proposal, TXT/CNAME, certificate path | OpenVibe.Domains | Connect never writes DNS or holds a provider credential |
| Durable event store, v2 signature, retries, DLQ, replay | OpenVibe.Events | Connect creates the subscription; the edge does not queue |
| Usage, abuse analytics, billing | OpenVibe.Report / OpenVibe.Billing | Connect emits usage; it does not price or bill |
--| Firewall helper, local posture read | OpenVibe.Node, local approval | no remote "open this port" API |
++| Firewall helper, local posture read | OpenVibe Link, local approval | no remote "open this port" API |
| Thin client for apps | `openvibe-sdk/connect` (MIT) | no tunnel or mesh code in the SDK |
## Rejected reuse paths
@@@ -238,7 +238,7 @@ OpenVibe sign-in) and from what the network runs on today: one host, every name
Connect, and Space's server_name leaves those hosts out. `connect` is reserved in Space.
3. **The computer's credential in v0 is a share-scoped connect key** (`ovc_` + 32 random bytes, stored as a SHA-256
hash, shown once, replaced on demand, which disconnects the old computer). Decision 4's two credentials (a node
-- principal token plus a single-use tunnel token) need the OpenVibe Node plugin (CON-4); the standalone client cannot
++ principal token plus a single-use tunnel token) need the OpenVibe Link plugin (CON-4); the standalone client cannot
hold a node principal. The key opens one share's tunnel and nothing else.
4. **The gate is Connect's, and every cookie is host-only.** `/grant` on connect.openvibe.website signs the visitor
in with OpenVibe, checks the share's policy, and hands the share host a one-time code (60 s, one use, bound to the
@@@ -254,6 +254,6 @@ OpenVibe sign-in) and from what the network runs on today: one host, every name
live), owning the `website.*` and `connect.*` namespaces. The 20 capabilities stay `planned` and owned by
`connect`; v0's API is a person's own (session or Network user token) and serves none of them. The share API's
shapes are Connect's own in v0; `website.share@1` conformance comes with CON-5.
--7. **What waits:** the QUIC edge (CON-3), the Node plugin and node principals (CON-4), custom domains (CON-7), the
++7. **What waits:** the QUIC edge (CON-3), the Link plugin and node principals (CON-4), custom domains (CON-7), the
inspector (CON-8), the abuse gate (CON-9), TCP/UDP shares (CON-11/12), the mesh (CON-13; the headscale tenant proof
stands), hooks (CON-15) and the launch gate for public shares (CON-21).
ddiff --git a/docs/adr/ADR-060-skills-run-sandboxed.md b/docs/adr/ADR-060-skills-run-sandboxed.md
iindex 0a1dd602..03f722a9 100644
---- a/docs/adr/ADR-060-skills-run-sandboxed.md
++++ b/docs/adr/ADR-060-skills-run-sandboxed.md
@@@ -1,7 +1,7 @@
# ADR-060: Skills that run code — never on our server, always in a sandbox
- **Status:** Accepted 2026-11-08 (plan track HO, wave 3: HO8), after Opus's security review.
--- **Owner:** OpenVibe.Actor (the registry and the `skill.run` step) and OpenVibe.Node (the runner).
++- **Owner:** OpenVibe.Actor (the registry and the `skill.run` step) and OpenVibe Link (the runner).
- **Contracts:** `actor.skill@2`, `actor.skill-listing@2`; `skill.run` joins the HO1 approval kinds.
- **Supersedes:** ADR-058 decision 2's "no code" for `runs: "sandbox"`. `actor.skill@1` stays valid and is still
what a prompt-only skill publishes.
@@@ -13,15 +13,15 @@
not only describe it, and both show the danger: OpenClaw's ClawHub needed a scan and a trust envelope because a
skill that steals credentials is what people fear.
- OpenVibe already has the trust spine: HO1 gives every consequential act a kind and an approval, HO2 made a skill a
-- document whose `requires` grants nothing, and Run already executes a registered artifact as a job on a Node with
++ document whose `requires` grants nothing, and Run already executes a registered artifact as a job on a Link with
pinned input bytes and per-job network egress.
- What OpenVibe did not have is a way to run a person's (or an agent's) code without putting it on our servers or
-- trusting the person's laptop. A Node that declares the skill runner, and Run's `function` class, is that place.
++ trusting the person's laptop. A Link that declares the skill runner, and Run's `function` class, is that place.
## Decision
1. **The rule.** A skill's code never runs on an OpenVibe server, never on the person's machine outside a sandbox,
-- and never sees a secret. It runs as a Run job of class `function` on a Node that declared the skill runner, with
++ and never sees a secret. It runs as a Run job of class `function` on a Link that declared the skill runner, with
the network denied unless the skill declared otherwise and the person approved it, and with the exact bytes the
registry scanned.
2. **The contracts.** `actor.skill@2` adds:
@@@ -38,7 +38,7 @@
`actor.skill-listing@2` carries, per code resource, `{ name, media_id, sha256 }`, so the bytes that run are the
bytes that were scanned, and its scan findings may name the `tool` they are about. `actor.skill@1` and
`actor.skill-listing@1` are unchanged.
--3. **The Node runner** (`[email protected]`, a declared function like `[email protected]`):
++3. **The Link runner** (`[email protected]`, a declared function like `[email protected]`):
- off unless the owner turns it on (`worker.functions`, `openvibe node skills on`);
- takes `{ tool, entry, input }` and the code as job `inputs` (Media objects pinned by sha256; a mismatch ends the
job before anything runs);
@@@ -103,5 +103,5 @@ the `skill.run` kind, and nothing ever ran on our servers, so there is no server
`secret`, `token`, `env` or `key`;
- a @2 listing pins each code resource's Media id and sha256, the pinned sha256 is the document's own bytes, and a
sandbox listing without code is invalid.
--Node's own tests (in OpenVibe.Node) prove the runner: a tool cannot read env, spawn, write a file, open a socket
++Node's own tests (in OpenVibe Link) prove the runner: a tool cannot read env, spawn, write a file, open a socket
under `deny`, or exceed its output.
ddiff --git a/docs/adr/README.md b/docs/adr/README.md
iindex 84368250..e0ddd549 100644
---- a/docs/adr/README.md
++++ b/docs/adr/README.md
@@@ -43,14 +43,14 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-042](ADR-042-events-fabric.md) | The events fabric: the delivery-policy class is the one vocabulary and TOPIC/QUEUE/STREAM a derived carrier class; PostgreSQL stays the record, carriers are transports that fail open to `pg-v1`; a delivery lease before any second worker; per-key ordering; the SDK planner places deliveries and explains them; JetStream for STREAM only; opaque cursors replace the global seq after one overlap release; hot/replay/archive tiers; amends ADR-004's NATS rejection | Accepted 2026-09-29 (plan T7) |
| [ADR-043](ADR-043-bot-devices-and-control.md) | Bot — devices, pairing, control and safety: robots, devices and credentials are separate (one hashed, rotatable, revocable device credential); pairing is a one-time code; one outbound WebSocket per device with absolute deadlines, device-side stops and a latched e-stop; robot profiles rather than rows; owner/operators/viewers with a timed public queue; everything audited; OpenVibe.Bot is its own service | Accepted 2026-09-29 (plan track T15, "Bot complete"; owner direction the same evening: openvibe.bot is an open control panel for robots, streaming optional; the first robots are the owner's Adeept 4WD Smart Car for Raspberry Pi and a Cozmo-based robot) |
| [ADR-044](ADR-044-actor-runtime.md) | The Actor runtime: one `platform.task@1` task (`POST /v1/tasks`) with a mode, a per-task and per-day budget, result, cost and explanation (a chain of `platform.placement-result@1`); adapters declare capabilities, limits, a reviewed rate card, a trust class and BYO keys; modes are `openvibe-sdk/placement` objectives; live signals auto-balance with hysteresis and immediate failover; a `verifying` state holds cheap-first cross-family checks and the task's own tests; the five ADR-046 trust classes, `private` narrowing to OpenVibe-only, Node control levels underneath; progress over SSE, cancel and webhooks; owner `network` (no Actor service manifest exists yet) | Proposed 2026-10-05 (plan T17), for the owner's review |
--| [ADR-045](ADR-045-node-universal-runtime.md) | The Node is OpenVibe's universal runtime, not a Bot/Actor helper: one checksummed release binary and one control link serving the execution worker (`worker:<class>`, per ADR-046 §5), computer control under device-set control levels with a local kill switch and recording, and cache/storage/delivery; local policy always overrides cloud commands; ADR-043 stays the robot/device half | Proposed 2026-10-05 (plan T14 and §8 revision; consistent with ADR-046 and ADR-048, amends ADR-043's credential paragraph), for the owner's review |
++| [ADR-045](ADR-045-node-universal-runtime.md) | The Link is OpenVibe's universal runtime, not a Bot/Actor helper: one checksummed release binary and one control link serving the execution worker (`worker:<class>`, per ADR-046 §5), computer control under device-set control levels with a local kill switch and recording, and cache/storage/delivery; local policy always overrides cloud commands; ADR-043 stays the robot/device half | Proposed 2026-10-05 (plan T14 and §8 revision; consistent with ADR-046 and ADR-048, amends ADR-043's credential paragraph), for the owner's review |
| [ADR-046](ADR-046-universal-adaptive-fabric.md) | The universal adaptive fabric: offers, requirements, placement results and signed plans through one placer (`openvibe-sdk/placement`); health, cost, telemetry and usage samples; five trust classes, with `user-owned` eligible only when a workload names it; `worker:<class>` Node capabilities; the data plane keeps running on the last valid signed plan; T6 AI the reference consumer | Proposed 2026-10-03 (plan T1 step 3), for the owner's review |
| [ADR-048](ADR-048-services-control-plane.md) | The Services control plane: Services aggregates authorities and owns no other service's rows; every console action is a `common.resource-control-request@1` to the owning authority, answered with `common.resource-control-result@1` (idempotency keys, `confirmation_id` for sensitive actions); OVRN `ovrn:<service>:<project_id>:<type>/<id>` (`common.resource-name@1`) is the one cross-service resource name | Accepted 2026-10-04 (plan T13) |
| [ADR-053](ADR-053-website-host-boundary.md) | The Website/Host boundary: OpenVibe.Website owns authoring (projects, pages, revisions, builds, Media asset pointers and intended domains) and OpenVibe.Host owns site, deploy, activation, domain binding/verification/TLS and serving; Website keeps no duplicate `host_sites`/`host_deploys` rows and calls Host's routes; a Website build is not a Host deploy; Website composes Host + Codes + Actor + Run + Media and is not OpenVibe.Sites | Proposed 2026-10-05 (plan T19), for the owner's review |
| [ADR-054](ADR-054-inventory-authority.md) | The OpenVibe inventory: a new authority, OpenVibe.Inventory (`inventory`, `ov_inventory`), owns item kinds (contract + renderer per surface), definitions (`itd_`), instances (`inv_`), the equipped set and an append-only ledger; issuers grant within their namespace; no selling, buying, trading or conversion until a later money ADR (ADR-012/ADR-025); Live's cosmetics are the first kinds, migrated convert-verify-delete | Accepted 2026-10-09 (plan T21) |
| [ADR-055](ADR-055-inventory-money.md) | Money and the inventory, in phases: A primary sales (an issuer prices an item in Vibes, booked by Billing as an ENTITLEMENT with a creator share, or in OpenCoins as a loyalty shop with no payout); B gifting between people for giftable items (an ADR-012 rule 6 amendment); C trading and a market stay closed (ADR-025); never paid chance (no cases, crates, loot boxes) | Proposed 2026-10-09 (plan T21), for the owner's decision |
| [ADR-056](ADR-056-project-secrets.md) | Project secrets: OpenVibe.Network keeps them per project and environment, write-only (no API returns a value to a person), AES-256-GCM under `NETWORK_SECRETS_KEY`; a service uses one only when the project granted it, to `sign` (HMAC, the value never leaves Network) or to `reveal` it for a listed https origin, through first-party `network.secret.use`; archiving a project shreds its secrets | Accepted 2026-10-15 (plan T13/T17) |
--| [ADR-057](ADR-057-website-connect.md) | Website Connect: a focused Go `quic-go` edge relay for Share, a Node OpenVibe.Connect control API, headscale + embedded `tsnet` for the private mesh with a tenant-isolation proof, and Events-owned durable hook delivery; two-credential node/share tunnel handshake, immediate revocation, edge policy enforcement, no secret or body in audit, a thin MIT SDK boundary, and the single-label `agents-<owner>.openvibe.website` first proof (Universal SSL does not cover `agents.owner.openvibe.website`; deep labels are CON-2); registers 9 v1 contracts and 20 planned capabilities, grants and deploys nothing | Proposed 2026-10-29 (CON-1; plan 2026-10-28 §5.9b), for the owner's review |
++| [ADR-057](ADR-057-website-connect.md) | Website Connect: a focused Go `quic-go` edge relay for Share, a Link OpenVibe.Connect control API, headscale + embedded `tsnet` for the private mesh with a tenant-isolation proof, and Events-owned durable hook delivery; two-credential node/share tunnel handshake, immediate revocation, edge policy enforcement, no secret or body in audit, a thin MIT SDK boundary, and the single-label `agents-<owner>.openvibe.website` first proof (Universal SSL does not cover `agents.owner.openvibe.website`; deep labels are CON-2); registers 9 v1 contracts and 20 planned capabilities, grants and deploys nothing | Proposed 2026-10-29 (CON-1; plan 2026-10-28 §5.9b), for the owner's review |
| [ADR-058](ADR-058-skills.md) | Skills: a skill is a document (`actor.skill@1`: a prompt with text resources, an integrity hash that is not a signature, a `requires` that grants nothing and names HO1's approval kinds); the registry's listing (`actor.skill-listing@1`) vouches for the publisher and carries the scan and the curator's state to every site: passed installs in one tap, flagged after the person reads the findings, failed never | Accepted 2026-11-07 (plan HO2, the skeleton) |
| [ADR-059](ADR-059-canvas.md) | The canvas: an agent's live panel is data (`actor.canvas@1`: headings, text, lists, checklists, tables, metrics, bar and line charts, pictures from openvibe.media, links, buttons; no HTML, style, script or frame), drawn by one Shared renderer on every site; a button only sends its label to the agent as the person's message, and a checklist tick tells the agent which item changed | Accepted 2026-11-08 (plan HO9) |
--| [ADR-060](ADR-060-skills-run-sandboxed.md) | Skills that run code: `actor.skill@2` adds `runs: sandbox` with 1–5 JavaScript tools, code as text resources in the integrity hash, `net` (`deny` default) and `limits` under caps; `actor.skill-listing@2` pins each code resource's Media id and sha256 and names the tool a finding is about; `skill.run` joins the HO1 kinds; code runs as a Run `function` job on a Node that declared the skill runner, never on an OpenVibe server and never with a secret | Accepted 2026-11-08 (plan HO8) |
++| [ADR-060](ADR-060-skills-run-sandboxed.md) | Skills that run code: `actor.skill@2` adds `runs: sandbox` with 1–5 JavaScript tools, code as text resources in the integrity hash, `net` (`deny` default) and `limits` under caps; `actor.skill-listing@2` pins each code resource's Media id and sha256 and names the tool a finding is about; `skill.run` joins the HO1 kinds; code runs as a Run `function` job on a Link that declared the skill runner, never on an OpenVibe server and never with a secret | Accepted 2026-11-08 (plan HO8) |
ddiff --git a/fixtures/bot.robot-profile/valid/community-plugin.json b/fixtures/bot.robot-profile/valid/community-plugin.json
iindex ade29690..e15e6d5e 100644
---- a/fixtures/bot.robot-profile/valid/community-plugin.json
++++ b/fixtures/bot.robot-profile/valid/community-plugin.json
@@@ -4,7 +4,7 @@
"name": "Ballbot",
"vendor": "A community member",
"kind": "onboard",
-- "description": "A self-balancing ball robot on an ESP32, driven by a community Node plugin its owner installs.",
++ "description": "A self-balancing ball robot on an ESP32, driven by a community Link plugin its owner installs.",
"capabilities": ["drive.differential", "battery"],
"mapping": { "driver": "plugin", "plugin": "ballbot" },
"commands": { "drive": { "axes": { "throttle": [-1, 1], "steer": [-1, 1] } } },
ddiff --git a/fixtures/bot.robot-profile/valid/looi.json b/fixtures/bot.robot-profile/valid/looi.json
iindex ad89878a..533443af 100644
---- a/fixtures/bot.robot-profile/valid/looi.json
++++ b/fixtures/bot.robot-profile/valid/looi.json
@@@ -4,7 +4,7 @@
"name": "LOOI desktop robot",
"vendor": "LOOI",
"kind": "bridge",
-- "description": "LOOI, the tracked desk robot that uses a phone as its face: differential drive and head tilt over Bluetooth from a bridge computer (Windows, macOS or Linux) running the OpenVibe Node with the Generic LOOI driver. The phone streams its own video, for example from its browser to the owner's OpenVibe.Live channel, where the robot's panel shows under the stream.",
++ "description": "LOOI, the tracked desk robot that uses a phone as its face: differential drive and head tilt over Bluetooth from a bridge computer (Windows, macOS or Linux) running the OpenVibe Link with the Generic LOOI driver. The phone streams its own video, for example from its browser to the owner's OpenVibe.Live channel, where the robot's panel shows under the stream.",
"capabilities": ["drive.differential", "head", "battery"],
"mapping": { "driver": "looi", "plugin": "looi" },
"commands": {
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 1885e346..b11bd636 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -6554,7 +6554,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/codes/skill.v1.json",
"title": "CodesSkill",
-- "description": "codes.skill@1: one skill, rule, MCP server, command or hook in the OpenVibe.Codes hub (the Compute plan §5, E6). KIND says what it is: a skill is a SKILL.md folder the agent tools load, a rule is text for the tools' AGENTS.md/CLAUDE.md files, an mcp record is one MCP server definition, and command/hook are a slash command and a hook. SLUG is the folder name (lowercase letters, digits and hyphens) and is stable within one owner. VERSION is the newest record in codes.skill-version@1; BODY is that version's content. AUDIENCE is the four sharing audiences of openvibe-sdk/share: private (the owner only), people (the owner and the members listed in codes_skill_members), link (anyone with the link, not in the gallery) and public (the gallery, search and agents). A private, people or link record is never in the gallery. OFFICIAL marks OpenVibe's own skills. TOOLS names the harnesses the skill is written for; an empty list means every tool the Node knows. The body is untrusted text: the page renders it through the markup gate (escape first, then a small Markdown subset), never as HTML.",
++ "description": "codes.skill@1: one skill, rule, MCP server, command or hook in the OpenVibe.Codes hub (the Compute plan §5, E6). KIND says what it is: a skill is a SKILL.md folder the agent tools load, a rule is text for the tools' AGENTS.md/CLAUDE.md files, an mcp record is one MCP server definition, and command/hook are a slash command and a hook. SLUG is the folder name (lowercase letters, digits and hyphens) and is stable within one owner. VERSION is the newest record in codes.skill-version@1; BODY is that version's content. AUDIENCE is the four sharing audiences of openvibe-sdk/share: private (the owner only), people (the owner and the members listed in codes_skill_members), link (anyone with the link, not in the gallery) and public (the gallery, search and agents). A private, people or link record is never in the gallery. OFFICIAL marks OpenVibe's own skills. TOOLS names the harnesses the skill is written for; an empty list means every tool the Link knows. The body is untrusted text: the page renders it through the markup gate (escape first, then a small Markdown subset), never as HTML.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -6665,7 +6665,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/codes/skill-version.v1.json",
"title": "CodesSkillVersion",
-- "description": "codes.skill-version@1: one immutable version of a codes.skill@1 record in the OpenVibe.Codes skills hub. Every save writes a new version; VERSION starts at 1 and only grows, and SKILL_ID plus VERSION is the key. BODY and SUMMARY are the content at that version, TOOLS and AUDIENCE the settings it had, and NOTE says why it was written (created, edited, published, or rolled back to N). A rollback never rewrites an old row: it copies the chosen version into a new one, so the history of what every machine synced stays exact and the Node's state can name the version it wrote.",
++ "description": "codes.skill-version@1: one immutable version of a codes.skill@1 record in the OpenVibe.Codes skills hub. Every save writes a new version; VERSION starts at 1 and only grows, and SKILL_ID plus VERSION is the key. BODY and SUMMARY are the content at that version, TOOLS and AUDIENCE the settings it had, and NOTE says why it was written (created, edited, published, or rolled back to N). A rollback never rewrites an old row: it copies the chosen version into a new one, so the history of what every machine synced stays exact and the Link's state can name the version it wrote.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -14614,12 +14614,12 @@
"anthropic",
"node"
],
-- "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Node and OpenVibe.Bot (no key, no base_url, no cost)."
++ "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Link and OpenVibe.Bot (no key, no base_url, no cost)."
},
"machine": {
"type": "string",
"pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
},
"base_url": {
"type": "string",
@@@ -14755,7 +14755,7 @@
"machine": {
"type": "string",
"pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model, or `any` of their own."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model, or `any` of their own."
},
"models": {
"type": "object",
@@@ -71535,7 +71535,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/network/machine.v1.json",
"title": "NetworkMachine",
-- "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
++ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Link install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
"type": "object",
"required": [
"id",
@@@ -71572,7 +71572,7 @@
"os": {
"type": "string",
"maxLength": 40,
-- "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in."
++ "description": "The operating system the Link reports (linux, darwin, windows). Absent until the machine has checked in."
},
"online": {
"type": "boolean",
@@@ -71586,7 +71586,7 @@
"node_version": {
"type": "string",
"maxLength": 40,
-- "description": "The OpenVibe Node version the machine reports. Absent until it has checked in."
++ "description": "The OpenVibe Link version the machine reports. Absent until it has checked in."
},
"roles": {
"type": "array",
@@@ -71789,7 +71789,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/network/machine-roles.v1.json",
"title": "NetworkMachineRoles",
-- "description": "network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).",
++ "description": "network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Link turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).",
"type": "object",
"required": [
"roles"
@@@ -72334,7 +72334,7 @@
"type": "string",
"pattern": "^[a-z][a-z0-9-]*:[a-z0-9.-]+$"
},
-- "description": "e.g. node:http, worker:browser, worker:ffmpeg, worker:ai-gpu, events:gateway, events:durable, object:r2. Kind harness: task:edit, task:review, task:browse, task:run, task:test and task:plan for the task kinds it takes (its detail's task_capabilities), and harness:mcp, harness:resume, harness:host-access and harness:long-autonomy for its detail's capabilities that are true. The `worker:<class>` names that map to platform.runtime-class@1 values are reserved for OpenVibe.Node's job worker, one per class: worker:function, worker:code, worker:browser, worker:linux, worker:desktop and worker:gpu ($defs.reservedWorkerCapabilities). A node advertises the name of each class it runs (Node's status.capabilities.worker); every other `worker:` name (worker:ffmpeg, worker:ai-gpu) is an ordinary capability name. Beside them, a kind harness names `runtime:<class>` for each platform.runtime-class@1 class it can run (runtime:function, runtime:code, runtime:browser, runtime:linux, runtime:desktop, runtime:gpu); these are ordinary capability names, not reserved, and OpenVibe.Codes already advertises the classes it offers."
++ "description": "e.g. node:http, worker:browser, worker:ffmpeg, worker:ai-gpu, events:gateway, events:durable, object:r2. Kind harness: task:edit, task:review, task:browse, task:run, task:test and task:plan for the task kinds it takes (its detail's task_capabilities), and harness:mcp, harness:resume, harness:host-access and harness:long-autonomy for its detail's capabilities that are true. The `worker:<class>` names that map to platform.runtime-class@1 values are reserved for OpenVibe Link's job worker, one per class: worker:function, worker:code, worker:browser, worker:linux, worker:desktop and worker:gpu ($defs.reservedWorkerCapabilities). A node advertises the name of each class it runs (Node's status.capabilities.worker); every other `worker:` name (worker:ffmpeg, worker:ai-gpu) is an ordinary capability name. Beside them, a kind harness names `runtime:<class>` for each platform.runtime-class@1 class it can run (runtime:function, runtime:code, runtime:browser, runtime:linux, runtime:desktop, runtime:gpu); these are ordinary capability names, not reserved, and OpenVibe.Codes already advertises the classes it offers."
},
"capacity": {
"type": "object",
@@@ -72672,7 +72672,7 @@
],
"$defs": {
"reservedWorkerCapabilities": {
-- "description": "The reserved Fabric capability names of OpenVibe.Node's job worker, one per platform.runtime-class@1 value: `worker:` + the class. These six names are reserved in this contract — the `capabilities` pattern alone accepted any `worker:` name — and a node advertises only the classes it actually runs, in class order.",
++ "description": "The reserved Fabric capability names of OpenVibe Link's job worker, one per platform.runtime-class@1 value: `worker:` + the class. These six names are reserved in this contract — the `capabilities` pattern alone accepted any `worker:` name — and a node advertises only the classes it actually runs, in class order.",
"type": "string",
"enum": [
"worker:function",
@@@ -74901,7 +74901,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/platform/runtime-class.v1.json",
"title": "RuntimeClass",
-- "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe.Node's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
++ "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe Link's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
"$comment": "Additive: a class may be added to v1; none is removed, renamed or reordered.",
"type": "string",
"oneOf": [
@@@ -74959,7 +74959,7 @@
"id": {
"type": "string",
"pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Node command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`)."
++ "description": "Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Link command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`)."
},
"class": {
"$ref": "runtime-class.v1.json"
@@@ -75029,7 +75029,7 @@
"openvibe-only"
],
"default": "deny",
-- "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe.Node's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
++ "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe Link's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
},
"inputs": {
"type": "array",
@@@ -75142,7 +75142,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/platform/job-frame.v1.json",
"title": "JobFrame",
-- "description": "platform.job-frame@1: one job lifecycle frame on the device control link (plan T14 Run; ADR-034 proposed). The link is OpenVibe.Node's WebSocket to OpenVibe.Bot (Node docs/protocol.md section 2); Fabric may terminate it later. Each frame is one JSON object in one text message, with the link's envelope at the top level: `v` (1), `seq` (per-direction counter from 1 on each connection), `ts` (sender's clock, Unix ms) and `type`. Server to device: `job`, `job_cancel`, `job_exit_ack`. Device to server: `job_started`, `job_stdout`, `job_usage`, `job_exit`. The link's existing `ack`/`nack` frames, keyed by the job id, accept or refuse a `job`; a worker that runs jobs advertises its classes in `status.capabilities`. A running `function` job is metered per wall-clock second into platform.usage-sample@1 readings: see `job_usage` and `job_exit`.",
++ "description": "platform.job-frame@1: one job lifecycle frame on the device control link (plan T14 Run; ADR-034 proposed). The link is OpenVibe Link's WebSocket to OpenVibe.Bot (Link docs/protocol.md section 2); Fabric may terminate it later. Each frame is one JSON object in one text message, with the link's envelope at the top level: `v` (1), `seq` (per-direction counter from 1 on each connection), `ts` (sender's clock, Unix ms) and `type`. Server to device: `job`, `job_cancel`, `job_exit_ack`. Device to server: `job_started`, `job_stdout`, `job_usage`, `job_exit`. The link's existing `ack`/`nack` frames, keyed by the job id, accept or refuse a `job`; a worker that runs jobs advertises its classes in `status.capabilities`. A running `function` job is metered per wall-clock second into platform.usage-sample@1 readings: see `job_usage` and `job_exit`.",
"$comment": "Additive: new frame types and optional fields may extend this v1 contract; a receiver ignores frame types it does not know.",
"oneOf": [
{
@@@ -75533,7 +75533,7 @@
"$id": "https://openvibe.network/contracts/platform/task.v1.json",
"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 (actor.task-webhook@1, signed with `webhook_secret`, which only the creating answer carries). Owned by `network` like every other platform.* contract and implemented by OpenVibe.Actor (manifests/services/actor.json). This v1 contract may grow optional fields. A-1 adds AGENT_ID (the agent contact the task runs as; absent means the person's Vibe), LEVEL (the thread's own approval level), EFFECTIVE_LEVEL (max of the agent level and the person's own inherited line) and AGENT_LEVEL (the agent contact's own level), so a reader never has to guess why a step did or did not ask.",
-- "$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.",
++ "$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 Link WebSocket.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -78491,7 +78491,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/device-message.v1.json",
"title": "BotDeviceMessage",
-- "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).",
++ "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe Link internal/protocol/protocol.go (the Go structs of the device agent).",
"oneOf": [
{
"description": "Server to device, once authenticated.",
@@@ -78679,7 +78679,7 @@
},
"value": {
"type": "object",
-- "description": "The command's value. screen_stream (OpenVibe.Node v0.12.0): {action: 'start', whip_url, publish_key, seconds? (30-1800, default 600), fps? (1-5, default 2)} or {action: 'stop'}: the machine's screen shown live on its own private OpenRestream stream. Bot alone fills in whip_url and publish_key (the machine's stream, playback private, never recorded); no operator, agent or service sends this kind."
++ "description": "The command's value. screen_stream (OpenVibe Link v0.12.0): {action: 'start', whip_url, publish_key, seconds? (30-1800, default 600), fps? (1-5, default 2)} or {action: 'stop'}: the machine's screen shown live on its own private OpenRestream stream. Bot alone fills in whip_url and publish_key (the machine's stream, playback private, never recorded); no operator, agent or service sends this kind."
},
"deadline_ms": {
"type": "integer",
@@@ -78714,7 +78714,7 @@
},
"target": {
"type": "string",
-- "description": "OpenVibe.Node only: the driver to send it to."
++ "description": "OpenVibe Link only: the driver to send it to."
}
}
},
@@@ -79010,7 +79010,7 @@
}
},
{
-- "description": "Device to server: what the agent runs; OpenVibe.Node sends drivers as objects, older agents as names.",
++ "description": "Device to server: what the agent runs; OpenVibe Link sends drivers as objects, older agents as names.",
"type": "object",
"required": [
"v",
@@@ -79245,12 +79245,12 @@
},
"data": {
"type": "object",
-- "description": "OpenVibe.Node: a computer command's answer (exec, fs, screen), at most 512 KB of JSON."
++ "description": "OpenVibe Link: a computer command's answer (exec, fs, screen), at most 512 KB of JSON."
}
}
},
{
-- "description": "Device to server: the command was refused, with OpenVibe.Node's fault code (bad_value, estopped, local_stop, expired, …).",
++ "description": "Device to server: the command was refused, with OpenVibe Link's fault code (bad_value, estopped, local_stop, expired, …).",
"type": "object",
"required": [
"v",
@@@ -79324,7 +79324,7 @@
"fleet": {
"type": "array",
"maxItems": 16,
-- "description": "What this machine's coding agents have left (Codes C9c): each agent's id, whether it is installed and signed in, and each limit window's label, what it has used of its limit, and when it frees (RFC 3339 or empty when the agent's login file does not say). OpenVibe.Node reads it with the OpenVibe.Codes runner's own logins probe as the machine's own user and reports labels, numbers and times only: no key, token or account name. Absent means the machine has no reading (no OpenVibe.Codes runner, or its probe has not answered yet), never that nothing is installed.",
++ "description": "What this machine's coding agents have left (Codes C9c): each agent's id, whether it is installed and signed in, and each limit window's label, what it has used of its limit, and when it frees (RFC 3339 or empty when the agent's login file does not say). OpenVibe Link reads it with the OpenVibe.Codes runner's own logins probe as the machine's own user and reports labels, numbers and times only: no key, token or account name. Absent means the machine has no reading (no OpenVibe.Codes runner, or its probe has not answered yet), never that nothing is installed.",
"items": {
"type": "object",
"required": [
@@@ -79570,7 +79570,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/command.v1.json",
"title": "BotCommand",
-- "description": "bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9). The computer kinds (exec, fs, screen, input, browser; OpenVibe.Node's computer plugin, Node docs/computer.md) are taken only for a person's own machine, from its owner or an agent the owner let use it (bot.computer.control); the machine's owner sets on the machine what they may do, and the answer comes back in bot.command-result@1's data. probe (a read-only check of something on the machine's own network) is the one computer kind OpenVibe.Watch may send, for owners who let their watches use the computer. input (added in 0.171.0) moves the mouse, clicks, scrolls, types or presses keys on the machine's screen, at coordinates in the space of the screen picture the caller last saw (its width and height; the Node scales them to the real screen): refused at level observe, approved one by one at ask. browser (added in 0.174.0) drives a Chromium-family browser the agent uses on the machine, in a profile of its own: open an http(s) URL, click a CSS selector or the button or link showing a text, fill a field, press a named key, go back, read the page, take a screenshot, or close it. read, screenshot and close run at every level; the rest act (refused at observe, approved one by one at ask), and below level full open refuses an address on the machine's own network. desk (added in 0.214.0; plan track K, Vibe's desk) starts, stops or reports a private desk on the machine: a display of its own (Xvfb, a window manager, its own session and browser profile) where an agent works without touching the person's screen, which the person can watch and take over; screen, input and browser take desk: private to act there instead of on the person's screen (main, the default). Starting and stopping a desk act (refused at observe, approved one by one at ask); status runs at every level.",
++ "description": "bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9). The computer kinds (exec, fs, screen, input, browser; OpenVibe Link's computer plugin, Link docs/computer.md) are taken only for a person's own machine, from its owner or an agent the owner let use it (bot.computer.control); the machine's owner sets on the machine what they may do, and the answer comes back in bot.command-result@1's data. probe (a read-only check of something on the machine's own network) is the one computer kind OpenVibe.Watch may send, for owners who let their watches use the computer. input (added in 0.171.0) moves the mouse, clicks, scrolls, types or presses keys on the machine's screen, at coordinates in the space of the screen picture the caller last saw (its width and height; the Link scales them to the real screen): refused at level observe, approved one by one at ask. browser (added in 0.174.0) drives a Chromium-family browser the agent uses on the machine, in a profile of its own: open an http(s) URL, click a CSS selector or the button or link showing a text, fill a field, press a named key, go back, read the page, take a screenshot, or close it. read, screenshot and close run at every level; the rest act (refused at observe, approved one by one at ask), and below level full open refuses an address on the machine's own network. desk (added in 0.214.0; plan track K, Vibe's desk) starts, stops or reports a private desk on the machine: a display of its own (Xvfb, a window manager, its own session and browser profile) where an agent works without touching the person's screen, which the person can watch and take over; screen, input and browser take desk: private to act there instead of on the person's screen (main, the default). Starting and stopping a desk act (refused at observe, approved one by one at ask); status runs at every level.",
"type": "object",
"required": [
"type",
@@@ -79758,11 +79758,11 @@
"desk": {
"type": "string",
"pattern": "^(main|private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which session it runs in (Node v0.26.0): the person's own (main, the default), or a desk's (private or desk-<name>): the desk's display, session bus and temp folder, so a program with windows opens on that desk; start the desk first"
++ "description": "which session it runs in (Link v0.26.0): the person's own (main, the default), or a desk's (private or desk-<name>): the desk's display, session bus and temp folder, so a program with windows opens on that desk; start the desk first"
},
"detach": {
"type": "boolean",
-- "description": "on a whole desk only (Node v0.26.0): start a program that keeps running (an office suite, a terminal, an editor) and answer at once with its pid; it runs until the desk stops. Never on the person's own session or a browser desk"
++ "description": "on a whole desk only (Link v0.26.0): start a program that keeps running (an office suite, a terminal, an editor) and answer at once with its pid; it runs until the desk stops. Never on the person's own session or a browser desk"
}
}
}
@@@ -79863,7 +79863,7 @@
"desk": {
"type": "string",
"pattern": "^(main|private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Node v0.19.0: one per agent task); start the desk first"
++ "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Link v0.19.0: one per agent task); start the desk first"
}
}
}
@@@ -79990,7 +79990,7 @@
"desk": {
"type": "string",
"pattern": "^(main|private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Node v0.19.0: one per agent task); start the desk first"
++ "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Link v0.19.0: one per agent task); start the desk first"
}
}
}
@@@ -80078,7 +80078,7 @@
"desk": {
"type": "string",
"pattern": "^(main|private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Node v0.19.0: one per agent task); start the desk first"
++ "description": "which display: the person's own screen (main, the default), the machine's first private desk (private) or a named desk (desk-<name>, Link v0.19.0: one per agent task); start the desk first"
}
},
"allOf": [
@@@ -80195,24 +80195,24 @@
"status",
"list"
],
-- "description": "start a desk (a no-op when it runs), stop it, report it, or list the desks that run (list, Node v0.19.0)"
++ "description": "start a desk (a no-op when it runs), stop it, report it, or list the desks that run (list, Link v0.19.0)"
},
"desk": {
"type": "string",
"pattern": "^(private|desk-[a-z0-9][a-z0-9-]{0,23})$",
-- "description": "which desk (Node v0.19.0): private (the default, the first desk) or desk-<name>; each desk has its own display, browser profile, downloads and temp folder, and a machine runs at most its max_desks at once"
++ "description": "which desk (Link v0.19.0): private (the default, the first desk) or desk-<name>; each desk has its own display, browser profile, downloads and temp folder, and a machine runs at most its max_desks at once"
},
"kind": {
"enum": [
"desktop",
"browser"
],
-- "description": "start only (Node v0.23.0, Desks plan D2): what the desk is. desktop (the default) is a whole screen: a display, a window manager and a browser shown on it (Linux with Xvfb). browser is a browser alone with no display (Linux or macOS with Chromium), about half the cost: its screen, mouse and keyboard and its live view are the page's own. A machine says which it runs in its computer capabilities (desk.kinds)"
++ "description": "start only (Link v0.23.0, Desks plan D2): what the desk is. desktop (the default) is a whole screen: a display, a window manager and a browser shown on it (Linux with Xvfb). browser is a browser alone with no display (Linux or macOS with Chromium), about half the cost: its screen, mouse and keyboard and its live view are the page's own. A machine says which it runs in its computer capabilities (desk.kinds)"
},
"profile": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{0,23}$",
-- "description": "start only, a named desk (Node v0.24.0): a browser profile kept on the machine by that name. The desk's browser starts from it (its cookies and site storage: the person signs in once, on any desk) and the desk saves it back when it stops (the last desk to stop wins); caches are never kept. The private desk keeps its own profile. A machine that keeps profiles says so in its computer capabilities (desk.profiles)"
++ "description": "start only, a named desk (Link v0.24.0): a browser profile kept on the machine by that name. The desk's browser starts from it (its cookies and site storage: the person signs in once, on any desk) and the desk saves it back when it stops (the last desk to stop wins); caches are never kept. The private desk keeps its own profile. A machine that keeps profiles says so in its computer capabilities (desk.profiles)"
},
"width": {
"type": "integer",
@@@ -80254,7 +80254,7 @@
"messages"
],
"additionalProperties": false,
-- "description": "one chat completion on the person's own machine (OpenVibe.Node v0.27.0's ai plugin, relayed to a local OpenAI-compatible model server: Ollama, llama.cpp, LM Studio); the answer's data is { model, message: { role, content, tool_calls? }, finish_reason?, usage? }. It reads nothing on the machine and changes nothing there. TOOLS (since 0.277.0, Node v0.36.0's ai plugin; a machine that can pass them says so with ai.tools: true in its capabilities) are the functions the model may ask for: the machine passes them to its model server and hands back the calls the model asked for in message.tool_calls; it never runs one. The caller runs them and sends the next command with the assistant's message (its tool_calls) and one tool message per call (TOOL_CALL_ID)",
++ "description": "one chat completion on the person's own machine (OpenVibe Link v0.27.0's ai plugin, relayed to a local OpenAI-compatible model server: Ollama, llama.cpp, LM Studio); the answer's data is { model, message: { role, content, tool_calls? }, finish_reason?, usage? }. It reads nothing on the machine and changes nothing there. TOOLS (since 0.277.0, Link v0.36.0's ai plugin; a machine that can pass them says so with ai.tools: true in its capabilities) are the functions the model may ask for: the machine passes them to its model server and hands back the calls the model asked for in message.tool_calls; it never runs one. The caller runs them and sends the next command with the assistant's message (its tool_calls) and one tool message per call (TOOL_CALL_ID)",
"properties": {
"model": {
"type": "string",
@@@ -80550,7 +80550,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/job-dispatch-result.v1.json",
"title": "BotJobDispatchResult",
-- "description": "bot.job-dispatch-result@1: what OpenVibe.Bot answers OpenVibe.Run's bot.job.dispatch calls (server/api/v1.js and server/jobs/index.js). POST /api/v1/jobs is 201 { job, sent }: `job` is Bot's run_jobs record and `sent` says whether the `job` frame went out on the device link now (false while the paired Node is away; it is resent on every reconnect until the Node acks it). POST /api/v1/jobs/:id/cancel is 200 { job, sent } with the same shape: `sent` says whether a `job_cancel` frame went out. GET /api/v1/jobs/:id is 200 { job, stdout }: the record plus the last 1 MiB of the job's captured stdout, or null when the Node sent none. The record's `state` follows the run_jobs lifecycle queued -> placed (the Node acked) -> running (job_started) -> one end state, and its `job` field is the platform.job@1 body Run dispatched, stored and resent verbatim. A refused dispatch answers a problem instead (422 bot.invalid_job, 404 bot.device_not_found, 409 bot.class_unadvertised, 409 bot.job_id_reused), never this contract.",
++ "description": "bot.job-dispatch-result@1: what OpenVibe.Bot answers OpenVibe.Run's bot.job.dispatch calls (server/api/v1.js and server/jobs/index.js). POST /api/v1/jobs is 201 { job, sent }: `job` is Bot's run_jobs record and `sent` says whether the `job` frame went out on the device link now (false while the paired Link is away; it is resent on every reconnect until the Link acks it). POST /api/v1/jobs/:id/cancel is 200 { job, sent } with the same shape: `sent` says whether a `job_cancel` frame went out. GET /api/v1/jobs/:id is 200 { job, stdout }: the record plus the last 1 MiB of the job's captured stdout, or null when the Link sent none. The record's `state` follows the run_jobs lifecycle queued -> placed (the Link acked) -> running (job_started) -> one end state, and its `job` field is the platform.job@1 body Run dispatched, stored and resent verbatim. A refused dispatch answers a problem instead (422 bot.invalid_job, 404 bot.device_not_found, 409 bot.class_unadvertised, 409 bot.job_id_reused), never this contract.",
"oneOf": [
{
"$ref": "#/$defs/dispatched"
@@@ -80595,7 +80595,7 @@
"node_id": {
"type": "string",
"pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The paired device the job was sent to; a Node never reads another device's job."
++ "description": "The paired device the job was sent to; a Link never reads another device's job."
},
"class": {
"$ref": "../platform/runtime-class.v1.json"
@@@ -80610,7 +80610,7 @@
"cancelled",
"expired"
],
-- "description": "queued (stored; sent and resent until acked), placed (the Node acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl)."
++ "description": "queued (stored; sent and resent until acked), placed (the Link acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl)."
},
"project_id": {
"type": "string",
@@@ -80643,7 +80643,7 @@
"null"
],
"maxLength": 64,
-- "description": "The Node's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable."
++ "description": "The Link's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable."
},
"sent_at": {
"type": [
@@@ -80659,7 +80659,7 @@
"null"
],
"minimum": 0,
-- "description": "The Node's job_started started_ms: the anchor of every usage second; null until it started."
++ "description": "The Link's job_started started_ms: the anchor of every usage second; null until it started."
},
"finished_at": {
"type": [
@@@ -80786,7 +80786,7 @@
"state": {
"type": "object",
"additionalProperties": false,
-- "description": "The answer of GET /api/v1/jobs/:id (200): the row and the job's held stdout (null when the Node sent none).",
++ "description": "The answer of GET /api/v1/jobs/:id (200): the row and the job's held stdout (null when the Link sent none).",
"required": [
"job",
"stdout"
@@@ -81188,7 +81188,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/browser-provider/job-create-request.v1.json",
"title": "BrowserProviderJobCreateRequest",
-- "description": "browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.",
++ "description": "browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -81469,7 +81469,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/sandbox-provider/job-create-request.v1.json",
"title": "SandboxProviderJobCreateRequest",
-- "description": "sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is code or linux, the platform.runtime-class@1 classes a sandbox hosts. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is sandbox-provider.job-create-result@1.",
++ "description": "sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is code or linux, the platform.runtime-class@1 classes a sandbox hosts. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is sandbox-provider.job-create-result@1.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -81752,7 +81752,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/run/job-create-request.v1.json",
"title": "RunJobCreateRequest",
-- "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).",
++ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -81910,7 +81910,7 @@
"openvibe-only"
],
"default": "none",
-- "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
++ "description": "OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
},
"idempotency_key": {
"type": "string",
@@@ -93517,7 +93517,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/run/session.v1.json",
"title": "RunSession",
-- "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
++ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe Link runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -94304,7 +94304,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/desk-lease.v1.json",
"title": "BotDeskLease",
-- "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
++ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe Link runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -101998,7 +101998,7 @@
"null"
],
"maxLength": 100,
-- "description": "The Node machine id it came from, when it came from one."
++ "description": "The Link machine id it came from, when it came from one."
},
"no_looks": {
"type": "boolean",
@@@ -102310,7 +102310,7 @@
"null"
],
"maxLength": 100,
-- "description": "The Node machine id the item came from, when it came from one."
++ "description": "The Link machine id the item came from, when it came from one."
},
"seen_at": {
"type": "string",
@@@ -103014,7 +103014,7 @@
"type": "string",
"minLength": 3,
"maxLength": 300,
-- "description": "The local target the Node may forward to, e.g. 127.0.0.1:3000, tcp://127.0.0.1:25565 or udp://127.0.0.1:19132. Loopback by default; a LAN target is allowed only when the owner enabled it locally."
++ "description": "The local target the Link may forward to, e.g. 127.0.0.1:3000, tcp://127.0.0.1:25565 or udp://127.0.0.1:19132. Loopback by default; a LAN target is allowed only when the owner enabled it locally."
},
"status": {
"enum": [
@@@ -103125,7 +103125,7 @@
"type": "string",
"minLength": 3,
"maxLength": 300,
-- "description": "The local port the Node exposes, e.g. 127.0.0.1:3000; never an arbitrary internet host."
++ "description": "The local port the Link exposes, e.g. 127.0.0.1:3000; never an arbitrary internet host."
},
"policy": {
"allOf": [
@@@ -103167,7 +103167,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/website/share-connect-request.v1.json",
"title": "WebsiteShareConnectRequest",
-- "description": "website.share-connect-request@1: what a Node presents to open the tunnel for one share (POST /api/v1/shares/:id/tunnel, capability website.share.connect). The Node proves its Network node principal with a client-credentials token whose audience is openvibe.website, and this short-lived, single-use tunnel_token minted by Connect binds the connection to one share. The edge checks both; neither alone opens the route. The token is presented once and never stored, logged or returned.",
++ "description": "website.share-connect-request@1: what a Link presents to open the tunnel for one share (POST /api/v1/shares/:id/tunnel, capability website.share.connect). The Link proves its Network node principal with a client-credentials token whose audience is openvibe.website, and this short-lived, single-use tunnel_token minted by Connect binds the connection to one share. The edge checks both; neither alone opens the route. The token is presented once and never stored, logged or returned.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -103197,17 +103197,17 @@
"http2-ws"
],
"default": "quic",
-- "description": "The data-plane transport the Node is trying. quic is ALPN ovconnect/1; http2-ws is the 443 fallback when UDP is blocked."
++ "description": "The data-plane transport the Link is trying. quic is ALPN ovconnect/1; http2-ws is the 443 fallback when UDP is blocked."
},
"node_version": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$",
-- "description": "The Node release, for posture and diagnostics; never used for authorization."
++ "description": "The Link release, for posture and diagnostics; never used for authorization."
},
"region": {
"type": "string",
"maxLength": 40,
-- "description": "The edge region the Node is dialing; the edge refuses a region it does not serve."
++ "description": "The edge region the Link is dialing; the edge refuses a region it does not serve."
}
}
}
@@@ -103728,7 +103728,7 @@
"type": "string",
"minLength": 1,
"maxLength": 40,
-- "description": "The Node release running on the device."
++ "description": "The Link release running on the device."
},
"addresses": {
"type": "array",
@@@ -104294,7 +104294,7 @@
"type": "integer",
"minimum": 1,
"maximum": 3600,
-- "description": "How far a signed timestamp may drift before the Node refuses it; 300 is the default."
++ "description": "How far a signed timestamp may drift before the Link refuses it; 300 is the default."
}
}
},
@@@ -106712,7 +106712,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/live/agent-stream-create-request.v1.json",
"title": "LiveAgentStreamCreateRequest",
-- "description": "live.agent-stream-create-request@1 (the Agent Streams plan, S1): the body of POST /api/streams/agent on OpenVibe.Live (capability live.agent_stream.publish). It answers 201 with the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1): { stream, publish }. The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds live.agent_stream.publish for the audience openvibe.live; Network grants it to exactly those principals. Never a developer-app token, never another person's stream. TITLE is the card's title, TASK says what the agent is doing and where that task lives, VISIBILITY says who may watch, and ALLOW_SITES is the optional list of hosts a browser desk may navigate to (a desk that leaves the list raises the curtain).",
++ "description": "live.agent-stream-create-request@1 (the Agent Streams plan, S1): the body of POST /api/streams/agent on OpenVibe.Live (capability live.agent_stream.publish). It answers 201 with the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1): { stream, publish }. The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds live.agent_stream.publish for the audience openvibe.live; Network grants it to exactly those principals. Never a developer-app token, never another person's stream. TITLE is the card's title, TASK says what the agent is doing and where that task lives, VISIBILITY says who may watch, and ALLOW_SITES is the optional list of hosts a browser desk may navigate to (a desk that leaves the list raises the curtain).",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -109169,7 +109169,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/machine-pool.v1.json",
"title": "BotMachinePool",
-- "description": "bot.machine-pool@1 (the pooled machines design, 2026-11-06): several of one owner's machines on the same local network that serve one model together through llama.cpp RPC, `rpc-server` on the members and `llama-server --rpc` on the head. OpenVibe.Network holds the record: the owner proposes it on /machines (POST /api/v1/machine-pools, two to eight of their own machines and an optional head; the default head has the most free VRAM), and each machine reads it with its own node token (GET /api/v1/node/self/pools/:id, network.node.self.manage) and approves it locally (POST /api/v1/node/self/pools/:id/approve with { certificate }). A machine's `certificate` is null until it approves on that machine: the Node generates an Ed25519 key there, keeps the private key, and uploads only its self-signed public certificate, which the other members pin for the pool's private link. A pool with any null certificate never serves. The record never carries a private key or a prompt. What a schema cannot say, Network and Node check at run time: the members are distinct and the head is one of them; every member has the pool's owner, today, through Bot; every member was seen from the same public address with a private address in the same subnet; the head's direct link probe to each member passes; each machine's certificate is its own; and any change of membership, owner, head or key revokes the pool, so all machines approve a new one. A machine that leaves (removed, signed out, a different owner) drops its link and its key at once.",
++ "description": "bot.machine-pool@1 (the pooled machines design, 2026-11-06): several of one owner's machines on the same local network that serve one model together through llama.cpp RPC, `rpc-server` on the members and `llama-server --rpc` on the head. OpenVibe.Network holds the record: the owner proposes it on /machines (POST /api/v1/machine-pools, two to eight of their own machines and an optional head; the default head has the most free VRAM), and each machine reads it with its own node token (GET /api/v1/node/self/pools/:id, network.node.self.manage) and approves it locally (POST /api/v1/node/self/pools/:id/approve with { certificate }). A machine's `certificate` is null until it approves on that machine: the Link generates an Ed25519 key there, keeps the private key, and uploads only its self-signed public certificate, which the other members pin for the pool's private link. A pool with any null certificate never serves. The record never carries a private key or a prompt. What a schema cannot say, Network and Node check at run time: the members are distinct and the head is one of them; every member has the pool's owner, today, through Bot; every member was seen from the same public address with a private address in the same subnet; the head's direct link probe to each member passes; each machine's certificate is its own; and any change of membership, owner, head or key revokes the pool, so all machines approve a new one. A machine that leaves (removed, signed out, a different owner) drops its link and its key at once.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -109328,7 +109328,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/ai/offer.v1.json",
"title": "AiOffer",
-- "description": "ai.offer@1: one community AI offer, a person's machine (OpenVibe.Node's ai plugin, \"Offer to the network\") serving its local models to requests that opt in to community capacity. OpenVibe.Bot reports these fields (POST /internal/ai/offers/report on Network, network.resource.report; a report with any other field is refused whole), and OpenVibe.AI reads them from Network (GET /internal/ai/offers, network.registry.read, { offers, generated_at, count }). Network adds `pool` (ai.offer-pool@1) to the head's offer when the machine is the head of an approved pool (bot.machine-pool@1), and leaves a pool member's own offer out of the list while it is in a pool. For a pool the hours, audience, price, readiness, idle-only and verification apply to the whole pool: it is ready only while every member has approved, renewed its readiness in the last few seconds, and is still the owner's. Nothing here is public: AI shows Exchange a hashed offer id and no owner or machine id.",
++ "description": "ai.offer@1: one community AI offer, a person's machine (OpenVibe Link's ai plugin, \"Offer to the network\") serving its local models to requests that opt in to community capacity. OpenVibe.Bot reports these fields (POST /internal/ai/offers/report on Network, network.resource.report; a report with any other field is refused whole), and OpenVibe.AI reads them from Network (GET /internal/ai/offers, network.registry.read, { offers, generated_at, count }). Network adds `pool` (ai.offer-pool@1) to the head's offer when the machine is the head of an approved pool (bot.machine-pool@1), and leaves a pool member's own offer out of the list while it is in a pool. For a pool the hours, audience, price, readiness, idle-only and verification apply to the whole pool: it is ready only while every member has approved, renewed its readiness in the last few seconds, and is still the owner's. Nothing here is public: AI shows Exchange a hashed offer id and no owner or machine id.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -111182,7 +111182,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/actor/skill.v2.json",
"title": "ActorSkillV2",
-- "description": "actor.skill@2: a skill, the document an agent loads to do one kind of job well (ADR-058, ADR-060). It keeps every field and meaning of actor.skill@1 and adds a way for the skill to bring CODE that runs in a sandbox: RUNS is prompt (as @1, text and instructions only) or sandbox. A sandbox skill carries its code as ordinary TEXT resources (a .js path with media_type text/javascript, at most 64 KiB each, in the integrity hash); TOOLS (1 to 5, only when runs is sandbox) say what an agent may call: a name, a description the agent reads, language javascript (the only one in v1), an ENTRY naming one of the skill's own .js resources, ARGS as a JSON Schema subset (an object whose properties are string, number, integer, boolean or an array of those; enum, maxLength, and pattern at most 200 characters; no $ref and no nested object), NET (deny by default, openvibe-only, public) and LIMITS (wall_ms at most 30000, default 10000; mem_bytes at most 268435456, default 134217728). A sandbox skill's requires.kinds names skill.run; a prompt skill declares no tools. A skill GRANTS NOTHING: requires only declares; the agent's level and approvals decide every step (actor approvals, HO1), and a step of a kind the skill did not declare is a scan finding (actor.skill-listing@2). Code never runs on an OpenVibe server and never sees a secret: it runs as a Run job of class function on a Node that declared the skill runner, with the network its tool declared and the exact bytes the registry scanned. The INTEGRITY block is a sha256 over the canonical JSON of the document: it shows the file was not edited after it was written and says nothing about who wrote it, so the AUTHOR is a claim; the registry's listing is the only place a publisher is vouched for. The canonical JSON is the document's own fields, in this order: skill, slug, name, description, version, instructions, resources, tools, requires, runs, license, author; an optional field that is absent or empty is left out; resources are [{ path, media_type, text }] in the document's order; tools are [{ name, description, language, entry, args, net, limits }] in the document's order, with an absent net or limits (and each absent limits field) left out and args as the object the document wrote; requires is { kinds, apps, hosts } with empty lists left out; author is { subject } or { subject, name }; the value is the compact JSON of that object (no spaces). CREATED_AT is not hashed.",
++ "description": "actor.skill@2: a skill, the document an agent loads to do one kind of job well (ADR-058, ADR-060). It keeps every field and meaning of actor.skill@1 and adds a way for the skill to bring CODE that runs in a sandbox: RUNS is prompt (as @1, text and instructions only) or sandbox. A sandbox skill carries its code as ordinary TEXT resources (a .js path with media_type text/javascript, at most 64 KiB each, in the integrity hash); TOOLS (1 to 5, only when runs is sandbox) say what an agent may call: a name, a description the agent reads, language javascript (the only one in v1), an ENTRY naming one of the skill's own .js resources, ARGS as a JSON Schema subset (an object whose properties are string, number, integer, boolean or an array of those; enum, maxLength, and pattern at most 200 characters; no $ref and no nested object), NET (deny by default, openvibe-only, public) and LIMITS (wall_ms at most 30000, default 10000; mem_bytes at most 268435456, default 134217728). A sandbox skill's requires.kinds names skill.run; a prompt skill declares no tools. A skill GRANTS NOTHING: requires only declares; the agent's level and approvals decide every step (actor approvals, HO1), and a step of a kind the skill did not declare is a scan finding (actor.skill-listing@2). Code never runs on an OpenVibe server and never sees a secret: it runs as a Run job of class function on a Link that declared the skill runner, with the network its tool declared and the exact bytes the registry scanned. The INTEGRITY block is a sha256 over the canonical JSON of the document: it shows the file was not edited after it was written and says nothing about who wrote it, so the AUTHOR is a claim; the registry's listing is the only place a publisher is vouched for. The canonical JSON is the document's own fields, in this order: skill, slug, name, description, version, instructions, resources, tools, requires, runs, license, author; an optional field that is absent or empty is left out; resources are [{ path, media_type, text }] in the document's order; tools are [{ name, description, language, entry, args, net, limits }] in the document's order, with an absent net or limits (and each absent limits field) left out and args as the object the document wrote; requires is { kinds, apps, hosts } with empty lists left out; author is { subject } or { subject, name }; the value is the compact JSON of that object (no spaces). CREATED_AT is not hashed.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 4103bbd1..aceb4b42 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -6023,7 +6023,7 @@
"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 (actor.task-webhook@1, signed with `webhook_secret`, which only the creating answer carries). Owned by `network` like every other platform.* contract and implemented by OpenVibe.Actor (manifests/services/actor.json). This v1 contract may grow optional fields. A-1 adds AGENT_ID (the agent contact the task runs as; absent means the person's Vibe), LEVEL (the thread's own approval level), EFFECTIVE_LEVEL (max of the agent level and the person's own inherited line) and AGENT_LEVEL (the agent contact's own level), so a reader never has to guess why a step did or did not ask.",
-- "$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.",
++ "$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 Link WebSocket.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 3daa92cf..52dbb01f 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -775,9 +775,9 @@
"anthropic",
"node"
],
-- "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Node and OpenVibe.Bot (no key, no base_url, no cost)."
++ "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Link and OpenVibe.Bot (no key, no base_url, no cost)."
},
-- "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Node and OpenVibe.Bot (no key, no base_url, no cost)."
++ "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Link and OpenVibe.Bot (no key, no base_url, no cost)."
},
{
"name": "machine",
@@@ -786,9 +786,9 @@
"schema": {
"type": "string",
"pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
},
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
},
{
"name": "base_url",
@@@ -930,9 +930,9 @@
"anthropic",
"node"
],
-- "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Node and OpenVibe.Bot (no key, no base_url, no cost)."
++ "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Link and OpenVibe.Bot (no key, no base_url, no cost)."
},
-- "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Node and OpenVibe.Bot (no key, no base_url, no cost)."
++ "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Link and OpenVibe.Bot (no key, no base_url, no cost)."
},
{
"name": "machine",
@@@ -941,9 +941,9 @@
"schema": {
"type": "string",
"pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
},
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
},
{
"name": "base_url",
@@@ -5298,12 +5298,12 @@
"anthropic",
"node"
],
-- "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Node and OpenVibe.Bot (no key, no base_url, no cost)."
++ "description": "openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Link and OpenVibe.Bot (no key, no base_url, no cost)."
},
"machine": {
"type": "string",
"pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
-- "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine."
},
"base_url": {
"type": "string",
@@@ -6130,7 +6130,7 @@
"null"
],
"maxLength": 100,
-- "description": "The Node machine id it came from, when it came from one."
++ "description": "The Link machine id it came from, when it came from one."
},
"no_looks": {
"type": "boolean",
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex 59d64bb2..098f633b 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -151,8 +151,8 @@
"/api/v1/jobs": {
"post": {
"operationId": "post_api_v1_jobs",
-- "summary": "OpenVibe.Run hands a platform.job@1 to a paired Node over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 ",
-- "description": "**bot.job.dispatch** (internal): OpenVibe.Run hands a platform.job@1 to a paired Node over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 bot.class_unadvertised when the device does not advertise the job's class), POST /api/v1/jobs/:id/cancel cancels it, GET /api/v1/jobs/:id returns its state with the captured stdout. project_id is required (422 without it): the dispatch carries the project, and Run is the payer. Service-to-service only (OpenVibe.Run's Network service token); never granted to an app or mod and never delegated to people.",
++ "summary": "OpenVibe.Run hands a platform.job@1 to a paired Link over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 ",
++ "description": "**bot.job.dispatch** (internal): OpenVibe.Run hands a platform.job@1 to a paired Link over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 bot.class_unadvertised when the device does not advertise the job's class), POST /api/v1/jobs/:id/cancel cancels it, GET /api/v1/jobs/:id returns its state with the captured stdout. project_id is required (422 without it): the dispatch carries the project, and Run is the payer. Service-to-service only (OpenVibe.Run's Network service token); never granted to an app or mod and never delegated to people.",
"tags": [
"bot.job"
],
@@@ -207,8 +207,8 @@
"/api/v1/jobs/{id}": {
"get": {
"operationId": "get_api_v1_jobs_id",
-- "summary": "OpenVibe.Run hands a platform.job@1 to a paired Node over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 ",
-- "description": "**bot.job.dispatch** (internal): OpenVibe.Run hands a platform.job@1 to a paired Node over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 bot.class_unadvertised when the device does not advertise the job's class), POST /api/v1/jobs/:id/cancel cancels it, GET /api/v1/jobs/:id returns its state with the captured stdout. project_id is required (422 without it): the dispatch carries the project, and Run is the payer. Service-to-service only (OpenVibe.Run's Network service token); never granted to an app or mod and never delegated to people.",
++ "summary": "OpenVibe.Run hands a platform.job@1 to a paired Link over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 ",
++ "description": "**bot.job.dispatch** (internal): OpenVibe.Run hands a platform.job@1 to a paired Link over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 bot.class_unadvertised when the device does not advertise the job's class), POST /api/v1/jobs/:id/cancel cancels it, GET /api/v1/jobs/:id returns its state with the captured stdout. project_id is required (422 without it): the dispatch carries the project, and Run is the payer. Service-to-service only (OpenVibe.Run's Network service token); never granted to an app or mod and never delegated to people.",
"tags": [
"bot.job"
],
@@@ -339,9 +339,9 @@
"openvibe-only"
],
"default": "deny",
-- "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe.Node's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
++ "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe Link's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
},
-- "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe.Node's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
++ "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe Link's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
},
{
"name": "inputs",
@@@ -409,8 +409,8 @@
"/api/v1/jobs/{id}/cancel": {
"post": {
"operationId": "post_api_v1_jobs_id_cancel",
-- "summary": "OpenVibe.Run hands a platform.job@1 to a paired Node over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 ",
-- "description": "**bot.job.dispatch** (internal): OpenVibe.Run hands a platform.job@1 to a paired Node over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 bot.class_unadvertised when the device does not advertise the job's class), POST /api/v1/jobs/:id/cancel cancels it, GET /api/v1/jobs/:id returns its state with the captured stdout. project_id is required (422 without it): the dispatch carries the project, and Run is the payer. Service-to-service only (OpenVibe.Run's Network service token); never granted to an app or mod and never delegated to people.",
++ "summary": "OpenVibe.Run hands a platform.job@1 to a paired Link over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 ",
++ "description": "**bot.job.dispatch** (internal): OpenVibe.Run hands a platform.job@1 to a paired Link over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 bot.class_unadvertised when the device does not advertise the job's class), POST /api/v1/jobs/:id/cancel cancels it, GET /api/v1/jobs/:id returns its state with the captured stdout. project_id is required (422 without it): the dispatch carries the project, and Run is the payer. Service-to-service only (OpenVibe.Run's Network service token); never granted to an app or mod and never delegated to people.",
"tags": [
"bot.job"
],
@@@ -648,7 +648,7 @@
"post": {
"operationId": "post_api_v1_machines_id_desk_leases",
"summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",
-- "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
++ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Link sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
"tags": [
"bot.desk"
],
@@@ -714,7 +714,7 @@
"delete": {
"operationId": "delete_api_v1_machines_id_desk_leases_desk",
"summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",
-- "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
++ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Link sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
"tags": [
"bot.desk"
],
@@@ -815,7 +815,7 @@
"get": {
"operationId": "get_api_v1_machines_id_desk_leases_desk",
"summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",
-- "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
++ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Link sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
"tags": [
"bot.desk"
],
@@@ -918,7 +918,7 @@
"post": {
"operationId": "post_api_v1_machines_id_desk_leases_desk_commands",
"summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",
-- "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.\n\nRoute note: bot.command@1, answered by bot.command-result@1",
++ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Link sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.\n\nRoute note: bot.command@1, answered by bot.command-result@1",
"tags": [
"bot.desk"
],
@@@ -2300,7 +2300,7 @@
},
"bot.desk-lease.v1": {
"title": "BotDeskLease",
-- "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
++ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe Link runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -2482,7 +2482,7 @@
},
"bot.job-dispatch-result.v1": {
"title": "BotJobDispatchResult",
-- "description": "bot.job-dispatch-result@1: what OpenVibe.Bot answers OpenVibe.Run's bot.job.dispatch calls (server/api/v1.js and server/jobs/index.js). POST /api/v1/jobs is 201 { job, sent }: `job` is Bot's run_jobs record and `sent` says whether the `job` frame went out on the device link now (false while the paired Node is away; it is resent on every reconnect until the Node acks it). POST /api/v1/jobs/:id/cancel is 200 { job, sent } with the same shape: `sent` says whether a `job_cancel` frame went out. GET /api/v1/jobs/:id is 200 { job, stdout }: the record plus the last 1 MiB of the job's captured stdout, or null when the Node sent none. The record's `state` follows the run_jobs lifecycle queued -> placed (the Node acked) -> running (job_started) -> one end state, and its `job` field is the platform.job@1 body Run dispatched, stored and resent verbatim. A refused dispatch answers a problem instead (422 bot.invalid_job, 404 bot.device_not_found, 409 bot.class_unadvertised, 409 bot.job_id_reused), never this contract.",
++ "description": "bot.job-dispatch-result@1: what OpenVibe.Bot answers OpenVibe.Run's bot.job.dispatch calls (server/api/v1.js and server/jobs/index.js). POST /api/v1/jobs is 201 { job, sent }: `job` is Bot's run_jobs record and `sent` says whether the `job` frame went out on the device link now (false while the paired Link is away; it is resent on every reconnect until the Link acks it). POST /api/v1/jobs/:id/cancel is 200 { job, sent } with the same shape: `sent` says whether a `job_cancel` frame went out. GET /api/v1/jobs/:id is 200 { job, stdout }: the record plus the last 1 MiB of the job's captured stdout, or null when the Link sent none. The record's `state` follows the run_jobs lifecycle queued -> placed (the Link acked) -> running (job_started) -> one end state, and its `job` field is the platform.job@1 body Run dispatched, stored and resent verbatim. A refused dispatch answers a problem instead (422 bot.invalid_job, 404 bot.device_not_found, 409 bot.class_unadvertised, 409 bot.job_id_reused), never this contract.",
"oneOf": [
{
"$ref": "#/components/schemas/bot.job-dispatch-result.v1/$defs/dispatched"
@@@ -2527,7 +2527,7 @@
"node_id": {
"type": "string",
"pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The paired device the job was sent to; a Node never reads another device's job."
++ "description": "The paired device the job was sent to; a Link never reads another device's job."
},
"class": {
"$ref": "#/components/schemas/platform.runtime-class.v1"
@@@ -2542,7 +2542,7 @@
"cancelled",
"expired"
],
-- "description": "queued (stored; sent and resent until acked), placed (the Node acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl)."
++ "description": "queued (stored; sent and resent until acked), placed (the Link acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl)."
},
"project_id": {
"type": "string",
@@@ -2575,7 +2575,7 @@
"null"
],
"maxLength": 64,
-- "description": "The Node's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable."
++ "description": "The Link's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable."
},
"sent_at": {
"type": [
@@@ -2591,7 +2591,7 @@
"null"
],
"minimum": 0,
-- "description": "The Node's job_started started_ms: the anchor of every usage second; null until it started."
++ "description": "The Link's job_started started_ms: the anchor of every usage second; null until it started."
},
"finished_at": {
"type": [
@@@ -2718,7 +2718,7 @@
"state": {
"type": "object",
"additionalProperties": false,
-- "description": "The answer of GET /api/v1/jobs/:id (200): the row and the job's held stdout (null when the Node sent none).",
++ "description": "The answer of GET /api/v1/jobs/:id (200): the row and the job's held stdout (null when the Link sent none).",
"required": [
"job",
"stdout"
@@@ -3833,7 +3833,7 @@
"id": {
"type": "string",
"pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Node command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`)."
++ "description": "Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Link command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`)."
},
"class": {
"$ref": "#/components/schemas/platform.runtime-class.v1"
@@@ -3903,7 +3903,7 @@
"openvibe-only"
],
"default": "deny",
-- "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe.Node's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
++ "description": "Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe Link's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker."
},
"inputs": {
"type": "array",
@@@ -4009,7 +4009,7 @@
},
"platform.runtime-class.v1": {
"title": "RuntimeClass",
-- "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe.Node's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
++ "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe Link's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
"$comment": "Additive: a class may be added to v1; none is removed, renamed or reordered.",
"type": "string",
"oneOf": [
@@@ -4045,7 +4045,7 @@
},
"run.session.v1": {
"title": "RunSession",
-- "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
++ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe Link runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex 19e1805b..820b0973 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -694,7 +694,7 @@
"post": {
"operationId": "post_api_streams_agent",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -750,7 +750,7 @@
"post": {
"operationId": "post_api_streams_agent_id_control",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -816,7 +816,7 @@
"get": {
"operationId": "get_api_streams_agent_id_controls",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -920,7 +920,7 @@
"post": {
"operationId": "post_api_streams_agent_id_curtain",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -986,7 +986,7 @@
"post": {
"operationId": "post_api_streams_agent_id_end",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1052,7 +1052,7 @@
"put": {
"operationId": "put_api_streams_agent_id_frame",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1184,7 +1184,7 @@
"get": {
"operationId": "get_api_streams_agent_id_state",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1288,7 +1288,7 @@
"delete": {
"operationId": "delete_api_streams_agent_id_ticket",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1390,7 +1390,7 @@
"post": {
"operationId": "post_api_streams_agent_id_ticket",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1456,7 +1456,7 @@
"post": {
"operationId": "post_api_streams_agent_desk_heartbeat",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1512,7 +1512,7 @@
"get": {
"operationId": "get_api_streams_agent_schedules",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1606,7 +1606,7 @@
"post": {
"operationId": "post_api_streams_agent_schedules",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1662,7 +1662,7 @@
"delete": {
"operationId": "delete_api_streams_agent_schedules_id",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1766,7 +1766,7 @@
"post": {
"operationId": "post_api_streams_agent_schedules_id_go",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -1832,7 +1832,7 @@
"post": {
"operationId": "post_api_streams_agent_schedules_id_remind",
"summary": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1).",
-- "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "**live.agent_stream.publish** (public): Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"tags": [
"live.agent_stream"
],
@@@ -3549,7 +3549,7 @@
},
"live.agent-stream-create-request.v1": {
"title": "LiveAgentStreamCreateRequest",
-- "description": "live.agent-stream-create-request@1 (the Agent Streams plan, S1): the body of POST /api/streams/agent on OpenVibe.Live (capability live.agent_stream.publish). It answers 201 with the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1): { stream, publish }. The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds live.agent_stream.publish for the audience openvibe.live; Network grants it to exactly those principals. Never a developer-app token, never another person's stream. TITLE is the card's title, TASK says what the agent is doing and where that task lives, VISIBILITY says who may watch, and ALLOW_SITES is the optional list of hosts a browser desk may navigate to (a desk that leaves the list raises the curtain).",
++ "description": "live.agent-stream-create-request@1 (the Agent Streams plan, S1): the body of POST /api/streams/agent on OpenVibe.Live (capability live.agent_stream.publish). It answers 201 with the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1): { stream, publish }. The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds live.agent_stream.publish for the audience openvibe.live; Network grants it to exactly those principals. Never a developer-app token, never another person's stream. TITLE is the card's title, TASK says what the agent is doing and where that task lives, VISIBILITY says who may watch, and ALLOW_SITES is the optional list of hosts a browser desk may navigate to (a desk that leaves the list raises the curtain).",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex dfc49bae..3ab49588 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -6007,7 +6007,7 @@
},
"network.machine.v1": {
"title": "NetworkMachine",
-- "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
++ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Link install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
"type": "object",
"required": [
"id",
@@@ -6044,7 +6044,7 @@
"os": {
"type": "string",
"maxLength": 40,
-- "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in."
++ "description": "The operating system the Link reports (linux, darwin, windows). Absent until the machine has checked in."
},
"online": {
"type": "boolean",
@@@ -6058,7 +6058,7 @@
"node_version": {
"type": "string",
"maxLength": 40,
-- "description": "The OpenVibe Node version the machine reports. Absent until it has checked in."
++ "description": "The OpenVibe Link version the machine reports. Absent until it has checked in."
},
"roles": {
"type": "array",
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex 8efe78f2..465b3913 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -1110,7 +1110,7 @@
},
"platform.runtime-class.v1": {
"title": "RuntimeClass",
-- "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe.Node's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
++ "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe Link's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
"$comment": "Additive: a class may be added to v1; none is removed, renamed or reordered.",
"type": "string",
"oneOf": [
@@@ -1283,7 +1283,7 @@
},
"run.job-create-request.v1": {
"title": "RunJobCreateRequest",
-- "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).",
++ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -1441,7 +1441,7 @@
"openvibe-only"
],
"default": "none",
-- "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
++ "description": "OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
},
"idempotency_key": {
"type": "string",
@@@ -2315,7 +2315,7 @@
},
"run.session.v1": {
"title": "RunSession",
-- "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
++ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe Link runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/generated/openapi/watch.json b/generated/openapi/watch.json
iindex 3d96159d..803c0354 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -2328,7 +2328,7 @@
},
"platform.runtime-class.v1": {
"title": "RuntimeClass",
-- "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe.Node's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
++ "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe Link's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
"$comment": "Additive: a class may be added to v1; none is removed, renamed or reordered.",
"type": "string",
"oneOf": [
@@@ -2501,7 +2501,7 @@
},
"run.job-create-request.v1": {
"title": "RunJobCreateRequest",
-- "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).",
++ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -2659,7 +2659,7 @@
"openvibe-only"
],
"default": "none",
-- "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
++ "description": "OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
},
"idempotency_key": {
"type": "string",
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 28c255f9..35da553b 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -87026,7 +87026,7 @@ export interface CodesModerationActionPayload {
/** [email protected] (owner: codes) */
/**
-- * codes.skill@1: one skill, rule, MCP server, command or hook in the OpenVibe.Codes hub (the Compute plan §5, E6). KIND says what it is: a skill is a SKILL.md folder the agent tools load, a rule is text for the tools' AGENTS.md/CLAUDE.md files, an mcp record is one MCP server definition, and command/hook are a slash command and a hook. SLUG is the folder name (lowercase letters, digits and hyphens) and is stable within one owner. VERSION is the newest record in codes.skill-version@1; BODY is that version's content. AUDIENCE is the four sharing audiences of openvibe-sdk/share: private (the owner only), people (the owner and the members listed in codes_skill_members), link (anyone with the link, not in the gallery) and public (the gallery, search and agents). A private, people or link record is never in the gallery. OFFICIAL marks OpenVibe's own skills. TOOLS names the harnesses the skill is written for; an empty list means every tool the Node knows. The body is untrusted text: the page renders it through the markup gate (escape first, then a small Markdown subset), never as HTML.
++ * codes.skill@1: one skill, rule, MCP server, command or hook in the OpenVibe.Codes hub (the Compute plan §5, E6). KIND says what it is: a skill is a SKILL.md folder the agent tools load, a rule is text for the tools' AGENTS.md/CLAUDE.md files, an mcp record is one MCP server definition, and command/hook are a slash command and a hook. SLUG is the folder name (lowercase letters, digits and hyphens) and is stable within one owner. VERSION is the newest record in codes.skill-version@1; BODY is that version's content. AUDIENCE is the four sharing audiences of openvibe-sdk/share: private (the owner only), people (the owner and the members listed in codes_skill_members), link (anyone with the link, not in the gallery) and public (the gallery, search and agents). A private, people or link record is never in the gallery. OFFICIAL marks OpenVibe's own skills. TOOLS names the harnesses the skill is written for; an empty list means every tool the Link knows. The body is untrusted text: the page renders it through the markup gate (escape first, then a small Markdown subset), never as HTML.
*/
export interface CodesSkill {
/**
@@@ -87121,7 +87121,7 @@ export interface CodesSkill {
/** [email protected] (owner: codes) */
/**
-- * codes.skill-version@1: one immutable version of a codes.skill@1 record in the OpenVibe.Codes skills hub. Every save writes a new version; VERSION starts at 1 and only grows, and SKILL_ID plus VERSION is the key. BODY and SUMMARY are the content at that version, TOOLS and AUDIENCE the settings it had, and NOTE says why it was written (created, edited, published, or rolled back to N). A rollback never rewrites an old row: it copies the chosen version into a new one, so the history of what every machine synced stays exact and the Node's state can name the version it wrote.
++ * codes.skill-version@1: one immutable version of a codes.skill@1 record in the OpenVibe.Codes skills hub. Every save writes a new version; VERSION starts at 1 and only grows, and SKILL_ID plus VERSION is the key. BODY and SUMMARY are the content at that version, TOOLS and AUDIENCE the settings it had, and NOTE says why it was written (created, edited, published, or rolled back to N). A rollback never rewrites an old row: it copies the chosen version into a new one, so the history of what every machine synced stays exact and the Link's state can name the version it wrote.
*/
export interface CodesSkillVersion {
skill_id: string;
@@@ -158267,11 +158267,11 @@ export type AiCredentialPut = {
[k: string]: unknown | undefined;
} & {
/**
-- * openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Node and OpenVibe.Bot (no key, no base_url, no cost).
++ * openai = any OpenAI-compatible endpoint. node = the person's own machine: a model on their own model server, through their OpenVibe Link and OpenVibe.Bot (no key, no base_url, no cost).
*/
provider: "openai" | "anthropic" | "node";
/**
-- * provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine.
++ * provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model (the ai plugin, contracts 0.219.0), or `any` (contracts 0.221.0): whichever of their machines is on, lets their AI use its models and offers the model, the fastest first, the next one when it is busy or away. Never anyone else's machine.
*/
machine?: string;
base_url?: string;
@@@ -158319,7 +158319,7 @@ export type AiCredentialPutV2 = {
*/
base_url?: string;
/**
-- * provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model, or `any` of their own.
++ * provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Link runs the model, or `any` of their own.
*/
machine?: string;
/**
@@@ -196546,7 +196546,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -196633,7 +196633,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -196719,7 +196719,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -196807,7 +196807,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -196893,7 +196893,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -196979,7 +196979,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197067,7 +197067,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197153,7 +197153,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197239,7 +197239,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197325,7 +197325,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197413,7 +197413,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197499,7 +197499,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197585,7 +197585,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197671,7 +197671,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197757,7 +197757,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197845,7 +197845,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -197931,7 +197931,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198017,7 +198017,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198103,7 +198103,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198189,7 +198189,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198275,7 +198275,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198363,7 +198363,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198449,7 +198449,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198535,7 +198535,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198621,7 +198621,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198707,7 +198707,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198793,7 +198793,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198879,7 +198879,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -198967,7 +198967,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199053,7 +199053,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199139,7 +199139,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199225,7 +199225,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199311,7 +199311,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199397,7 +199397,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199483,7 +199483,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199569,7 +199569,7 @@ export interface Watch {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199796,7 +199796,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199883,7 +199883,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -199969,7 +199969,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200057,7 +200057,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200143,7 +200143,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200229,7 +200229,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200317,7 +200317,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200403,7 +200403,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200489,7 +200489,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200575,7 +200575,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200663,7 +200663,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200749,7 +200749,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200835,7 +200835,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -200921,7 +200921,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201007,7 +201007,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201095,7 +201095,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201181,7 +201181,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201267,7 +201267,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201353,7 +201353,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201439,7 +201439,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201525,7 +201525,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201613,7 +201613,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201699,7 +201699,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201785,7 +201785,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201871,7 +201871,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -201957,7 +201957,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202043,7 +202043,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202129,7 +202129,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202217,7 +202217,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202303,7 +202303,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202389,7 +202389,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202475,7 +202475,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202561,7 +202561,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202647,7 +202647,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202733,7 +202733,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -202819,7 +202819,7 @@ export interface WatchRequest {
mem_bytes: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
ttl_ms?: number;
@@@ -209271,7 +209271,7 @@ export interface NetworkNodeListResult {
/** [email protected] (owner: network) */
/**
-- * network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.
++ * network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Link install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.
*/
export interface NetworkMachine {
/**
@@@ -209291,7 +209291,7 @@ export interface NetworkMachine {
*/
kind: "onboard" | "bridge" | "server";
/**
-- * The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in.
++ * The operating system the Link reports (linux, darwin, windows). Absent until the machine has checked in.
*/
os?: string;
/**
@@@ -209303,7 +209303,7 @@ export interface NetworkMachine {
*/
last_seen_at?: string;
/**
-- * The OpenVibe Node version the machine reports. Absent until it has checked in.
++ * The OpenVibe Link version the machine reports. Absent until it has checked in.
*/
node_version?: string;
/**
@@@ -209400,7 +209400,7 @@ export interface NetworkMachineRole {
/** [email protected] (owner: network) */
/**
-- * network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).
++ * network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Link turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).
*/
export interface NetworkMachineRoles {
/**
@@@ -209652,7 +209652,7 @@ export type ResourceOffer = {
*/
owner?: string;
/**
-- * e.g. node:http, worker:browser, worker:ffmpeg, worker:ai-gpu, events:gateway, events:durable, object:r2. Kind harness: task:edit, task:review, task:browse, task:run, task:test and task:plan for the task kinds it takes (its detail's task_capabilities), and harness:mcp, harness:resume, harness:host-access and harness:long-autonomy for its detail's capabilities that are true. The `worker:<class>` names that map to platform.runtime-class@1 values are reserved for OpenVibe.Node's job worker, one per class: worker:function, worker:code, worker:browser, worker:linux, worker:desktop and worker:gpu ($defs.reservedWorkerCapabilities). A node advertises the name of each class it runs (Node's status.capabilities.worker); every other `worker:` name (worker:ffmpeg, worker:ai-gpu) is an ordinary capability name. Beside them, a kind harness names `runtime:<class>` for each platform.runtime-class@1 class it can run (runtime:function, runtime:code, runtime:browser, runtime:linux, runtime:desktop, runtime:gpu); these are ordinary capability names, not reserved, and OpenVibe.Codes already advertises the classes it offers.
++ * e.g. node:http, worker:browser, worker:ffmpeg, worker:ai-gpu, events:gateway, events:durable, object:r2. Kind harness: task:edit, task:review, task:browse, task:run, task:test and task:plan for the task kinds it takes (its detail's task_capabilities), and harness:mcp, harness:resume, harness:host-access and harness:long-autonomy for its detail's capabilities that are true. The `worker:<class>` names that map to platform.runtime-class@1 values are reserved for OpenVibe Link's job worker, one per class: worker:function, worker:code, worker:browser, worker:linux, worker:desktop and worker:gpu ($defs.reservedWorkerCapabilities). A node advertises the name of each class it runs (Node's status.capabilities.worker); every other `worker:` name (worker:ffmpeg, worker:ai-gpu) is an ordinary capability name. Beside them, a kind harness names `runtime:<class>` for each platform.runtime-class@1 class it can run (runtime:function, runtime:code, runtime:browser, runtime:linux, runtime:desktop, runtime:gpu); these are ordinary capability names, not reserved, and OpenVibe.Codes already advertises the classes it offers.
*/
capabilities: string[];
capacity?: {
@@@ -210398,7 +210398,7 @@ export interface HarnessOffer {
/** [email protected] (owner: network) */
/**
-- * platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe.Node's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.
++ * platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe Link's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.
*/
export type RuntimeClass = ("function" | "code" | "browser" | "linux" | "desktop" | "gpu") & string;
@@@ -210410,7 +210410,7 @@ export type Job = {
[k: string]: unknown | undefined;
} & {
/**
-- * Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Node command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`).
++ * Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Link command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`).
*/
id: string;
class: RuntimeClass;
@@@ -210450,7 +210450,7 @@ export type Job = {
mem_bytes: number;
};
/**
-- * Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe.Node's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker.
++ * Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe Link's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker.
*/
net?: "deny" | "none" | "public" | "openvibe-only";
/**
@@@ -210468,7 +210468,7 @@ export type Job = {
/** [email protected] (owner: network) */
/**
-- * platform.job-frame@1: one job lifecycle frame on the device control link (plan T14 Run; ADR-034 proposed). The link is OpenVibe.Node's WebSocket to OpenVibe.Bot (Node docs/protocol.md section 2); Fabric may terminate it later. Each frame is one JSON object in one text message, with the link's envelope at the top level: `v` (1), `seq` (per-direction counter from 1 on each connection), `ts` (sender's clock, Unix ms) and `type`. Server to device: `job`, `job_cancel`, `job_exit_ack`. Device to server: `job_started`, `job_stdout`, `job_usage`, `job_exit`. The link's existing `ack`/`nack` frames, keyed by the job id, accept or refuse a `job`; a worker that runs jobs advertises its classes in `status.capabilities`. A running `function` job is metered per wall-clock second into platform.usage-sample@1 readings: see `job_usage` and `job_exit`.
++ * platform.job-frame@1: one job lifecycle frame on the device control link (plan T14 Run; ADR-034 proposed). The link is OpenVibe Link's WebSocket to OpenVibe.Bot (Link docs/protocol.md section 2); Fabric may terminate it later. Each frame is one JSON object in one text message, with the link's envelope at the top level: `v` (1), `seq` (per-direction counter from 1 on each connection), `ts` (sender's clock, Unix ms) and `type`. Server to device: `job`, `job_cancel`, `job_exit_ack`. Device to server: `job_started`, `job_stdout`, `job_usage`, `job_exit`. The link's existing `ack`/`nack` frames, keyed by the job id, accept or refuse a `job`; a worker that runs jobs advertises its classes in `status.capabilities`. A running `function` job is metered per wall-clock second into platform.usage-sample@1 readings: see `job_usage` and `job_exit`.
*/
export type JobFrame =
| {
@@@ -212162,7 +212162,7 @@ export interface BotRobotProfile {
/** [email protected] (owner: bot) */
/**
-- * bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).
++ * bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe Link internal/protocol/protocol.go (the Go structs of the device agent).
*/
export type BotDeviceMessage =
| {
@@@ -212222,7 +212222,7 @@ export type BotDeviceMessage =
| "browser"
| "screen_stream";
/**
-- * The command's value. screen_stream (OpenVibe.Node v0.12.0): {action: 'start', whip_url, publish_key, seconds? (30-1800, default 600), fps? (1-5, default 2)} or {action: 'stop'}: the machine's screen shown live on its own private OpenRestream stream. Bot alone fills in whip_url and publish_key (the machine's stream, playback private, never recorded); no operator, agent or service sends this kind.
++ * The command's value. screen_stream (OpenVibe Link v0.12.0): {action: 'start', whip_url, publish_key, seconds? (30-1800, default 600), fps? (1-5, default 2)} or {action: 'stop'}: the machine's screen shown live on its own private OpenRestream stream. Bot alone fills in whip_url and publish_key (the machine's stream, playback private, never recorded); no operator, agent or service sends this kind.
*/
value?: {};
/**
@@@ -212239,7 +212239,7 @@ export type BotDeviceMessage =
turn_epoch?: number;
robot_id?: string;
/**
-- * OpenVibe.Node only: the driver to send it to.
++ * OpenVibe Link only: the driver to send it to.
*/
target?: string;
}
@@@ -212370,7 +212370,7 @@ export type BotDeviceMessage =
id: string;
latency_ms?: number;
/**
-- * OpenVibe.Node: a computer command's answer (exec, fs, screen), at most 512 KB of JSON.
++ * OpenVibe Link: a computer command's answer (exec, fs, screen), at most 512 KB of JSON.
*/
data?: {};
}
@@@ -212391,7 +212391,7 @@ export type BotDeviceMessage =
t?: number;
rtt_ms?: number;
/**
-- * What this machine's coding agents have left (Codes C9c): each agent's id, whether it is installed and signed in, and each limit window's label, what it has used of its limit, and when it frees (RFC 3339 or empty when the agent's login file does not say). OpenVibe.Node reads it with the OpenVibe.Codes runner's own logins probe as the machine's own user and reports labels, numbers and times only: no key, token or account name. Absent means the machine has no reading (no OpenVibe.Codes runner, or its probe has not answered yet), never that nothing is installed.
++ * What this machine's coding agents have left (Codes C9c): each agent's id, whether it is installed and signed in, and each limit window's label, what it has used of its limit, and when it frees (RFC 3339 or empty when the agent's login file does not say). OpenVibe Link reads it with the OpenVibe.Codes runner's own logins probe as the machine's own user and reports labels, numbers and times only: no key, token or account name. Absent means the machine has no reading (no OpenVibe.Codes runner, or its probe has not answered yet), never that nothing is installed.
*
* @maxItems 16
*/
@@@ -261453,7 +261453,7 @@ export type BotDeviceMessage =
/** [email protected] (owner: bot) */
/**
-- * bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9). The computer kinds (exec, fs, screen, input, browser; OpenVibe.Node's computer plugin, Node docs/computer.md) are taken only for a person's own machine, from its owner or an agent the owner let use it (bot.computer.control); the machine's owner sets on the machine what they may do, and the answer comes back in bot.command-result@1's data. probe (a read-only check of something on the machine's own network) is the one computer kind OpenVibe.Watch may send, for owners who let their watches use the computer. input (added in 0.171.0) moves the mouse, clicks, scrolls, types or presses keys on the machine's screen, at coordinates in the space of the screen picture the caller last saw (its width and height; the Node scales them to the real screen): refused at level observe, approved one by one at ask. browser (added in 0.174.0) drives a Chromium-family browser the agent uses on the machine, in a profile of its own: open an http(s) URL, click a CSS selector or the button or link showing a text, fill a field, press a named key, go back, read the page, take a screenshot, or close it. read, screenshot and close run at every level; the rest act (refused at observe, approved one by one at ask), and below level full open refuses an address on the machine's own network. desk (added in 0.214.0; plan track K, Vibe's desk) starts, stops or reports a private desk on the machine: a display of its own (Xvfb, a window manager, its own session and browser profile) where an agent works without touching the person's screen, which the person can watch and take over; screen, input and browser take desk: private to act there instead of on the person's screen (main, the default). Starting and stopping a desk act (refused at observe, approved one by one at ask); status runs at every level.
++ * bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9). The computer kinds (exec, fs, screen, input, browser; OpenVibe Link's computer plugin, Link docs/computer.md) are taken only for a person's own machine, from its owner or an agent the owner let use it (bot.computer.control); the machine's owner sets on the machine what they may do, and the answer comes back in bot.command-result@1's data. probe (a read-only check of something on the machine's own network) is the one computer kind OpenVibe.Watch may send, for owners who let their watches use the computer. input (added in 0.171.0) moves the mouse, clicks, scrolls, types or presses keys on the machine's screen, at coordinates in the space of the screen picture the caller last saw (its width and height; the Link scales them to the real screen): refused at level observe, approved one by one at ask. browser (added in 0.174.0) drives a Chromium-family browser the agent uses on the machine, in a profile of its own: open an http(s) URL, click a CSS selector or the button or link showing a text, fill a field, press a named key, go back, read the page, take a screenshot, or close it. read, screenshot and close run at every level; the rest act (refused at observe, approved one by one at ask), and below level full open refuses an address on the machine's own network. desk (added in 0.214.0; plan track K, Vibe's desk) starts, stops or reports a private desk on the machine: a display of its own (Xvfb, a window manager, its own session and browser profile) where an agent works without touching the person's screen, which the person can watch and take over; screen, input and browser take desk: private to act there instead of on the person's screen (main, the default). Starting and stopping a desk act (refused at observe, approved one by one at ask); status runs at every level.
*/
export type BotCommand = {
[k: string]: unknown | undefined;
@@@ -261507,7 +261507,7 @@ export interface BotCommandResult {
/** [email protected] (owner: bot) */
/**
-- * bot.job-dispatch-result@1: what OpenVibe.Bot answers OpenVibe.Run's bot.job.dispatch calls (server/api/v1.js and server/jobs/index.js). POST /api/v1/jobs is 201 { job, sent }: `job` is Bot's run_jobs record and `sent` says whether the `job` frame went out on the device link now (false while the paired Node is away; it is resent on every reconnect until the Node acks it). POST /api/v1/jobs/:id/cancel is 200 { job, sent } with the same shape: `sent` says whether a `job_cancel` frame went out. GET /api/v1/jobs/:id is 200 { job, stdout }: the record plus the last 1 MiB of the job's captured stdout, or null when the Node sent none. The record's `state` follows the run_jobs lifecycle queued -> placed (the Node acked) -> running (job_started) -> one end state, and its `job` field is the platform.job@1 body Run dispatched, stored and resent verbatim. A refused dispatch answers a problem instead (422 bot.invalid_job, 404 bot.device_not_found, 409 bot.class_unadvertised, 409 bot.job_id_reused), never this contract.
++ * bot.job-dispatch-result@1: what OpenVibe.Bot answers OpenVibe.Run's bot.job.dispatch calls (server/api/v1.js and server/jobs/index.js). POST /api/v1/jobs is 201 { job, sent }: `job` is Bot's run_jobs record and `sent` says whether the `job` frame went out on the device link now (false while the paired Link is away; it is resent on every reconnect until the Link acks it). POST /api/v1/jobs/:id/cancel is 200 { job, sent } with the same shape: `sent` says whether a `job_cancel` frame went out. GET /api/v1/jobs/:id is 200 { job, stdout }: the record plus the last 1 MiB of the job's captured stdout, or null when the Link sent none. The record's `state` follows the run_jobs lifecycle queued -> placed (the Link acked) -> running (job_started) -> one end state, and its `job` field is the platform.job@1 body Run dispatched, stored and resent verbatim. A refused dispatch answers a problem instead (422 bot.invalid_job, 404 bot.device_not_found, 409 bot.class_unadvertised, 409 bot.job_id_reused), never this contract.
*/
export type BotJobDispatchResult =
| {
@@@ -261520,12 +261520,12 @@ export type BotJobDispatchResult =
*/
id: string;
/**
-- * The paired device the job was sent to; a Node never reads another device's job.
++ * The paired device the job was sent to; a Link never reads another device's job.
*/
node_id: string;
class: RuntimeClass;
/**
-- * queued (stored; sent and resent until acked), placed (the Node acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl).
++ * queued (stored; sent and resent until acked), placed (the Link acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl).
*/
state: "queued" | "placed" | "running" | "succeeded" | "failed" | "cancelled" | "expired";
/**
@@@ -261545,7 +261545,7 @@ export type BotJobDispatchResult =
*/
cancel_requested: boolean;
/**
-- * The Node's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable.
++ * The Link's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable.
*/
fault_code: string | null;
/**
@@@ -261553,7 +261553,7 @@ export type BotJobDispatchResult =
*/
sent_at: number | null;
/**
-- * The Node's job_started started_ms: the anchor of every usage second; null until it started.
++ * The Link's job_started started_ms: the anchor of every usage second; null until it started.
*/
started_ms: number | null;
/**
@@@ -261610,12 +261610,12 @@ export type BotJobDispatchResult =
*/
id: string;
/**
-- * The paired device the job was sent to; a Node never reads another device's job.
++ * The paired device the job was sent to; a Link never reads another device's job.
*/
node_id: string;
class: RuntimeClass;
/**
-- * queued (stored; sent and resent until acked), placed (the Node acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl).
++ * queued (stored; sent and resent until acked), placed (the Link acked it), running (job_started), then one end state from job_exit: succeeded (exited 0), failed (another exit code, or reason limit, stopped or failed, or a nack), cancelled (a cancel before start, or reason cancelled), expired (reason ttl).
*/
state: "queued" | "placed" | "running" | "succeeded" | "failed" | "cancelled" | "expired";
/**
@@@ -261635,7 +261635,7 @@ export type BotJobDispatchResult =
*/
cancel_requested: boolean;
/**
-- * The Node's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable.
++ * The Link's nack fault code, or why Bot stopped sending the job (bot.class_unadvertised); null while it is still sendable.
*/
fault_code: string | null;
/**
@@@ -261643,7 +261643,7 @@ export type BotJobDispatchResult =
*/
sent_at: number | null;
/**
-- * The Node's job_started started_ms: the anchor of every usage second; null until it started.
++ * The Link's job_started started_ms: the anchor of every usage second; null until it started.
*/
started_ms: number | null;
/**
@@@ -261821,7 +261821,7 @@ export interface BotCommandRefusedPayload {
/** [email protected] (owner: run) */
/**
-- * browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.
++ * browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.
*/
export interface BrowserProviderJobCreateRequest {
/**
@@@ -261868,7 +261868,7 @@ export interface BrowserProviderJobCreateRequest {
mem_bytes?: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
}
@@@ -261947,7 +261947,7 @@ export interface BrowserProviderJobState {
/** [email protected] (owner: run) */
/**
-- * sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is code or linux, the platform.runtime-class@1 classes a sandbox hosts. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is sandbox-provider.job-create-result@1.
++ * sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is code or linux, the platform.runtime-class@1 classes a sandbox hosts. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is sandbox-provider.job-create-result@1.
*/
export interface SandboxProviderJobCreateRequest {
/**
@@@ -261994,7 +261994,7 @@ export interface SandboxProviderJobCreateRequest {
mem_bytes?: number;
};
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
}
@@@ -262073,7 +262073,7 @@ export interface SandboxProviderJobState {
/** [email protected] (owner: run) */
/**
-- * run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).
++ * run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe Link's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict. NODE pins the machine: the device id (dev_…) of the job's own machine in Run's placement (platform.resource-offer@1 node_id, as GET /api/v1/machines on OpenVibe.Bot names it), answered by 409 run.job.node_offline when that machine is not connected, never moved to another one (Codes C9c).
*/
export type RunJobCreateRequest = {
[k: string]: unknown | undefined;
@@@ -262132,7 +262132,7 @@ export type RunJobCreateRequest = {
*/
ttl_ms?: number;
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress?: "none" | "public" | "openvibe-only";
requirements?: WorkloadRequirements;
@@@ -262217,7 +262217,7 @@ export type RunJobReadResult = {
*/
ttl_ms: number;
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
requirements: WorkloadRequirements;
@@@ -262392,7 +262392,7 @@ export interface RunJobQueuedPayload {
version: string;
} | null;
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
placement: null;
@@@ -262442,7 +262442,7 @@ export interface RunJobStartedPayload {
version: string;
} | null;
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
placement: {
@@@ -262502,7 +262502,7 @@ export interface RunJobSucceededPayload {
version: string;
} | null;
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
placement: {
@@@ -262574,7 +262574,7 @@ export interface RunJobFailedPayload {
version: string;
} | null;
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
placement: {
@@@ -262649,7 +262649,7 @@ export interface RunJobCancelledPayload {
version: string;
} | null;
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
placement: {
@@@ -262718,7 +262718,7 @@ export interface RunJobExpiredPayload {
version: string;
} | null;
/**
-- * OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
++ * OpenVibe Link worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only).
*/
egress: "none" | "public" | "openvibe-only";
placement: {
@@@ -274351,7 +274351,7 @@ export interface ChatInboxConversationsResult {
/** [email protected] (owner: run) */
/**
-- * run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).
++ * run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe Link runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).
*/
export type RunSession = {
[k: string]: unknown | undefined;
@@@ -274720,7 +274720,7 @@ export interface RunSessionEndedPayload {
/** [email protected] (owner: bot) */
/**
-- * bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).
++ * bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe Link runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).
*/
export interface BotDeskLease {
machine_id: string;
@@@ -293097,7 +293097,7 @@ export interface AiMemoryItem {
created_at: string;
updated_at: string;
/**
-- * The Node machine id it came from, when it came from one.
++ * The Link machine id it came from, when it came from one.
*/
machine?: string | null;
/**
@@@ -293223,7 +293223,7 @@ export interface AiMemoryExportV1 {
status?: "active" | "suggested" | "archived" | "conflict";
confidence?: number;
/**
-- * The Node machine id the item came from, when it came from one.
++ * The Link machine id the item came from, when it came from one.
*/
machine?: string | null;
seen_at?: string;
@@@ -293508,7 +293508,7 @@ export interface WebsiteShare {
*/
protocol: "http" | "https" | "tcp" | "udp";
/**
-- * The local target the Node may forward to, e.g. 127.0.0.1:3000, tcp://127.0.0.1:25565 or udp://127.0.0.1:19132. Loopback by default; a LAN target is allowed only when the owner enabled it locally.
++ * The local target the Link may forward to, e.g. 127.0.0.1:3000, tcp://127.0.0.1:25565 or udp://127.0.0.1:19132. Loopback by default; a LAN target is allowed only when the owner enabled it locally.
*/
local_target: string;
/**
@@@ -293553,7 +293553,7 @@ export interface WebsiteShareCreateRequest {
name: string;
protocol: "http" | "https" | "tcp" | "udp";
/**
-- * The local port the Node exposes, e.g. 127.0.0.1:3000; never an arbitrary internet host.
++ * The local port the Link exposes, e.g. 127.0.0.1:3000; never an arbitrary internet host.
*/
local_target: string;
/**
@@@ -293579,7 +293579,7 @@ export interface WebsiteShareCreateRequest {
/** [email protected] (owner: connect) */
/**
-- * website.share-connect-request@1: what a Node presents to open the tunnel for one share (POST /api/v1/shares/:id/tunnel, capability website.share.connect). The Node proves its Network node principal with a client-credentials token whose audience is openvibe.website, and this short-lived, single-use tunnel_token minted by Connect binds the connection to one share. The edge checks both; neither alone opens the route. The token is presented once and never stored, logged or returned.
++ * website.share-connect-request@1: what a Link presents to open the tunnel for one share (POST /api/v1/shares/:id/tunnel, capability website.share.connect). The Link proves its Network node principal with a client-credentials token whose audience is openvibe.website, and this short-lived, single-use tunnel_token minted by Connect binds the connection to one share. The edge checks both; neither alone opens the route. The token is presented once and never stored, logged or returned.
*/
export interface WebsiteShareConnectRequest {
share_id: string;
@@@ -293592,15 +293592,15 @@ export interface WebsiteShareConnectRequest {
*/
tunnel_token: string;
/**
-- * The data-plane transport the Node is trying. quic is ALPN ovconnect/1; http2-ws is the 443 fallback when UDP is blocked.
++ * The data-plane transport the Link is trying. quic is ALPN ovconnect/1; http2-ws is the 443 fallback when UDP is blocked.
*/
transport?: "quic" | "http2-ws";
/**
-- * The Node release, for posture and diagnostics; never used for authorization.
++ * The Link release, for posture and diagnostics; never used for authorization.
*/
node_version?: string;
/**
-- * The edge region the Node is dialing; the edge refuses a region it does not serve.
++ * The edge region the Link is dialing; the edge refuses a region it does not serve.
*/
region?: string;
}
@@@ -293868,7 +293868,7 @@ export interface ConnectDevice {
os: "linux" | "macos" | "windows" | "ios" | "android" | "other";
arch?: "amd64" | "arm64" | "386" | "arm" | "other";
/**
-- * The Node release running on the device.
++ * The Link release running on the device.
*/
version: string;
/**
@@@ -295713,7 +295713,7 @@ export interface EventsHook {
signature: {
version: "v2";
/**
-- * How far a signed timestamp may drift before the Node refuses it; 300 is the default.
++ * How far a signed timestamp may drift before the Link refuses it; 300 is the default.
*/
tolerance_seconds: number;
};
@@@ -296540,7 +296540,7 @@ export interface LiveAgentStream {
/** [email protected] (owner: live) */
/**
-- * live.agent-stream-create-request@1 (the Agent Streams plan, S1): the body of POST /api/streams/agent on OpenVibe.Live (capability live.agent_stream.publish). It answers 201 with the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1): { stream, publish }. The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds live.agent_stream.publish for the audience openvibe.live; Network grants it to exactly those principals. Never a developer-app token, never another person's stream. TITLE is the card's title, TASK says what the agent is doing and where that task lives, VISIBILITY says who may watch, and ALLOW_SITES is the optional list of hosts a browser desk may navigate to (a desk that leaves the list raises the curtain).
++ * live.agent-stream-create-request@1 (the Agent Streams plan, S1): the body of POST /api/streams/agent on OpenVibe.Live (capability live.agent_stream.publish). It answers 201 with the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1): { stream, publish }. The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds live.agent_stream.publish for the audience openvibe.live; Network grants it to exactly those principals. Never a developer-app token, never another person's stream. TITLE is the card's title, TASK says what the agent is doing and where that task lives, VISIBILITY says who may watch, and ALLOW_SITES is the optional list of hosts a browser desk may navigate to (a desk that leaves the list raises the curtain).
*/
export interface LiveAgentStreamCreateRequest {
title: string;
@@@ -299237,7 +299237,7 @@ export interface ChatParty {
/** [email protected] (owner: bot) */
/**
-- * bot.machine-pool@1 (the pooled machines design, 2026-11-06): several of one owner's machines on the same local network that serve one model together through llama.cpp RPC, `rpc-server` on the members and `llama-server --rpc` on the head. OpenVibe.Network holds the record: the owner proposes it on /machines (POST /api/v1/machine-pools, two to eight of their own machines and an optional head; the default head has the most free VRAM), and each machine reads it with its own node token (GET /api/v1/node/self/pools/:id, network.node.self.manage) and approves it locally (POST /api/v1/node/self/pools/:id/approve with { certificate }). A machine's `certificate` is null until it approves on that machine: the Node generates an Ed25519 key there, keeps the private key, and uploads only its self-signed public certificate, which the other members pin for the pool's private link. A pool with any null certificate never serves. The record never carries a private key or a prompt. What a schema cannot say, Network and Node check at run time: the members are distinct and the head is one of them; every member has the pool's owner, today, through Bot; every member was seen from the same public address with a private address in the same subnet; the head's direct link probe to each member passes; each machine's certificate is its own; and any change of membership, owner, head or key revokes the pool, so all machines approve a new one. A machine that leaves (removed, signed out, a different owner) drops its link and its key at once.
++ * bot.machine-pool@1 (the pooled machines design, 2026-11-06): several of one owner's machines on the same local network that serve one model together through llama.cpp RPC, `rpc-server` on the members and `llama-server --rpc` on the head. OpenVibe.Network holds the record: the owner proposes it on /machines (POST /api/v1/machine-pools, two to eight of their own machines and an optional head; the default head has the most free VRAM), and each machine reads it with its own node token (GET /api/v1/node/self/pools/:id, network.node.self.manage) and approves it locally (POST /api/v1/node/self/pools/:id/approve with { certificate }). A machine's `certificate` is null until it approves on that machine: the Link generates an Ed25519 key there, keeps the private key, and uploads only its self-signed public certificate, which the other members pin for the pool's private link. A pool with any null certificate never serves. The record never carries a private key or a prompt. What a schema cannot say, Network and Node check at run time: the members are distinct and the head is one of them; every member has the pool's owner, today, through Bot; every member was seen from the same public address with a private address in the same subnet; the head's direct link probe to each member passes; each machine's certificate is its own; and any change of membership, owner, head or key revokes the pool, so all machines approve a new one. A machine that leaves (removed, signed out, a different owner) drops its link and its key at once.
*/
export interface BotMachinePool {
/**
@@@ -300912,7 +300912,7 @@ export interface AiOfferPool {
/** [email protected] (owner: ai) */
/**
-- * ai.offer@1: one community AI offer, a person's machine (OpenVibe.Node's ai plugin, "Offer to the network") serving its local models to requests that opt in to community capacity. OpenVibe.Bot reports these fields (POST /internal/ai/offers/report on Network, network.resource.report; a report with any other field is refused whole), and OpenVibe.AI reads them from Network (GET /internal/ai/offers, network.registry.read, { offers, generated_at, count }). Network adds `pool` (ai.offer-pool@1) to the head's offer when the machine is the head of an approved pool (bot.machine-pool@1), and leaves a pool member's own offer out of the list while it is in a pool. For a pool the hours, audience, price, readiness, idle-only and verification apply to the whole pool: it is ready only while every member has approved, renewed its readiness in the last few seconds, and is still the owner's. Nothing here is public: AI shows Exchange a hashed offer id and no owner or machine id.
++ * ai.offer@1: one community AI offer, a person's machine (OpenVibe Link's ai plugin, "Offer to the network") serving its local models to requests that opt in to community capacity. OpenVibe.Bot reports these fields (POST /internal/ai/offers/report on Network, network.resource.report; a report with any other field is refused whole), and OpenVibe.AI reads them from Network (GET /internal/ai/offers, network.registry.read, { offers, generated_at, count }). Network adds `pool` (ai.offer-pool@1) to the head's offer when the machine is the head of an approved pool (bot.machine-pool@1), and leaves a pool member's own offer out of the list while it is in a pool. For a pool the hours, audience, price, readiness, idle-only and verification apply to the whole pool: it is ready only while every member has approved, renewed its readiness in the last few seconds, and is still the owner's. Nothing here is public: AI shows Exchange a hashed offer id and no owner or machine id.
*/
export interface AiOffer {
/**
@@@ -307087,7 +307087,7 @@ export interface ActorSkill {
/** [email protected] (owner: actor) */
/**
-- * actor.skill@2: a skill, the document an agent loads to do one kind of job well (ADR-058, ADR-060). It keeps every field and meaning of actor.skill@1 and adds a way for the skill to bring CODE that runs in a sandbox: RUNS is prompt (as @1, text and instructions only) or sandbox. A sandbox skill carries its code as ordinary TEXT resources (a .js path with media_type text/javascript, at most 64 KiB each, in the integrity hash); TOOLS (1 to 5, only when runs is sandbox) say what an agent may call: a name, a description the agent reads, language javascript (the only one in v1), an ENTRY naming one of the skill's own .js resources, ARGS as a JSON Schema subset (an object whose properties are string, number, integer, boolean or an array of those; enum, maxLength, and pattern at most 200 characters; no $ref and no nested object), NET (deny by default, openvibe-only, public) and LIMITS (wall_ms at most 30000, default 10000; mem_bytes at most 268435456, default 134217728). A sandbox skill's requires.kinds names skill.run; a prompt skill declares no tools. A skill GRANTS NOTHING: requires only declares; the agent's level and approvals decide every step (actor approvals, HO1), and a step of a kind the skill did not declare is a scan finding (actor.skill-listing@2). Code never runs on an OpenVibe server and never sees a secret: it runs as a Run job of class function on a Node that declared the skill runner, with the network its tool declared and the exact bytes the registry scanned. The INTEGRITY block is a sha256 over the canonical JSON of the document: it shows the file was not edited after it was written and says nothing about who wrote it, so the AUTHOR is a claim; the registry's listing is the only place a publisher is vouched for. The canonical JSON is the document's own fields, in this order: skill, slug, name, description, version, instructions, resources, tools, requires, runs, license, author; an optional field that is absent or empty is left out; resources are [{ path, media_type, text }] in the document's order; tools are [{ name, description, language, entry, args, net, limits }] in the document's order, with an absent net or limits (and each absent limits field) left out and args as the object the document wrote; requires is { kinds, apps, hosts } with empty lists left out; author is { subject } or { subject, name }; the value is the compact JSON of that object (no spaces). CREATED_AT is not hashed.
++ * actor.skill@2: a skill, the document an agent loads to do one kind of job well (ADR-058, ADR-060). It keeps every field and meaning of actor.skill@1 and adds a way for the skill to bring CODE that runs in a sandbox: RUNS is prompt (as @1, text and instructions only) or sandbox. A sandbox skill carries its code as ordinary TEXT resources (a .js path with media_type text/javascript, at most 64 KiB each, in the integrity hash); TOOLS (1 to 5, only when runs is sandbox) say what an agent may call: a name, a description the agent reads, language javascript (the only one in v1), an ENTRY naming one of the skill's own .js resources, ARGS as a JSON Schema subset (an object whose properties are string, number, integer, boolean or an array of those; enum, maxLength, and pattern at most 200 characters; no $ref and no nested object), NET (deny by default, openvibe-only, public) and LIMITS (wall_ms at most 30000, default 10000; mem_bytes at most 268435456, default 134217728). A sandbox skill's requires.kinds names skill.run; a prompt skill declares no tools. A skill GRANTS NOTHING: requires only declares; the agent's level and approvals decide every step (actor approvals, HO1), and a step of a kind the skill did not declare is a scan finding (actor.skill-listing@2). Code never runs on an OpenVibe server and never sees a secret: it runs as a Run job of class function on a Link that declared the skill runner, with the network its tool declared and the exact bytes the registry scanned. The INTEGRITY block is a sha256 over the canonical JSON of the document: it shows the file was not edited after it was written and says nothing about who wrote it, so the AUTHOR is a claim; the registry's listing is the only place a publisher is vouched for. The canonical JSON is the document's own fields, in this order: skill, slug, name, description, version, instructions, resources, tools, requires, runs, license, author; an optional field that is absent or empty is left out; resources are [{ path, media_type, text }] in the document's order; tools are [{ name, description, language, entry, args, net, limits }] in the document's order, with an absent net or limits (and each absent limits field) left out and args as the object the document wrote; requires is { kinds, apps, hosts } with empty lists left out; author is { subject } or { subject, name }; the value is the compact JSON of that object (no spaces). CREATED_AT is not hashed.
*/
export type ActorSkillV2 = {
[k: string]: unknown | undefined;
ddiff --git a/manifests/capabilities/bot.computer.control.json b/manifests/capabilities/bot.computer.control.json
iindex d3651687..ca0ce9a1 100644
---- a/manifests/capabilities/bot.computer.control.json
++++ b/manifests/capabilities/bot.computer.control.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "first-party",
"sensitive": true,
-- "description": "Use a person's own computer with a Network service token naming that person in X-OV-Subject: the computer kinds of bot.command@1 (exec, fs, screen, input, browser) through POST /api/v1/robots/:id/commands, for a machine (openvibe.bot/machines) whose owner is that person and who turned on 'Let my agent use this computer'. Bot takes them from no one else (never an operator or a queue member), and the machine's owner sets on the machine itself what may happen (OpenVibe.Node's computer plugin: observe, ask, trusted or full; ask waits for the owner's approval there). The answer is bot.command-result@1's data; every command is audited, a refused one emits bot.command.refused. OpenVibe.Watch (a `node` source) may send only `probe` — one GET or one TCP connect to a private, loopback or link-local address from the machine — and only where the owner turned on \"Let my watches check things from this computer\". The live screen (screen_stream) is not part of it: only the machine's owner starts it, on openvibe.bot, and Bot fills in the machine's private stream.",
++ "description": "Use a person's own computer with a Network service token naming that person in X-OV-Subject: the computer kinds of bot.command@1 (exec, fs, screen, input, browser) through POST /api/v1/robots/:id/commands, for a machine (openvibe.bot/machines) whose owner is that person and who turned on 'Let my agent use this computer'. Bot takes them from no one else (never an operator or a queue member), and the machine's owner sets on the machine itself what may happen (OpenVibe Link's computer plugin: observe, ask, trusted or full; ask waits for the owner's approval there). The answer is bot.command-result@1's data; every command is audited, a refused one emits bot.command.refused. OpenVibe.Watch (a `node` source) may send only `probe` — one GET or one TCP connect to a private, loopback or link-local address from the machine — and only where the owner turned on \"Let my watches check things from this computer\". The live screen (screen_stream) is not part of it: only the machine's owner starts it, on openvibe.bot, and Bot fills in the machine's private stream.",
"permissions": [
"bot:computer:control"
],
ddiff --git a/manifests/capabilities/bot.desk.session.json b/manifests/capabilities/bot.desk.session.json
iindex 764bf62f..4d61f953 100644
---- a/manifests/capabilities/bot.desk.session.json
++++ b/manifests/capabilities/bot.desk.session.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "first-party",
"sensitive": true,
-- "description": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
++ "description": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Link sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
"inputSchema": "bot.desk-lease-create-request@1",
"outputSchema": "bot.desk-lease@1",
"permissions": [
ddiff --git a/manifests/capabilities/bot.job.dispatch.json b/manifests/capabilities/bot.job.dispatch.json
iindex b4f82bea..c0697030 100644
---- a/manifests/capabilities/bot.job.dispatch.json
++++ b/manifests/capabilities/bot.job.dispatch.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "internal",
"sensitive": true,
-- "description": "OpenVibe.Run hands a platform.job@1 to a paired Node over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 bot.class_unadvertised when the device does not advertise the job's class), POST /api/v1/jobs/:id/cancel cancels it, GET /api/v1/jobs/:id returns its state with the captured stdout. project_id is required (422 without it): the dispatch carries the project, and Run is the payer. Service-to-service only (OpenVibe.Run's Network service token); never granted to an app or mod and never delegated to people.",
++ "description": "OpenVibe.Run hands a platform.job@1 to a paired Link over the device link and reads its state: POST /api/v1/jobs {node_id, job, project_id, subject, provider} dispatches it (idempotent by job id; 409 bot.class_unadvertised when the device does not advertise the job's class), POST /api/v1/jobs/:id/cancel cancels it, GET /api/v1/jobs/:id returns its state with the captured stdout. project_id is required (422 without it): the dispatch carries the project, and Run is the payer. Service-to-service only (OpenVibe.Run's Network service token); never granted to an app or mod and never delegated to people.",
"permissions": [
"bot:jobs"
],
ddiff --git a/manifests/capabilities/bot.machine.ai.json b/manifests/capabilities/bot.machine.ai.json
iindex 6183c7a2..450ffdb4 100644
---- a/manifests/capabilities/bot.machine.ai.json
++++ b/manifests/capabilities/bot.machine.ai.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "first-party",
"sensitive": true,
-- "description": "Run a model on a person's own machine for them (the AI-on-your-machines plan, the roadmap's user-owned GPU route): bot.command@1 kind ai through POST /api/v1/machines/:id/commands, with a Network service token naming that person in X-OV-Subject, for a machine whose owner is that person and who turned on 'Let my AI use this computer's models'. The machine's OpenVibe.Node relays the chat completion to its own local model server and answers with the reply; it reads and changes nothing else there. Bot takes it from no one else, never places one person's request on another's machine, and audits the model and token counts, never the text.",
++ "description": "Run a model on a person's own machine for them (the AI-on-your-machines plan, the roadmap's user-owned GPU route): bot.command@1 kind ai through POST /api/v1/machines/:id/commands, with a Network service token naming that person in X-OV-Subject, for a machine whose owner is that person and who turned on 'Let my AI use this computer's models'. The machine's OpenVibe Link relays the chat completion to its own local model server and answers with the reply; it reads and changes nothing else there. Bot takes it from no one else, never places one person's request on another's machine, and audits the model and token counts, never the text.",
"permissions": [
"bot:machine:ai"
],
ddiff --git a/manifests/capabilities/live.agent_stream.publish.json b/manifests/capabilities/live.agent_stream.publish.json
iindex db24e867..224b5f02 100644
---- a/manifests/capabilities/live.agent_stream.publish.json
++++ b/manifests/capabilities/live.agent_stream.publish.json
@@@ -4,7 +4,7 @@
"owner": "live",
"status": "active",
"visibility": "public",
-- "description": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Node principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
++ "description": "Publish an agent's stream on OpenVibe.Live (the Agent Streams plan, S1). POST /api/streams/agent creates the agent stream and its publish ticket, POST /api/streams/agent/:id/ticket renews that ticket, DELETE /api/streams/agent/:id/ticket revokes it (the kill switch: Live ends the WHIP session at once), POST /api/streams/agent/:id/curtain raises the desk's privacy screen, PUT /api/streams/agent/:id/frame sends the last frame while Live wants no video, POST /api/streams/agent/:id/end ends the stream, and GET /api/streams/agent/:id/state tells the desk whether to encode, and POST /api/streams/agent/:id/control is one of the four controls (pause, curtain, mute, end) with the moderator rules of the Agent Streams plan, S5. The publish capability also covers the step credit: a step may name the suggestion it took (\"Suggested by @mira\"). The caller is the person's own session token, or a first-party service token acting for that person (a Link principal acting for its owner, Codes or Actor) that holds this capability for the audience openvibe.live: Network grants it to exactly those principals. Never a developer-app token, never another person's stream. The Agent Streams design calls it live.agent-stream.publish; a capability id in this system carries no hyphen (the capability pattern and the token claims' cap pattern both allow letters, digits and underscores only), so the id is live.agent_stream.publish. S7 adds the owner's schedules: POST /api/streams/agent/schedules writes one, GET /api/streams/agent/schedules lists the caller's, DELETE /api/streams/agent/schedules/:id cancels one, POST /api/streams/agent/schedules/:id/remind opts the caller in to its reminder, POST /api/streams/agent/schedules/:id/go is the owner's Go for a chatplays schedule, and POST /api/streams/agent/desk/heartbeat says the owner's desk is online so a due schedule can start. A schedule that finds the desk offline is recorded as missed ('didn't start: the desk was offline') and starts nothing.",
"inputSchema": "live.agent-stream-create-request@1",
"outputSchema": "live.agent-stream-publish@1",
"permissions": [
ddiff --git a/manifests/capabilities/website.share.connect.json b/manifests/capabilities/website.share.connect.json
iindex 455e2ee0..6be2856d 100644
---- a/manifests/capabilities/website.share.connect.json
++++ b/manifests/capabilities/website.share.connect.json
@@@ -5,7 +5,7 @@
"status": "planned",
"visibility": "first-party",
"sensitive": true,
-- "description": "PLANNED for OpenVibe.Website Connect (audience openvibe.website, node principal): a Node opens one share's tunnel with POST /api/v1/shares/:id/tunnel and a website.share-connect-request@1. The edge checks the Network node token (client credentials, subject node:nod_…, audience openvibe.website) and the short-lived, single-use tunnel token minted by Connect; neither alone opens the route. The node token's subject must equal node:<node_id> from the request, and a mismatch is refused and audited. Not delegated to user or app tokens.",
++ "description": "PLANNED for OpenVibe.Website Connect (audience openvibe.website, node principal): a Link opens one share's tunnel with POST /api/v1/shares/:id/tunnel and a website.share-connect-request@1. The edge checks the Network node token (client credentials, subject node:nod_…, audience openvibe.website) and the short-lived, single-use tunnel token minted by Connect; neither alone opens the route. The node token's subject must equal node:<node_id> from the request, and a mismatch is refused and audited. Not delegated to user or app tokens.",
"permissions": [
"website:tunnels:connect"
],
ddiff --git a/manifests/capabilities/website.share.create.json b/manifests/capabilities/website.share.create.json
iindex 0b4696d6..a05d5c81 100644
---- a/manifests/capabilities/website.share.create.json
++++ b/manifests/capabilities/website.share.create.json
@@@ -5,7 +5,7 @@
"status": "planned",
"visibility": "public",
"sensitive": true,
-- "description": "PLANNED for OpenVibe.Website Connect (audience openvibe.website, owner or project scope): create a Share with POST /api/v1/shares from a website.share-create-request@1 — one DNS label, protocol, local target and policy. The POST returns only website.share@1 in draft, with no tunnel token. CON-5 defines the separate authenticated exchange that gives the Node a short-lived, single-use, share-scoped tunnel token; it is never stored, logged or put in an event. A public share stays in review until the abuse screen passes; no capability is granted by this contract.",
++ "description": "PLANNED for OpenVibe.Website Connect (audience openvibe.website, owner or project scope): create a Share with POST /api/v1/shares from a website.share-create-request@1 — one DNS label, protocol, local target and policy. The POST returns only website.share@1 in draft, with no tunnel token. CON-5 defines the separate authenticated exchange that gives the Link a short-lived, single-use, share-scoped tunnel token; it is never stored, logged or put in an event. A public share stays in review until the abuse screen passes; no capability is granted by this contract.",
"permissions": [
"website:shares:create"
],
ddiff --git a/manifests/products/openvibe.actor.json b/manifests/products/openvibe.actor.json
iindex 4ab60928..9f1f20ad 100644
---- a/manifests/products/openvibe.actor.json
++++ b/manifests/products/openvibe.actor.json
@@@ -5,7 +5,7 @@
"icon": "fa-user-astronaut",
"accent": "#a78bfa",
"tagline": "Every AI agent, every device, one command console",
-- "description": "OpenVibe.Actor sends each task to the best agent for it, across hosted agent platforms, open-source runtimes, coding agents and OpenVibe's own, balanced live on price, speed and success. Install the OpenVibe Node on your computers, servers, Raspberry Pis and robots, and your agents can work on them for real, at the control level you choose, with a kill switch you hold. Run everything from one console, or host the whole thing yourself.",
++ "description": "OpenVibe.Actor sends each task to the best agent for it, across hosted agent platforms, open-source runtimes, coding agents and OpenVibe's own, balanced live on price, speed and success. Install the OpenVibe Link on your computers, servers, Raspberry Pis and robots, and your agents can work on them for real, at the control level you choose, with a kill switch you hold. Run everything from one console, or host the whole thing yourself.",
"pillars": [
{
"title": "Your agents",
@@@ -51,7 +51,7 @@
},
{
"question": "Can I run Actor myself?",
-- "answer": "Yes. The router, the runtime, the console and the Node are open source and run on your own server with local models, and can link to openvibe.actor when you want hosted agents or the rest of the network."
++ "answer": "Yes. The router, the runtime, the console and the Link are open source and run on your own server with local models, and can link to openvibe.actor when you want hosted agents or the rest of the network."
},
{
"question": "What does it cost?",
@@@ -204,7 +204,7 @@
{
"id": "devices",
"title": "Your devices, under control",
-- "lead": "Install the OpenVibe Node on a computer, a server, a Raspberry Pi or a phone. No open ports: it connects out. You decide how much an agent may do.",
++ "lead": "Install the OpenVibe Link on a computer, a server, a Raspberry Pi or a phone. No open ports: it connects out. You decide how much an agent may do.",
"cards": [
[
"fa-terminal",
ddiff --git a/manifests/products/openvibe.bot.json b/manifests/products/openvibe.bot.json
iindex fa206a55..b5ec0217 100644
---- a/manifests/products/openvibe.bot.json
++++ b/manifests/products/openvibe.bot.json
@@@ -43,7 +43,7 @@
},
{
"question": "What hardware works?",
-- "answer": "A Raspberry Pi or another Linux board running the OpenVibe Node, with a driver for your model; a ROS 2 robot; a Cozmo or a LOOI through a computer next to it; or any phone, in its browser, with nothing to install. Anything else joins through the generic relay and your own script, or a driver you write and submit for others to use. The Adeept kit's page lists its parts; OpenVibe does not sell hardware."
++ "answer": "A Raspberry Pi or another Linux board running the OpenVibe Link, with a driver for your model; a ROS 2 robot; a Cozmo or a LOOI through a computer next to it; or any phone, in its browser, with nothing to install. Anything else joins through the generic relay and your own script, or a driver you write and submit for others to use. The Adeept kit's page lists its parts; OpenVibe does not sell hardware."
},
{
"question": "How does it keep the robot safe?",
ddiff --git a/manifests/services/ai.json b/manifests/services/ai.json
iindex 84392ee7..8579f12e 100644
---- a/manifests/services/ai.json
++++ b/manifests/services/ai.json
@@@ -102,7 +102,7 @@
{
"id": "ai.host",
"name": "AI host",
-- "installs": "the local model server (Ollama, LM Studio) as OpenVibe Node's ai plugin",
++ "installs": "the local model server (Ollama, LM Studio) as OpenVibe Link's ai plugin",
"shows": "Serves your own AI models, on your own machine."
}
]
ddiff --git a/manifests/services/connect.json b/manifests/services/connect.json
iindex e63f5223..14f99d3c 100644
---- a/manifests/services/connect.json
++++ b/manifests/services/connect.json
@@@ -68,7 +68,7 @@
"none": "one process holds every tunnel; a second process would need the share-to-tunnel routing CON-3 adds"
}
},
-- "notes": "Port 5240, unit openvibe-connect.service, PostgreSQL database ov_connect. Website Connect v0 (plan 2026-10-28 §5.9b; ADR-057 as reviewed 2026-11-02): a share is one port on a person's computer at https://<label>--<user>.openvibe.website behind OpenVibe sign-in, carried by a one-file client (Node 22) over one WebSocket on 443 (ovconnect-ws/1). Space site names never contain \"--\", so share hosts and Space sites never collide; nginx sends *--*.openvibe.website here and every other *.openvibe.website to Space. Modes owner, named and signin (staff-first); no public shares until CON-9 and CON-21. The share API is a person's (session or Network user token): GET/POST /api/v1/shares, GET/PATCH/DELETE /api/v1/shares/:id, POST /api/v1/shares/:id/key, GET /api/v1/shares/:id/visits. The website.*/connect.*/events.hook* capabilities stay planned until the QUIC edge, the Node plugin and the hooks land (CON-3/4/5/15). OAuth client `connect`; answers account export and deletion (ADR-033) and subscribes to those Events topics.",
++ "notes": "Port 5240, unit openvibe-connect.service, PostgreSQL database ov_connect. Website Connect v0 (plan 2026-10-28 §5.9b; ADR-057 as reviewed 2026-11-02): a share is one port on a person's computer at https://<label>--<user>.openvibe.website behind OpenVibe sign-in, carried by a one-file client (Node 22) over one WebSocket on 443 (ovconnect-ws/1). Space site names never contain \"--\", so share hosts and Space sites never collide; nginx sends *--*.openvibe.website here and every other *.openvibe.website to Space. Modes owner, named and signin (staff-first); no public shares until CON-9 and CON-21. The share API is a person's (session or Network user token): GET/POST /api/v1/shares, GET/PATCH/DELETE /api/v1/shares/:id, POST /api/v1/shares/:id/key, GET /api/v1/shares/:id/visits. The website.*/connect.*/events.hook* capabilities stay planned until the QUIC edge, the Link plugin and the hooks land (CON-3/4/5/15). OAuth client `connect`; answers account export and deletion (ADR-033) and subscribes to those Events topics.",
"health": "/api/health",
"ready": "/api/ready",
"internalOrigin": "http://127.0.0.1:5240",
ddiff --git a/manifests/services/run.json b/manifests/services/run.json
iindex 5ee23a67..99997f04 100644
---- a/manifests/services/run.json
++++ b/manifests/services/run.json
@@@ -100,7 +100,7 @@
"position": 35,
"tld": "run",
"accent": "#a78bfa",
-- "description": "OpenVibe.Run executes jobs: a project submits a job (a pre-registered artifact, its input and its limits) to one API, Run chooses a node from the network's offers that its requirements allow, OpenVibe.Bot hands it to the node and meters every wall-clock second, the output streams back, and the job ends succeeded, failed, cancelled or expired. Alpha: jobs run on nodes paired with OpenVibe through the OpenVibe Node.",
++ "description": "OpenVibe.Run executes jobs: a project submits a job (a pre-registered artifact, its input and its limits) to one API, Run chooses a node from the network's offers that its requirements allow, OpenVibe.Bot hands it to the node and meters every wall-clock second, the output streams back, and the job ends succeeded, failed, cancelled or expired. Alpha: jobs run on nodes paired with OpenVibe through the OpenVibe Link.",
"pillars": [
{
"title": "One API",
@@@ -126,7 +126,7 @@
},
{
"question": "Where do jobs run?",
-- "answer": "On nodes paired with OpenVibe through the OpenVibe Node, including computers of your own."
++ "answer": "On nodes paired with OpenVibe through the OpenVibe Link, including computers of your own."
},
{
"question": "How is a job charged?",
ddiff --git a/test/platform-fabric.test.js b/test/platform-fabric.test.js
iindex e1884710..3a4beaaf 100644
---- a/test/platform-fabric.test.js
++++ b/test/platform-fabric.test.js
@@@ -84,7 +84,7 @@ checks++;
assert.strictEqual(contracts.validate('platform.workload-requirements', { ...userRequirement, trust: ['other'] }).valid, false, 'an unknown requirement trust is refused');
checks++;
--// The fixtures (ADR-046): a user-owned node advertises the Node job worker's worker:function, and the
++// The fixtures (ADR-046): a user-owned node advertises the Link job worker's worker:function, and the
// unknown-trust offer is refused for its trust alone.
const fixture = (p) => require(`../fixtures/platform.resource-offer/${p}.json`);
const userNode = fixture('valid/user-owned-node');