openvibers/openvibe.link AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Link |
| 2 | |
| 3 | ## Project Overview |
| 4 | |
| 5 | OpenVibe.Link ("OpenVibe Node") connects a person's computers, servers, Raspberry Pis, phones and robots to the |
| 6 | OpenVibe network. It pairs once, keeps **one outbound WebSocket** to OpenVibe.Bot (credential in the `Authorization` |
| 7 | header only), opens **no ports**, and lets OpenVibe.Bot (robots) and OpenVibe.Actor (computer control for agents) use |
| 8 | the device at the level the owner sets. It is a **client**, not a service: no database, no server, no deploy of its own. |
| 9 | |
| 10 | - **Kind:** client · **Version:** 0.59.0 · **License:** AGPL-3.0 · **Runtime:** Go 1.24 (one static binary per |
| 11 | platform), Python 3 plugins, an ESP32 Arduino library, an Android app (gomobile + Kotlin/Compose) |
| 12 | - **Read first:** [README.md](README.md), [STATUS.json](STATUS.json), [docs/protocol.md](docs/protocol.md), |
| 13 | [docs/plugins.md](docs/plugins.md), [docs/computer.md](docs/computer.md), [docs/panel.md](docs/panel.md), |
| 14 | [docs/worker.md](docs/worker.md), [docs/access-and-agents.md](docs/access-and-agents.md), |
| 15 | [ADR-043](docs/ADR-043-bot-devices-and-control.md). |
| 16 | |
| 17 | **Network context:** pairing and the device credential are OpenVibe.Bot's (or Network's pairing flow); Network owns the |
| 18 | account, roles, signed access grants (`access request/approve/revoke`) and the machines hub; Actor sends computer |
| 19 | commands through Bot; Codes' job runner sends jobs to the worker; Connect carries port shares; Exchange lists a local |
| 20 | model; Locker/Send move sealed files and text between the person's machines; Live receives WHIP video; Git serves the |
| 21 | clone/git-connect relay. Link never becomes an authority for any of them. |
| 22 | |
| 23 | ## Commands |
| 24 | |
| 25 | ```bash |
| 26 | go vet ./... && go test -race ./... # the e2e tests run the Python dry-run plugin |
| 27 | pip install pytest pillow numpy # plugin test deps |
| 28 | for p in sdk dryrun adeept_adr036 cozmo relay ros2 computer; do python3 -m pytest -q plugins/$p/tests; done |
| 29 | scripts/dist.sh v0.0.0-dev # every binary, plugin bundle and SHA256SUMS in dist/ |
| 30 | scripts/package.sh # distro packages |
| 31 | go build ./cmd/openvibe-link # one binary |
| 32 | openvibe-link # guided first run: pair, check the link, report the first job |
| 33 | openvibe-link status | stop | resume | quit | approvals | tray | panel |
| 34 | openvibe-link memory scan|upload|sync | skills sync | exchange serve --model <id> --public-url <https> |
| 35 | openvibe-link share <port> | share add | party host <party-id> |
| 36 | openvibe-link access request|list|approve|deny|revoke ... | openvibe-link mcp [--print-config] |
| 37 | openvibe-link clone '<link>' | git connect '<link>' |
| 38 | openvibe-link profile verify community.<name> [--key-id <id>] |
| 39 | ``` |
| 40 | |
| 41 | Install: `curl -fsSL https://openvibe.link/install | sh` (and `--robot/--driver`, `--network/--pairing/--code`, |
| 42 | `--computer`, `--ai`, `--worker`, `--plan-logins`). CI builds Windows x64/ARM64, macOS arm64/Intel, Linux x64/ARM64 |
| 43 | (`.deb`, `.rpm`, tarball, install script) and the Android APK; releases carry SHA256SUMS only (no code signing yet). |
| 44 | |
| 45 | ## Architecture at a Glance |
| 46 | |
| 47 | - **Core Go binary** ([cmd/openvibe-link/](cmd/openvibe-link/main.go), [internal/](internal/)): pairing, the outbound |
| 48 | WebSocket, heartbeats/deadman, the safety rules (deadlines, owner limits clamped before any driver sees a value, a |
| 49 | persisted stop, the local kill switch), the plugin supervisor, WHIP video (pion), the system service |
| 50 | (systemd/launchd/Windows). |
| 51 | - **Plugins** ([plugins/](plugins/), separate processes, JSON lines over stdin/stdout): `dryrun`, |
| 52 | `adeept_adr036` (Adeept 4WD), `cozmo` (PyCozmo), `relay` (existing Live hardware clients), `ros2` (rclpy or |
| 53 | rosbridge: cmd_vel, battery, scan/range, odometry, camera), `computer` (terminal/files/screen/input/browser), plus the |
| 54 | community LOOI driver contract. A plugin stops its own actuators when stdin closes, at a command deadline and after |
| 55 | 1 s without a heartbeat — the core dying never leaves motors running. |
| 56 | - **ESP32 library** ([esp32/](esp32/README.md), `OpenVibeLink`): pairing, control link, drive/actuator commands, |
| 57 | telemetry, heartbeat, deadman and stop; no jobs or video. |
| 58 | - **Computer plugin** ([docs/computer.md](docs/computer.md), [internal/agent/](internal/agent/)): `--computer |
| 59 | observe|ask|trusted|full`; runs as the person's account; approvals in the tray / notification / `openvibe-link |
| 60 | approve`; browser over DevTools `--remote-debugging-pipe` (no port); exact-origin task checks; nonce-fenced page |
| 61 | text; a live screen on openvibe.bot for its owner only (≤30 min); `openvibe-link stop` ends it at once. |
| 62 | - **Worker** ([docs/worker.md](docs/worker.md), [internal/agent/jobs.go](internal/agent/jobs.go)): `--worker` runs |
| 63 | each job as an unprivileged user in a sandbox; Codes' runner can start a task from any browser; `--plan-logins` names |
| 64 | existing login files without reading/printing them. |
| 65 | - **Network roles:** `codes.worker`, `desks.host`, `ai.host`, `bot.robot` are read at check-in and kept, but the local |
| 66 | configuration always wins — a role never installs anything or turns on a feature turned off here. |
| 67 | - **Panel** ([docs/panel.md](docs/panel.md), [internal/panel/](internal/panel/)): `127.0.0.1`-only, a fresh random port |
| 68 | and 32-byte token per run (`#t=` fragment), every `/panel/api/*` needs `X-Link-Panel`, Origin must be the panel's own, |
| 69 | no CORS, only three files served. A restart invalidates the old address/token. |
| 70 | - **Sharing/relay:** `share`/`share add` (Connect), `party host`, `clone`/`git connect` (Git, outbound), Locker-sealed |
| 71 | Send between machines, the access-grant relay (six scopes), `mcp` (stdio + local control socket) for agent CLIs, |
| 72 | memory/skills sync (dry run, one marked block per file, opt-in per tool). |
| 73 | - **Android** ([android/](android/), [docs/android.md](docs/android.md)): v0 pairing/connection in Kotlin/Compose; |
| 74 | tokens + pairing AES-GCM-sealed with a non-exportable Keystore key; approvals (fingerprint for money, passwords, |
| 75 | deletions and a new recipient), read-only Dock activity, **Send to OpenVibe**; no AccessibilityService and no screen |
| 76 | capture. |
| 77 | |
| 78 | ## Safety, Egress and Security |
| 79 | |
| 80 | - **The kill switch is the promise:** `openvibe-link stop` latches (persisted stop); `resume` clears it. Every |
| 81 | actuator path checks deadlines and clamped owner limits before a driver sees a value; the deadman/heartbeat stops |
| 82 | motion when the control link is lost. |
| 83 | - **No open ports:** the core connects out; the panel is loopback-only with a per-run token; the browser is driven over |
| 84 | a pipe; `git connect`, clone, shares and the worker all connect out. |
| 85 | - **Browsing egress** ([docs/link-egress.md](docs/link-egress.md)): public web destinations on 80/443 only, DNS |
| 86 | resolved and pinned on the device, private/local/metadata/IPv6-translation addresses refused; per-owner limits, |
| 87 | opt-out and a monthly cap; Network checks every stream; no cookie or ordinary share key grants it; a missing Network |
| 88 | **never** means direct fallback; on expiry/revocation/opt-out/battery/metered it closes within 5 s. |
| 89 | - **Access grants** (`openvibe-link access`): the target verifies Network's signed grant (signature, audience, sender, |
| 90 | scope, expiry, live revocation) and its own level; the owner may narrow but never widen; a missing/expired/revoked |
| 91 | grant fails closed. Relayed ops go through Bot's device relay; Link Send's LAN hand-over checks the same grant. |
| 92 | - **Community plugins/profiles:** a `community.<name>` profile is reviewed and signed by Bot (Ed25519 over canonical |
| 93 | JSON); `profile verify` checks it against Bot's published key (pin with `--key-id`), and pairing with a robot on a |
| 94 | community profile runs the same check and warns. Install a community plugin only when it passes. |
| 95 | - **Secrets:** the device credential appears only in the `Authorization` header; Android seals it with the Keystore; |
| 96 | never log a token, a login file's content or a sealed envelope. The panel's token is in the fragment and must stay |
| 97 | there. |
| 98 | - **Local configuration wins** over a network role; a role is a description, not a remote installer. |
| 99 | |
| 100 | ## Deployment and Data |
| 101 | |
| 102 | None of its own: people install it on their machines/robots (install script or release binary), it pairs through |
| 103 | OpenVibe.Bot/Network and runs as a system service. There is no database; local state lives in the Link's state |
| 104 | directory (config, stop latch, panel.json, usage/reservations, memory/skills settings). Production is a distribution |
| 105 | matter, not a service deploy. |
| 106 | |
| 107 | ## Key Pitfalls |
| 108 | |
| 109 | - **Never add an inbound listener** (other than the loopback panel) or a shared secret/key. Pairing is a one-time code |
| 110 | and the credential goes in the `Authorization` header only. |
| 111 | - **Never let a network role or a remote command turn on a local feature**; the local config is the upper bound. |
| 112 | Approvals at `ask` must be answerable (tray/notification/CLI) and deny by default. |
| 113 | - **A plugin that keeps moving when its stdin closes is a safety bug.** Every driver must stop on stdin close, deadline |
| 114 | and 1 s without a heartbeat; keep the persisted stop checked on every path. |
| 115 | - **Computer/browser:** exact-origin checks for task origins, no direct DNS/QUIC/non-proxied WebRTC UDP in the shared |
| 116 | browser, nonce-fenced page text, and no port. Do not "simplify" the DevTools pipe into a TCP debug port. |
| 117 | - **Egress failure is closed:** a Network/Bot outage must never fall back to direct browsing or a relay that bypasses |
| 118 | the owner's cap; the saved offline choice ("use OpenVibe's address or wait") applies only when the Link connection |
| 119 | cannot be made. |
| 120 | - **Panel:** loopback only, token on every API call, Origin checked, no CORS, no directory listing; a restart must |
| 121 | rotate the port and token. |
| 122 | - **Worker:** run jobs as an unprivileged user in the sandbox; never read or print a login file's content; a job that |
| 123 | a person did not install a role for must not run. |
| 124 | - **Community profiles:** verify the Ed25519 signature and plugin name before running a community driver; a failed |
| 125 | check is a warning/refusal, not a shrug. |
| 126 | - **Android v0** has no automation: no AccessibilityService, no screen capture. Do not add one without the owner's |
| 127 | decision and a security review. |
| 128 | - **Releases:** CI builds every platform; the release has SHA256SUMS only (no signing/notarization, no auto-update). |
| 129 | Do not claim a store/signed build. |
| 130 | - `settings`/memory/skills sync must stay opt-in per tool, dry-run first, one marked block per file, with an undo; never |
| 131 | overwrite a tool's own content. |
| 132 | |
| 133 | ## Testing |
| 134 | |
| 135 | `go vet ./... && go test -race ./...` covers the core, pairing, safety, protocol, worker, panel, access grants, Git |
| 136 | relay, memory/skills and the e2e flow against [internal/fakebot](internal/fakebot) and |
| 137 | [internal/fakenetwork](internal/fakenetwork). Plugin tests are Python (`pytest` per plugin; the e2e Go tests run the |
| 138 | dry-run plugin). `scripts/dist.sh` proves every binary/plugin bundle builds. Add a test with every safety, grant or |
| 139 | egress change; a skipped test is not a pass. |
| 140 | |
| 141 | ## Documentation |
| 142 | |
| 143 | - [README.md](README.md) — install, what is in here, development. |
| 144 | - [docs/protocol.md](docs/protocol.md) — the wire format with Bot; [docs/plugins.md](docs/plugins.md) — driver protocol, |
| 145 | bundled drivers, community plugins; [docs/install.md](docs/install.md); [docs/computer.md](docs/computer.md); |
| 146 | [docs/panel.md](docs/panel.md); [docs/worker.md](docs/worker.md); [docs/access-and-agents.md](docs/access-and-agents.md); |
| 147 | [docs/connect.md](docs/connect.md); [docs/party-desktop.md](docs/party-desktop.md); [docs/ai.md](docs/ai.md); |
| 148 | [docs/link-egress.md](docs/link-egress.md); [docs/stream.md](docs/stream.md); [docs/voice.md](docs/voice.md); |
| 149 | [docs/memory.md](docs/memory.md); [docs/skills.md](docs/skills.md); [docs/android.md](docs/android.md); |
| 150 | [docs/hardware-adeept.md](docs/hardware-adeept.md), [docs/hardware-cozmo.md](docs/hardware-cozmo.md), |
| 151 | [docs/hardware-looi.md](docs/hardware-looi.md); [ADR-043](docs/ADR-043-bot-devices-and-control.md). |
| 152 | - [SECURITY.md](SECURITY.md), [STATUS.json](STATUS.json), [esp32/README.md](esp32/README.md), |
| 153 | [plugins/](plugins/) READMEs. |
| 154 |