diff --git a/crates/kernel/src/system_tools.rs b/crates/kernel/src/system_tools.rs index 04f32dc9..6a460ed6 100644 --- a/crates/kernel/src/system_tools.rs +++ b/crates/kernel/src/system_tools.rs @@ -4,8 +4,8 @@ //! - [`SYSTEM_AGENT_TOOLS`]: the octos tools the system agent //! (`_main:api:octosense#system`) gets by default. With what the person //! grants it ([`SystemAgentTools`]: toolbox tools, other apps' shareable -//! tools, and, planned, command execution behind a Settings switch that -//! does not exist yet; see [`SystemAgentTools::grant_command_execution`]) +//! tools, and command execution behind the Setup → Assistant → Command +//! execution switch (#132); see [`SystemAgentTools::grant_command_execution`]) //! that is its whole set: ADR 0004 §12's "exactly its grants". //! - [`tool_policy`]: the `_main` profile's policy, the ceiling for every //! `_main` session. It is every tool any grant can give: OctoSense @@ -32,10 +32,13 @@ //! client's turns on the session, so the shell registers without it; the //! exact list waits for a durable host-only list (octos#2605). The //! real-kernel exact-list test is kept, ignored until then. -//! - **App peers are not capped below what they can be granted**: each is -//! narrowed to its grants by its turns' `generic_tools` (octos#2567, plan -//! step 6). Host-routed tools (app, toolbox, cross-app tools, command -//! execution) are registered after the policy, so it never strips them. +//! - **App peers are not capped below what they can be granted**, and are +//! not yet narrowed to their grants either: the broker registers them with +//! `generic_tools` omitted, so each keeps octos's whole kernel roster +//! except what this policy denies (octos's shell). Narrowing each peer to +//! its grants through `generic_tools` is plan step 6, not done yet. +//! Host-routed tools (app, toolbox, cross-app tools, command execution) +//! are registered after the policy, so it never strips them. //! - **Talk to Octos external turns are unaffected**: octos confines them to //! its external allowlist ([`EXTERNAL_TURN_TOOLS`]), none of which is the //! shell (UPCR-2026-036). diff --git a/docs/adr/0002-event-driven-app-agents.md b/docs/adr/0002-event-driven-app-agents.md index 345dfc6f..411cdd14 100644 --- a/docs/adr/0002-event-driven-app-agents.md +++ b/docs/adr/0002-event-driven-app-agents.md @@ -1,7 +1,7 @@ # ADR 0002: Event-driven app agents: apps think on their own triggers and publish cards to the glance screen - **Date:** 2026-09-27 -- **Status:** Proposed; amended 2026-09-28 by [ADR 0004](0004-native-apps-hosting-and-peers.md) (sections 1, 4, 6, 10, 12 and 13; see the amendment at the end) +- **Status:** Proposed; amended 2026-09-28 by [ADR 0004](0004-native-apps-hosting-and-peers.md) (sections 1, 4, 6, 10, 12 and 13; see the amendment at the end); notes dated 2026-09-29 record what has since changed - **Scope:** How the assistant works in OctoSense when no person is typing, for any app: which agent runs, what starts it, which tools and data it may use, how it gathers information, how it produces and checks a card, and where the card and what it learned go. - **Relates to:** [ADR 0001](0001-one-octosense-repository.md) (one repository; [`crates/kernel`](../../crates/kernel), [`crates/app-peers`](../../crates/app-peers), [`crates/ai-host`](../../crates/ai-host), [`crates/shell`](../../crates/shell)); [Home ADR 0002](home/0002-agentic-app-security-model.md) (agentic app security model), [Home ADR 0003](home/0003-app-hub-and-store.md) (App Hub), [Home ADR 0004](home/0004-system-apps-are-contained-script-apps.md) (system apps are contained script apps); [Rinx ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md) (host-owned octos app peers); octos ADR "personal memory tiers" (octos-org/octos#2365); OctoScript [`docs/ui-profile-l0.md`](https://github.com/OctoSense-org/OctoScript/blob/main/docs/ui-profile-l0.md) (the L0/L1/L2 card levels). @@ -209,7 +209,7 @@ Everything above applies the same way to **contained script apps** (run by App H Outside developer mode (ADR 0004 §13, which overrides every approval), in every case approvals go through the kernel's existing approval path and the shell, which draws the sheet for `confirm: host` (in the owning app's conversation or batched in the system chat) and hands `confirm: app` to the owning app (section 10), deduplicated by `//`, and the system agent cannot answer approvals for host-bound peers (octos#2560). -**The system agent's tool set is defined and enforced** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §12)*. The system agent is held to the same model as app agents: an explicit list of tools, in octos or in the shell's host configuration, made of its supervision tools (the peer tools, glance curation, policy and budgets), the toolbox tools it is granted, and the cross-app tools granted to it (section 1). Command execution is grantable to it as to app agents: off unless the person grants it in Settings, and each command still goes through the shell with a live approval (section 10) outside developer mode (ADR 0004 §13). Its tool set is exactly its grants, enforced by the shell, and a test starts a system-agent turn and checks that it is offered exactly that list. **Today it is not:** the system agent's turns get octos's full default tool set, shell included. That must change before cross-app grants reach it. +**The system agent's tool set is defined and enforced** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §12)*. The system agent is held to the same model as app agents: an explicit list of tools, in octos or in the shell's host configuration, made of its supervision tools (the peer tools, glance curation, policy and budgets), the toolbox tools it is granted, and the cross-app tools granted to it (section 1). Command execution is grantable to it as to app agents: off unless the person grants it in Settings, and each command still goes through the shell with a live approval (section 10) outside developer mode (ADR 0004 §13). Its tool set is exactly its grants, enforced by the shell, and a test starts a system-agent turn and checks that it is offered exactly that list. **Today it is not:** the system agent's turns get octos's full default tool set, shell included. That must change before cross-app grants reach it. *(2026-09-29: partly stale. Since [#117](https://github.com/OctoSense-org/OctoSense/pull/117) the shell writes a `tool_policy` denying `group:runtime` (octos's shell) into the `_main` profile before every kernel start, so the system agent no longer has octos's shell; the rest of the default set remains, a ceiling rather than the exact list, which waits for octos#2605. See the amendment of 2026-09-29.)* **Cards.** Anything a native module publishes to the glance screen goes through `glance.publish` as an L0 card with the same `card-studio` checks. A native module's own in-app surfaces (for example, Rinx's in-room mini apps) stay under that module's authority model (Rinx ADRs 0002 and 0005) and are not routed through the glance render and critique unless they are published as glance cards. @@ -358,12 +358,12 @@ Mail follows as the first app with approvals (`mail.send`, on the shell's sheet) ADR 0004 supplies the hosting and transport this ADR assumes and changes it in these places: -- **§1 (system agent and app agents).** The system agent talks to app agents through octos's peer mechanism (`peer_send_input`, the blackboard, `peer_respond`). A turn it starts on an app peer runs with the app's tools, memory and context: octos delivers that input to the shell's driving connection as `peer/input`, and the shell starts the turn (octos#2567); the app agent's questions on such turns reach the system agent through the shell's `host.ask` tool (ADR 0004 §6). +- **§1 (system agent and app agents).** The system agent talks to app agents through octos's peer mechanism (`peer_send_input`, the blackboard, `peer_respond`). A turn it starts on an app peer runs with the app's tools, memory and context: octos delivers that input to the shell's driving connection as `peer/input`, and the shell starts the turn (octos#2567); the app agent's questions on such turns reach the system agent through the shell's `host.ask` tool (ADR 0004 §6). *(2026-09-29: `host.ask` is dropped; the app agent asks with octos's own `ask_user_question` and the shell routes the question. See the amendment of 2026-09-29.)* - **§1 and §4 (cross-app tools; decided on OctoSense #110).** App agents **and the system agent** may call another app's tools when the calling app's manifest asks for them and they are granted: by the shell at install for script apps, in the reviewed `native-apps.json` for native apps. This replaces "the system agent never holds app tools". The shell registers and routes each granted tool, marks it with its owning app, and checks it on every call; it authorizes cross-app calls against the grants, with no second agent-level consent. ADR 0004 §7. - **§6 (toolbox).** Toolbox tools reach agents as host-routed tools under that model: `workflow.run` and `workflow.fork`; `toolbox.search` and `toolbox.web_read` with `research`; `toolbox.deep_crawl` with `crawl`. Beyond the toolbox, an app agent gets whatever its manifest declares and the person grants at install (octos's `web_search`, `deep_search` and other generic tools, other apps' tools, command execution), with no fixed exclusions (ADR 0004 §12). Registration is additive, host-routed tools are kept out of external turns, and the system agent may receive granted tools too. - **§7 and §8 (cards).** App agents publish with `glance.publish` themselves; the system agent curates and announces completed cross-app requests. A card and its follow-up conversation are kept by reference (card and context id) by the app's host service. - **§10 (approvals).** "Only the person approves" becomes "only the person approves, **live or in advance through standing rules the shell enforces**; never the system agent". Every app tool call, from the owning app's agent, another app's agent or the system agent, goes through the shell, which authorizes, audits and routes it. The shell draws the sheet for `confirm: host` tools, in the app's conversation or batched in the system chat; for `confirm: app` tools it hands the confirmation, with who is calling, to the owning app, which shows its own sheet for every caller (section 12); when the app is not running or nobody is present the call waits or is refused visibly (octos#2567). An agent's own text is never an approval surface; each line shows the owning app, the tool and the exact arguments, and for a cross-app call the calling app. The system agent may batch one request's `confirm: host` approvals into one sheet in its chat. Standing rules are keyed to (owning app, tool), conditioned, capped, time-boxed at their broadest, notified and audited; tools marked `auto_approvable: false`, unknown outcomes and (by default) runs started by incoming content always ask. All of this holds outside developer mode, which overrides every approval, `auto_approvable: false` included, behind its banner, audit, gate and developer profile, and never for external clients (ADR 0004 §13). ADR 0004 §8. -- **§12 (native modules and script apps).** How each hosting kind reaches its agent (injection, the peer link, `host.request`) is ADR 0004 §4 and §5; every app's agent needs the person's consent at first use. The system agent's tool set is its grants (command execution only if the person grants it in Settings, each command approved live), enforced by the shell, with a test; today its turns get octos's full default tool set, shell included, which must change (ADR 0004 §12). +- **§12 (native modules and script apps).** How each hosting kind reaches its agent (injection, the peer link, `host.request`) is ADR 0004 §4 and §5; every app's agent needs the person's consent at first use. The system agent's tool set is its grants (command execution only if the person grants it in Settings, each command approved live), enforced by the shell, with a test; today its turns get octos's full default tool set, shell included, which must change (ADR 0004 §12). *(2026-09-29: octos's shell is denied since #117; see the amendment of 2026-09-29.)* - **§13 (autonomy).** Adds the `auto_approvable` flag and the incoming-content rule. ## Amendment, 2026-09-28 (search the web as the person) @@ -376,3 +376,11 @@ The maintainer changed §6 rules 1, 2, 3 and 5 on 2026-09-28, in two steps: What stays excluded is circumvention: forged fingerprints, hidden browser automation, imitated human input and automatic CAPTCHA solving. A challenge goes to the person to solve in their browser, or the attempt ends when nobody is present. Requests keep a human pace per site. A search engine's terms may not allow automated queries; like robots.txt (rule 5), that is weighed against OctoSense acting for one person, and an operator who needs stricter behaviour turns results-page search off. Implementation in octos: results-page search on by default (octos#2607), then searches and page reads through the person's browser profile with challenges handed to the person, and clean-room DuckDuckGo, Bing and Google engines in the octos metasearch under these rules. + +## Amendment, 2026-09-29 (ADR 0004 gap review) + +Facts that changed after the 2026-09-28 amendment, and two decisions made in ADR 0004 on 2026-09-29: + +- **§10 and §12 (the system agent's tool set).** Since [#117](https://github.com/OctoSense-org/OctoSense/pull/117) the shell writes a `tool_policy` denying `group:runtime` (`shell`, `bash`, `exec_command`, `write_stdin`) into the `_main` profile before every kernel start (`crates/kernel/src/system_tools.rs`, `enforce`). The system agent and the app peers (sessions on the same profile) therefore no longer get octos's shell (when the policy is written: on a foreign policy or the person's own octos home `enforce` refuses and the kernel still starts without it, a known gap). The rest of octos's default set remains: the policy is a ceiling, not the exact list, and the exact-list test stays ignored until octos#2605. Command execution for the system agent is the Setup → Assistant → Command execution switch ([#132](https://github.com/OctoSense-org/OctoSense/pull/132)), which registers the host tool `terminal.run`, each command approved live. +- **§1 (questions).** `host.ask` is dropped (ADR 0004 §6, decided 2026-09-29). octos keeps `ask_user_question` on host-driven turns, so app agents and the system agent ask with it, and the shell routes each question to the app's own conversation, or to the system chat for system-agent turns (including `peer/input` turns). +- **§10 (command execution).** `terminal.run` exists only where the Terminal runs as its own sandboxed process: macOS, Windows, and Linux with Vulkan and Wayland. The in-process Terminal is read-only (ADR 0004 §10, decided 2026-09-29). diff --git a/docs/adr/0003-shared-octos-client-access.md b/docs/adr/0003-shared-octos-client-access.md index 142fce9b..92d4d80d 100644 --- a/docs/adr/0003-shared-octos-client-access.md +++ b/docs/adr/0003-shared-octos-client-access.md @@ -95,6 +95,18 @@ separate kernel would be a different assistant with different memory. view (`/proc/self`, `/proc//…`, `/dev/fd`), judged on the raw, normalized and canonical path. The shell policy's text check on `/proc//environ` is only a backstop for the host's own turns. + + *Amended 2026-09-29:* since + [#117](https://github.com/OctoSense-org/OctoSense/pull/117) OctoSense does + configure the tool set, in one place: before every kernel start the shell + writes a `tool_policy` denying `group:runtime` (`shell`, `bash`, + `exec_command`, `write_stdin`) into the `_main` profile + (`crates/kernel/src/system_tools.rs`, `enforce`), so no `_main` session + (the system agent, the app peers) gets octos's shell either. It replaces + only a policy OctoSense wrote and refuses a foreign policy or the person's + own octos home; on such a refusal the kernel still starts without the + denial (a known gap). The sandbox is still octos's default. See + [ADR 0004 §12](0004-native-apps-hosting-and-peers.md#12-agent-tools-what-the-manifest-declares-and-the-person-grants). 3. **Pairing, not copying.** A web client gets the external token only through octos's pairing: an 8-character code shown on the trusted sheet (with a QR of the web client's link), valid for five minutes and one claim. diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md index 8b4f2fd8..4bbcbc95 100644 --- a/docs/adr/0004-native-apps-hosting-and-peers.md +++ b/docs/adr/0004-native-apps-hosting-and-peers.md @@ -1,9 +1,9 @@ # ADR 0004: Native apps, app agents and cross-app work: one manifest, hosting per target, an agent for every app, approvals by the person - **Date:** 2026-09-28 -- **Status:** Accepted (2026-09-28) +- **Status:** Accepted (2026-09-28); amended 2026-09-29 (section 6: `host.ask` dropped for octos's `ask_user_question`; sections 10 and 12: `terminal.run` only where the Terminal runs as a process; factual corrections from the gap review, marked where made) - **Scope:** How OctoSense declares, links, hosts, isolates and trusts its bundled native Rust apps on each target; where every app keeps its data and what its agent may read; how every bundled app, native or script, owns an octos app agent; how the system agent and app agents work across apps; and how the person approves what they do, live or in advance. -- **Relates to:** [ADR 0001](0001-one-octosense-repository.md); [ADR 0002](0002-event-driven-app-agents.md) (app agents; amended by this ADR); [Home ADR 0002](home/0002-agentic-app-security-model.md) and [Home ADR 0004](home/0004-system-apps-are-contained-script-apps.md) (amended); Rinx [ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md); octos UPCR-2026-034 (host-owned app peers, merged), UPCR-2026-035 ([octos#2567](https://github.com/octos-org/octos/pull/2567), host tools per app peer, open) and UPCR-2026-036 (`serve --host-managed`, [octos#2591](https://github.com/octos-org/octos/pull/2591), merged); [octos#2601](https://github.com/octos-org/octos/pull/2601) (tool origin, per-connection ownership); [ADR 0003](0003-shared-octos-client-access.md) (Talk to Octos, OctoSense [#98](https://github.com/OctoSense-org/OctoSense/pull/98): external clients never reach app agents). +- **Relates to:** [ADR 0001](0001-one-octosense-repository.md); [ADR 0002](0002-event-driven-app-agents.md) (app agents; amended by this ADR); [Home ADR 0002](home/0002-agentic-app-security-model.md) and [Home ADR 0004](home/0004-system-apps-are-contained-script-apps.md) (amended); Rinx [ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md); octos UPCR-2026-034 (host-owned app peers, merged), UPCR-2026-035 ([octos#2567](https://github.com/octos-org/octos/pull/2567), host tools per app peer, merged; OctoSense pins its follow-ups, octos acffad3b) and UPCR-2026-036 (`serve --host-managed`, [octos#2591](https://github.com/octos-org/octos/pull/2591), merged); [octos#2601](https://github.com/octos-org/octos/pull/2601) (tool origin, per-connection ownership); [ADR 0003](0003-shared-octos-client-access.md) (Talk to Octos, OctoSense [#98](https://github.com/OctoSense-org/OctoSense/pull/98): external clients never reach app agents). ## Context @@ -43,23 +43,25 @@ ### What octos provides (octos `main` e6223ef, verified) +Verified at e6223ef when this ADR was written. OctoSense now pins octos acffad3b (the merge of octos#2616, the #2567 follow-ups); the corrections dated 2026-09-29 below were checked against that pin. + - **Peers are sessions, not processes.** One kernel process per shell; every peer is a session in it (tokio tasks) with its own workspace, memory namespace, transcript and model lane. Tools such as the shell run as short child processes in the peer's workspace. - **No 8-peer ceiling.** `peer/prepare` stages 1–8 peers **per call** (`n`); the only total is a soft cap of 8192 live peer registrations. The model's `peer_handoff` tool is limited to 4 per turn; host-owned app peers are not created through it. OctoSense's broker prepares one peer per call. - **Master and peer talk through an inbox and a file blackboard.** Master → peer: `brief.md` at creation, then `peer_send_input`, delivered as the peer's next user turn (originator only; in serve through a durable queue drained every 2–5 s, best-effort at-least-once). Peer → master: each turn writes `peers//result.md` (+ `result-N.md`, `turns.txt`), read with `peer_gather` / `peer_list`, "the only cross-peer channel". A peer asks with `ask_user_question`; the master answers with `peer_respond`. Depth 1: peers cannot create, steer or close peers. - **Host-owned app peers** (UPCR-2026-034): owned by the shell's system-agent session; exclusive workspace and memory namespace per (app, account); resumable with a host token; **request contexts** (`peer/context/open`) give an app's clients separate transcripts, folders and child memory namespaces. The system agent may answer an app peer's questions, never approve its tools. - **Request contexts are fenced** (UPCR-2026-034): a context's workspace is `/contexts//`; the peer's own folder is refused as a context workspace, so a context turn cannot read the files beside it. The peer's own session is bound to the peer's folder and reads everything under it, every context's folder included. - **Closing is final** (UPCR-2026-034): a closed peer cannot be resumed, and it keeps its (app, account) namespace and workspace reservation ("their stores still hold data"), so no replacement can be created for them. octos has no suspend, reclaim or purge. -- **Host tools per app peer** (UPCR-2026-035, #2567, open): the host registers the app's `tools.json`; the kernel sends every call to the **host's connection** (`peer/tool/call`, carrying `context_id`), the host answers (`peer/tool/result`). A call carries `session_id`, `context_id` (null on the peer's own session) and `turn_id`, and no account or client field. A turn driven with a `generic_tools` list keeps only the peer-safe kernel tools (file reads, search, memory), and `ask_user_question` is not among them. Once-only execution, timeouts, audit, approvals for destructive and outward tools, budgets. The kernel never talks to an app. As agreed on #2567: registration is **additive** (the peer-safe generic tools always stay); ownership is **per connection** (the registering connection is the tool host and owns the peer's approvals); host-routed tools have their own origin and are excluded from Talk to Octos external turns (#2601's builtin-only allowlist); and the system agent's input to a host-owned peer is delivered to the host's driving connection as **`peer/input`**. +- **Host tools per app peer** (UPCR-2026-035, #2567, merged): the host registers the app's `tools.json`; the kernel sends every call to the **host's connection** (`peer/tool/call`, carrying `context_id`), the host answers (`peer/tool/result`). A call carries `session_id`, `context_id` (null on the peer's own session) and `turn_id`, and no account or client field. `generic_tools` is an exact list the host sets per peer: a turn keeps exactly the listed kernel tools that the session has (an empty list keeps none, and `ask_user_question` stays when it is listed); with `generic_tools` omitted the peer keeps its whole kernel roster, `ask_user_question` and `shell` included. *(Corrected 2026-09-29: this bullet said a `generic_tools` turn keeps only peer-safe tools and never `ask_user_question`; octos tests `should_offer_exactly_the_generic_tools_the_host_sets`, `should_keep_the_peers_kernel_tools_when_it_registers_an_empty_set` and `should_offer_ask_user_question_on_a_peer_input_turn_when_the_host_lists_it` show otherwise.)* Once-only execution, timeouts, audit, approvals for destructive and outward tools, budgets. The kernel never talks to an app. As agreed on #2567: registration is **additive** (with `generic_tools` omitted, the peer's kernel tools all stay); ownership is **per connection** (the registering connection is the tool host and owns the peer's approvals); host-routed tools have their own origin and are excluded from Talk to Octos external turns (#2601's builtin-only allowlist); and the system agent's input to a host-owned peer is delivered to the host's driving connection as **`peer/input`**. - **Host-managed serve** (UPCR-2026-036, OctoSense #98): opt-in websocket kernel with a host token and an external token. External clients (a web client, a TUI) get no peer methods and cannot name an app peer's session. ### Gaps 1. **A system-agent turn on an app peer got none of the app's context.** #2567 gives tools, memory and context only to turns driven by the host's connection, and `peer_send_input` used to arrive as a tool-less kernel continuation. Closed on #2567 by `peer/input` (section 6); the shell has to handle it. -2. **App tools are per peer.** An app agent cannot call another app's tool, and the system agent has no app tools; the system agent's turns instead get octos's full default tool set, shell included (section 12). -3. **Script apps have no agent on `main`.** [#106](https://github.com/OctoSense-org/OctoSense/pull/106) (open) adds the `octos` host service, one peer `card.` per app; it declines tool approvals (the Card runner has no approval sheet yet). +2. **App tools are per peer.** An app agent cannot call another app's tool, and the system agent has no app tools; the system agent's turns instead get octos's full default tool set, shell included (section 12). *(Since [#117](https://github.com/OctoSense-org/OctoSense/pull/117), 2026-09-28, the `_main` profile's `tool_policy` denies octos's shell; the rest of the default set remains.)* +3. **Script apps have no agent on `main`.** [#106](https://github.com/OctoSense-org/OctoSense/pull/106) (merged 2026-09-28) adds the `octos` host service, one peer `card.` per app; it declines tool approvals (the Card runner has no approval sheet yet). 4. **Native apps in their own process cannot reach an agent**: `ai_host::offer` hands the peer to a module in-process only. 5. Only Rinx is granted an agent, in code (`Policy::shipped()`). -6. **Host-driven turns cannot ask questions**: a turn driven with a `generic_tools` list, which the shell sets from each app's manifest and grants (section 12), loses `ask_user_question`. +6. ~~**Host-driven turns cannot ask questions**~~. *(Withdrawn 2026-09-29: the premise was false. octos keeps `ask_user_question` on host-driven turns: `generic_tools` is an exact list the host chooses, and omitted it keeps the whole roster. What is missing is on the shell's side: the broker does not forward a peer's `user_question/requested` to any conversation yet. See the decision in section 6.)* 7. **Request contexts cannot read their account's data** (the fence above). 8. **An agent cannot be paused or erased**: signing out cannot use `peer_close`, and removing an account or the app cannot free or erase its agent. @@ -143,15 +145,15 @@ It rides the process's hub socket as its **own channel**, addressed to that app' - up: `PeerRequest { req_id, method: "octos.session.open" | "octos.turn.start" | …, args }`, `PeerToolResult { call_id, ok, data | error | awaiting_confirmation }`; - down: `PeerReply { req_id, … }` and streamed turn events, `PeerToolCall { call_id, name, args, risk, confirm_required, timeout_ms, account, context_id, client, caller }`, `PeerToolCancel { call_id }`. -**Identity travels with every call, stamped by the shell.** The socket identifies the app, not who inside it asked. When the app opens a request context (`octos.context.open { account, client }`, where `client` is the app's own label such as a Rinx mini app id), the shell records `context_id → (app, account, client)`. On each `peer/tool/call` it looks up the kernel's `context_id` (null: the agent's own session, `client` null) and the peer's account, and puts them on `PeerToolCall`, with **`caller`**: the app's own agent, or for a cross-app call the calling app or the system agent (section 7). The app never supplies them on a call, and a process may use only contexts it opened. The app checks its own grants against them (section 9), and a `confirm: app` sheet shows the caller (section 8). In-process modules and script apps' host services get the same fields. +**Identity travels with every call, stamped by the shell.** The socket identifies the app, not who inside it asked. When the app opens a request context (`octos.session.open { account, client }`, where `client` is the app's own label such as a Rinx mini app id), the shell records `context_id → (app, account, client)`. On each `peer/tool/call` it looks up the kernel's `context_id` (null: the agent's own session, `client` null) and the peer's account, and puts them on `PeerToolCall`, with **`caller`**: the app's own agent, or for a cross-app call the calling app or the system agent (section 7). The app never supplies them on a call, and a process may use only contexts it opened. The app checks its own grants against them (section 9), and a `confirm: app` sheet shows the caller (section 8). In-process modules and script apps' host services get the same fields. The shell stamps the app's identity from the socket, enforces #2567's host obligations for the app (once per call, nothing after cancel, acknowledge before a confirmation sheet), and when the process dies fails its outstanding calls, closes its request contexts and keeps the peer. The client API lives in Makepad's `makepad-ai-services` (Makepad apps cannot depend on OctoSense crates) and serves in-process modules too, so an app does not know how it is hosted. ### 6. The system agent and app agents -- The system agent is the originator of every app peer. It briefs and asks with `peer_send_input`, reads answers on the blackboard, answers an app agent's questions (through `host.ask`, below, or `peer_respond` where octos keeps `ask_user_question`), and never approves an app agent's tools. +- The system agent is the originator of every app peer. It briefs and asks with `peer_send_input`, reads answers on the blackboard, answers an app agent's questions (its `ask_user_question`, below), and never approves an app agent's tools. - **A turn the system agent starts on an app peer runs with the app's tools, memory and context: the `peer/input` event** (agreed on #2567). The system agent's `peer_send_input` to a host-owned peer is not run as a kernel continuation; octos delivers it to the host's driving connection as `peer/input {peer, session_id, input_id, turn_id, text}`, and the shell starts the turn on that connection (`turn/start` with the kernel's `turn_id`), so it is host-driven: it has the app's tools and memory, and its approvals are raised in the app's conversation. The system agent follows it on the blackboard. If no host connection holds the peer (never registered, or disconnected), octos runs and queues nothing and tells the system agent the app is not connected; the system agent waits for the app or reports the failure visibly. -- **Questions from host-driven turns go through a host tool.** The shell drives turns with a `generic_tools` list set from the app's manifest and grants (section 12), and octos drops `ask_user_question` from any such turn, so `peer_respond` has nothing to answer. The shell registers **`host.ask {question, options?}`** on every app peer and routes it: on a turn the system agent started, to the system agent, whose answer (or its own question to the person) becomes the tool result; on a turn the app or the person started, to the app's conversation. It is never an approval: approvals stay with the person (section 8). We also ask octos to keep `ask_user_question` on host-driven turns; `host.ask` remains the route while it is missing. +- **Questions go through octos's own `ask_user_question` (decided 2026-09-29; there is no `host.ask`).** App agents and the system agent ask with `ask_user_question`, which octos keeps on host-driven turns (`generic_tools` is an exact list the shell sets, and omitted keeps the whole roster). The shell routes each `user_question/requested` to the right conversation: on the app's peer session or one of its request contexts, to the app's own conversation; on a system-agent turn, including a `peer/input` turn the system agent started on an app peer, to the system chat, where the system agent (or the person) answers. It is never an approval: approvals stay with the person (section 8). *(Until 2026-09-29 this section had the shell register a `host.ask` host tool on every app peer, on the premise that octos drops `ask_user_question` from host-driven turns; the premise was false and `host.ask` is dropped. No `host.ask` is registered on `main`; any remaining shell code for it from [#145](https://github.com/OctoSense-org/OctoSense/pull/145) is being removed separately.)* - **The system agent may also call granted tools of apps directly** (section 7), under its defined tool set (section 12). - In OctoSense the AI services bus is not the system agent's channel to apps. It stays for upstream Makepad apps. @@ -191,7 +193,7 @@ One agent for Rinx and one request context per mini app is enough while mini app ### 10. The terminal - Desktop: its own process on macOS and Windows, and on Linux with Vulkan and Wayland (in-process otherwise, section 2). -- Its AI may read (`read_screen`, `read_scrollback`) and **type commands**, in every hosting. Each typed command goes through the shell with a live approval showing the exact command, and is `auto_approvable: false`, so no standing rule approves it (consistent with makepad#41, which already requires confirmation for each run). Outside developer mode, that is: developer mode runs them without a sheet (section 13). +- Its AI may read (`read_screen`, `read_scrollback`) in every hosting, and **type commands only when the Terminal runs as its own sandboxed process**: macOS, Windows, and Linux with Vulkan and Wayland. The in-process Terminal (Linux without Vulkan, phones) is read-only: its module offers only the read tools (Makepad `apps/terminal/src/module.rs`, `read_only_manifest`). *(Decided 2026-09-29; this bullet said "type commands, in every hosting".)* Each typed command goes through the shell with a live approval showing the exact command, and is `auto_approvable: false`, so no standing rule approves it (consistent with makepad#41, which already requires confirmation for each run). Outside developer mode, that is: developer mode runs them without a sheet (section 13). - An agent for the terminal is optional and later. Its output is untrusted text: anything that types into a shell stays behind the person's confirmation and is `auto_approvable: false`. - `terminal-ctl`, its control socket for OctoLoop, is off unless the person enables it and is unrelated to the agent. @@ -255,11 +257,11 @@ An app agent gets **whatever tools its manifest declares, once the person grants | memory | every app agent, its own namespace | octos | | one-shot model calls (`model`) | apps granted `model` | host service | | octos's generic tools (`web_search`, `deep_search`, `deep_research` and the rest) | apps that declare them and are granted them | octos | -| **command execution** | apps that declare it and are granted it, and the system agent when the person grants it in Settings (below); each command approved live under section 8 | host tool (for example `terminal.run`) | +| **command execution** | apps that declare it and are granted it, and the system agent when the person grants it in Settings (below); each command approved live under section 8 | host tool (for example `terminal.run`, only where a process Terminal exists: section 10) | -**The system agent's tool set is its grants, enforced by the shell (decided on #110).** Today the system agent's turns get octos's full default tool set, shell included, granted or not. The system agent gets an explicit list instead: its supervision tools (the peer tools, glance curation, policy and budgets), the toolbox tools it is granted, the cross-app tools granted to it (section 7), and command execution only when the person grants it in Settings, off by default, each command going through the shell with a live approval (section 8). The shell enforces the list (with octos's help where needed), and a test starts a system-agent turn and checks it is offered exactly that list (in particular, no `shell` unless granted). This is a plan step (step 4), and it lands before any cross-app grant reaches the system agent. +**The system agent's tool set is its grants, enforced by the shell (decided on #110).** Today the system agent's turns get octos's full default tool set, shell included, granted or not. *(Since [#117](https://github.com/OctoSense-org/OctoSense/pull/117), 2026-09-28: the shell writes a `tool_policy` denying `group:runtime` into the `_main` profile, so octos's shell is gone; the rest of the default set remains, a ceiling rather than the exact list; see the follow-ups.)* The system agent gets an explicit list instead: its supervision tools (the peer tools, glance curation, policy and budgets), the toolbox tools it is granted, the cross-app tools granted to it (section 7), and command execution only when the person grants it in Settings, off by default, each command going through the shell with a live approval (section 8). The shell enforces the list (with octos's help where needed), and a test starts a system-agent turn and checks it is offered exactly that list (in particular, no `shell` unless granted). This is a plan step (step 4), and it lands before any cross-app grant reaches the system agent. -**Command execution is a grantable capability like any other, and the widest.** A shell escapes every other boundary: the workspace fence (`cat` reads anywhere, including `secrets/`), the network policy (`curl` reaches any host), budgets and audit (one call does anything), and it turns prompt injection into code execution. So the install-time grant says so plainly, and each destructive or outward call goes through section 8. A narrow tool often serves better (for example a sandboxed `toolbox.run_python`: no network, only its folder, time and memory limits). Running real commands on the person's machine is the Terminal app's shareable `terminal.run`: destructive, `auto_approvable: false`, each command approved live with the exact command shown, run in a terminal the person sees. Every live approval and `auto_approvable: false` in this section holds outside developer mode, which approves them all (section 13). octos refuses its own `shell` to app peers today (#2567's generic-tool allowlist) and to external clients (#98), so a granted app reaches command execution through a host tool. +**Command execution is a grantable capability like any other, and the widest.** A shell escapes every other boundary: the workspace fence (`cat` reads anywhere, including `secrets/`), the network policy (`curl` reaches any host), budgets and audit (one call does anything), and it turns prompt injection into code execution. So the install-time grant says so plainly, and each destructive or outward call goes through section 8. A narrow tool often serves better (for example a sandboxed `toolbox.run_python`: no network, only its folder, time and memory limits). Running real commands on the person's machine is the Terminal app's shareable `terminal.run`: destructive, `auto_approvable: false`, each command approved live with the exact command shown, run in a terminal the person sees. `terminal.run` exists only where the Terminal runs as its own sandboxed process (macOS, Windows, Linux with Vulkan and Wayland; section 10), so a grant of command execution, the system agent's Settings switch included, takes effect only there; on Linux without Vulkan and on phones it has no target (decided 2026-09-29). Every live approval and `auto_approvable: false` in this section holds outside developer mode, which approves them all (section 13). octos refuses its own `shell` to external clients (#98). For app peers and the system agent, octos's `shell` is kept out by the `_main` profile's `tool_policy`, which denies `group:runtime` (#117), so a granted app reaches command execution through a host tool. *(Corrected 2026-09-29: this sentence said octos refuses `shell` to app peers through #2567's generic-tool allowlist. It does not: with `generic_tools` omitted, as the broker registers every peer today, a peer keeps octos's whole roster, `shell` included (octos test `should_offer_exactly_the_registered_tools_and_refuse_an_unlisted_one`); the profile's `tool_policy` is the only barrier (and it fails open: when `enforce` refuses a foreign policy the kernel still starts without it) until each peer's `generic_tools` is set from its grants, plan step 6.)* ### 13. Developer mode: everything granted, for building apps fast @@ -303,17 +305,17 @@ How it is turned on and kept from leaking into normal use: - Every app can have an agent with its full context; the system agent delegates to it through `peer/input`, and may call the tools it is granted. - Cross-app tools are granted by manifest and authorized by the shell on every call, for app agents and the system agent alike; octos supplies the additive, host-routed registration (#2567, #2601). - Every app tool call, whoever makes it, goes through the shell, which authorizes, audits and routes it. The person decides every outward action, on the shell's sheet or, for a `confirm: app` tool, the owning app's own sheet showing the caller, now or through narrow, time-boxed, audited rules keyed to (owning app, tool). -- The system agent loses its default shell access and gets a tested tool set made of its grants; command execution only if the person grants it, each command approved live. +- The system agent loses its default shell access and gets a tested tool set made of its grants; command execution only if the person grants it, each command approved live, and only where a process Terminal exists (section 10). - New work: the generator; the system agent's tool set; `peer/input` in the shell; process sandboxes; the peer link; shell tool routing; the approval router and its Settings; consent at first use; release packaging of process apps; module panic containment. ## Plan 1. `native-apps.json` and `tools/native_apps.py` with `--check`; delete the native News, Maps and Photos crates. No behaviour change otherwise. 2. Terminal as a process on macOS and Windows, the only process app for now; release packages ship its `bin`. App Hub and Rinx stay in-process (section 2). -3. Land octos#2567 with the agreed design: additive registration, per-connection ownership, host-routed origin kept out of external turns (#2601), and `peer/input` (and the UPCR-2026-034 approval wording, section 8). Ask octos for `ask_user_question` on host-driven turns, a read-only view of the peer's folder for request contexts, and a host-only `peer/purge`. +3. Land octos#2567 (merged) with the agreed design: additive registration, per-connection ownership, host-routed origin kept out of external turns (#2601), and `peer/input` (and the UPCR-2026-034 approval wording, section 8). Ask octos for a read-only view of the peer's folder for request contexts, and a host-only `peer/purge`. *(2026-09-29: the earlier ask for `ask_user_question` on host-driven turns is closed as already met: octos keeps it; see section 6.)* 4. **The system agent's tool set** (section 12): its grants (supervision tools, granted toolbox tools, granted cross-app tools; command execution only when the person grants it in Settings, each command approved live), enforced by the shell, and a test that a system-agent turn is offered exactly that list. Before any cross-app grant reaches the system agent. -5. Land #106 (script apps' agents) and #108 (toolbox tools); rebase #85 on #98. -6. #2567 in the shell: register app tools, execute or relay `peer/tool/call` with the stamped identity and caller (section 5), set each peer's `generic_tools` from its manifest and grants (section 12), handle `peer/input`, `host.ask` and the host read tools, suspend on sign-out (section 11); grant cross-app tools from manifests (at install for script apps, from `native-apps.json` for native apps), register them marked with the owning app, and check every call against the grants. +5. Land #106 (script apps' agents; merged 2026-09-28) and #108 (toolbox tools); rebase #85 on #98. +6. #2567 in the shell: register app tools, execute or relay `peer/tool/call` with the stamped identity and caller (section 5), set each peer's `generic_tools` from its manifest and grants (section 12), handle `peer/input`, route agents' `ask_user_question` to the right conversation (section 6), handle the host read tools, suspend on sign-out (section 11); grant cross-app tools from manifests (at install for script apps, from `native-apps.json` for native apps), register them marked with the owning app, and check every call against the grants. 7. Shell-drawn approval sheets for `confirm: host` tools and the hand-off of `confirm: app` confirmations to the owning app with the caller (owning app, tool, exact arguments, calling app); the approval router, standing approvals keyed to (owning app, tool) and their Settings; consent at first use. 8. The peer link and its Makepad client API; process sandboxes. Then Rinx may move to a process by a reviewed `hosting` change. 9. Module panic containment. @@ -329,7 +331,7 @@ Gaps between this decision and `main` found after it was accepted (2026-09-28; s - **The system agent's exact tool list** (section 12, step 4): the `_main` profile's `tool_policy` is a ceiling, not the list. octos#2567's host session set can narrow the session with `generic_tools`, but that list narrows every client's turns on it, so the shell registers without it (as agreed on #2567); the exact list waits for a durable host-only list (octos#2605). - [x] **The Settings switch for the system agent's command execution** (section 12): done. Setup → Assistant → Command execution sets `SystemAgentTools::grant_command_execution` with the person's typed confirmation, applies from the next kernel start (with a restart offered), and routes each command through the approval router as `auto_approvable: false`. While it is on, the system chat registers `terminal.run` on the system session (octos#2567's host session target; withdrawn when it goes off), and each approved call is typed into the Terminal the person sees. - **The agent workspace is the account folder** (section 11): partly done. A peer created since the octos 665209e5 pin gets its account folder as the `peer/prepare` `cwd`, recorded beside its host token; a peer created before keeps the kernel-provisioned workspace, because octos resumes a peer only under the workspace it was made with. The account folder name (SHA-256, `app_storage`'s `account_hash`) and the memory namespace tag (FNV-1a, `account_tag` in the `crates/app-peers` broker) are two different hashes of one account; one should derive from the other. Changing the memory tag re-keys every app's memory, so it needs a migration (or the folder name follows the tag). -- [x] **octos#2567 in the shell** (plan step 6, in part): the broker registers every app peer's tools after each prepare and reconnect (`generic_tools` omitted), takes `peer/tool/call` (stamped, once per occurrence, nothing after a cancel, calls of an interrupted turn refused), starts `peer/input` turns and hands `host_tool` approvals to the router; the relay (`crates/shell/src/host_tools/`) authorizes each call by (owning app, tool) and caller and routes it to the owning app. Still open: no app declares `tools.json` (so every peer registers an empty set) and nothing grants cross-app tools yet; Rinx must hand its send sheet over (`OctosAppService::set_confirm_sheet`) to be the `rinx.message.send` confirmation; `host.ask` and the host read tools are not registered; a refused `peer/input` is only logged (octos has no way to tell the system agent); script apps' host services have no executor seam wired. +- [x] **octos#2567 in the shell** (plan step 6, in part): the broker registers every app peer's tools after each prepare and reconnect (`generic_tools` omitted), takes `peer/tool/call` (stamped, once per occurrence, nothing after a cancel, calls of an interrupted turn refused), starts `peer/input` turns and hands `host_tool` approvals to the router; the relay (`crates/shell/src/host_tools/`) authorizes each call by (owning app, tool) and caller and routes it to the owning app. Still open: no app declares `tools.json` (so every peer registers an empty set) and nothing grants cross-app tools yet; Rinx must hand its send sheet over (`OctosAppService::set_confirm_sheet`) to be the `rinx.message.send` confirmation; the host read tools are not registered; a peer's `user_question/requested` is not forwarded to any conversation yet (section 6; `host.ask` was dropped on 2026-09-29); a refused `peer/input` is only logged (octos has no way to tell the system agent); script apps' host services have no executor seam wired. - [x] **A shell-native chat with the system agent**: done. `crates/shell/src/system_chat/` (Setup → Assistant → Assistant chat, F8; full screen on a phone) talks to `_main:api:octosense#system` over the shell's own kernel connection, and its approvals go through the approval router, batched per request. ## Open questions diff --git a/docs/ai-services.md b/docs/ai-services.md index 06cf958d..eb14a6e3 100644 --- a/docs/ai-services.md +++ b/docs/ai-services.md @@ -2,7 +2,7 @@ English | [简体中文](ai-services.zh-CN.md) -How the assistant is wired into OctoSense: the [octos](https://github.com/octos-org/octos) agent kernel the shell runs, where the person configures it, which apps may use it and how, and what is planned. It describes `main` as of 2026-09-28 (OctoSense `ad0d738`, which pins octos `5e7577f0` and OctoSense-App-Hub `e8601b80`). Everything under **Works today** was read in that code; **In progress** means an open pull request; **Planned** means an ADR or an open issue, with nothing merged yet. +How the assistant is wired into OctoSense: the [octos](https://github.com/octos-org/octos) agent kernel the shell runs, where the person configures it, which apps may use it and how, and what is planned. It describes `main` as of 2026-09-28 (OctoSense `ad0d738`, which pins octos `5e7577f0` and OctoSense-App-Hub `e8601b80`); on 2026-09-29 the statements about octos#2567, host tools and approvals, the peer link, the system chat and command execution were rechecked against OctoSense `baa90bd`, which pins octos `acffad3b` and the same App Hub. Everything under **Works today** was read in that code; **In progress** means an open pull request; **Planned** means an ADR or an open issue, with nothing merged yet. This page is about the assistant *inside* OctoSense. Building an app needs no AI service and no particular coding agent: the app harness, [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow), works with any agent or none. Its [AI-SERVICES](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/AI-SERVICES.md) page is the app developer's short version of this one. @@ -31,11 +31,11 @@ For the whole system around it (processes per platform, agents, protocols, tools | One-shot model calls for contained apps (`model`, `model.complete`) | Works today ([#95](https://github.com/OctoSense-org/OctoSense/pull/95)): registered by `crates/ai-host` with the `llm` service, for apps granted `model` ([App-Hub#24](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/24), in the shells' App Hub pin) | [below](#contained-script-apps-system-and-store) | | News data service (`news`, no model) | Merged ([#69](https://github.com/OctoSense-org/OctoSense/pull/69)); answers `os.*` apps only | [`apps/news/host-service`](../apps/news/host-service/README.md) | | `glance.publish`: L0 cards on the glance screen | Merged ([#72](https://github.com/OctoSense-org/OctoSense/pull/72)); contained apps granted the `glance` capability publish ([#86](https://github.com/OctoSense-org/OctoSense/pull/86)). In progress: `sys.digest` sources ([#87](https://github.com/OctoSense-org/OctoSense/pull/87), draft) | [`crates/shell/src/glance.rs`](../crates/shell/src/glance.rs) | -| Approvals, first-use consent, developer mode | Works today ([#120](https://github.com/OctoSense-org/OctoSense/pull/120), [#118](https://github.com/OctoSense-org/OctoSense/pull/118)): the shell's approval router, standing rules and sheets, fed today by the AI services bus only; consent before an app's agent first runs; developer mode turned on only by the person | [architecture § Approvals](architecture.md#5-approvals) | -| The system agent's tool set | Works today, partly enforced ([#117](https://github.com/OctoSense-org/OctoSense/pull/117)): octos's shell is never offered; the exact list waits for octos#2567 | [architecture § Tools and grants](architecture.md#4-tools-and-grants) | +| Approvals, first-use consent, developer mode | Works today ([#120](https://github.com/OctoSense-org/OctoSense/pull/120), [#118](https://github.com/OctoSense-org/OctoSense/pull/118)): the shell's approval router, standing rules and sheets, fed by the AI services bus and by the kernel's `host_tool` approvals through the host-tool relay ([#145](https://github.com/OctoSense-org/OctoSense/pull/145)); consent before an app's agent first runs; developer mode turned on only by the person | [architecture § Approvals](architecture.md#5-approvals) | +| The system agent's tool set | Works today, partly enforced ([#117](https://github.com/OctoSense-org/OctoSense/pull/117)): octos's shell is never offered (the `_main` profile's `tool_policy`); the exact list waits for a durable host-only list, octos#2605. Command execution is the Setup → Assistant → Command execution switch ([#132](https://github.com/OctoSense-org/OctoSense/pull/132)): `terminal.run`, each command approved live, only where the Terminal runs as its own process | [architecture § Tools and grants](architecture.md#4-tools-and-grants) | | Native apps declared once (`native-apps.json`), hosting per target; the Terminal a system app in its own process on the desktop | Works today ([#113](https://github.com/OctoSense-org/OctoSense/pull/113)) | [architecture § Native apps](architecture.md#native-apps-in-process-or-their-own-process) | -| An app's own agent: `tools.json`, `AGENT.md`, skills, model requirements, triggers | Planned ([ADR 0002](adr/0002-event-driven-app-agents.md), Proposed). App Hub admits the files ([App-Hub#18](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/18)); no shell runs them; the kernel side is in progress ([octos#2567](https://github.com/octos-org/octos/pull/2567), draft) | [below](#planned-event-driven-app-agents-adr-0002) | -| Native apps in their own process reaching their agent (the peer link, ADR 0004 §5) | Planned (plan step 8). Makepad's client side is merged ([OctoSense-org/makepad#54](https://github.com/OctoSense-org/makepad/pull/54), `makepad_ai_services::peer`); the shell side is not written (nothing in `crates/shell/src/hub.rs`) | [architecture § An app and its own agent](architecture.md#an-app-and-its-own-agent) | +| An app's own agent: `tools.json`, `AGENT.md`, skills, model requirements, triggers | Planned ([ADR 0002](adr/0002-event-driven-app-agents.md), Proposed). App Hub admits the files ([App-Hub#18](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/18)); no shell runs them. The kernel side is merged ([octos#2567](https://github.com/octos-org/octos/pull/2567), in the pin), and the shell registers each peer's tools and relays its calls ([#145](https://github.com/OctoSense-org/OctoSense/pull/145)), but no app declares `tools.json` yet, so every peer registers an empty set | [below](#planned-event-driven-app-agents-adr-0002) | +| Native apps in their own process reaching their agent (the peer link, ADR 0004 §5) | Built, no user yet. Makepad's client side ([OctoSense-org/makepad#54](https://github.com/OctoSense-org/makepad/pull/54), `makepad_ai_services::peer`) and the shell side ([#130](https://github.com/OctoSense-org/OctoSense/pull/130), `crates/shell/src/peer_link/`) are merged; no process app is granted an agent yet (the Terminal, the only process app, declares none) | [architecture § An app and its own agent](architecture.md#an-app-and-its-own-agent) | ## Architecture @@ -119,7 +119,7 @@ The host **policy** decides which native modules may use the assistant, by exact ### The system agent -Today the system agent is the **owner** of every app peer: the session that created it and can address it. It does not hold any app's tools, and it cannot answer an app peer's approvals (octos [#2560](https://github.com/octos-org/octos/pull/2560)). Its tools are a defined set the shell writes before every kernel start, with octos's shell never offered ([#117](https://github.com/OctoSense-org/OctoSense/pull/117), `crates/kernel/src/system_tools.rs`; see [architecture § Tools and grants](architecture.md#4-tools-and-grants)). The person reaches it today from a Talk to Octos client ([#98](https://github.com/OctoSense-org/OctoSense/pull/98), [ADR 0003](adr/0003-shared-octos-client-access.md)); the shell draws no chat of its own for it yet. Its larger role in ADR 0002 (supervising app agents, budgets, the kill switch, ranking the glance screen, improving app agents) is **planned**; the glance screen currently orders cards by priority and recency. +Today the system agent is the **owner** of every app peer: the session that created it and can address it. It does not hold any app's tools, and it cannot answer an app peer's approvals (octos [#2560](https://github.com/octos-org/octos/pull/2560)). Its tools are a defined set the shell writes before every kernel start, with octos's shell never offered ([#117](https://github.com/OctoSense-org/OctoSense/pull/117), `crates/kernel/src/system_tools.rs`; see [architecture § Tools and grants](architecture.md#4-tools-and-grants)). The person reaches it in the shell's **system chat** ([#132](https://github.com/OctoSense-org/OctoSense/pull/132), `crates/shell/src/system_chat/`: Setup → Assistant → Assistant chat, or F8; full screen on a phone), whose approvals go through the approval router, or from a Talk to Octos client ([#98](https://github.com/OctoSense-org/OctoSense/pull/98), [ADR 0003](adr/0003-shared-octos-client-access.md)). Its larger role in ADR 0002 (supervising app agents, budgets, the kill switch, ranking the glance screen, improving app agents) is **planned**; the glance screen currently orders cards by priority and recency. ### Other assistants in the shell @@ -159,7 +159,7 @@ A native module is trusted Rust linked into the shell. **Rinx** ([hagency-org/Ri 4. `availability()` reports `Unavailable` (no kernel, not granted, signed out), `Idle`, `Ready` or `Failed`; the app's ordinary UI keeps working in every state. `settings_entry()` is `Host`: the app offers no provider form of its own and points the person to AI providers. -**Tools.** Rinx defines its assistant tools once (`status`, `list_rooms`, `open_room`, `draft_message`, `read_room`, `open_mini_app`, `send_message`; `src/assistant/mod.rs`). Today the shell reaches them through Rinx's `ServiceExecutor` on the AI services bus. `read_room` asks for a per-room grant on Rinx's read sheet; `send_message` is Destructive and self-confirmed: Rinx's send sheet is the one confirmation. Registering these tools with Rinx's octos peer, so that the peer's own model can call them, waits for the kernel's host-registered tools ([octos#2567](https://github.com/octos-org/octos/pull/2567), open). +**Tools.** Rinx defines its assistant tools once (`status`, `list_rooms`, `open_room`, `draft_message`, `read_room`, `open_mini_app`, `send_message`; `src/assistant/mod.rs`). Today the shell reaches them through Rinx's `ServiceExecutor` on the AI services bus. `read_room` asks for a per-room grant on Rinx's read sheet; `send_message` is Destructive and self-confirmed: Rinx's send sheet is the one confirmation. Registering these tools with Rinx's octos peer, so that the peer's own model can call them, needs Rinx to declare them: the kernel's host-registered tools ([octos#2567](https://github.com/octos-org/octos/pull/2567)) are merged and the shell registers and relays each peer's tools ([#145](https://github.com/OctoSense-org/OctoSense/pull/145)), but Rinx declares no `tools.json` yet and has not handed its send sheet to the shell (`OctosAppService::set_confirm_sheet`). **Rinx mini apps.** Rinx hosts reviewed OctoScript mini apps and serves them the same four `octos.*` services, each running instance in its own request context of Rinx's peer ([example](https://github.com/hagency-org/Rinx/tree/main/examples/miniapps/matrix-octos-script)). That is Rinx's own mini-app host, for bundles a person imports into Rinx after review; it is not the App Hub install path. @@ -174,7 +174,7 @@ A script app runs in App Hub's Card runner and reaches the shell only through `h | `news` | `os.*` apps only (`may_call` in `apps/news/host-service/src/lib.rs`) | News's data service (feeds, ledger, `news.list`, `news.read`, …); no model. The shells' App Hub pin knows the `news` capability, but the News bundle does not declare it yet (`apps/news/bundle/manifest.json`), so it still fetches on its own | | `glance` | apps granted the `glance` capability ([#86](https://github.com/OctoSense-org/OctoSense/pull/86), [App-Hub#22](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/22)) | Publishing L0 cards to the glance screen, under the app's own id (`crates/shell/src/glance.rs`) | | `model` | apps granted the `model` capability ([App-Hub#24](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/24)) | One-shot `model.complete {task, input, schema, class?, allow_urls?}` and `model.budget` ([#95](https://github.com/OctoSense-org/OctoSense/pull/95), `apps/ai-providers/host-service/src/complete/`, registered in `crates/ai-host/src/lib.rs`): `class` `fast` or `strong`; the host picks the model from the person's providers, validates the reply against the schema, refuses URLs unless asked, keeps a per-app daily budget; no tools, memory or history, and the app never sees a key | -| `octos` | any app whose manifest declares the exact `octos.*` names, while the shell hosts a kernel, `Policy::contained_apps` is on (**off by default**; set `OCTOSENSE_CONTAINED_APPS=1` to try it) and the person allowed the app's agent at first use (`consent_for_contained`, [#120](https://github.com/OctoSense-org/OctoSense/pull/120)) | The assistant, through the app's own host-owned peer `card.` (`crates/ai-host/src/contained.rs`, [#106](https://github.com/OctoSense-org/OctoSense/pull/106)): the four `octos.*` calls with Rinx's argument rules (`octos.turn.start` takes only `text`, non-blank, at most 32 KiB; the others take `{}`). Tool approvals the peer raises are declined and listed in the reply's `denied_approvals` (the shell's approval router does not take kernel approvals until octos#2567); in developer mode they are answered for the apps it covers (`crates/app-peers/src/host_approvals.rs`). Replies over 2 MiB are refused | +| `octos` | any app whose manifest declares the exact `octos.*` names, while the shell hosts a kernel, `Policy::contained_apps` is on (**off by default**; set `OCTOSENSE_CONTAINED_APPS=1` to try it) and the person allowed the app's agent at first use (`consent_for_contained`, [#120](https://github.com/OctoSense-org/OctoSense/pull/120)) | The assistant, through the app's own host-owned peer `card.` (`crates/ai-host/src/contained.rs`, [#106](https://github.com/OctoSense-org/OctoSense/pull/106)): the four `octos.*` calls with Rinx's argument rules (`octos.turn.start` takes only `text`, non-blank, at most 32 KiB; the others take `{}`). Tool approvals the peer raises are declined and listed in the reply's `denied_approvals` (the shell's approval router takes the kernel's `host_tool` approvals since #145, but not these other approvals of a contained app's peer); in developer mode they are answered for the apps it covers (`crates/app-peers/src/host_approvals.rs`). Replies over 2 MiB are refused | So a contained app, store or system, reaches the assistant only through `octos.*`, in a shell that hosts a kernel. What it hears: @@ -202,8 +202,8 @@ The manifest's `agent` field (a permission profile, generic tools, iteration and | --- | --- | --- | | `tools.json`: tools named `.`, JSON Schema input and output, `risk` (`read`, `act`, `destructive`), `confirm` (`host` or `app`), `background`, `shareable`, `implemented_by` (`host-service` or `app`) | §4, §12 | App Hub admits and pins it ([App-Hub#18](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/18), `crates/app-policy/src/agent.rs`). No shell loads it yet. | | `AGENT.md`, data-only skills, model **requirements** (never a provider or model name), `background`, `triggers` (cron, host-service events) | §2, §3 | Same: admitted by App Hub (`crates/app-policy/src/agent.rs`), and the shells' App Hub pin `e8601b80` includes them. No shell runs them yet. | -| Kernel: host-registered tools per peer (`peer/tools/register`, `peer/tool/call`/`result`/`cancel`), tool-list and risk enforcement, approvals only on the host connection, an allowlist of generic tools, `peer/input` | §4, §13 | In progress: [octos#2567](https://github.com/octos-org/octos/pull/2567) (UPCR-2026-035, draft). The shell side is ADR 0004 plan step 6. | -| Risk decides supervision: read and in-app act run unattended; destructive waits for the person (`confirm: host`: the kernel's approval path; `confirm: app`: the app's own sheet when the person is present, an approval request in the app's conversation when absent) | §4, §12 | Declared in App Hub (#18) and octos#2567. The shell's approval router, sheets and standing rules are merged ([#120](https://github.com/OctoSense-org/OctoSense/pull/120)) and take the AI services bus's `confirm: host` calls; kernel tool calls reach it once octos#2567 lands ([architecture § Approvals](architecture.md#5-approvals)). Rinx's `send_message` already works the `confirm: app` way on its own surface. | +| Kernel: host-registered tools per peer (`peer/tools/register`, `peer/tool/call`/`result`/`cancel`), tool-list and risk enforcement, approvals only on the host connection, an allowlist of generic tools, `peer/input` | §4, §13 | Kernel side merged: [octos#2567](https://github.com/octos-org/octos/pull/2567) (UPCR-2026-035), in the pin with its follow-ups (octos#2616). The shell side is in part on main ([#145](https://github.com/OctoSense-org/OctoSense/pull/145): tool registration with `generic_tools` omitted, the relay, `peer/input`); per-app `generic_tools`, `tools.json` loading and grants are ADR 0004 plan step 6. | +| Risk decides supervision: read and in-app act run unattended; destructive waits for the person (`confirm: host`: the kernel's approval path; `confirm: app`: the app's own sheet when the person is present, an approval request in the app's conversation when absent) | §4, §12 | Declared in App Hub (#18) and octos#2567. The shell's approval router, sheets and standing rules are merged ([#120](https://github.com/OctoSense-org/OctoSense/pull/120)) and take the AI services bus's `confirm: host` calls; since [#145](https://github.com/OctoSense-org/OctoSense/pull/145) the kernel's `host_tool` approvals reach it through the relay ([architecture § Approvals](architecture.md#5-approvals)). Rinx's `send_message` already works the `confirm: app` way on its own surface. | | News M1: the `news` data service (no model) | First slice | Merged ([#69](https://github.com/OctoSense-org/OctoSense/pull/69)); issue [#60](https://github.com/OctoSense-org/OctoSense/issues/60). | | News M2: `os.news` gets a peer and its tools are registered | First slice | Planned: [#61](https://github.com/OctoSense-org/OctoSense/issues/61). | | News M3: triggers and an unattended run | First slice | Planned: [#62](https://github.com/OctoSense-org/OctoSense/issues/62). | diff --git a/docs/architecture.md b/docs/architecture.md index 30cdba67..b1836740 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -32,7 +32,7 @@ The decisions behind it are [ADR 0001](adr/0001-one-octosense-repository.md) (on ![OctoSense AI services: one granted cross-app call](images/ai-services-cross-app-call.png) -*Illustrations (generated). Solid lines are on main; dashed ones are planned or in progress (the peer link for process apps; `peer/input` and host tools are on main since the octos 665209e5 pin, octos#2567). Where a picture and the text below differ, the text is authoritative: for example, `peer_send_input` puts the message in the peer's inbox, and the peer takes it as its next turn.* +*Illustrations (generated). Solid lines are on main; dashed ones are planned or in progress (the peer link for process apps has its shell side on main since [#130](https://github.com/OctoSense-org/OctoSense/pull/130) but no process app uses it yet; `peer/input` and host tools are on main since the octos 665209e5 pin, octos#2567). Where a picture and the text below differ, the text is authoritative: for example, `peer_send_input` puts the message in the peer's inbox, and the peer takes it as its next turn.* ```mermaid flowchart LR @@ -128,7 +128,7 @@ How the shell decides at run time (`crates/shell/src/apps.rs`, `AppRegistry::hos - The shell starts the app as a child with `--stdin-loop` (`cargo run … -- --stdin-loop` in a checkout, else the sibling binary), in its own process group, with `STUDIO_HOST=http://127.0.0.1:` and its client id. - The child connects back to the shell's **hub**, an HTTP/WebSocket server the shell binds on the first free loopback port in 8765–8785 (`WmHub::start`), and speaks Makepad's studio protocol (`AppToStudio` / `StudioToApp`). - Frames reach the compositor without copies where the OS allows (implemented in Makepad `platform/src/os`): IOSurface on macOS, D3D11 shared handles on Windows, DMA_BUF on Linux with Vulkan and Wayland; a Linux OpenGL build reads every frame back through the CPU. -- A process app that dies takes only itself down: the shell removes its client and shows "App stopped". (Only in-process modules get the Restart face today; see [the disagreements](#where-the-code-and-the-adrs-disagree).) +- A process app that dies takes only itself down: an unexpected death of a window the person has keeps its tile, closed with a Restart, and posts "App stopped" ([#130](https://github.com/OctoSense-org/OctoSense/pull/130); `ClientSlot::stops_in_place` in `clients.rs`). A warm instance, the AI pane, a preview or a client being closed goes away as before. **In-process hosting** (`crates/shell/src/module_host.rs`): each instance of a module gets its own splash isolate (`alloc_splash_vm_with_network`) and storage namespace. The isolate separates the script heap only; the module's Rust code shares the shell's memory. Every call into a module runs under `catch_unwind` (`contain`, `contain_outside`): a panic marks that module failed, answers its in-flight tool calls "outcome unknown", closes its extra windows and shows a Restart face, while the shell keeps running ([#114](https://github.com/OctoSense-org/OctoSense/pull/114), ADR 0004 step 9). **On main.** @@ -164,7 +164,7 @@ flowchart TB | Agent | What it is | Status | | --- | --- | --- | -| **The system agent** | the session `_main:api:octosense#system` on the `_main` profile (`SYSTEM_SESSION` in `crates/kernel/src/network.rs`). It owns every app peer and supervises them. The person reaches it today through a Talk to Octos client; the shell draws no chat for it yet | On main | +| **The system agent** | the session `_main:api:octosense#system` on the `_main` profile (`SYSTEM_SESSION` in `crates/kernel/src/network.rs`). It owns every app peer and supervises them. The person reaches it in the shell's **system chat** (`crates/shell/src/system_chat/`, [#132](https://github.com/OctoSense-org/OctoSense/pull/132): Setup → Assistant → Assistant chat or F8; a side panel on the desktop, full screen on a phone), or through a paired Talk to Octos client | On main | | **An app agent** | one host-owned octos **peer** per (app, account), owned by the system agent (octos UPCR-2026-034, Rinx [ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md)) | On main for Rinx (native) and for script apps behind a switch; see below | Each app peer has, separately from every other: @@ -172,7 +172,7 @@ Each app peer has, separately from every other: - a **workspace**: the folder the peer's session is bound to. A peer created since the octos 665209e5 pin gets its account folder (`apps//accounts//`, ADR 0004 §11) as `peer/prepare`'s `cwd` (`host_tools::agent_workspace`), recorded beside its host token so a resume names the same folder; a peer created before keeps the kernel-provisioned one, since octos refuses to resume a peer under another workspace (see [the disagreements](#where-the-code-and-the-adrs-disagree)); - a **memory namespace** `app//acct-` (`app_namespace()` in `crates/app-peers/src/broker.rs`); a kernel that does not return it is refused; - its own **transcript** and **model** lane (the host sets the model with `peer/prepare` / `peer/model/set`); -- its own **tool list**: the kernel's peer-safe defaults plus the tools the shell registers (octos [#2567](https://github.com/octos-org/octos/pull/2567), UPCR-2026-035): after every `peer/prepare` and reconnect the broker registers, on the connection that drives the peer's turns, the app's declared tools and the other apps' shareable tools granted to it, each marked with its owning app (`crates/shell/src/host_tools/`), with `generic_tools` omitted. No app declares `tools.json` on `main` yet, so every peer registers an empty set, which still gives its turns the app's memory and context (a peer that cannot register runs no turn); +- its own **tool list**: the kernel's default roster (less what the `_main` profile's `tool_policy` denies) plus the tools the shell registers (octos [#2567](https://github.com/octos-org/octos/pull/2567), UPCR-2026-035): after every `peer/prepare` and reconnect the broker registers, on the connection that drives the peer's turns, the app's declared tools and the other apps' shareable tools granted to it, each marked with its owning app (`crates/shell/src/host_tools/`), with `generic_tools` omitted. Omitted means the peer keeps octos's whole kernel roster (octos's `shell` included, except that the `_main` profile's `tool_policy` denies it: see [4. Tools and grants](#4-tools-and-grants)); per-app `generic_tools` lists from the manifest and grants are planned (step 6). No app declares `tools.json` on `main` yet, so every peer registers an empty set, which still gives its turns the app's memory and context (a peer that cannot register runs no turn); - **request contexts** (`peer/context/open`): one per client instance (a Rinx mini app, a card thread), each with its own transcript, folder `contexts//` inside the peer's workspace and child memory namespace. A context cannot read the files beside it. Who gets a peer today (`crates/ai-host/src/lib.rs`, `Policy::shipped()`; `crates/app-peers/src/hosted.rs`, `effective_services` = declared ∩ supported ∩ policy): @@ -180,7 +180,7 @@ Who gets a peer today (`crates/ai-host/src/lib.rs`, `Policy::shipped()`; `crates - **Rinx**, the only native app granted the assistant (the four `octos.*` services), after first-use consent. - **Script apps** that declare `octos.*`: one peer `card.` each, account `device` (`crates/ai-host/src/contained.rs`, [#106](https://github.com/OctoSense-org/OctoSense/pull/106)), only while `Policy::contained_apps` is on (off in the shipped policy; `OCTOSENSE_CONTAINED_APPS=1` turns it on) and the person consented at first use. - **AppCard** (opt-in) takes its own kernel connection and sessions, not a peer. -- Process apps get no agent yet: the offer is made only in `ModuleHost::create` (the peer link is planned, step 8). +- **Process apps** whose `native-apps.json` entry grants `agent.octos` services reach their peer through the **peer link** (`crates/shell/src/peer_link/`, [#130](https://github.com/OctoSense-org/OctoSense/pull/130)), after first-use consent. None does yet: the Terminal, the only process app, grants none. Peers are kept, not thrown away: the broker resumes a peer with its host token (stored under `/../app-peers`, 0600), and never calls `peer_close` on sign-out, because octos cannot resume a closed peer or create a replacement for its (app, account) (ADR 0004 §11). @@ -204,7 +204,7 @@ flowchart LR end rinx["Rinx (module)"] -- "OctosAppService" --> broker card["script app"] -- "host.request(octos.*)" --> broker - proc["process app"] -. "peer link (planned)" .-> broker + proc["process app"] -. "peer link (shell side on main, no user yet)" .-> broker broker -- "OUP: peer/prepare, peer/context/open, turn/start" --> kernel sys -- "peer_send_input" --> p1 p1 -- "writes" --> bb @@ -232,7 +232,7 @@ octos's peer mechanism, depth 1 (peers cannot create, steer or close peers). **O - **System agent → app agent.** `peer_send_input` (originator only, at most 64 KB) delivers text as the peer's next user turn, through the peer's inbox (a durable queue in serve, drained every few seconds, at least once). - **App agent → system agent: the blackboard.** Each peer turn writes `peers//result.md` (plus `result-.md`) and a line in `turns.txt`; the system agent reads them with `peer_gather` and `peer_list` (`awaiting_input` shows a peer waiting on a question). It is the only cross-peer channel. -- **Questions.** A peer asks with `ask_user_question`; the system agent answers with `peer_respond`. `peer_respond` never answers approvals (octos refuses). Host-driven turns lose `ask_user_question`, so ADR 0004 §6 adds a shell tool **`host.ask`** that the shell routes to the system agent or to the app's conversation. **Planned** (step 6). +- **Questions.** A peer asks with `ask_user_question`; the system agent answers with `peer_respond`. `peer_respond` never answers approvals (octos refuses). octos keeps `ask_user_question` on host-driven turns (`generic_tools` is an exact list the host sets, and omitted keeps the whole roster). ADR 0004 §6 (decided 2026-09-29) has the shell route each question to the right conversation: the app's own conversation, or the system chat for system-agent turns, `peer/input` turns included; there is no `host.ask`. **On main** for the system agent's own questions (the system chat answers `user_question/requested` on the system session); **planned** for app peers (the broker does not forward a peer's questions yet). ### The host-owned path: `peer/input` @@ -244,7 +244,7 @@ For a host-owned peer, a plain `peer_send_input` runs as a kernel continuation w | --- | --- | --- | | In-process native module (Rinx) | the **injected service**: `ai_host::offer` before `create`, `octosense_app_peers::injection::claim` inside it, giving a scoped `OctosAppService` (`Open`, `History`, `Turn`, `Interrupt`, `Approval`); the module never sees the protocol | On main | | Script app | `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt", …)` to the `octos` host service (`crates/ai-host/src/contained.rs`); gated by the manifest, `Policy::contained_apps` and first-use consent (`consent_for_contained`); tool approvals its peer raises are declined and listed in `denied_approvals` | On main ([#106](https://github.com/OctoSense-org/OctoSense/pull/106), consent from [#120](https://github.com/OctoSense-org/OctoSense/pull/120)) | -| Native app in its own process | the **peer link**: its own channel on the app's hub connection (`PeerRequest`, `PeerReply`, `PeerToolCall` with the identity and caller stamped by the shell), never registered with the AI bus; client API in Makepad's `makepad-ai-services` | Planned (ADR 0004 §5, step 8); nothing in `hub.rs` yet | +| Native app in its own process | the **peer link**: its own channel on the app's hub connection (`PeerRequest`, `PeerReply`, `PeerToolCall` with the identity and caller stamped by the shell), never registered with the AI bus; client API in Makepad's `makepad-ai-services` | Shell side on main ([#130](https://github.com/OctoSense-org/OctoSense/pull/130), `crates/shell/src/peer_link/`, frames routed from the hub connection in `lib.rs`); no process app is granted an agent yet | ### Makepad's AI services bus vs OctoSense's app agents @@ -269,16 +269,16 @@ Where an agent's tools come from: | --- | --- | --- | --- | | The app's own tools (`tools.json`: name `.`, schemas, `risk`, `confirm: host` or `app`, `shareable`) | `mail.send`, `rinx.message.send` | the app's host service, module or process, called by the shell | Registered and routed on main (`crates/shell/src/host_tools/`): authorized by (owning app, tool) and caller, stamped with account, context and client, routed to a process app's peer link or an in-process module's executor (`OctosAppService::set_tool_executor`). No app declares `tools.json` yet, so none is registered; Rinx's tools reach the AI pane over the bus | | The system toolbox, by capability (`research`, `crawl`, `model`) | `toolbox.search`, `toolbox.web_read`, `toolbox.deep_crawl`, `workflow.run` | host | In progress ([#108](https://github.com/OctoSense-org/OctoSense/pull/108)); `model` not registered yet | -| octos's generic tools | file reads fenced to the workspace, memory, `web_search`, `deep_search` | octos | On main as octos's peer-safe defaults; per-app `generic_tools` lists planned (step 6) | +| octos's generic tools | file reads fenced to the workspace, memory, `web_search`, `deep_search` | octos | On main as octos's whole kernel roster (`generic_tools` omitted), less octos's `shell`, which the `_main` profile's `tool_policy` denies; per-app `generic_tools` lists planned (step 6) | | Other apps' shareable tools, routed by the shell | Calendar's agent calling `mail.send` | the owning app, via the shell | Registered, checked on every call and routed on main (`relay::Catalog`); nothing grants one yet (App Hub at install and `native-apps.json` grants are step 6) | -| Command execution | `terminal.run` (the Terminal's shareable tool: `confirm: host`, `auto_approvable: false`) | host tool, in a terminal the person sees | On main for the system agent: registered on its session while Setup › Assistant › Command execution is on, each call approved live through the router as a command, typed into the running Terminal (a process app on macOS and Windows; the in-process Terminal offers its reads only). Granting it to app agents is step 11 | +| Command execution | `terminal.run` (the Terminal's shareable tool: `confirm: host`, `auto_approvable: false`) | host tool, in a terminal the person sees | On main for the system agent: registered on its session while Setup › Assistant › Command execution is on, each call approved live through the router as a command, typed into the running Terminal (a process app on macOS, Windows, and Linux with Vulkan and Wayland; the in-process Terminal offers its reads only, and by ADR 0004 §10, decided 2026-09-29, stays read-only, so the grant takes effect only where a process Terminal exists). Granting it to app agents is step 11 | **The system agent's tool set** (`crates/kernel/src/system_tools.rs`, [#117](https://github.com/OctoSense-org/OctoSense/pull/117)). **On main**, partly enforced: - Its default list, `SYSTEM_AGENT_TOOLS`: supervision (`peer_send_input`, `peer_gather`, `peer_list`, `peer_respond`, `peer_close`; not `peer_handoff`), its workspace's file tools, `ask_user_question` and media viewing, memory, `web_search` / `web_fetch`, `tool_search`. Grants add to it (`SystemAgentTools`: toolbox tools, other apps' shareable tools, command execution). - **octos's own shell is never offered.** Before every kernel start `enforce` writes the `_main` profile's `tool_policy`, denying `group:runtime` (`shell`, `bash`, `exec_command`, `write_stdin`). It replaces only a policy OctoSense wrote and refuses the person's own octos home. - **Not yet exact.** octos#2567's host session set can narrow the session with `generic_tools`, but the shell registers without it (as agreed on #2567: the list would narrow every client's turns on the session), so the system agent still gets every other tool octos registers; the exact-list test is kept but ignored (a durable host-only list is octos#2605). -- **Command execution from Settings** (off by default, each command approved live): Setup › Assistant › Command execution sets the grant; while it is on, the system chat registers `terminal.run` on the system session over its own connection (`peer/tools/register` without `peer`), and withdraws it the moment it is turned off. octos takes an app peer's host token as the credential, so until an app's agent has started the chat says it cannot offer it yet. +- **Command execution from Settings** (off by default, each command approved live): Setup › Assistant › Command execution sets the grant; while it is on, the system chat registers `terminal.run` on the system session over its own connection (`peer/tools/register` without `peer`), and withdraws it the moment it is turned off. octos takes an app peer's host token as the credential, so until an app's agent has started the chat says it cannot offer it yet. The grant takes effect only where the Terminal runs as a process (ADR 0004 §10, decided 2026-09-29): on Linux without Vulkan and on phones `terminal.run` has no target. **External clients** get only octos's fixed allowlist ([section 3](#oup-between-the-kernel-and-its-clients)); host-routed tools, developer grants and `dev.run` never reach them. @@ -338,7 +338,7 @@ One host-owned layout for every app, declared by its manifest (`storage` block) - **The OctoSense home** is the platform's app data directory on a phone, else `~/.octosense` (`OCTOSENSE_HOME` overrides; `crates/shell/src/octosense/paths.rs`). The apps root may be moved with `OCTOSENSE_APP_DATA`; the secrets root is always `/secrets`, and the two never overlap. Directories are 0700 and symlinked components are refused. - **The account hash** is 128 bits of a domain-separated SHA-256 of the normalized account id (`account_hash`). -- **The agent workspace is the account folder**, by the ADR. Today the folder exists and a module gets it from `AppStorage::agent_workspace`, but the broker does not yet hand it to `peer/prepare` as the peer's `cwd`: peers work in the kernel-provisioned workspace (planned, step 10). +- **The agent workspace is the account folder**, by the ADR. A peer created since the octos 665209e5 pin gets it as `peer/prepare`'s `cwd` (`host_tools::agent_workspace`, recorded beside its host token); a peer created before keeps the kernel-provisioned workspace, because octos resumes a peer only under the workspace it was made with. The folder name and the memory namespace tag are still two different hashes of one account ([#139](https://github.com/OctoSense-org/OctoSense/issues/139)). - **Secrets** (`secrets.rs`, `AppStorage::secrets`): the keychain on macOS and iOS (one item per profile, app and key), elsewhere one owner-only (0600) plaintext file per key in `secrets//`; the file store also for tests, headless runs and `OCTOSENSE_SECRETS=file`. The Windows, Linux and Android vaults are a TODO. Secrets are never under `apps/`. - **The startup check** (`check.rs`): no agent workspace may contain or reach `secrets/` (a symlinked workspace or jail, a symlink or hard link into the secrets, the secrets root inside it). A flagged workspace is refused (`agent_workspace` answers `Refused`, for one account or all) until a later start finds it clean; startup continues and nothing is deleted. - **Sign-out** suspends an account's agent and never closes it: `Storage::sign_out` makes the workspace answer `SignedOut`. The shell has no account events yet (a module binds its account through `OctosAppService::set_account`); what `Storage::sign_out` marks, the broker and the relay honour: tool calls for the account are answered `signed_out`, no `peer/input` turn starts, and a suspended account's peer is not prepared. @@ -363,7 +363,7 @@ One host-owned layout for every app, declared by its manifest (`storage` block) +-------v----------+ +--------v--------------------+ | process apps | | octos kernel | | (OS sandbox: | | workspace fence per peer | - | planned) | | external clients: allowlist| + | macOS on main) | | external clients: allowlist| +------------------+ +-----------------------------+ ``` @@ -371,8 +371,8 @@ One host-owned layout for every app, declared by its manifest (`storage` block) | --- | --- | --- | | Script app ↔ shell | App Hub's nested isolate: no `mod.res` / `mod.run`, a jail and quota, only `host.request` for granted families; password fields inert; secrets on host sheets | On main | | Native module ↔ shell | none in memory: reviewed first-party code only (`native-apps.json`); **panic containment** at the module boundary (`catch_unwind`, [#114](https://github.com/OctoSense-org/OctoSense/pull/114)); limits: a panic while unwinding, `panic = "abort"`, FFI | On main | -| Process app ↔ shell | its own address space; an **OS sandbox** from the manifest's `sandbox` and `storage` (macOS sandbox profile, Linux Landlock and seccomp, Windows AppContainer) | Sandboxes planned (ADR 0004 §3, step 8); today a process app runs with the user's rights | -| App ↔ kernel | apps never speak the kernel protocol and **never see the host token**: modules get a scoped `OctosAppService`, script apps a host service, process apps (planned) the peer link | On main | +| Process app ↔ shell | its own address space; an **OS sandbox** from the manifest's `sandbox` and `storage` (macOS sandbox profile, Linux Landlock and seccomp, Windows AppContainer) | macOS on main (`crates/shell/src/sandbox/macos.rs`, a Seatbelt profile, built and tested; [#130](https://github.com/OctoSense-org/OctoSense/pull/130)); Linux Landlock and seccomp built and compile-checked but not yet run on a real kernel ([#138](https://github.com/OctoSense-org/OctoSense/issues/138)); Windows planned ([#137](https://github.com/OctoSense-org/OctoSense/issues/137)): a process app there runs with the user's rights | +| App ↔ kernel | apps never speak the kernel protocol and **never see the host token**: modules get a scoped `OctosAppService`, script apps a host service, process apps the peer link | On main | | Peer ↔ peer | octos: separate workspace (overlapping workspaces refused), memory namespace, transcript; request contexts fenced to `contexts//` | On main (octos UPCR-2026-034) | | Agent ↔ secrets | secrets outside every jail and workspace; startup check | On main | | External client ↔ kernel | external token, method and tool allowlists, `Host` and origin checks, no peer access | On main ([ADR 0003](adr/0003-shared-octos-client-access.md)) | @@ -418,7 +418,7 @@ sequenceDiagram | Step | What happens | Status | | --- | --- | --- | -| 1 | The person asks the system agent, in the system conversation `_main:api:octosense#system`. | Reachable today only from a paired Talk to Octos client; the shell has no chat of its own for the system agent yet (the desktop's AI pane is Makepad's `aichat` on the bus, not the system agent) | +| 1 | The person asks the system agent, in the system conversation `_main:api:octosense#system`. | On main: the shell's system chat ([#132](https://github.com/OctoSense-org/OctoSense/pull/132)), or a paired Talk to Octos client (the desktop's AI pane is Makepad's `aichat` on the bus, not the system agent) | | 2 | The system agent plans. Ambiguity is asked, not guessed ("two Edwards?"); for a bounded read it may call a granted tool directly. Work that needs Calendar's judgement goes to Calendar's agent with `peer_send_input`. | `peer_send_input` on main; direct cross-app grants planned (step 6) | | 3 | octos delivers the input to the shell as `peer/input`; the shell starts the turn on Calendar's peer with `turn/start`, so it has Calendar's tools, memory and account context. If Calendar's peer has no host connection, the system agent is told the app is not connected. | On main ([octos#2567](https://github.com/octos-org/octos/pull/2567) in the pin; the broker starts the turn) | | 4 | Calendar's agent calls `calendar.create_event` (its own tool) and Mail's shareable `mail.send` once per invitee. Each call reaches the shell as `peer/tool/call`; the shell stamps the caller (Calendar's agent), account and context. | Relay on main (`host_tools`); Calendar and Mail's tools planned | @@ -430,18 +430,20 @@ sequenceDiagram ## Where the code and the ADRs disagree -Found while writing this page (2026-09-28); none is fixed here, as this change is documentation only. +Found while writing this page (2026-09-28) and rechecked against `main` on 2026-09-29 (the ADR 0004 gap review); items marked fixed were fixed in code or in the ADR text since. 1. **Process apps beyond the Terminal.** Fixed: the opt-in Sheets and Reference are `module` on every target, and the generator refuses plain `process` on Linux (ADR 0004 §2). -2. **Restart of a dead process app.** ADR 0004 §2 says its tile shows it closed with a restart. The shell removes the client and posts "App stopped"; the Restart face exists only for in-process modules (`module_view.rs`). -3. **Agent workspace = account folder.** Partly fixed: a peer created since the octos 665209e5 pin gets its account folder as the `peer/prepare` `cwd`; a peer created before keeps the kernel-provisioned workspace, because octos resumes a peer only under the workspace it was made with (moving it needs a migration octos does not offer). The account folder name (`account_hash`, SHA-256) and the memory namespace tag (FNV-1a in `broker.rs`) are still different hashes of the account; changing the tag would re-key every app's memory. -4. **ADR 0003 "What the profile runs"** says OctoSense configures neither the tool set nor the sandbox. Since [#117](https://github.com/OctoSense-org/OctoSense/pull/117) the shell writes a `tool_policy` denying `group:runtime` into the `_main` profile before every start, so the host's own turns have no octos shell either. +2. **Restart of a dead process app.** Fixed ([#130](https://github.com/OctoSense-org/OctoSense/pull/130)): an unexpected death keeps the tile, closed with a Restart (`ClientSlot::stops_in_place`; test `an_unexpected_death_keeps_the_tile_closed_with_restart`). +3. **Agent workspace = account folder.** Partly fixed: a peer created since the octos 665209e5 pin gets its account folder as the `peer/prepare` `cwd`; a peer created before keeps the kernel-provisioned workspace, because octos resumes a peer only under the workspace it was made with (moving it needs a migration octos does not offer). The account folder name (`account_hash`, SHA-256) and the memory namespace tag (FNV-1a in `broker.rs`) are still different hashes of the account; changing the tag would re-key every app's memory ([#139](https://github.com/OctoSense-org/OctoSense/issues/139)). +4. **ADR 0003 "What the profile runs"** says OctoSense configures neither the tool set nor the sandbox. Since [#117](https://github.com/OctoSense-org/OctoSense/pull/117) the shell writes a `tool_policy` denying `group:runtime` into the `_main` profile before every start, so the host's own turns have no octos shell either. Fixed in the text: ADR 0003 carries a dated amendment (2026-09-29), as do ADR 0002 §12 and its amendment. (The policy fails open: when `enforce` refuses a foreign policy or the person's own octos home, the kernel still starts without it.) 5. **Stale context lines.** Fixed: ADR 0004's context table and the README layout tables no longer list the native News, Maps and Photos modules ADR 0004 step 1 deleted (#113), and [ai-services.md](ai-services.md) now describes `main` on 2026-09-28 (the Card runner's `octos` service from #106, consent from #120; the `Policy::contained_apps` switch is still off by default). 6. **Command execution "turned on in Settings".** Fixed: Setup › Assistant › Command execution sets it (#132), and the system chat registers `terminal.run` on the system session while it is on. 7. **`host::processes_available()`'s test** checks only `wasm32` while the function also excludes native mobile. 8. **Approvals, ADR 0004 §8.** Every app tool call reaches the shell through `peer/tool/call` and every gated one the router (above). No app registers its own `confirm: app` sheet yet (Rinx needs to hand its send sheet over through `OctosAppService::set_confirm_sheet`), so such a call would wait and be refused. The audit keeps a digest of the arguments, not the arguments. The send queue and undo window are not built. 9. **Storage, ADR 0004 §11.** Secrets use the OS keychain only on macOS and iOS (plaintext 0600 files elsewhere). The startup check refuses workspaces that reach the secrets through links or containment rather than looking for a `secrets/` path, and does not stop startup. (Fixed: `storage.accounts` defaults to `false` in both app storage and the consent sheet, which now reads `StorageSpec`.) 10. **Developer mode, ADR 0004 §13.** `dev.run` is not registered; Settings turns it on only for all apps (a chosen list only through `OCTOSENSE_DEV_MODE`); there is no phone gesture; in-process modules still show their own confirmation sheets. +11. **Questions, ADR 0004 §6 (decided 2026-09-29).** Agents ask with octos's `ask_user_question` and the shell routes each question to the app's conversation or the system chat; `host.ask` is dropped. The system chat answers the system agent's own questions, but the broker does not forward an app peer's `user_question/requested` (on its own session, a request context or a `peer/input` turn) to any conversation yet. +12. **Process sandboxes, ADR 0004 §3.** Only the macOS sandbox is built and tested; Linux is built but unvalidated (#138) and Windows is not built (#137). ## Source map