Skip to content

ADR 0004: the peer link for process apps, process sandboxes, Restart tile for dead process apps [skip ci] - #130

Merged
ymote merged 2 commits into
mainfrom
adr0004/peer-link
Sep 29, 2026
Merged

ymote merged 2 commits into
mainfrom
adr0004/peer-link

Conversation

@ymote

@ymote ymote commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

ADR 0004 plan step 8 (the coordinator's step 7): the peer link for native apps hosted in their own process, and the process sandboxes (§3, §5, §11 enforcement table). Crash isolation (§2) is checked and fixed on the way.

The peer link (crates/shell/src/peer_link/)

  • Its own channel on the process app's hub socket: studio Custom frames under the "octos_peer" envelope, taken before the WM and AI-bus parsers and never registered with the bus (lib.rs on_app_msg).
  • Up: request {req_id, method, args} (octos.session.open|history, octos.turn.start|interrupt, octos.context.close) and tool_result {call_id, ok, data | error | awaiting_confirmation}. Down: reply, streamed event, tool_call {call_id, name, args, risk, confirm_required, timeout_ms, account, context_id, client, caller}, tool_cancel, context_closed.
  • Identity is stamped from the socket: the app is the client slot the shell launched, never the frame; a call's account, context and client come from the shell's own context records; a process may use only contexts it opened.
  • Grants and consent: a link opens only for an app whose native-apps.json entry grants agent.octos (all services in developer mode); each request needs consent::granted (the first-use sheet asks once, through approvals::module_gate); each method must be granted.
  • Host obligations: once per call (a repeated call id is refused, a second result ignored), nothing after cancel, and an acknowledgement to the relay before any confirmation sheet. confirm: host calls wait on the ADR 0004 §8/§4: approval router, standing rules, shell sheets, first-use consent #120 router's sheet (decisions come back on their own peerlink: route in approvals/mod.rs); confirm: app calls go to the app with confirm_required, and a result without the app's awaiting_confirmation first is refused. Deadlines time calls out and cancel them at the app.
  • A dead process fails its outstanding calls (outcome_unknown unless they only read), closes its request contexts and keeps the peer: a restarted process gets the same service, not a new peer.
  • Requests run on the app's one peer through the same service in-process modules get (crates/app-peers' broker via hosted::launch, under octos-core), as an adapter until #2567's relay exists.
  • The Makepad client (makepad_ai_services::peer, ai-services: the peer link client (OctoSense ADR 0004 §5) makepad#54, merged) serves in-process modules too. The shell parses the same JSON with serde_json and needs no Makepad type, so nothing here waits on a repin; the fixture test pins the exact frames the client writes.

Seams for home-96 (octos#2567 relay)

Seam What
peer_link::set_tool_relay(Box<dyn ToolRelay>) install the relay; outcomes queued before are handed over
ToolRelay::acknowledged(app, call_id) / finished(app, call_id, ToolCallResult) where each call's ack and one result go (Ok, Error, OutcomeUnknown)
peer_link::tool_call(app, KernelToolCall) -> Result<(), Refused> hand over a kernel peer/tool/call (kernel context_id, stamped caller, risk, timeout_ms, trigger, outcome_unknown); Refused::{NotConnected, Duplicate, UnknownContext, Declined}
peer_link::tool_cancel(app, call_id) the kernel cancelled
peer_link::context_owner(app, kernel_context_id) account and client of a context (matches octos's <nonce>-<handle> ids)
peer_link::has_link(app), peer_link::close_account(app, account) connected?; sign-out closes that account's contexts and tells the app

RecordingToolRelay is the test double. Nothing in crates/app-peers or crates/ai-host changed.

Process sandboxes (crates/shell/src/sandbox/)

Built generically from each entry's sandbox and storage (now generated into native_apps.rs: network, processes, accounts, external; tools/native_apps.py validates them) and applied in clients::spawn_client:

  • macOS: a Seatbelt profile through /usr/bin/sandbox-exec; a cargo run launch gets it as cargo's runner, so only the app is sandboxed, never the build. The person's data roots (home, /Volumes) are closed except the jail, its secrets and external; the program and resources are read-only; network: none allows only the hub on loopback; processes: false denies fork and exec. sandbox-exec/sandbox_init are deprecated in Apple's headers but remain the working mechanism; if it disappears the app runs unsandboxed with a logged warning.
  • Linux: Landlock (paths, and TCP ports from ABI 4) plus seccomp (ptrace, process_vm_*, perf_event_open, bpf, userfaultfd, kexec, mounts, namespaces, keyring; fork/non-thread clone for processes: false), installed between fork and exec, best-effort with the layers that took in the log. A dev launch through cargo sandboxes the build too, with the build's paths added. Compile-checked only; not run.
  • Windows: a documented TODO with the AppContainer design (sandbox/mod.rs); the app runs unsandboxed and the log says so.
  • No child inherits OCTOS_* or OCTOSENSE_* (host and external tokens, kernel and core dirs, the vault): sandbox::scrub_env on every spawn.
  • OCTOSENSE_SANDBOX_NARROW=<app,…> narrows an app to its jail and no network for test runs; it can only take rights away.

Crash isolation

A process app that died was removed with a notification. Now its tile stays, closed (" stopped", "Click to restart"), its peer link, bus registration and pane links go, and a press restarts it through the existing Restart action (ClientSlot::stops_in_place, MpRunView::show_stopped).

Checks

All run locally on macOS (arm64), after rebasing on main (makepad 4fdcfccc):

  • cargo check --locked -p octosense and --features mobile-apps; from phone/: cargo check --locked -p octosense-home --features mobile-apps
  • cargo test --locked -p octosense-shell --lib (455 passed) and from phone/ with --features mobile-apps (466 passed), including 12 peer-link tests (framing against the Makepad client's fixture frames, identity stamping, grants/consent, once per call, nothing after cancel, ack before sheet, confirm: app ack, deadlines, death, sign-out), 7 sandbox tests (a real child under a jail-only profile reads its jail and secrets and is refused a file beside it, another app's jail, a write outside, a non-hub TCP connection and a fork; the Terminal's broad policy; the profile's rule order; no OCTOS_*/OCTOSENSE_* in a child's environment; cargo [env] host variables) and the new crash-tile and processes_available() tests
  • tools/check-shell-graph.sh -p octosense and -p octosense-home; tools/setup.py --check --cargo; tools/native_apps.py --check; python3 -m unittest discover -s tools -p 'test_*.py'
  • Hidden-window run (MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE, ended with /gq): the Terminal as a process under its sandbox narrowed with OCTOSENSE_SANDBOX_NARROW=terminal rendered and ran its shell; typed into it, ls ~ gave "Operation not permitted", curl https://example.com failed to resolve, the jail was writable, and env showed no OCTOS* variable although the shell was started with a fake OCTOS_AUTH_TOKEN and OCTOS_APP_CORE_DIR. Reference (network none, no processes, jail only) rendered normally under its profile and its environment had no OCTOS* variable. kill -9 of either child left the tile as " stopped / Click to restart" with the shell running; a click restarted it.
  • The Terminal declares no agent, so no peer link opened in the run; the link is covered by the unit tests.
  • Linux: sandbox/ compile-checked for x86_64-unknown-linux-gnu and aarch64-unknown-linux-ohos, and Windows (x86_64-pc-windows-msvc) through a standalone crate holding the same files. A whole-shell cargo check --target x86_64-unknown-linux-gnu stops in ring's C build (no Linux C cross toolchain here). Unverified: nothing ran on Linux or Windows.
  • Makepad side: cargo test -p makepad-ai-services --lib (61 passed), ai-services: the peer link client (OctoSense ADR 0004 §5) makepad#54, merged.

Follow-ups

  • Windows AppContainer (design in sandbox/mod.rs).
  • Run the Linux Landlock/seccomp path on a real kernel (and Vulkan/Wayland process hosting).
  • In-process adoption of Makepad's PendingPeerLinks in module_host.rs, now that the pin carries the client, so an in-process Makepad module uses the same OctosPeer API.
  • home-96: wire octos#2567's relay to the seams above (and pass exact kernel context ids if the broker exposes them).
  • macOS profile: consider closing keychain mach services for jail-only apps; the network rule allows the hub port on any loopback listener.

🤖 Generated with Claude Code

ymote and others added 2 commits September 28, 2026 18:07
…its tile [skip ci]

ADR 0004 §3, §11 and §2. tools/native_apps.py validates and generates
each app's `sandbox` (network, processes) and `storage` (accounts,
external) into native_apps.rs. sandbox/ builds one policy per app and
applies it in spawn_client:

- macOS: a Seatbelt profile through /usr/bin/sandbox-exec (cargo's
  runner for a `cargo run` launch, so the build stays outside): the
  person's data roots closed but for the jail, its secrets and
  `external`; program and resources read-only; network `none` = the hub
  on loopback only; `processes: false` = no fork or exec.
- Linux: Landlock (paths; TCP ports from ABI 4) and seccomp, installed
  between fork and exec, best-effort with the layers logged.
- Windows: a documented AppContainer TODO; the log says unsandboxed.
- No child inherits OCTOS_* / OCTOSENSE_* (scrub_env), and a cargo
  launch's [env] host variables are unset in its runner.
- OCTOSENSE_SANDBOX_NARROW narrows an app for test runs (only removes).

A process app that dies unexpectedly now keeps its tile, closed with
"<App> stopped" / "Click to restart" (MpRunView::show_stopped), and a
press restarts it; the shell and other apps keep running. The
processes_available() test now covers the phone targets too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ADR 0004 §5. peer_link/ gives a process app its own channel to its octos
app agent on its hub socket ("octos_peer" frames, never the AI bus):
requests on its one peer through the app-peers service in-process
modules get (an adapter until octos#2567's relay), and the tool-call
half behind seams for the relay (set_tool_relay, tool_call, tool_cancel,
context_owner, has_link, close_account).

Identity is the socket's slot; a call's account, context and client come
from the shell's own context records. Links open only for apps whose
native-apps.json entry grants agent.octos, and requests need consent.
The shell keeps the host obligations (once per call, nothing after
cancel, acknowledge before any sheet; confirmations through the #120
router, whose decisions get their own peerlink: route), and a dead
process fails its calls (outcome_unknown unless read-only), closes its
contexts and keeps the peer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ymote
ymote merged commit 6e9517e into main Sep 29, 2026
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
Factual corrections checked against main baa90bd and octos acffad3b:
octos#2567 and OctoSense-org#106 are merged; the pin is acffad3b; generic_tools is an
exact list and omitting it keeps the whole roster (ask_user_question and
shell included), so octos's shell is kept out only by the _main
tool_policy (OctoSense-org#117); the peer link method is octos.session.open.
architecture.md and ai-services.md now reflect the system chat (OctoSense-org#132),
the peer link's shell side, the macOS sandbox and the Restart tile
(OctoSense-org#130), and the host-tool relay (OctoSense-org#145). ADRs 0002 and 0003 get dated
amendment notes for OctoSense-org#117. The system_tools.rs module doc no longer says
the command-execution switch does not exist.

Records two decisions of 2026-09-29 in ADR 0004: host.ask is dropped for
octos's ask_user_question routed by the shell (G11), and terminal.run is
process-only (G12).

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