openvibers/openvibe.actor AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Actor |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | OpenVibe's own general agent and the router for agent work: a task in plain words is sorted (answer, |
| 6 | lookup, research, live web search or code), placed on the best agent for it, run while its steps |
| 7 | stream, checked by a model of another family, then delivered with its cost and the reason it went |
| 8 | where it did. Node.js/Express, PostgreSQL (`ov_actor`), CommonJS, **no build step**. |
| 9 | |
| 10 | - Service id `actor`, port `4950`, origin `https://openvibe.actor`, unit `openvibe-actor.service`, |
| 11 | release root `/opt/openvibe.actor`; live on openvibe-ovh. |
| 12 | - Design: plan T17 and ADR-044 (`openvibe-contracts`). Actor is also the front desk of the one agent |
| 13 | system: it can delegate coding to Codes, a robot to Bot and a machine's agent to Link. |
| 14 | |
| 15 | ## What it owns |
| 16 | |
| 17 | - Tasks, their event logs and daily spend (`migrations/0001_tasks.sql`); tasks are kept 30 days, then |
| 18 | pruned with their events. Nothing stored is a credential, a key or a token. |
| 19 | - The agent catalog and its human-typed rate cards; the routing policy over them (placement itself is |
| 20 | `openvibe-sdk/placement`). |
| 21 | - An agent's **voice** as a file (`actor.voice@1`, `server/agents/voice.js`), its integrity hash and |
| 22 | public shares at `/voices/<id>`. |
| 23 | - The pages (`/`, `/agents`, `/docs`, `/tasks`, `/tasks/:id`, `/updates`), the console |
| 24 | (`/console`), the task API, chat asks (`@actor` in Chat), triggers/cron, webhooks, agent mail, |
| 25 | connections (MCP/A2A), skills, memory, sharing, task artefacts and the `actor.task.*` events. |
| 26 | - Request telemetry (`telemetry_samples`, 14 days; route templates only, never raw URLs). |
| 27 | |
| 28 | Not owned: coding runs (Codes), execution sandboxes/browsers/desktops (Run/Link), accounts, apps and |
| 29 | grants (Network/Services), the tools (Tools), search (Search), the model providers' routing (AI when |
| 30 | its router is on). |
| 31 | |
| 32 | ## Who calls it, and whom it calls |
| 33 | |
| 34 | - Callers: people in the browser; apps, agents and services with a Network service token audience |
| 35 | `openvibe.actor` and the route's capability; a first-party `svc:` acting for a person with |
| 36 | `X-OV-Subject` (e.g. Watch's actor-task action); OpenVibe.Chat (`@actor <task>`). |
| 37 | - Depends on (all base URLs in [server/config.js](server/config.js)): |
| 38 | - **Network** — OAuth2+PKCE (client `actor`), JWKS, account export/deletion pushes. |
| 39 | - **Search** (`ACTOR_SEARCH_URL`) — `search_openvibe` anonymous; `search_web` as Actor's principal |
| 40 | with `search.query.delegate` and `X-OV-Subject`, so paid steps count against the person. |
| 41 | - **Tools** (`ACTOR_TOOLS_URL`) — catalog, run API, page reader, as an anonymous caller. |
| 42 | - **Bot** (`ACTOR_BOT_URL`, default `http://127.0.0.1:4630`) — the person's robots and computers |
| 43 | (`bot.robot.*`, `bot.computer.*`), owner-only and only where the owner turned the switch on. |
| 44 | - **Events** (`ACTOR_EVENTS_URL`) — account-data subscriptions, `chat.message.created`. |
| 45 | - **Chat** (`ACTOR_CHAT_URL`, default `http://127.0.0.1:4400`) — asks and replies. |
| 46 | - **AI** (`ACTOR_AI_ROUTER=1` in production) — every model call is an `ai.complete` run with |
| 47 | `on_behalf_of` the task's person; each tier names an AI policy (`ACTOR_AI_MODEL_*`). With the |
| 48 | router off, a self-hosted Actor uses DeepSeek/OpenAI/a local server directly. |
| 49 | - PostgreSQL and Valkey; `pdftotext` for PDF attachments. |
| 50 | |
| 51 | ## How a task runs |
| 52 | |
| 53 | 1. **Sort** — [server/agents/classify.js](server/agents/classify.js) (cheap model, or a free heuristic |
| 54 | in private mode). |
| 55 | 2. **Route** — [server/agents/router.js](server/agents/router.js) over |
| 56 | [server/agents/catalog.js](server/agents/catalog.js): hard requirements first (capability, private |
| 57 | trust, health, budget), then the mode's objective (cheapest/balanced/best/fastest/private). The |
| 58 | result is a `platform.placement-result@1` stored as the explanation. |
| 59 | 3. **Run** — [server/agents/adapters.js](server/agents/adapters.js); steps stream as events; model |
| 60 | calls and searches are metered at once, so a cancelled task still costs what it used. |
| 61 | 4. **Check** — [server/agents/check.js](server/agents/check.js): a model of another family reads the |
| 62 | answer (DeepSeek ↔ OpenAI). In private mode the open model checks itself and the task says so. An |
| 63 | answer that cannot be checked is never delivered. |
| 64 | 5. **Deliver or hand on** — [server/tasks/engine.js](server/tasks/engine.js): a pass returns the |
| 65 | result, cost and explanation; an error or failed check hands to the next eligible agent (at most |
| 66 | two), appending each placement to the explanation. |
| 67 | |
| 68 | ## Repository map |
| 69 | |
| 70 | - **Entry:** [server/index.js](server/index.js) — `createApp()`, HTTP listen, status reconciliation; |
| 71 | `createLifecycle` stops tasks and leaves their rows open for checkpoint resume. |
| 72 | - **App:** [server/app.js](server/app.js) — helmet/CSP, rate limits, `/auth`, `/internal/events`, |
| 73 | `/shared`, `/api/v1` (tasks, resources, fabric), `/api`, pages, console, chat/agent/memory pages. |
| 74 | - **Config:** [server/config.js](server/config.js), [server/ai-catalog-config.js](server/ai-catalog-config.js). |
| 75 | - **Database:** [server/db.js](server/db.js), [server/tasks/store.js](server/tasks/store.js); schema in |
| 76 | [migrations/](migrations/). |
| 77 | - **Tasks:** [server/tasks/](server/tasks/) — `engine.js`, `create.js`, `stream.js`, `webhooks.js`. |
| 78 | - **Agents/runtime:** [server/agents/](server/agents/), [server/runtime/](server/runtime/) (loop, |
| 79 | models, checkpoints, subagent, evidence, tools, stats, artifacts). |
| 80 | - **Auth:** [server/auth/keys.js](server/auth/keys.js), [server/auth/sso.js](server/auth/sso.js), |
| 81 | [server/http/principal.js](server/http/principal.js). |
| 82 | - **HTTP/pages:** [server/http/](server/http/) — `api.js`, `pages.js`, `chat-pages.js`, |
| 83 | `agent-pages.js`, `console-pages.js`, `memory-pages.js`. |
| 84 | - **Integrations:** [server/chat/](server/chat/), [server/triggers/](server/triggers/), |
| 85 | [server/mail/](server/mail/), [server/connections/](server/connections/). |
| 86 | - **Self-host:** [scripts/selfhost-init.js](scripts/selfhost-init.js), [docs/self-host.md](docs/self-host.md), |
| 87 | [docker-compose.selfhost.yml](docker-compose.selfhost.yml), [Dockerfile](Dockerfile). |
| 88 | |
| 89 | ## Commands |
| 90 | |
| 91 | ```bash |
| 92 | npm install |
| 93 | npm run dev # http://localhost:4950 (agents without keys are unavailable) |
| 94 | npm start # node server/index.js |
| 95 | npm test # every test/*.test.js on temp PGlite, mock Network/Events/Media |
| 96 | npm run test:pg # same suite on PostgreSQL (ACTOR_TEST_STORE=pg) |
| 97 | npm test -- tasks # filter by file name; --strict makes a skip fail |
| 98 | npm run selfhost:init # owner subject, password hash, token, session secret |
| 99 | ov test # the harness's sharded run of what the change reaches |
| 100 | ``` |
| 101 | |
| 102 | ## Auth and capabilities |
| 103 | |
| 104 | - A person acting for themself needs no capability. App/agent/service tokens for audience |
| 105 | `openvibe.actor` need the route's capability: `actor.task.create`, `actor.task.read`, |
| 106 | `actor.task.list`; `actor.agent.read` covers the public catalog/dry run. |
| 107 | - A **first-party `svc:`** may act for a person named in `X-OV-Subject`; the task is the person's |
| 108 | (allowance, list, export, deletion) and records the service as `via`; it can read/list/cancel only |
| 109 | the tasks it started. A developer app or an agent may never name a person (`403 subject.not_delegated`). |
| 110 | - Sessions are HttpOnly cookies from Network SSO; a FedCM assertion or an app/service token is never a |
| 111 | session. Never read identity from a body or a query. |
| 112 | |
| 113 | ## Frontend loading model |
| 114 | |
| 115 | Server-rendered pages and forms, progressively enhanced by no-build scripts under |
| 116 | [public/js/](public/js/); there is no SPA framework, bundler or transpiler. `openvibe-shared` supplies |
| 117 | the Frame, shell, showcase and `/shared/*` (served from the pinned node_modules). The home page has |
| 118 | size budgets (`test/home.test.js`, `test/asset-cache.test.js`); CSP is set in |
| 119 | [server/app.js](server/app.js). |
| 120 | |
| 121 | ## Deploy |
| 122 | |
| 123 | `sudo ovhost deploy actor` (git checkout at `/opt/openvibe.actor`, unit `openvibe-actor.service` on |
| 124 | `127.0.0.1:4950`, env `/etc/openvibe/actor.env`, database `ov_actor` on the data role); nginx |
| 125 | [deploy/nginx/openvibe.actor.conf](deploy/nginx/openvibe.actor.conf) installed with `ov-vhost-install`, |
| 126 | with the task event stream unbuffered. Rollback is automatic when `/api/ready` fails. **A stop or a |
| 127 | deploy marks running tasks `actor.task.interrupted`; they are never re-run behind the person's back.** |
| 128 | |
| 129 | ## Tests |
| 130 | |
| 131 | `npm test` runs each `test/*.test.js` in its own process on PGlite with in-process Network/Events/ |
| 132 | Media mocks and stand-in providers. The important ones: `tasks.test.js` (end-to-end, routing, check, |
| 133 | hand-off, private mode, budgets, idempotency, cancel, stream, visibility), `contracts.test.js` |
| 134 | (released manifest/capabilities match the routes), `account-data.test.js`, `caller-limits.test.js`, |
| 135 | `security-ssrf.test.js`, `security-secrets.test.js`, `security-session.test.js`. `ov test` runs the same. |
| 136 | |
| 137 | ## Conventions |
| 138 | |
| 139 | - CommonJS, 4-space indent, single quotes, semicolons; factories with injectable config/db/clock/ |
| 140 | fetch/log; domain logic in `server/tasks/`, `server/agents/`, `server/runtime/`, HTTP in `server/http/`. |
| 141 | - Contracts and ids come from `openvibe-contracts`; errors are problem+json with stable codes and |
| 142 | request ids. No secret in a response, an event or a log. |
| 143 | - Rate cards are typed in by a person, with the provider's page and the date checked; a model never |
| 144 | writes a price. |
| 145 | - For self-hosted deployments the owner/secret check is deliberate; do not loosen `checkSelfHosted`. |
| 146 | |
| 147 | ## Pitfalls |
| 148 | |
| 149 | - **Actor never fetches a URL from a task or a model.** Tools' own SSRF guard decides what a tool may |
| 150 | reach; the same applies to `read_page`. Keep outbound calls to configured bases. |
| 151 | - **Private mode must not send the text outside OpenVibe** — including the classifier and the check. |
| 152 | - **An unchecked answer is never delivered.** With the AI router the check is one family, and the |
| 153 | verdict says so; do not silently drop the check. |
| 154 | - **`@actor` asks are public:** they may use public tools only, and the engine refuses a robot, |
| 155 | computer or coding task (`actor.task.not_public`). Limits: 5/hour per person, 60 overall. |
| 156 | - **`X-OV-Subject` is only for first-party services**; apps/agents cannot delegate. |
| 157 | - **Budgets are hard** and metered per call; a cancelled task still charges what it used, and the free |
| 158 | tier is checked before any paid call. |
| 159 | - **Interrupted tasks are not re-run**; never "retry" a person's task automatically after a deploy. |
| 160 | - **Self-hosted boot refuses** without `ACTOR_OWNER_SUBJECT`, `ACTOR_OWNER_PASSWORD_HASH`, |
| 161 | `ACTOR_OWNER_TOKEN` (32+) and `ACTOR_SESSION_SECRET` (32+). |
| 162 | - **A shared voice's author is only a claim**; the integrity hash proves the file was not edited, not |
| 163 | who wrote it. |
| 164 | |
| 165 | ## Documentation |
| 166 | |
| 167 | - [README.md](README.md) — the full product, agents, money, API, acceptance and threats. |
| 168 | - [docs/one-engine.md](docs/one-engine.md) — the shared engine with Codes and Run. |
| 169 | - [docs/agent-runs.md](docs/agent-runs.md), [docs/task-view.md](docs/task-view.md). |
| 170 | - [docs/personal-actor.md](docs/personal-actor.md), [docs/self-host.md](docs/self-host.md), |
| 171 | [docs/work-fabric.md](docs/work-fabric.md). |
| 172 | - ADR-044 in OpenVibe.Contracts is the binding design. |
| 173 |