Skip to content

feat: Talk to Octos, opt-in shared Octos for native, web and terminal clients - #98

Merged
ymote merged 9 commits into
mainfrom
feat/octos-shared-websocket
Sep 28, 2026
Merged

ymote merged 9 commits into
mainfrom
feat/octos-shared-websocket

Conversation

@ymote

@ymote ymote commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Lets a web client or a terminal UI talk to the same Octos system agent that the native apps use, without starting a second kernel. Talk to Octos is off by default. This revision reworks the first version after two security reviews.

octos pin: every octos crate is pinned at octos main e6223efc, the merge of octos#2591 (serve --host-managed, UPCR-2026-036), which contains 3b5d17a4 from #107. This branch is rebased on OctoSense main b89cd52.

What changed, by review finding

  1. Off by default. The kernel stays the private octos serve --stdio child. AI providers → Talk to Octos → Turn on restarts it as octos serve --host-managed and mints the external token. Revoke all clients rotates that token. Turn off returns to the pipe, so nothing listens and the connection file is removed. The setting is saved; the token lives only for the shell's lifetime.
  2. Upstream, no overlay. tools/runtime-patches/octos-host-managed.patch and octos-runtime-patches.lock.json are gone. serve --host-managed is octos code (#2591). The native feature list now comes from octos (octos_core::ui_protocol::UI_PROTOCOL_STDIO_DEFAULT_FEATURES, pinned by an octos test) instead of a hand copy.
  3. Two tokens and pairing. Native consumers use a host token that never leaves the shell. The kernel gets both tokens as its first two stdin lines, never in its environment. External clients get a separate token that opens /api/ui-protocol/ws only, and there only an allowlist of methods: sessions and turns of non-peer sessions, message paging, answers on sessions the connection opened, and read-only capability/status. Everything else is refused (external_method_denied): provider, key and skill configuration, snapshots, fork and delete, peer methods, and server/shutdown. Any call naming an app peer's peer-…/peerctx-… session is refused too. Turns an external client starts keep only a fixed allowlist of built-in tools, filtered on the finished registry (no shell, git, spawn, peer_*, send_file, task, MCP or plugin tools). External calls name no profile but _main, set no topic, cwd or sandbox, and steer, interrupt or answer only their own turns (approvals once-only). "Copy access token" is removed. A web client pairs with octos's one-time code: 8 characters, 5 minutes, rate-limited, audited and single use. The sheet shows the code with a QR of the web link, and pairing is enabled only while that sheet is open, including the phone's Back, which calls cancel(). Pairing on/off is serialized, so a late off never cancels a newer code. No copy action remains.
  4. validate_origin. https is required. On a desktop http is also accepted for localhost, 127.0.0.1 or [::1]; on Android, https only.
  5. Port squatting. The shell binds the listener once and passes it to each kernel generation (--listen-fd 3, Unix). A restart keeps the port, and no other app can take it in between. Without descriptor passing (Windows), a restart reuses the port when it is free and otherwise falls back to port 0, rotates the external token and rewrites the connection file.
  6. Desktop orphan. The child's stdin is its lifeline, and octos stops on stdin EOF (plus PR_SET_PDEATHSIG on Linux/Android). The new crates/kernel test a_host_that_dies_takes_its_kernel_with_it SIGKILLs a host process and checks that its kernel exits, in both modes.
  7. Windows descriptor. The connection file is kept in %LOCALAPPDATA%\OctoSense\, whose default ACL admits this user, SYSTEM and administrators; this is documented. It holds only the external token (mode 0600 elsewhere).
  8. web-client-origin.txt. It is written atomically (temp file, then rename). A malformed file reads as no origin, and the kernel stays available (tested).
  9. CI --offline. The host-patch step is gone. The remaining desktop build step (kernel-artifact.py --host) passes --offline in offline plans, with a test assertion.
  10. ADR 0003 is rewritten. It now has a threat model (other APKs and local users reach loopback; browsers; shell death; port races), a table of what each token grants, the peer-approval rule, the opt-in listener and stdin-EOF shutdown. The false claim that a desktop crash stops the kernel is corrected: it now does, through stdin EOF, and a test covers it.
  11. Docs. The kernel README (EN and zh-CN, bilingual per the repo rules), the app/desktop/phone READMEs and the llm service docs are updated.

Validation (local, macOS; rerun on the head 6eee96d with octos e6223efc, including the services toolbox with --features octos-engine)

  • cargo test --locked -p octosense-kernel: 19 unit and 11 core tests pass. They include off-by-default, the toggle switching the launch, a malformed origin, and shutdown during startup.
  • Real kernel (OCTOS_CORE_TEST_KERNEL = release octos built from #2591): all 5 pass.
    • The providers/restart test runs with Talk to Octos off.
    • With it on: the external token gets 401/403 on admin and REST routes, cannot call or see server/shutdown, and cannot answer a peer approval. A bad Host gets 421. No token, a spoofed profile header, solo login and untrusted origins are all refused. Restart keeps the port and token, and rotation kills the old token. After Turn off nothing listens.
    • Pairing is single use and returns the external token.
    • Native and browser clients share the system conversation.
    • The killed-host test passes in both modes.
  • App-peer real-kernel tests (6) pass with Talk to Octos off (cargo test -p octosense-app-peers --features octos-core --test real_kernel), covering peer memory namespaces.
  • octosense-llm-service --features octos-core: all pass (21 unit, 8 image, 19 service tests; the sheet-control, timer and pairing-sheet tests are new).
  • octosense-ai-host --features octos-core,llm tests pass and its clippy is clean. octosense-app-peers passes with default features and with octos-core,ws.
  • python3 -m unittest discover -s tools -p 'test_*.py': 28 OK. rom/tests/test_home_build.py: 14 OK.
  • tools/setup.py --check --cargo passes. check-shell-graph.sh passes for -p octosense and -p octosense-home (one octos). cargo check --locked -p octosense-home --features mobile-apps passes, and so does clippy (-D warnings) on the kernel and llm service.
  • Hidden-window desktop run (on 74a6fe3 with octos 2cd29e44; the later pin adds only the peer control-plane gate, which the real-kernel test covers; MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE, temp OCTOSENSE_HOME and core dir, ended with /gq):
    • With Talk to Octos off, no kernel listens.
    • Turn on started octos serve --host-managed --listen-fd 3 and wrote a 0600 connection file.
    • A probe with the external token got: 401 on /api/admin/{overview,stop-all,token/rotate,host/pairing}; 403 on /api/my/profile; server_shutdown_unavailable (not advertised); host_owned_peer_answer_denied for peer- and peerctx-; 421 for a rebinding Host; 401 on WS without a token; 101 with octos-ui selected for the bearer subprotocol.
    • Pair a web client showed the code and the QR. /pair/info answered 200 while the code's sheet was open and 404 after Back/Done. A claim returned the external token, and a second claim got 400.
    • Saving the origin restarted the kernel on the same inherited socket.
    • Turn off stopped the kernel, and the port was closed in both processes.

Unverified: Android packaging and device behavior, adb forward tunnels, a terminal UI reading the connection file, Windows.

Re-review fixes

  • H1/M1 (octos e6223efc): the allowlist, peer-session refusal and external-turn tool restriction above; enumerated by an octos test over the dispatch table.
  • M2: cancel() in the connect, pairing and export sheets, and a test that every service sheet defines it.
  • M3: tokens on stdin; octos refuses env tokens, fences /proc/*/environ|cmdline in file tools and the shell policy; ADR 0003 states the profile's tools and sandbox. The live desktop probe found the external token absent from the kernel's environment (ps eww).
  • L1 (octos): claims rate-limited, Origin-checked and audited. L2: pairing epochs. L3: Android https only. L4: apps.yml job kernel-security runs the real-kernel tests against octos built at the pin, plus a test that Revoke all ends a live session. L5: restart warnings on the sheet.

Overlaps

🤖 Generated with Claude Code

@ymote
ymote force-pushed the feat/octos-shared-websocket branch from 6666bb6 to d5e20d9 Compare September 28, 2026 07:53
@ymote ymote changed the title feat: share Octos system agent with native and web clients feat: Talk to Octos, opt-in shared Octos for native, web and terminal clients Sep 28, 2026
@ymote
ymote force-pushed the feat/octos-shared-websocket branch 3 times, most recently from d469971 to db967a6 Compare September 28, 2026 14:20
ymote and others added 9 commits September 28, 2026 09:55
…d serve

Rework of the shared Octos server after two security reviews.

- Off by default: the kernel stays the private `serve --stdio` child. Talk to
  Octos (AI providers) restarts it as octos's upstream
  `serve --host-managed` (octos#2591) and mints an external token; Revoke
  rotates it; Turn off returns to the pipe, stops the listener and removes
  the connection file.
- Two tokens: native consumers use a host token that never leaves the shell;
  external clients get a token that opens the UI Protocol socket only (no
  REST/admin, no server/shutdown, no answers to host-owned app peers).
- Web clients pair with octos's one-time code (8 chars, 5 minutes, single
  use), shown with a QR of the web link, enabled only while the sheet is open.
  No copy-token action remains.
- Web origins: https, http only for loopback; a malformed saved origin reads
  as none and the kernel stays available; atomic writes.
- The shell keeps the listener and passes it to each generation
  (--listen-fd, Unix); elsewhere a taken port moves to a new port and token.
- The child's stdin is its lifeline (a killed shell takes its kernel with it;
  tested in both modes). Native consumers request octos's own
  UI_PROTOCOL_STDIO_DEFAULT_FEATURES.
- Windows keeps the connection file in per-user local AppData.
- Drops the octos overlay patch and its lock; kernel-artifact --host builds
  the pinned revision (offline stays offline, tested).
- ADR 0003: threat model, token grants, opt-in listener, stdin-EOF shutdown.

The octos pin points at the octos#2591 head for testing; it must move to the
merged octos commit before this merges.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The external token also cannot manage host-owned app peers (octos
host_owned_peer_control_denied); the real-kernel test and docs say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pairing

- M3: the kernel gets its host and external tokens as its first two stdin
  lines, never in its environment (/proc/<pid>/environ is readable by this
  app's own processes, the kernel's tools included). Pins octos#2591 at
  45046be0, which refuses env tokens, fences /proc/*/environ|cmdline from
  file tools and the shell policy, and gives external turns no code, admin
  or peer tools. ADR 0003 states the tool set and sandbox the profile runs.
- M1/H1 (octos): external clients call an allowlist and nothing on app
  peers' sessions; the connect sheet, ADR 0003 section 2 and the kernel
  README say exactly that. Real-kernel tests check profile/llm/list,
  profile/llm/upsert, sub-providers, skills and snapshot/restore are
  refused and an app peer's session cannot be opened.
- M2: the connect, pairing and export sheets define cancel(), so the phone's
  Back closes them through the service and pairing ends; a test requires
  cancel() on every service sheet.
- L2: pairing on/off is serialized with an epoch: an "off" from a closed
  sheet never turns off a newer code.
- L3: Android trusts https web origins only.
- L4: apps.yml job kernel-security builds octos at the pinned revision
  (cached by it) and runs the kernel and app-peer real-kernel tests; a new
  check that Revoke all ends a live external session.
- L5: the sheet warns that turning on or off, revoking and saving an origin
  restart the assistant and stop work in progress.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, _main only)

ADR 0003 section 2 follows: external turns keep a fixed allowlist of
built-in tools (no MCP or plugin tools either), and external calls name no
profile but _main.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…unes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Pins octos#2591 at 81d2180b (on octos main 5c5b6a53): the external tool
  allowlist applies to the finished per-turn registry; external clients
  cannot pass topic, cwd or sandbox, steer, interrupt or answer only their
  own turns (approvals once-only), never negotiate workspace_cwd, and run
  no continuations; process views (/proc/self, /proc/<pid>/..., /dev/fd)
  are fenced from file tools on the canonical path.
- Real-kernel test: the browser's cwd is refused and it lands in the saved
  system workspace; it cannot interrupt the host's turn.
- ADR 0003 section 2 now states exactly what is enforced; the sheet's text
  matches.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…2180b)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every octos crate (octos-core, octos-cli, octos-research) moves from the
temporary #2591 head to its merge commit on octos main, e6223efc (same
tree as 64a233c7; contains 3b5d17a4 from #107). The READMEs and the
kernel-artifact test name the new rev.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ymote
ymote force-pushed the feat/octos-shared-websocket branch from b5f32dd to 6eee96d Compare September 28, 2026 17:04
@ymote
ymote merged commit a373cef into main Sep 28, 2026
7 checks passed
tyreseluo pushed a commit to tyreseluo/OctoSense that referenced this pull request Oct 4, 2026
…s, CEF profile under MAKEPAD_HOME)

Moves the runtime from makepad 39a9de88 to makepad main c155f61d,
through Octoscript-Makepad 2cc5ef37 (OctoSense-org#71). New in makepad:
- OctoSense-org#94 Files, Task and Browser: an empty library beside each binary
- OctoSense-org#95 Calculator, Clock, Notes, Reminders, Weather: each module opens its
  own agent's link and answers its tools there
- OctoSense-org#96 hosted Notes and Reminders start empty
- OctoSense-org#97 Files' chat panel only reads
- OctoSense-org#98 CEF keeps its stable profile under MAKEPAD_HOME when that is set

Root Cargo.toml (every makepad revision, octoscript-makepad and
octoscript-node), the Sheets and Terminal revisions in native-apps.json,
both native-runtime.lock.json files, and runtime-patches.lock.json: base
c155f61d, the five patches reapply unchanged (none touches a changed
file), new tree 3eadf628. Cargo.lock does not change. The phone docs
that name the pinned hashes move with it; rom/docs/home-build.md gets
its own follow-up, as with OctoSense-org#289.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant