From 4498fa5e711810c12bd1dc3cfc735e60b8d6a2f0 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:29:52 -0700 Subject: [PATCH 1/8] docs(adr): 0004 native apps, app agents and cross-app work (proposed) A new ADR, 0004, and dated amendments to ADR 0002 and Home ADRs 0002 and 0004 that point to it. ADR 0004 records: - two manifests: system-apps.json for script apps, a new native-apps.json (and a generator with --check) in place of five hand edits; the native News, Maps and Photos crates are deleted - hosting per target: own process on macOS, Windows and Linux with Vulkan+Wayland; in-process on Linux without them, on mobile and wasm - the trust boundary: native apps first-party only; an OS sandbox per process app; a process app reaches its agent only through the shell, which relays and checks everything (ADR 0003: external clients never reach app agents) - an app agent for every app, native or script, with consent at first use; the peer link for process apps - verified octos facts: peers are sessions in one kernel process; the 8-peer limit is per peer/prepare call; master and peers talk through the inbox and the file blackboard; and the octos#2567 gap (a system-agent turn on an app peer gets none of the app's context) - cross-app work: the owning app's agent makes changes; the shell routes shareable tool calls - approvals by the person only, live or through standing rules the shell enforces, capped at time-boxed rules - app storage: one layout for every app, declared in the manifest; the agent's workspace is its account folder; secrets never in a jail - toolbox grants per capability and no command execution; developer mode grants everything for building apps fast - worked examples: Mail fact-check; Calendar + Mail meeting - open: D1 (read-only questions may use shareable read tools directly) Co-Authored-By: Claude Opus 5.5 --- docs/adr/0002-event-driven-app-agents.md | 11 + .../adr/0004-native-apps-hosting-and-peers.md | 301 ++++++++++++++++++ docs/adr/README.md | 1 + docs/adr/README.zh-CN.md | 1 + .../home/0002-agentic-app-security-model.md | 8 + ...4-system-apps-are-contained-script-apps.md | 4 + 6 files changed, 326 insertions(+) create mode 100644 docs/adr/0004-native-apps-hosting-and-peers.md diff --git a/docs/adr/0002-event-driven-app-agents.md b/docs/adr/0002-event-driven-app-agents.md index 0691c424..cafdc90b 100644 --- a/docs/adr/0002-event-driven-app-agents.md +++ b/docs/adr/0002-event-driven-app-agents.md @@ -330,3 +330,14 @@ Mail follows as the first app with approvals (`mail.send`), reusing M2–M7. - Budget defaults per app, and how cost is shown. - Whether a card may carry a short-lived action (Act) or only open its app's conversation. - How approvals behave across devices (approve on the phone a run that happened on the desktop). + +## Amendment, 2026-09-28 ([ADR 0004](0004-native-apps-hosting-and-peers.md)) + +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 must run with the app's tools, memory and context; octos#2567 as written gives kernel continuations none, so OctoSense asks octos for originator-driven host turns and, until then, the shell relays those turns (ADR 0004 §6). +- **§4 (tools).** The system agent does not call app tools to make changes: it delegates to the owning app's agent, which acts with its own tools and, where granted, other apps' shareable tools. The shell routes those cross-app calls. Read-only questions may use shareable read tools directly (proposed). ADR 0004 §7. +- **§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". The system agent may batch one request's approvals into one sheet in its chat. Standing rules are per app and 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. 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. +- **§13 (autonomy).** Adds the `auto_approvable` flag and the incoming-content rule. diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md new file mode 100644 index 00000000..5cfa0744 --- /dev/null +++ b/docs/adr/0004-native-apps-hosting-and-peers.md @@ -0,0 +1,301 @@ +# 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:** Proposed +- **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); [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 + +### Two kinds of bundled app + +| | Native app | Script system app | +| --- | --- | --- | +| Today | App Hub (the store and the Card runner), Rinx, Terminal; opt-in Sheets, Reference, AppCard; native News, Maps and Photos crates kept "for comparison" | News, Maps, Photos, Camera, Mail, AI providers | +| Source | a crate pinned in the root `Cargo.toml` | `apps//bundle/`, packed from the same commit (`desktop/system-apps.json`) | +| Code | Rust: full process rights, `unsafe`, files, PTYs, threads | OctoScript in App Hub's Card runner; reaches the shell only through `host.request` for families its manifest was granted | +| Runs | in the shell process, one splash isolate per instance (`crates/shell/src/module_host.rs`); the isolate separates the script heap only | in the shell process, a nested isolate per instance with `mod.res` and `mod.run` stripped | +| A bug can | take the whole shell down | fail inside its isolate | + +**Adding a native app takes five hand edits** (the pin in the root `Cargo.toml`, an optional dependency and an `app-` feature in `crates/shell/Cargo.toml`, the default in `desktop/` or `phone/`, a `#[cfg]` line in `linked_modules()`, and `Cargo.lock`), and nothing checks that they agree. + +**A native crash is a shell crash.** On 2026-09-27 a bitmap-only font chosen in the terminal made Makepad's font loader panic; the recovery path panicked again and aborted the OctoSense process, taking Rinx with it. Fuzzing then found three more panics on corrupt fonts in the vendored parser and shaper (fixed in the Makepad fork). + +**In-process means no boundary, only trust.** An in-process native app shares the shell's memory: its Rust code can read anything the shell holds (the host token for the kernel, other apps' data in memory) and open any file the shell can. Splash isolates do not contain native code. + +### Process hosting on the desktop + +`Hosting::Process` starts a Makepad app as a child (`--stdin-loop`, `crates/shell/src/clients.rs`) connected to the shell's in-process hub (`hub.rs`); a per-app override switches a module to a process. How its frames reach the compositor (Makepad `platform/src/os`): + +| OS | Frames | Needs | +| --- | --- | --- | +| macOS | Metal IOSurface, zero copy | nothing more | +| Windows | D3D11 shared handles | nothing more | +| Linux, Vulkan build | DMA_BUF / OPAQUE_FD, zero copy | `MAKEPAD=vulkan` and a Wayland session (Vulkan windowing panics on X11) | +| Linux, OpenGL build | CPU readback of every frame | works; costly | + +`host::processes_available()` is false for native mobile targets and wasm. Mobile GL contexts are per process. Process apps are started with `cargo run` from a checkout, else the sibling binary; release packaging does not ship them yet ([#94](https://github.com/OctoSense-org/OctoSense/pull/94) is open). + +### Two AI models + +- **Makepad's AI services bus** (`libs/ai/services`; the shell's half is `crates/shell/src/ai_bus.rs`): one central conversation calls typed tools that apps publish with a risk level. Apps offer a narrow API; the central agent holds all context; it runs one way. +- **App agents** (Rinx ADR 0007, ADR 0002): each app owns an octos agent with the app's full context: its workspace, memory namespace, history and tools. The system agent supervises and talks to app agents; it is not limited to an app's tool API. + +### What octos provides (octos `main` e6223ef, verified) + +- **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. +- **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`); once-only execution, timeouts, audit, approvals for destructive and outward tools, budgets. The kernel never talks to an app. +- **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 gets none of the app's context.** #2567 gives tools, memory and context only to turns driven by the host's connection; "kernel continuations get a minimal prompt and no memory at all", and `peer_send_input` arrives as such a continuation. +2. **App tools are per peer.** An app agent cannot call another app's tool, and the system agent has no app tools. +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). +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()`). + +## Decision + +### 1. Two manifests, one per kind + +Script system apps stay in `desktop/system-apps.json` and `phone/system-apps.json`. Native apps are declared only in **`native-apps.json`** at the repository root: + +```json +{ + "schema": 1, + "apps": [ + { + "id": "terminal", + "crate": "makepad-terminal", + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "", "local": ".sources/makepad/apps/terminal" }, + "module": "makepad_terminal::TERMINAL_MODULE", + "bin": "terminal", + "crate_features": [], + "hosting": { "macos": "process", "windows": "process", "linux": "process-if-vulkan", "android": "module", "ios": "module", "ohos": "module" }, + "shells": { "desktop": "default", "phone": "off" }, + "sandbox": { "network": "any", "processes": true }, + "storage": { "accounts": false, "agent_workspace": "none", "external": ["home:rw"] }, + "agent": { "octos": [], "tools": null } + } + ] +} +``` + +`tools/native_apps.py` generates every derived place (marked blocks in the root, shell, desktop and phone `Cargo.toml`s; `crates/shell/src/native_apps.rs`, included by `linked_modules()`; the agent grants) and updates `Cargo.lock` for changed pins; `--check` fails CI on drift. It refuses `process` on mobile or wasm, and `process` without `bin`. + +**The native News, Maps and Photos crates are deleted** (`apps/{news,maps,photos}/native`, their `app-*` features, their place in `mobile-apps`). The script apps are the only versions. + +### 2. Hosting per target + +| Target | Native app | +| --- | --- | +| macOS, Windows | own process by default | +| Linux with a Vulkan build and a Wayland session | own process by default | +| Linux without them (OpenGL, X11) | **in-process for every app**, for now | +| Android, iOS, OpenHarmony, wasm | in-process only | + +A process app that dies takes only itself down: its tile shows it closed with a restart; the shell and other apps keep running. Desktop release packages ship each process app's `bin`. + +In-process modules (all of mobile, and Linux without Vulkan) must meet the bar a process does not: fuzzed parsing of outside data, no panics on it, and panic containment at the module boundary (a second panic must not abort the shell). + +### 3. Trust boundary + +- **Native apps are first-party and reviewed only.** A native app is added by a reviewed change to `native-apps.json`. Anything installable from the store is a script app. +- **Process apps on the desktop run under an OS sandbox** built from the manifest's `sandbox` and `storage` (files: the jail, its secrets and `external`; network; child processes): macOS sandbox profile, Linux Landlock and seccomp, Windows AppContainer. The terminal's is necessarily broad; most apps' are narrow. +- **A process app reaches its agent only through the shell.** It never connects to the kernel (an external client is barred from app agents by UPCR-2026-036) and never sees the host token. +- **The shell checks what it relays**: grants and consent; budgets, rate limits and background policy; each tool call's name and arguments against the declared `tools.json`; each result against its declared schema and size; its own audit log; crash cleanup. +- **What no one can check**: what a native app's code does inside its own tool or why it starts a turn. The sandbox and review are the controls. + +### 4. Every app can own an app agent + +An app that declares `agent.octos` (native) or `octos.*` capabilities (script) and is granted them gets ONE host-owned app peer on the shell's one kernel, per (app, account). No app starts a kernel of its own. **The person consents at first use**: the first time an app asks for its agent, the shell shows what the agent may read and use and where the model runs; Settings lists every app's agent with an off switch. + +Three flows: + +| Flow | Native module | Native app in its own process | Script app | +| --- | --- | --- | --- | +| 1. The app talks to its agent (UI, triggers) | the service injected at `create` | the **peer link** (section 5) | `host.request("octos.*")` (#106) | +| 2. The agent uses the app's tools with full context | the shell executes the call in-process | the peer link carries `peer/tool/call` in and the result out | the app's host service executes it | +| 3. The system agent talks to the agent | in the kernel | in the kernel | in the kernel | + +The shell is always the kernel's host connection: it holds the host token, registers the app's tools, drives the app peer's turns and routes tool calls. + +**Triggers** (a button, a data event, a schedule, a file change) are the app's (ADR 0002 §2). The shell gates each: script triggers arrive as `host.request`, native ones on the peer link or at injection, under the same grants, budgets and background policy. + +### 5. The peer link for process-hosted native apps + +It rides the process's hub socket as its **own channel**, addressed to that app's peer and never registered with the AI services bus: + +- 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 }`, `PeerToolCancel { call_id }`. + +The shell stamps 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 questions with `peer_respond`, and never approves an app agent's tools. +- **A turn the system agent starts on an app peer must run with the app's tools, memory and context.** We ask octos for it on #2567 (originator input from the same host's system agent is host-driven). Until then the shell relays: it drives that turn on its own host connection and leaves the result on the blackboard. +- In OctoSense the AI services bus is not the system agent's channel to apps. It stays for upstream Makepad apps. + +### 7. Cross-app work + +**The system agent plans; the app that owns the domain acts.** + +- **Changes** (create, send, update, delete) go to the owning app's agent, which acts with its own tools and, where granted, other apps' **shareable** tools: "schedule a meeting" goes to Calendar, which calls Mail's `mail.send` for the invitations and records them on the event. +- **Read-only questions** may call an app's shareable **read** tools directly from the system agent ("what's on my calendar today?"). *(Proposed; D1 awaits confirmation.)* +- **The shell routes cross-app tool calls.** An app declares in `tools.json` which tools are shareable; an app's manifest asks for others' shareable tools; the person grants them. The shell registers a granted tool in the calling app's peer tool list (as `mail.send`, marked as Mail's), receives the call like any other, checks the grant and hands it to the owning app's host service or process. No octos change is needed; it may move into octos once proven. +- **Partial failure is reported, not hidden**: which step succeeded, which did not. A call whose outcome is unknown is never retried without the person (octos marks it `outcome_unknown`). +- **Ambiguity is asked**, not guessed (two Edwards, no free slot). +- **Completion** is announced by the system app ("booked; invites sent to 4"); each app's own UI shows the change because its data changed. + +### 8. Approvals: by the person, live or in advance + +Only the person approves. The system agent never does, and cannot be talked into it: it reads untrusted text, and rules are checked mechanically. + +- **Live.** Pending approvals surface in the app's conversation, on its card and as a notification; the system agent may batch those of one request into one sheet in its chat (a plan plus its outward actions, with exact arguments). The shell answers each approval with the person's decision. +- **In advance: standing approvals.** The person sets rules; the shell's **approval router** evaluates each approval request against them on the exact arguments and answers it, or surfaces it: + - scope: per app and per tool, with conditions (recipients in contacts or in the thread, no attachments, triggered by the person, amount or count limits) and a daily cap; + - created in Settings → Assistant → Approvals, or from an approval sheet ("always for people in my contacts", "allow for 1 hour"); the system agent may suggest a rule, only the person creates one; + - **no "everything, forever" rule**: the broadest rule is time-boxed ("approve everything this app asks for the next hour"), with a visible indicator; + - excluded always: tools that declare `auto_approvable: false` (permanent deletion, payments, sharing outside the device, account and security changes) and calls whose outcome is unknown; + - excluded by default: runs started by incoming content (an email or message someone else sent); + - after the fact: every auto-approval is notified and audited (rule, arguments, time, result); a send queue gives an undo window where the app supports it; one tap turns every rule off. + +Octos needs no change: the host already answers approvals, and it answers each one (octos approvals are once-only). + +### 9. Rinx + +One agent for Rinx and one request context per mini app is enough while mini apps are clients, not agents (Rinx ADR 0007). Because #2567 registers tools per peer, **Rinx checks each tool call's `context_id` against that mini app's grants** (rooms, Matrix actions) and shares one budget fairly between contexts. If mini apps ever become autonomous (their own triggers, instructions, model or budget), each needs its own peer, and the shell creates it for Rinx (peers cannot create peers). Its agent reads the files Rinx writes into its account folder (section 11); the Matrix store and keys live under `secrets/rinx/`, and room data the agent needs beyond those files comes through Rinx's tools, with its per-room checks. + +### 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 tools stay read-only in every hosting (`read_screen`, `read_scrollback`); the standalone app's `run` is not offered when hosted. +- 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. + +### 11. App storage: one layout, declared in the manifest, the same for every app + +Every app, script or native, stores its data in one host-owned layout, and its manifest declares it. The agent reads the app's files **directly from disk** (its workspace is the app's own account folder); tools remain for actions and for data that needs the app's own permission checks. + +``` +/apps// the app's jail: App Hub's jail root; a native app's sandbox root + accounts// one per signed-in account ("device" when the app has none): + the app's data for that account = that account's agent workspace + common/ app data not tied to an account (settings, catalogs) + cache/ evictable, not backed up +/secrets// host-owned: tokens, keys, passwords, encryption stores +``` + +Rules: + +- **The agent's workspace is its account's folder** (`peer/prepare` `cwd`), matching octos's one peer per (app, account) and its memory namespace `app//acct-`. An agent never sees another account's folder; octos refuses overlapping workspaces, so this holds by construction. The kernel's file tools read and write there, fenced to it; request contexts get `contexts//` inside it. +- **Secrets are never inside an app's jail.** Script apps reach theirs only through host services (Mail's passwords already live on host sheets). Native apps use a host secrets API (the OS keychain where the platform has one) that returns them to the app's code and never writes them under `apps/`. The shell checks at start that no agent workspace contains a `secrets/` path. +- **What the agent should not read raw stays out of its folder**: an app keeps encrypted or internal stores (Rinx's Matrix crypto store and tokens) under `secrets//`, and writes into its account folder what its agent should read (exported threads, shared attachments, the agent's own notes and results). Rinx, today in `~/.local/share/rinx` (#101), moves under this layout. +- **Paths come from the host**, never hard-coded: script apps get the jail from the Card runner, native apps from the host API that also gives their account folder. +- **Uninstall** deletes `apps//` and `secrets//`; **signing out** an account closes its agent and its contexts; deleting an account folder closes that agent. + +The contract, one block in both manifests (App Hub's `manifest.json` extends its existing `storage.max_bytes`; `native-apps.json` carries the same block): + +```json +"storage": { + "max_bytes": 536870912, + "accounts": true, + "agent_workspace": "account", + "cache_max_bytes": 1073741824, + "external": [] +} +``` + +- `accounts`: the app keeps data per account (one agent per account); `false` means one `device` folder. +- `agent_workspace`: `"account"` (default: the account folder) or `"none"` (the agent reads no files; tools only). +- `external`: native apps only, paths outside the jail the app needs, reviewed in `native-apps.json` (the terminal's `home:rw`). They are part of its OS sandbox and are **never** in an agent's workspace. + +Enforcement: + +| Area | Script app | Native app, own process | Native app, in-process | +| --- | --- | --- | --- | +| The app stays in its jail | the isolate's jail and quota (App Hub, today) | the OS sandbox allows the jail, its secrets and `external` only | review; the host API is the only source of paths | +| The agent stays in its account folder | octos workspace fence | octos workspace fence | octos workspace fence | +| Secrets out of reach of the agent | host services hold them | host secrets API; startup check | host secrets API; startup check | +| Quotas | the isolate's quota | the shell measures and warns; the sandbox cannot count bytes | the shell measures and warns | + +### 12. Toolbox grants: per capability, and no command execution + +An app agent gets toolbox tools only for the capabilities its manifest declares and the person grants (ADR 0002 §6; the registration in [#108](https://github.com/OctoSense-org/OctoSense/pull/108)): + +| Toolbox | Who gets it | Runs | +| --- | --- | --- | +| web search and page reading (`research`) | apps granted `research` | host | +| crawling (`crawl`, with depth and page limits) | apps granted `crawl` | host | +| the app's own files | every app agent | octos, fenced to its account folder | +| memory | every app agent, its own namespace | octos | +| one-shot model calls (`model`) | apps granted `model` | host service | +| **command execution** | **no app agent, and not the system agent** | – | + +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. octos already refuses it to app peers (#2567's generic-tool allowlist) and to external clients (#98). An app that needs computation gets a narrow tool instead (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`, granted only to an app that asks for it, each command approved live with the exact command shown, run in a terminal the person sees. + +### 13. Developer mode: everything granted, for building apps fast + +Developers need to try an agent with every capability without granting, consenting and approving each step. **Developer mode** does that in one switch: + +- **Grants**, for all apps or chosen ones: every capability an app could declare, every app's shareable tools to every other app, and a host tool **`dev.run {command, cwd?, timeout_ms?}`** that the shell executes (in the agent's workspace by default, with an output cap, every command logged). octos needs no change: `dev.run` is a host-registered tool like any app tool, and it is withdrawn the moment developer mode ends. +- **Approvals**: every action is approved automatically, including destructive, outward and `auto_approvable: false` tools; the cap of section 8 does not apply. +- **Consent**: no first-use prompts. + +How it is turned on and kept from leaking into normal use: + +- **Development builds** carry it (a `dev-mode` feature). A release build honours it only with an explicit launch flag, never from Settings alone. Store builds cannot turn it on. +- **Only the person** turns it on: Settings → Developer options (a confirmation phrase on a desktop; the familiar developer-options gesture on a phone), or for headless runs and CI `OCTOSENSE_DEV_MODE=all` / `--dev-grant-all` on a development build. No agent, app or rule can turn it on, and the system agent does not suggest it. +- **A developer profile** (its own OctoSense home, test accounts) is the intended place: there developer mode **stays on until turned off**, with no expiry and no prompts. With real accounts signed in, the shell warns, offers to switch to a developer profile, and if the person continues, developer mode ends by itself after 8 hours or at restart. Grants and rules made in developer mode never carry over to another profile. +- **Always visible**: a banner ("Developer mode: all apps have full access") with a one-tap off. +- **Always audited**: every tool call, `dev.run` command and automatic approval, which doubles as a debugging trace. + +## Worked examples + +### Mail: "Verify facts" + +1. The person taps **Verify facts** on an email. Mail calls `host.request("octos.turn.start", …)` with a structured request ("run the fact-check skill on message "), not free text. +2. Mail's agent (instructions in Mail's `AGENT.md`, procedure in a `fact-check` skill) calls `mail.read(id)` (Mail's host service), extracts the **claims**, and searches them with the system toolbox (`toolbox.search`, `toolbox.web_read`; the `research` grant, #108). It searches claims, not the whole email. +3. It builds an L0 card bound to a host-held source (`sys.digest`, #87), renders and checks it, stores it with the email (`mail.save_card(message_id, card, context_id)`), and publishes it with `glance.publish`: glance screen and notification. +4. Tapping the card opens Mail at that email's fact-check thread: the card and a conversation on the same agent context, so follow-up questions see the email and the findings. The chat history is the context's transcript; Mail keeps the reference and reloads it with `octos.session.history`. Deleting the email closes its context. + +### Calendar and Mail: "set up a group meeting plus Edward" + +1. The person asks the system agent (chat or voice). It asks Calendar's agent who "the group" is and when they are free; Calendar resolves Edward (from contacts, or Mail's shareable `mail.lookup_contact`). +2. The system agent proposes "Tue 3–4 pm, Ana, Bo, Chen and Edward (edward@…); book and send invitations?" +3. On yes, it delegates to Calendar's agent, which calls `calendar.create_event` and Mail's `mail.send` four times (routed by the shell). +4. The four sends are outward: one batched sheet in the chat shows the exact invitations, unless a standing rule ("Calendar may send invitations to my contacts") answers them. +5. Calendar's UI shows the event; the system app notifies "Meeting booked Tue 3 pm; invitations sent to 4." A failed invitation is named and not retried without the person. + +## Consequences + +- One manifest entry adds or updates a native app; CI keeps the derived places in step. +- On macOS, Windows and Vulkan Linux a native crash no longer takes the shell down; mobile and non-Vulkan Linux depend on the robustness bar. +- Every app can have an agent with its full context; the system agent can delegate to it once system-agent turns carry the app's context. +- Cross-app work has one owner per change and needs no octos change. +- The person decides every outward action, now or through narrow, time-boxed, audited rules. +- New work: the generator; 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; release packages ship its `bin`. +3. On octos#2567: originator-driven turns get the app's context (and the UPCR-2026-034 approval wording, section 8). +4. Land #106 (script apps' agents) and #108 (toolbox tools); rebase #85 on #98. +5. #2567 in the shell: register app tools, execute or relay `peer/tool/call`, shareable-tool routing. +6. The approval router, standing approvals and their Settings; consent at first use. +7. The peer link and its Makepad client API; process sandboxes. +8. Module panic containment. +9. The storage layout and contract (section 11): App Hub's `storage` fields; the host paths and secrets APIs; Rinx and the host services move under `apps/` and `secrets/`; the startup check. +10. Developer mode (section 13): the developer profile, the grants, `dev.run`, the banner and audit; `terminal.run` as a confirmed shareable tool (section 12). + +## Open questions + +- **D1 (proposed above, awaiting confirmation):** read-only questions call shareable read tools directly; every change goes through the owning app's agent. +- Linux without Vulkan: revisit once process hosting is measured there (readback cost for light apps). +- The exact `tools.json` fields rules can match (recipients, attachments, amounts), agreed with App Hub. diff --git a/docs/adr/README.md b/docs/adr/README.md index f596036f..c5235372 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -9,6 +9,7 @@ Decisions for the OctoSense repository: the shell, its services, the system apps | [0001](0001-one-octosense-repository.md) | One OctoSense repository for the shell, its services, the system apps and both packagings | Accepted | | [0002](0002-event-driven-app-agents.md) | Event-driven app agents: apps think on their own triggers and publish cards to the glance screen | Proposed | | [0003](0003-shared-octos-client-access.md) | Talk to Octos: one kernel for native and external clients (opt-in) | Implemented; Android unverified | +| [0004](0004-native-apps-hosting-and-peers.md) | Native apps, app agents and cross-app work: one manifest, hosting per target, an agent for every app, approvals by the person | Proposed | ## Home (phone shell) decisions, 2026-09-16 to 2026-09-25 diff --git a/docs/adr/README.zh-CN.md b/docs/adr/README.zh-CN.md index 90f61a57..f41b4cda 100644 --- a/docs/adr/README.zh-CN.md +++ b/docs/adr/README.zh-CN.md @@ -9,6 +9,7 @@ OctoSense 仓库的决策:Shell、Shell 服务、系统应用,以及桌面 | [0001](0001-one-octosense-repository.md)(英文) | 用一个 OctoSense 仓库承载 Shell、Shell 服务、系统应用和两种打包形态 | 已接受 | | [0002](0002-event-driven-app-agents.md)(英文) | 事件驱动的应用智能体:应用按自己的触发条件思考,并把卡片发布到一览屏 | 提议中 | | [0003](0003-shared-octos-client-access.md)(英文) | Talk to Octos:原生与外部客户端共用一个内核(需手动开启) | 已实现;Android 未验证 | +| [0004](0004-native-apps-hosting-and-peers.md)(英文) | 原生应用、应用智能体与跨应用协作:一份清单、按目标平台托管、每个应用都有智能体、由本人批准 | 提议中 | ## Home(手机 Shell)的决策,2026-09-16 至 2026-09-25 diff --git a/docs/adr/home/0002-agentic-app-security-model.md b/docs/adr/home/0002-agentic-app-security-model.md index c3fecd6d..e699b9d9 100644 --- a/docs/adr/home/0002-agentic-app-security-model.md +++ b/docs/adr/home/0002-agentic-app-security-model.md @@ -132,3 +132,11 @@ Running a real compiled card (`apps/calendar/cards/calendar-01`) through the ref - [ADR 0001: Hybrid Android launcher and system bridge](0001-hybrid-android-launcher-and-system-bridge.md) - Mini-program containment as prior art: dual-thread split with a host-owned bridge, per-app storage isolation with a size cap, and a registered domain allowlist per app — [container architecture](https://dev.to/ai_superapp/mini-program-container-architecture-how-dual-thread-rendering-works-3if7), [storage](https://developers.weixin.qq.com/miniprogram/en/dev/framework/ability/storage.html), [network](https://developers.weixin.qq.com/miniprogram/en/dev/framework/ability/network.html), [native renderer](https://developers.weixin.qq.com/miniprogram/en/dev/framework/runtime/skyline/introduction.html). + +## Amendment, 2026-09-28 ([ADR 0004](../0004-native-apps-hosting-and-peers.md)) + +- **Native trust tier.** Native Rust apps are first-party and reviewed only, declared in `native-apps.json`; anything from the store is a script app. An in-process native module is trusted code: a splash isolate does not contain native Rust. +- **Process isolation on desktops.** Native apps run in their own process on macOS, Windows and Linux with Vulkan and Wayland, under an OS sandbox declared per app (files, network, child processes). Mobile, wasm and Linux without Vulkan host them in-process. +- **App agents for every app,** reached through the shell only (a process app never talks to the kernel), with the person's consent at first use. +- **Standing approvals.** The person may approve in advance through narrow, time-boxed, audited rules that the shell enforces; the system agent never approves. +- **Storage.** Every app, script or native, uses one host layout (`apps//accounts//`, `common/`, `cache/`; secrets under `secrets//`, never in a jail), declared in its manifest's `storage` block. The agent's workspace is its account folder, as this ADR's `SessionProfile` already makes it the jail. diff --git a/docs/adr/home/0004-system-apps-are-contained-script-apps.md b/docs/adr/home/0004-system-apps-are-contained-script-apps.md index 1c17c976..47ece692 100644 --- a/docs/adr/home/0004-system-apps-are-contained-script-apps.md +++ b/docs/adr/home/0004-system-apps-are-contained-script-apps.md @@ -64,3 +64,7 @@ Found by building these three apps; each fix is general, not app-specific. - Photos: pinch-to-zoom is wired but unverified on a device; there is no device photo library, which would need its own capability and service. - Maps: no offline archives; places, routes and guidance come from the same keyless services as before. - Runtime: a view that starts hidden does not draw its `show_bg` background (use `SolidView`); string-keyed object maps with long keys are pathologically slow. + +## Amendment, 2026-09-28 ([ADR 0004](../0004-native-apps-hosting-and-peers.md)) + +The native exceptions to this ADR, and why they stay native: **App Hub** (the store and the Card runner every script app runs in), **Rinx** (a separate product with its own repository) and **Terminal** (a PTY and child processes, which the script sandbox deliberately forbids). They are declared in `native-apps.json`. The native News, Maps and Photos crates kept for comparison are deleted; the script apps are the only versions. From 6488e0987996aad7139446981ae684ff48d6d862 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:49:52 -0700 Subject: [PATCH 2/8] docs(adr): amend 0002 with the #110 decisions on cross-app tools and approvals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §1: app agents and the system agent may call another app's tools when the calling manifest asks and the shell grants them (at install for script apps, in native-apps.json for native apps); the shell registers, routes, marks and checks each call and authorizes it against the grants with no second agent-level consent. Replaces "the system agent never holds app tools". - §6: toolbox tools reach agents as host-routed tools: workflow.run and workflow.fork; toolbox.search and toolbox.web_read with research; toolbox.deep_crawl with crawl; not octos's deep_research. Registration is additive, host-routed tools stay out of external turns (octos#2601), ownership is per connection, and system-agent input arrives as peer/input (octos#2567). - §10: the shell renders every approval; an agent's text never approves; each line shows owning app, tool, exact args and the calling app; standing rules keyed to (owning app, tool). - §12: the system agent's tool set is defined and enforced with a test; today it gets the full default set, shell included. - §4, §13, Examples, Implementation, M5 and the amendment list follow. Co-Authored-By: Claude Opus 5.5 --- docs/adr/0002-event-driven-app-agents.md | 72 ++++++++++++++++-------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/docs/adr/0002-event-driven-app-agents.md b/docs/adr/0002-event-driven-app-agents.md index cafdc90b..18b2f119 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 +- **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) - **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). @@ -28,6 +28,14 @@ Every app that asks for one gets **its own octos agent**: its app peer, with pri The **system agent does not write prompts for app agents.** It is the supervisor and the outer loop: background-run policy, budgets, the kill switch, curating the glance screen, the few cross-app insights no single app owns (for example, a delayed flight from one app combined with a meeting in another), and **improving each app agent over time** from how it performs (section 11). +**Cross-app tools, by grant** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §7)*. 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. This replaces the earlier rule that the system agent never holds app tools. + +- **Granted by the shell.** For a script app the shell grants them at install, from its manifest; for a native app the grant is part of its reviewed entry in `native-apps.json`. The system agent's own grants are part of its defined tool set (section 12). +- **Registered, routed and checked by the shell.** The shell registers each granted tool in the caller's tool list as a host-routed tool, marked with its owning app (`mail.send`, Mail's), routes each call to the owning app's host service or process, and checks the grant on every call. +- **Authorized by the shell, once.** A call from app A's agent, or from the system agent, to app B's tool is checked against the manifest grants; no second, agent-level consent is asked. Outward and destructive tools still need the person: a live approval the shell draws, or a standing rule keyed to (owning app, tool) with the calling app shown (section 10). + +The system agent still prefers to **delegate** work that needs an app's own context and judgement to that app's agent (section 6 names the `peer/input` event that carries it); a granted tool is for a bounded call, not for taking over an app's thinking. + App agents are **peers inside the shell's one kernel**, not separate kernel processes. Peers already separate workspace, memory and history, so one kernel gives the isolation. It avoids starting a 100 MB+ kernel per app on a phone, avoids copying provider keys into every app, and lets the system agent see what apps publish without a cross-kernel protocol. A separate kernel for an untrusted third-party app may be added later as a policy option, not the default. ### 2. Triggers belong to the app @@ -59,7 +67,7 @@ Every app publishes a **tool manifest**: the operations that make sense for that - **Declared in `tools.json`**, next to `AGENT.md`: for each tool a name in the app's namespace (`.`), a description, a JSON Schema for its input and output, a **risk level** (Read, Act or Destructive), a **confirmation owner** (`confirm`: `host` or `app`, section 12), whether it may run in the background, and whether it is shareable. It is the one source for every kind of app (section 12); App Hub, or the shell build for native modules, checks the declarations and pins them with the app. - **Implemented where the capability lives.** Tools that need data, devices, network or secrets are implemented by the app's **host service** (native code; for example Mail's `list`, `read`, `draft_reply`, `send`), so a secret never reaches the model or the script. Tools that only reshape the app's own data may be implemented by the app itself. - **Registered with the kernel for the app's peer.** The kernel offers the model exactly these tools plus the **system toolbox tools the app was granted** (section 6), and routes each call to its implementation, with the calling peer's identity. Results are structured, size-capped and recorded in the run's audit log. -- **Callers.** The app's own agent always. The **system agent** and **other apps' agents** only where the host grants it, and only the tools the app marks as shareable (for example a Calendar `free_busy` Read tool for a travel app). The person's "Ask anything" assistant calls them the same way, so a request and a background run use one surface. +- **Callers.** The app's own agent always. The **system agent** and **other apps' agents** only when the calling app's manifest (for the system agent, its defined tool set) asks for the tool and the shell granted it, and only the tools the app marks as shareable (for example a Calendar `free_busy` Read tool for a travel app). The shell registers, routes and checks each such call (section 1). The person's "Ask anything" assistant calls them the same way, so a request and a background run use one surface. - **Risk decides supervision.** Read and in-app Act run unattended. An outward or destructive tool (send, post, share, buy, delete) called without a person present does not run: it becomes an **approval request** in the app's conversation (section 10), with the exact arguments, and runs only when the person approves there. ### 5. Deterministic collection, LLM thinking @@ -77,10 +85,10 @@ Gathering information beyond an app's own data (searching, deep research, crawli | Tool | What it does | Capability | |---|---|---| -| `search` | one query through the provider chain (below); structured results | `research` | -| `deep_research` | a planned, multi-source, multi-language investigation of a topic: sub-queries, reading, cross-checking, synthesis with citations; built from workflow templates (below) | `research` | -| `web_read` | render one page with a real browser and return its main text | `research` | -| `deep_crawl` | crawl one site within limits (same site, depth, page count, path prefix) | `crawl` | +| `toolbox.search` | one query through the provider chain (below); structured results | `research` | +| `toolbox.web_read` | render one page with a real browser and return its main text | `research` | +| `toolbox.deep_crawl` | crawl one site within limits (same site, depth, page count, path prefix) | `crawl` | +| deep research | a planned, multi-source, multi-language investigation of a topic: sub-queries, reading, cross-checking, synthesis with citations. It is a workflow template run with `workflow.run` (below), not a tool of its own, and not octos's `deep_research` pipeline | `research` | | `card_render`, `card_critique_payload` | render and measure a card (card-studio) | granted to every app with an agent | | `glance.publish` | publish a card to the glance screen | `glance` | | `workflow.run`, `workflow.fork` | run a toolbox workflow template with parameters, or fork it into the app's own variant (below) | granted with the capabilities its steps use | @@ -104,6 +112,16 @@ The host parses a grant with `Scope::from_grant` and narrows every call with its **Executed by the host, charged to the app.** Toolbox calls are host-routed tools: the kernel sends the call to the host, which runs the engine with the app's scope and budget and writes the results as **structured items** (title, URL, source, language, date, summary, citations) into the **calling app's folder**, returning a summary and item references to the agent. The system agent charges the work to the app's budget, runs heavy jobs when it is cheap (charging, Wi-Fi), and can queue or batch jobs across apps. +**How toolbox tools reach agents** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §6 and §12)*. They follow the same model as other apps' granted tools (section 1): the shell registers them as host-routed tools on the agent's peer through octos's host tools per app peer (UPCR-2026-035, octos#2567), executes each call and checks it against the grant. + +- `workflow.run` and `workflow.fork` for every app agent granted the capabilities the template's steps use; `toolbox.search` and `toolbox.web_read` with `research`; `toolbox.deep_crawl` with `crawl`. +- **Not octos's `deep_research`.** Deep research is a toolbox workflow template, run under the app's scope and budget; octos's own pipeline is not offered to app agents, because it runs outside that scope and budget. +- **Registration is additive.** Registering app and toolbox tools adds them to the peer's turns; it never removes octos's peer-safe generic tools (workspace read fenced to the app's folder, the app's memory; #2567's allowlist). The shell narrows that allowlist only to leave out octos's own search tools, so that research goes through the scoped toolbox. +- **Host-routed, never external.** Host-routed tools carry their own origin in octos (`ToolOrigin::HostRouted`, octos#2601), and a Talk to Octos external turn ([ADR 0003](0003-shared-octos-client-access.md)) keeps only builtin tools, so no toolbox or app tool reaches an external client whatever its name. +- **The system agent may receive granted tools too**, within its defined tool set (section 12). +- **Ownership is per connection.** The connection that registers a peer's tools is its tool host: it receives every `peer/tool/call` and alone answers that peer's approvals. In OctoSense that is always the shell. +- **The system agent's input to an app runs with the app's tools.** When the system agent sends input to a host-owned app peer (`peer_send_input`), octos delivers it to the shell's driving connection as a **`peer/input`** event, and the shell starts the turn with the app's tools and memory, so the app's agent answers with its full context. If the shell is not connected for that peer, octos runs nothing and says the app is not connected; the system agent waits or reports the failure visibly, never runs the turn without the app's context. + **One policy, enforced once.** Because every app goes through the toolbox, the rules below are implemented in one place and cannot be bypassed by an app: 1. **Provider chain, free first:** structured sources for the domain (feeds, public APIs and open datasets, for example RSS/Atom, RSSHub, Google News RSS or GDELT for news; official weather or transit APIs); then a SearXNG instance if configured; then a search API key the person chose to add. None is required. @@ -146,14 +164,16 @@ Each run records what it distilled into the **app's memory namespace** (octos Re Every app with an agent gets a **built-in conversation with its own agent**, drawn by the shell (the same component in every app, like a host sheet) and backed by the app's peer: the same context, history, memory, `AGENT.md`, skills and tools. It is where the person steps into the loop: -- **Approvals.** When a background run reaches an outward or destructive tool, it pauses and posts an **approval request** into the app's conversation: what it wants to do, the exact arguments (the reply text, the event change, the recipients), why, and what happens if the person declines. The person can **approve, edit the arguments, or decline**; the run continues from where it paused, or stops and records the decision. Requests expire, and an expired request is declined. +- **Approvals.** When a background run reaches an outward or destructive tool, it pauses and the shell posts an **approval request** into the app's conversation: what it wants to do, the exact arguments (the reply text, the event change, the recipients), why, and what happens if the person declines. The person can **approve, edit the arguments, or decline**; the run continues from where it paused, or stops and records the decision. Requests expire, and an expired request is declined. - **Questions.** An agent that cannot decide on its own (which of two meetings to keep, which topic the person meant) asks in the conversation instead of guessing, and the run waits or continues without that step, as its `AGENT.md` says. - **Follow-ups and steering.** The person can ask about a card ("why is this here?", "tell me more"), correct the agent ("less of this topic"), or change what it collects. Corrections become data (topics, filters, preferences) or memory, and feed the system agent's tuning of the app agent (section 11); they never edit the pinned `AGENT.md`. - **One thread per run.** Each background run that needs the person has its own thread, so its card, the approval and the outcome stay together. -**Where requests surface.** A pending approval shows on the app's glance card ("Needs you: approve reply to Alice") and as a notification; tapping either opens the app at that thread. Approving in the conversation is the only way an outward or destructive tool runs; a card or notification never approves on its own. The system agent may batch and order pending requests across apps, but it never approves on the person's behalf. +**The shell renders every approval** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §8)*. Every agent's tool calls pass through the shell, so the shell draws every approval sheet, whether it appears in the app's conversation or batched in the system chat. **An agent's own text is never an approval surface**: a "yes" typed to an agent that asked in prose approves nothing. Each line of a sheet shows the **owning app, the tool and the exact arguments**; for a cross-app call it also shows the **calling app** (or the system agent). + +**Where requests surface.** A pending approval shows on the app's glance card ("Needs you: approve reply to Alice") and as a notification; tapping either opens the app at that thread. An outward or destructive tool runs only on the person's decision: a **live approval** on a shell-drawn sheet, or a **standing rule** the person made in advance, keyed to (owning app, tool) and evaluated by the shell on the exact arguments (ADR 0004 §8). A card or notification never approves on its own. The system agent may batch one request's pending approvals into one shell-drawn sheet in its chat and order them across apps, but it never approves on the person's behalf. -**Person present versus absent.** With the person in the conversation, the agent may propose an outward action and run it after the person's explicit confirmation there. Without the person, it only queues the request. The approval record (request, arguments, decision, time) goes into the run's audit log. +**Person present versus absent.** With the person in the conversation, the agent may propose an outward action and run it after the person's explicit confirmation on the shell's sheet there. Without the person, it only queues the request. The approval record (request, arguments, decision, time) goes into the run's audit log. **Built on the UI Protocol.** The conversation uses the kernel's existing approval and question messages over the app peer's context, so octos, the shell's conversation component and other clients (octoscode-web, the TUI) handle approvals the same way. @@ -179,15 +199,17 @@ Everything above applies the same way to **contained script apps** (run by App H | **Tools** | `tools.json`; implemented by the app's host service or the app | `tools.json` is the only declaration. The app-peers broker loads it and builds its tool definitions from it; the module's Rust code only implements executors keyed by tool name. A build check requires every declared tool to have an executor and every executor to be declared. | | **Registration with the kernel** | the shell (`crates/ai-host`) calls `peer/tools/register` for the app's peer | the broker, which owns the host-owned peer (Rinx ADR 0007), calls `peer/tools/register`; the module never talks to the kernel | | **Enforcement** | kernel per peer, plus the Card runner's isolate and the app's grants | kernel per peer, plus the module's own code (trusted tier) | -| **Approvals** | the shell's shared conversation component in the app | the module's own conversation UI (for Rinx, its chat) | +| **Approvals** | the shell's approval sheet, in the shell's shared conversation component in the app | the shell's approval sheet, shown in the module's conversation (for Rinx, its chat); only a `confirm: app` sheet (below) is the module's own | | **Data service** | a host service (e.g. the News service) | the module's own sync (for Rinx, Matrix sync stays Rinx's code); the shell supplies the wake and the schedule | **Who confirms a destructive tool.** `risk` and `confirm` are separate fields; confirmation is never derived from risk: - `risk: destructive`, `confirm: host`: the kernel's approval path. Person present or absent, the call waits for approval in the owning app's conversation. -- `risk: destructive`, `confirm: app`: the app confirms it itself. With the person present, the app's own confirmation sheet is the only confirmation (for example, Rinx's send-message sheet), and the kernel does not prompt again. With the person absent, it becomes an approval request in the owning app's conversation. The person is never asked twice. +- `risk: destructive`, `confirm: app`: the app confirms it itself. With the person present, the app's own confirmation sheet is the only confirmation (for example, Rinx's send-message sheet), and the kernel does not prompt again. With the person absent, it becomes an approval request in the owning app's conversation. The person is never asked twice. The app's own sheet is the app's UI drawn by its trusted code, never the agent's text; a call to the tool from another app's agent or from the system agent is confirmed on the shell's sheet, which shows the calling app. + +In every case approvals go through the kernel's existing approval path to a shell-drawn sheet, in the owning app's conversation or batched in the system chat (section 10), deduplicated by `//`, and the system agent cannot answer approvals for host-bound peers (octos#2560). -In every case approvals go only to the owning app's conversation through the kernel's existing approval path, 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). It never gets command execution. 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. **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. @@ -202,12 +224,12 @@ Least privilege, declared by the app, checked by App Hub, granted by the host, e | Layer | Restriction | Enforced by | |---|---|---| -| **Tools** | only the app's own `tools.json` plus the generic tools its manifest names (`agent.tools`); tools of other apps only where granted and marked shareable; nothing else is visible to the model | kernel, per peer context (script apps and native modules alike) | +| **Tools** | only the app's own `tools.json` plus the generic tools its manifest names (`agent.tools`); tools of other apps only where the manifest asks, the shell granted them and the owning app marks them shareable, registered by the shell and marked with the owning app; nothing else is visible to the model | kernel, per peer context (script apps and native modules alike); the shell checks every cross-app call against the grants | | **Network** | only the manifest's declared hosts; wider information only through granted system toolbox tools (`research`, `crawl`), within the app's declared scope | host network policy on every fetch; the toolbox checks every call against the scope | | **Files** | only the app's folder | app jail and the kernel's per-app workspace | | **Memory** | only `app//…`; promotion by rule or approval | kernel memory namespaces | | **Secrets** | none; keys and passwords stay in host services | host services | -| **Risk** | each tool declares Read, Act or Destructive. Read and in-app Act run unattended; anything outward (send, post, share, buy, delete) pauses as an **approval request** in the app's conversation, surfaced on its card and as a notification | kernel approval gate; the app's conversation | +| **Risk** | each tool declares Read, Act or Destructive. Read and in-app Act run unattended; anything outward (send, post, share, buy, delete) pauses as an **approval request** on a shell-drawn sheet in the app's conversation, surfaced on its card and as a notification, unless a standing rule keyed to (owning app, tool) answers it | kernel approval gate; the shell's sheets and approval router | | **Model** | chosen by the host from the person's providers to meet the app's declared requirements; policy may lower the tier or force local models; the person may override | host, system agent, Settings | | **Budgets** | tokens, run time, research depth, browser pages, runs per day, Wi-Fi/charging conditions | system agent and kernel limits | | **Output** | cards only through `glance.publish`, only L0/L1, checked and pinned | shell | @@ -249,7 +271,7 @@ The answer is `{output, meta}`: `output` is the validated JSON; `meta` is `{clas ### Examples -The same loop serves every app; only the data service, `AGENT.md` and skills, the model requirements and the app's tools differ. +The same loop serves every app; only the data service, `AGENT.md` and skills, the model requirements and the app's tools differ. A shareable tool in the table (for example `calendar.free_busy`) may also be called by another app's agent or by the system agent when it is granted to that caller (section 1); an outward one (`mail.send`, `rinx.message.send`) still waits for the person's approval on the shell's sheet, which then names the calling app. | App | Data service (no model) | App tools (examples) | Agent (on event or schedule) | Card | Needs confirmation | |---|---|---|---|---|---| @@ -271,6 +293,7 @@ app agent (app peer) ├─ admit → glance.publish (outward actions pause as approval requests in the app's conversation) └─ propose changes to what is collected (data) → next run system agent: policy and budgets; rank the glance screen; cross-app insights; + delegate to app agents (peer/input) or call its granted app tools (shell-routed); observe runs → evaluate → adopt overlay changes to AGENT.md/skills ``` @@ -295,11 +318,11 @@ system agent: policy and budgets; rank the glance screen; cross-app insights; In order; each step usable on its own. -1. **Kernel (octos):** host-registered tools per peer (schema, risk, routing to the host); enforce a peer's tool list and tool risk levels; peers own schedules; a host-authorised "wake peer with event" call; memory ingestion from runs. -2. **Shell (`crates/ai-host`, `crates/app-peers`):** the built-in per-app conversation (threads per run; approvals with approve, edit and decline; questions; expiry; deep links from cards and notifications); installing each app's `AGENT.md` and skills into its peer workspace; selecting each peer's model from the person's providers under policy (`peer/model/set`), with per-app override in Settings; background peer handles for host services under host policy; `events` from data services to peers; registering each app's tools with its peer and routing calls to the host service or the app; budgets, kill switch and audit log in Settings. +1. **Kernel (octos):** host-registered tools per peer (schema, risk, routing to the host; additive, with their own origin kept out of external turns; octos#2567, #2601); the system agent's input to a host-owned peer delivered to the host as `peer/input`; enforce a peer's tool list and tool risk levels; the system agent's defined tool set (section 12); peers own schedules; a host-authorised "wake peer with event" call; memory ingestion from runs. +2. **Shell (`crates/ai-host`, `crates/app-peers`):** the built-in per-app conversation (threads per run; approvals with approve, edit and decline; questions; expiry; deep links from cards and notifications); installing each app's `AGENT.md` and skills into its peer workspace; selecting each peer's model from the person's providers under policy (`peer/model/set`), with per-app override in Settings; background peer handles for host services under host policy; `events` from data services to peers; registering each app's tools with its peer and routing calls to the host service or the app; registering, routing and checking granted cross-app tools, for app agents and the system agent; handling `peer/input`; drawing every approval sheet; budgets, kill switch and audit log in Settings. 3. **App Hub:** the bundle gains `AGENT.md`, the app's skills, its model requirements and `tools.json` (schemas, risk, `confirm`, background and shareable flags), and the manifest a background permission; admission checks and pins them. The same parser and checks are a library the app-peers broker uses for native modules' `tools.json`. 4. **Data services:** a common shape (collect, ledger, emit events) and the first services for the system apps that need them. -5. **System toolbox:** the research engine in octos (structured items, `lang`, `since`, per-domain limits, free providers and SearXNG, browser reading, robots.txt as an off-by-default operator setting, no disguised scraping; octos#2568) exposed as host-executed toolbox tools (`search`, `deep_research`, `web_read`, `deep_crawl`); App Hub `research` and `crawl` capabilities with a scope; per-app budgets and queueing by the system agent. +5. **System toolbox:** the research engine in octos (structured items, `lang`, `since`, per-domain limits, free providers and SearXNG, browser reading, robots.txt as an off-by-default operator setting, no disguised scraping; octos#2568) exposed as host-executed toolbox tools (`toolbox.search`, `toolbox.web_read`, `toolbox.deep_crawl`, and deep research as a template through `workflow.run`; not octos's `deep_research`); App Hub `research` and `crawl` capabilities with a scope; per-app budgets and queueing by the system agent. 6. **Outer loop:** per-app overlays for `AGENT.md` and skills (versioned, applied on top of the pinned base), run metrics and feedback signals, offline evaluation by replay or split trials, adoption and rollback, and the overlay history in Settings. 7. **Cards:** a `card-studio` skill (render in `card-host --remote`, measured checks, vision critique, revise within budget); `glance.publish` and the glance screen's curation. 8. **A first app end to end** through steps 1–7 (News; see *First slice: News*), then the other system apps. @@ -314,12 +337,12 @@ News drives the implementation because it needs no approvals, has free and stabl | **M2: Tools and a peer for a contained app** | The bundle's tool manifest (schemas, risk, background, shareable), `AGENT.md` and model requirements. `os.news` gets an app peer; its `news.*` tools are registered with the kernel and routed to the host service. | App Hub, `crates/app-peers`, `crates/ai-host`, octos | | **M3: Trigger and run** | Feed events ("N new items") and digest times wake the News peer. It clusters, ranks and writes a structured digest (`news.digest.write`) using only its tools, within budget, with an audit entry. | octos, `crates/ai-host`, News `AGENT.md` | | **M4: Digest card on the glance screen** | A `sys.digest(app: news)` source; an L0 digest card spec; `glance.publish` wired to the glance feed (today `GlanceFeed::push`, marked "nothing is wired yet"), with a new glance item that renders an L0 card through the Card runner; a glance panel on the desktop. | `phone/`, `desktop/`, `crates/shell`, App Hub | -| **M5: System toolbox** | The research engine as host-executed toolbox tools (`search`, `deep_research`, `web_read`, `deep_crawl`) granted to News by the `research` capability with a scope; structured items with citations in News's folder; free providers first, SearXNG if configured, pages read with a browser; the digest gains citations. | octos research engine, `crates/ai-host`, App Hub | +| **M5: System toolbox** | The research engine as host-executed toolbox tools (`toolbox.search`, `toolbox.web_read` and `workflow.run` for the deep-research template, granted to News by the `research` capability with a scope; `toolbox.deep_crawl` by `crawl`), registered additively as host-routed tools; structured items with citations in News's folder; free providers first, SearXNG if configured, pages read with a browser; the digest gains citations. | octos research engine, `crates/ai-host`, App Hub | | **M6: Render and critique** | A `card-studio` skill: render in `card-host --remote`, measured checks, vision critique, two styles, revise within budget, then publish. | octos skill, App Hub `card-host` | | **M7: Conversation and memory** | News's in-app conversation ("why this story?", "less of this topic" → topics); digest items recorded in the app's memory namespace. | `crates/shell`, octos memory | | **M8: Outer loop, first version** | Run metrics (critique score, cards opened or dismissed, topic corrections); a News `AGENT.md` overlay proposed by the system agent, evaluated by replay, shown in Settings. | `crates/ai-host`, Settings | -Mail follows as the first app with approvals (`mail.send`), reusing M2–M7. +Mail follows as the first app with approvals (`mail.send`, on the shell's sheet), reusing M2–M7. ## Open questions @@ -335,9 +358,10 @@ Mail follows as the first app with approvals (`mail.send`), reusing M2–M7. 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 must run with the app's tools, memory and context; octos#2567 as written gives kernel continuations none, so OctoSense asks octos for originator-driven host turns and, until then, the shell relays those turns (ADR 0004 §6). -- **§4 (tools).** The system agent does not call app tools to make changes: it delegates to the owning app's agent, which acts with its own tools and, where granted, other apps' shareable tools. The shell routes those cross-app calls. Read-only questions may use shareable read tools directly (proposed). ADR 0004 §7. +- **§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; ADR 0004 §6). +- **§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`; not octos's `deep_research`. 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". The system agent may batch one request's approvals into one sheet in its chat. Standing rules are per app and 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. 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. +- **§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". The shell renders every approval sheet, in the app's conversation or batched in the system chat; 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 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. 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 defined and enforced, in octos or host config, with a test; today its turns get octos's full default tool set, shell included, which must change (ADR 0004 §12). - **§13 (autonomy).** Adds the `auto_approvable` flag and the incoming-content rule. From 50d3131e72d0ff4295f77ed147188bed65e6b387 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:51:18 -0700 Subject: [PATCH 3/8] docs(adr): 0004 edits from the #110 review decisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - D1 replaced (§7): app agents and the system agent may call other apps' granted tools; granted at install for script apps, in native-apps.json for native apps; the shell registers, marks, routes and authorizes each call against the grants, with no second agent-level consent. - §2 and the plan: the Terminal is the only process app for now; App Hub stays in-process (it hosts the Card runner); Rinx stays in-process until the peer link and sandboxes exist. - §6: names the peer/input event agreed on octos#2567 and what happens when the host is absent; replaces the interim shell relay. - §8: the shell renders every approval sheet; an agent's text is never an approval surface; standing rules keyed to (owning app, tool), with the calling app shown. - §12 and plan step 4: define the system agent's tool set (no shell), enforce it in octos or host config, with a test. - §13: Talk to Octos external clients never get developer grants or dev.run; banner and audit unchanged. - §11 and the plan: the Rinx storage move (#101) and App Hub pin moves go through the tagged-Rinx rule and the lockstep fix (hagency-org/Rinx#37). - Context, §4, §12 toolbox names, the worked example and Consequences follow; octos#2601 added to Relates to. Co-Authored-By: Claude Opus 5.5 --- .../adr/0004-native-apps-hosting-and-peers.md | 79 +++++++++++-------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md index 5cfa0744..bacc90d4 100644 --- a/docs/adr/0004-native-apps-hosting-and-peers.md +++ b/docs/adr/0004-native-apps-hosting-and-peers.md @@ -3,7 +3,7 @@ - **Date:** 2026-09-28 - **Status:** Proposed - **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); [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, 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). ## Context @@ -47,13 +47,13 @@ - **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. -- **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`); once-only execution, timeouts, audit, approvals for destructive and outward tools, budgets. The kernel never talks to an app. +- **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`); 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-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 gets none of the app's context.** #2567 gives tools, memory and context only to turns driven by the host's connection; "kernel continuations get a minimal prompt and no memory at all", and `peer_send_input` arrives as such a continuation. -2. **App tools are per peer.** An app agent cannot call another app's tool, and the system agent has no app tools. +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). 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()`). @@ -93,11 +93,16 @@ Script system apps stay in `desktop/system-apps.json` and `phone/system-apps.jso | Target | Native app | | --- | --- | -| macOS, Windows | own process by default | -| Linux with a Vulkan build and a Wayland session | own process by default | +| macOS, Windows | own process where its `hosting` says so (for now only the Terminal, below) | +| Linux with a Vulkan build and a Wayland session | own process where its `hosting` says so (for now only the Terminal) | | Linux without them (OpenGL, X11) | **in-process for every app**, for now | | Android, iOS, OpenHarmony, wasm | in-process only | +**Where the process apps start (decided on #110).** The **Terminal is the only process app for now.** The other native apps stay in-process on every target until their reason is gone: + +- **App Hub stays in-process** because it hosts the Card runner that every script app runs in. +- **Rinx stays in-process** until the peer link (section 5) and the process sandboxes (section 3) exist; it then moves by a reviewed change to its `hosting` in `native-apps.json`. + A process app that dies takes only itself down: its tile shows it closed with a restart; the shell and other apps keep running. Desktop release packages ship each process app's `bin`. In-process modules (all of mobile, and Linux without Vulkan) must meet the bar a process does not: fuzzed parsing of outside data, no panics on it, and panic containment at the module boundary (a second panic must not abort the shell). @@ -120,7 +125,7 @@ Three flows: | --- | --- | --- | --- | | 1. The app talks to its agent (UI, triggers) | the service injected at `create` | the **peer link** (section 5) | `host.request("octos.*")` (#106) | | 2. The agent uses the app's tools with full context | the shell executes the call in-process | the peer link carries `peer/tool/call` in and the result out | the app's host service executes it | -| 3. The system agent talks to the agent | in the kernel | in the kernel | in the kernel | +| 3. The system agent talks to the agent | in the kernel; its input reaches the shell as `peer/input` and the shell drives the turn (section 6) | the same, relayed on the peer link | the same | The shell is always the kernel's host connection: it holds the host token, registers the app's tools, drives the app peer's turns and routes tool calls. @@ -138,16 +143,18 @@ The shell stamps identity from the socket, enforces #2567's host obligations for ### 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 questions with `peer_respond`, and never approves an app agent's tools. -- **A turn the system agent starts on an app peer must run with the app's tools, memory and context.** We ask octos for it on #2567 (originator input from the same host's system agent is host-driven). Until then the shell relays: it drives that turn on its own host connection and leaves the result on the blackboard. +- **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 and questions are raised in the app's conversation. The system agent follows it on the blackboard and answers questions with `peer_respond`. 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. +- **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. ### 7. Cross-app work -**The system agent plans; the app that owns the domain acts.** +**Cross-app tools, by grant (decided on #110; replaces D1).** 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. This supersedes the earlier rule that the system agent never holds app tools. -- **Changes** (create, send, update, delete) go to the owning app's agent, which acts with its own tools and, where granted, other apps' **shareable** tools: "schedule a meeting" goes to Calendar, which calls Mail's `mail.send` for the invitations and records them on the event. -- **Read-only questions** may call an app's shareable **read** tools directly from the system agent ("what's on my calendar today?"). *(Proposed; D1 awaits confirmation.)* -- **The shell routes cross-app tool calls.** An app declares in `tools.json` which tools are shareable; an app's manifest asks for others' shareable tools; the person grants them. The shell registers a granted tool in the calling app's peer tool list (as `mail.send`, marked as Mail's), receives the call like any other, checks the grant and hands it to the owning app's host service or process. No octos change is needed; it may move into octos once proven. +- **Granted.** An app declares in `tools.json` which of its tools are shareable; a calling app's manifest asks for others' shareable tools. The shell grants them when a script app is installed; for a native app the grant is part of its reviewed entry in `native-apps.json`. The system agent's grants are part of its defined tool set (section 12). +- **Registered, routed and checked by the shell.** The shell registers each granted tool in the caller's tool list as a host-routed tool (as `mail.send`, marked as Mail's), receives the call like any other, checks the grant on every call and hands it to the owning app's host service or process. It uses #2567's additive registration and host-routed origin, so no granted tool reaches a Talk to Octos external client; it may move further into octos once proven. +- **Authorized by the shell.** A call from app A's agent, or from the system agent, to app B's tool is checked by the shell against the manifest grants; no second, agent-level consent is needed. Outward and destructive tools still follow section 8: a live approval on the shell's sheet, or a standing rule keyed to (owning app, tool), with the calling app shown on the sheet. +- **Who acts.** The system agent plans. A bounded call it is granted (a read such as "what's on my calendar today?", or a single action) it may make directly. Work that needs an app's own context and judgement goes to that app's agent through `peer/input`: "schedule a meeting" goes to Calendar, which calls Mail's `mail.send` for the invitations and records them on the event. - **Partial failure is reported, not hidden**: which step succeeded, which did not. A call whose outcome is unknown is never retried without the person (octos marks it `outcome_unknown`). - **Ambiguity is asked**, not guessed (two Edwards, no free slot). - **Completion** is announced by the system app ("booked; invites sent to 4"); each app's own UI shows the change because its data changed. @@ -156,9 +163,10 @@ The shell stamps identity from the socket, enforces #2567's host obligations for Only the person approves. The system agent never does, and cannot be talked into it: it reads untrusted text, and rules are checked mechanically. -- **Live.** Pending approvals surface in the app's conversation, on its card and as a notification; the system agent may batch those of one request into one sheet in its chat (a plan plus its outward actions, with exact arguments). The shell answers each approval with the person's decision. +- **The shell renders every approval UI (decided on #110).** Every agent's tool calls pass through the shell, so the shell draws every approval sheet, whether it appears in the app's conversation or batched in the system chat. **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** (another app's agent, or the system agent). +- **Live.** Pending approvals surface on a shell-drawn sheet in the app's conversation, on its card and as a notification; the system agent may batch those of one request into one shell-drawn sheet in its chat (a plan plus its outward actions, with exact arguments). The shell answers each approval with the person's decision. - **In advance: standing approvals.** The person sets rules; the shell's **approval router** evaluates each approval request against them on the exact arguments and answers it, or surfaces it: - - scope: per app and per tool, with conditions (recipients in contacts or in the thread, no attachments, triggered by the person, amount or count limits) and a daily cap; + - scope: keyed to **(owning app, tool)**, whoever calls it, so a cross-app call is covered by the owning app's rule and the sheet or notification shows the calling app; with conditions (recipients in contacts or in the thread, no attachments, triggered by the person, amount or count limits) and a daily cap; - created in Settings → Assistant → Approvals, or from an approval sheet ("always for people in my contacts", "allow for 1 hour"); the system agent may suggest a rule, only the person creates one; - **no "everything, forever" rule**: the broadest rule is time-boxed ("approve everything this app asks for the next hour"), with a visible indicator; - excluded always: tools that declare `auto_approvable: false` (permanent deletion, payments, sharing outside the device, account and security changes) and calls whose outcome is unknown; @@ -195,7 +203,7 @@ Rules: - **The agent's workspace is its account's folder** (`peer/prepare` `cwd`), matching octos's one peer per (app, account) and its memory namespace `app//acct-`. An agent never sees another account's folder; octos refuses overlapping workspaces, so this holds by construction. The kernel's file tools read and write there, fenced to it; request contexts get `contexts//` inside it. - **Secrets are never inside an app's jail.** Script apps reach theirs only through host services (Mail's passwords already live on host sheets). Native apps use a host secrets API (the OS keychain where the platform has one) that returns them to the app's code and never writes them under `apps/`. The shell checks at start that no agent workspace contains a `secrets/` path. -- **What the agent should not read raw stays out of its folder**: an app keeps encrypted or internal stores (Rinx's Matrix crypto store and tokens) under `secrets//`, and writes into its account folder what its agent should read (exported threads, shared attachments, the agent's own notes and results). Rinx, today in `~/.local/share/rinx` (#101), moves under this layout. +- **What the agent should not read raw stays out of its folder**: an app keeps encrypted or internal stores (Rinx's Matrix crypto store and tokens) under `secrets//`, and writes into its account folder what its agent should read (exported threads, shared attachments, the agent's own notes and results). Rinx, today in `~/.local/share/rinx` (#101), moves under this layout. That move, and any App Hub pin move that comes with it, goes through the tagged-Rinx rule (OctoSense pins only Rinx release tags) and the App Hub lockstep fix, both in [hagency-org/Rinx#37](https://github.com/hagency-org/Rinx/issues/37). - **Paths come from the host**, never hard-coded: script apps get the jail from the Card runner, native apps from the host API that also gives their account folder. - **Uninstall** deletes `apps//` and `secrets//`; **signing out** an account closes its agent and its contexts; deleting an account folder closes that agent. @@ -226,17 +234,19 @@ Enforcement: ### 12. Toolbox grants: per capability, and no command execution -An app agent gets toolbox tools only for the capabilities its manifest declares and the person grants (ADR 0002 §6; the registration in [#108](https://github.com/OctoSense-org/OctoSense/pull/108)): +An app agent gets toolbox tools only for the capabilities its manifest declares and the person grants (ADR 0002 §6; the registration in [#108](https://github.com/OctoSense-org/OctoSense/pull/108)). They are host-routed tools registered additively on its peer, like granted cross-app tools (section 7): | Toolbox | Who gets it | Runs | | --- | --- | --- | -| web search and page reading (`research`) | apps granted `research` | host | -| crawling (`crawl`, with depth and page limits) | apps granted `crawl` | host | +| web search and page reading (`toolbox.search`, `toolbox.web_read`; deep research as a template through `workflow.run`, not octos's `deep_research`) | apps granted `research` | host | +| crawling (`toolbox.deep_crawl`, with depth and page limits) | apps granted `crawl` | host | | the app's own files | every app agent | octos, fenced to its account folder | | memory | every app agent, its own namespace | octos | | one-shot model calls (`model`) | apps granted `model` | host service | | **command execution** | **no app agent, and not the system agent** | – | +**The system agent's tool set is defined and enforced (decided on #110).** "Not the system agent" is not true today: the system agent's turns get octos's full default tool set, shell included. 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, and the cross-app tools granted to it (section 7); no command execution. The list is enforced in octos or in the shell's host configuration, and a test starts a system-agent turn and checks it is offered exactly that list (in particular, no `shell`). This is a plan step (step 4), and it lands before any cross-app grant reaches the system agent. + 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. octos already refuses it to app peers (#2567's generic-tool allowlist) and to external clients (#98). An app that needs computation gets a narrow tool instead (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`, granted only to an app that asks for it, each command approved live with the exact command shown, run in a terminal the person sees. ### 13. Developer mode: everything granted, for building apps fast @@ -254,6 +264,7 @@ How it is turned on and kept from leaking into normal use: - **A developer profile** (its own OctoSense home, test accounts) is the intended place: there developer mode **stays on until turned off**, with no expiry and no prompts. With real accounts signed in, the shell warns, offers to switch to a developer profile, and if the person continues, developer mode ends by itself after 8 hours or at restart. Grants and rules made in developer mode never carry over to another profile. - **Always visible**: a banner ("Developer mode: all apps have full access") with a one-tap off. - **Always audited**: every tool call, `dev.run` command and automatic approval, which doubles as a debugging trace. +- **Never for external clients (decided on #110).** Developer mode stays, to speed up building a system this complex, but Talk to Octos external clients ([ADR 0003](0003-shared-octos-client-access.md)) never get developer grants or `dev.run`: they are grants to app agents on the shell's host connection only. `dev.run` is host-routed, so octos already keeps it out of an external turn (#2601); the shell also never registers it on a session an external client can reach. ## Worked examples @@ -269,33 +280,35 @@ How it is turned on and kept from leaking into normal use: 1. The person asks the system agent (chat or voice). It asks Calendar's agent who "the group" is and when they are free; Calendar resolves Edward (from contacts, or Mail's shareable `mail.lookup_contact`). 2. The system agent proposes "Tue 3–4 pm, Ana, Bo, Chen and Edward (edward@…); book and send invitations?" 3. On yes, it delegates to Calendar's agent, which calls `calendar.create_event` and Mail's `mail.send` four times (routed by the shell). -4. The four sends are outward: one batched sheet in the chat shows the exact invitations, unless a standing rule ("Calendar may send invitations to my contacts") answers them. +4. The four sends are outward: one batched sheet, drawn by the shell in the chat, shows each invitation's owning app and tool (Mail, `mail.send`), the calling app (Calendar) and the exact arguments, unless a standing rule on (Mail, `mail.send`) ("send to my contacts") answers them. The shell had already checked that Calendar's manifest was granted `mail.send`; no agent is asked to consent. 5. Calendar's UI shows the event; the system app notifies "Meeting booked Tue 3 pm; invitations sent to 4." A failed invitation is named and not retried without the person. ## Consequences - One manifest entry adds or updates a native app; CI keeps the derived places in step. - On macOS, Windows and Vulkan Linux a native crash no longer takes the shell down; mobile and non-Vulkan Linux depend on the robustness bar. -- Every app can have an agent with its full context; the system agent can delegate to it once system-agent turns carry the app's context. -- Cross-app work has one owner per change and needs no octos change. -- The person decides every outward action, now or through narrow, time-boxed, audited rules. -- New work: the generator; 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. +- 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). +- The person decides every outward action, on a sheet only the shell draws, now or through narrow, time-boxed, audited rules keyed to (owning app, tool). +- The system agent loses its default shell access and gets a defined, tested tool set. +- 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; release packages ship its `bin`. -3. On octos#2567: originator-driven turns get the app's context (and the UPCR-2026-034 approval wording, section 8). -4. Land #106 (script apps' agents) and #108 (toolbox tools); rebase #85 on #98. -5. #2567 in the shell: register app tools, execute or relay `peer/tool/call`, shareable-tool routing. -6. The approval router, standing approvals and their Settings; consent at first use. -7. The peer link and its Makepad client API; process sandboxes. -8. Module panic containment. -9. The storage layout and contract (section 11): App Hub's `storage` fields; the host paths and secrets APIs; Rinx and the host services move under `apps/` and `secrets/`; the startup check. -10. Developer mode (section 13): the developer profile, the grants, `dev.run`, the banner and audit; `terminal.run` as a confirmed shareable tool (section 12). +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). +4. **The system agent's tool set** (section 12): define it (supervision tools, granted toolbox tools, granted cross-app tools; no shell), enforce it in octos or in the shell's host configuration, and add 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`, handle `peer/input`; 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 everywhere (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. +10. The storage layout and contract (section 11): App Hub's `storage` fields; the host paths and secrets APIs; Rinx and the host services move under `apps/` and `secrets/` (the Rinx move, #101, and any App Hub pin move through the tagged-Rinx rule and the lockstep fix, hagency-org/Rinx#37); the startup check. +11. Developer mode (section 13): the developer profile, the grants, `dev.run`, the banner and audit, and a test that an external client gets neither developer grants nor `dev.run`; `terminal.run` as a confirmed shareable tool (section 12). ## Open questions -- **D1 (proposed above, awaiting confirmation):** read-only questions call shareable read tools directly; every change goes through the owning app's agent. +- D1 is decided on #110 (section 7): app agents and the system agent may call other apps' granted tools, and the shell authorizes each call. - Linux without Vulkan: revisit once process hosting is measured there (readback cost for light apps). - The exact `tools.json` fields rules can match (recipients, attachments, amounts), agreed with App Hub. From 34b343e46d689dd997bf1bbe3da541c6a63a6d26 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:54:27 -0700 Subject: [PATCH 4/8] docs(adr): 0004 review: call identity, context reads, questions, sign-out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §5: PeerToolCall carries account, context_id and client, stamped by the shell from its context table; the app never supplies them on a call. §9: Rinx checks a mini app's grants against `client`. - §11: request contexts are fenced to contexts//, so they read account data through shell-executed host read tools (files.list/read/search); the shell never passes an explicit context cwd. Ask octos for a read-only view. - §6: host-driven turns carry a generic_tools allowlist without ask_user_question, so the shell registers host.ask and routes it. Ask octos to keep ask_user_question on those turns. - §11: sign-out suspends the agent (contexts closed, calls refused, no turns), never peer_close; account removal and uninstall need a host-only peer/purge upstream. - Context, Gaps, ADR 0002's amendment and the plan follow. Checked against octos#2567 head 7af6e0ce. Co-Authored-By: Claude Opus 5.5 --- docs/adr/0002-event-driven-app-agents.md | 2 +- .../adr/0004-native-apps-hosting-and-peers.md | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/adr/0002-event-driven-app-agents.md b/docs/adr/0002-event-driven-app-agents.md index 18b2f119..fc76c140 100644 --- a/docs/adr/0002-event-driven-app-agents.md +++ b/docs/adr/0002-event-driven-app-agents.md @@ -358,7 +358,7 @@ 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; 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). - **§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`; not octos's `deep_research`. 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. diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md index bacc90d4..79412ade 100644 --- a/docs/adr/0004-native-apps-hosting-and-peers.md +++ b/docs/adr/0004-native-apps-hosting-and-peers.md @@ -47,7 +47,9 @@ - **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. -- **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`); 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`**. +- **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-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 @@ -57,6 +59,9 @@ 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). 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** once the host narrows them with `generic_tools`, which it must to keep the shell out (section 12). +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. ## Decision @@ -136,14 +141,17 @@ The shell is always the kernel's host connection: it holds the host token, regis It rides the process's hub socket as its **own channel**, addressed to that app's peer and never registered with the AI services bus: - 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 }`, `PeerToolCancel { call_id }`. +- down: `PeerReply { req_id, … }` and streamed turn events, `PeerToolCall { call_id, name, args, risk, confirm_required, timeout_ms, account, context_id, client }`, `PeerToolCancel { call_id }`. -The shell stamps 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. +**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`; 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). 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 questions with `peer_respond`, 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 and questions are raised in the app's conversation. The system agent follows it on the blackboard and answers questions with `peer_respond`. 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. +- 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. +- **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 (no shell, section 12), which also drops `ask_user_question`, 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. - **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. @@ -177,7 +185,7 @@ Octos needs no change: the host already answers approvals, and it answers each o ### 9. Rinx -One agent for Rinx and one request context per mini app is enough while mini apps are clients, not agents (Rinx ADR 0007). Because #2567 registers tools per peer, **Rinx checks each tool call's `context_id` against that mini app's grants** (rooms, Matrix actions) and shares one budget fairly between contexts. If mini apps ever become autonomous (their own triggers, instructions, model or budget), each needs its own peer, and the shell creates it for Rinx (peers cannot create peers). Its agent reads the files Rinx writes into its account folder (section 11); the Matrix store and keys live under `secrets/rinx/`, and room data the agent needs beyond those files comes through Rinx's tools, with its per-room checks. +One agent for Rinx and one request context per mini app is enough while mini apps are clients, not agents (Rinx ADR 0007). Because #2567 registers tools per peer, **Rinx checks each tool call's `client` (the mini app, stamped by the shell with its `context_id`, section 5) against that mini app's grants** (rooms, Matrix actions) and shares one budget fairly between contexts. If mini apps ever become autonomous (their own triggers, instructions, model or budget), each needs its own peer, and the shell creates it for Rinx (peers cannot create peers). Its agent reads the files Rinx writes into its account folder (section 11); the Matrix store and keys live under `secrets/rinx/`, and room data the agent needs beyond those files comes through Rinx's tools, with its per-room checks. ### 10. The terminal @@ -201,11 +209,13 @@ Every app, script or native, stores its data in one host-owned layout, and its m Rules: -- **The agent's workspace is its account's folder** (`peer/prepare` `cwd`), matching octos's one peer per (app, account) and its memory namespace `app//acct-`. An agent never sees another account's folder; octos refuses overlapping workspaces, so this holds by construction. The kernel's file tools read and write there, fenced to it; request contexts get `contexts//` inside it. +- **The agent's workspace is its account's folder** (`peer/prepare` `cwd`), matching octos's one peer per (app, account) and its memory namespace `app//acct-`. An agent never sees another account's folder; octos refuses overlapping workspaces, so this holds by construction. The kernel's file tools read and write there, fenced to it. +- **Request contexts see their own folder only**: octos fences a context to `contexts//` inside the account folder and refuses the account folder itself. The shell always lets octos choose that default (never an explicit context `cwd`, which octos checks only for being inside the peer's folder). A context turn reads account data through **host read tools** the shell registers on every app peer, `files.list`, `files.read` and `files.search`, executed by the shell over the account folder (never another context's folder, never outside the account folder) and narrowed by the app's per-client grants where it declares them (a Rinx mini app sees its own rooms' exports only). The agent's own session reads the folder directly. We also ask octos for an optional read-only view of the peer's folder for contexts; the host tools stay for per-client narrowing. - **Secrets are never inside an app's jail.** Script apps reach theirs only through host services (Mail's passwords already live on host sheets). Native apps use a host secrets API (the OS keychain where the platform has one) that returns them to the app's code and never writes them under `apps/`. The shell checks at start that no agent workspace contains a `secrets/` path. - **What the agent should not read raw stays out of its folder**: an app keeps encrypted or internal stores (Rinx's Matrix crypto store and tokens) under `secrets//`, and writes into its account folder what its agent should read (exported threads, shared attachments, the agent's own notes and results). Rinx, today in `~/.local/share/rinx` (#101), moves under this layout. That move, and any App Hub pin move that comes with it, goes through the tagged-Rinx rule (OctoSense pins only Rinx release tags) and the App Hub lockstep fix, both in [hagency-org/Rinx#37](https://github.com/hagency-org/Rinx/issues/37). - **Paths come from the host**, never hard-coded: script apps get the jail from the Card runner, native apps from the host API that also gives their account folder. -- **Uninstall** deletes `apps//` and `secrets//`; **signing out** an account closes its agent and its contexts; deleting an account folder closes that agent. +- **Signing out suspends the account's agent; it never closes it.** octos cannot resume a closed peer or create a replacement for its (app, account), so the shell never calls `peer_close` for sign-out. It closes the account's request contexts (a closed context id is never reopened), answers every `peer/tool/call` for it with `signed_out`, starts no turn for it (the app's, a trigger's, or a `peer/input` from the system agent, which is told the account is signed out), and stops listing it to the system agent. Signing in again resumes the same peer with the host token and opens new contexts. +- **Removing an account or uninstalling the app** deletes its folders (`accounts//`, or `apps//` and `secrets//`) and suspends its agent as above. Its transcript and memory stay in octos until octos can reclaim a peer: we ask for a host-only `peer/purge` that erases a peer's stores and releases its namespace and workspace. Until then Settings says the agent's memory remains, and adding the account again resumes the same agent. The contract, one block in both manifests (App Hub's `manifest.json` extends its existing `storage.max_bytes`; `native-apps.json` carries the same block): @@ -297,10 +307,10 @@ How it is turned on and kept from leaking into normal use: 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). +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`. 4. **The system agent's tool set** (section 12): define it (supervision tools, granted toolbox tools, granted cross-app tools; no shell), enforce it in octos or in the shell's host configuration, and add 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`, handle `peer/input`; 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. +6. #2567 in the shell: register app tools, execute or relay `peer/tool/call` with the stamped identity (section 5), 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. 7. Shell-drawn approval sheets everywhere (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. From 91cc69f86a29fc0d7f971d7b8b706658a68e981e Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:11:14 -0700 Subject: [PATCH 5/8] docs(adr): confirm: app sheets for every caller; agent tools by manifest and grant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer decisions on #110, replacing 6488e09/50d3131's "the app's own sheet only for calls from its own agent while the person is present" and the "app agents get no web_search/deep_search" text. Approvals (ADR 0002 §1, §10, §12, §13, examples, plan, amendment; ADR 0004 §5, §7, §8, §9, Consequences, plan): - The shell is the single path and authority for every app tool call, from the owning app's agent, another app's agent or the system agent. - confirm: host: the shell draws the sheet. confirm: app: the shell hands the confirmation to the owning app, which shows its own sheet for every caller; PeerToolCall gains `caller` so the sheet can show it. - App not running or nobody present: waits or is refused visibly (#2567). Tools (ADR 0002 §6, §13, plan, amendment; ADR 0004 Gaps, §6, §12, plan): - App agents get whatever their manifest declares and the person grants at install, octos's web_search/deep_search and command execution included; no hard-coded exclusions. The shell sets generic_tools from the grant. External clients keep their fixed allowlist. Co-Authored-By: Claude Opus 5.5 --- docs/adr/0002-event-driven-app-agents.md | 42 +++++++++---------- .../adr/0004-native-apps-hosting-and-peers.md | 36 ++++++++-------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/docs/adr/0002-event-driven-app-agents.md b/docs/adr/0002-event-driven-app-agents.md index fc76c140..25be157d 100644 --- a/docs/adr/0002-event-driven-app-agents.md +++ b/docs/adr/0002-event-driven-app-agents.md @@ -32,7 +32,7 @@ The **system agent does not write prompts for app agents.** It is the supervisor - **Granted by the shell.** For a script app the shell grants them at install, from its manifest; for a native app the grant is part of its reviewed entry in `native-apps.json`. The system agent's own grants are part of its defined tool set (section 12). - **Registered, routed and checked by the shell.** The shell registers each granted tool in the caller's tool list as a host-routed tool, marked with its owning app (`mail.send`, Mail's), routes each call to the owning app's host service or process, and checks the grant on every call. -- **Authorized by the shell, once.** A call from app A's agent, or from the system agent, to app B's tool is checked against the manifest grants; no second, agent-level consent is asked. Outward and destructive tools still need the person: a live approval the shell draws, or a standing rule keyed to (owning app, tool) with the calling app shown (section 10). +- **Authorized by the shell, once.** A call from app A's agent, or from the system agent, to app B's tool is checked against the manifest grants; no second, agent-level consent is asked. Outward and destructive tools still need the person: a live approval (on the shell's sheet, or on the owning app's own sheet for a `confirm: app` tool, section 12), or a standing rule keyed to (owning app, tool) with the calling app shown (section 10). The system agent still prefers to **delegate** work that needs an app's own context and judgement to that app's agent (section 6 names the `peer/input` event that carries it); a granted tool is for a bounded call, not for taking over an app's thinking. @@ -79,7 +79,7 @@ Mechanical collection is **code, not a model**. Judgement is the model's. ### 6. The system toolbox: research and crawling are granted system services -Gathering information beyond an app's own data (searching, deep research, crawling a site, reading a page) is done by a **system toolbox** that the host and the system agent own and run. An app agent does not search, crawl or drive a browser itself; it is **granted** toolbox tools and calls them, and the host executes them outside the app and its peer. +Gathering information beyond an app's own data (searching, deep research, crawling a site, reading a page) is done by a **system toolbox** that the host and the system agent own and run. An app agent is **granted** toolbox tools and calls them, and the host executes them outside the app and its peer. The toolbox is the scoped, budgeted route, not the only one *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §12)*: an app agent gets whatever tools its manifest declares and the person grants at install, octos's own `web_search` and `deep_search` included, with no fixed exclusions. **The toolbox:** @@ -115,8 +115,8 @@ The host parses a grant with `Scope::from_grant` and narrows every call with its **How toolbox tools reach agents** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §6 and §12)*. They follow the same model as other apps' granted tools (section 1): the shell registers them as host-routed tools on the agent's peer through octos's host tools per app peer (UPCR-2026-035, octos#2567), executes each call and checks it against the grant. - `workflow.run` and `workflow.fork` for every app agent granted the capabilities the template's steps use; `toolbox.search` and `toolbox.web_read` with `research`; `toolbox.deep_crawl` with `crawl`. -- **Not octos's `deep_research`.** Deep research is a toolbox workflow template, run under the app's scope and budget; octos's own pipeline is not offered to app agents, because it runs outside that scope and budget. -- **Registration is additive.** Registering app and toolbox tools adds them to the peer's turns; it never removes octos's peer-safe generic tools (workspace read fenced to the app's folder, the app's memory; #2567's allowlist). The shell narrows that allowlist only to leave out octos's own search tools, so that research goes through the scoped toolbox. +- **octos's `deep_research` by grant, not by default.** Deep research in the toolbox is a workflow template, run under the app's scope and budget. octos's own pipeline runs outside that scope and budget; an app agent gets it, like any other tool, when its manifest declares it and the person grants it. +- **Registration is additive.** Registering app and toolbox tools adds them to the peer's turns; it never removes octos's peer-safe generic tools (workspace read fenced to the app's folder, the app's memory; #2567's allowlist). The shell sets that allowlist (`generic_tools`) from the app's manifest and install-time grant: octos's generic tools, its search tools included, are on it when declared and granted and off it otherwise; OctoSense adds no fixed exclusions. - **Host-routed, never external.** Host-routed tools carry their own origin in octos (`ToolOrigin::HostRouted`, octos#2601), and a Talk to Octos external turn ([ADR 0003](0003-shared-octos-client-access.md)) keeps only builtin tools, so no toolbox or app tool reaches an external client whatever its name. - **The system agent may receive granted tools too**, within its defined tool set (section 12). - **Ownership is per connection.** The connection that registers a peer's tools is its tool host: it receives every `peer/tool/call` and alone answers that peer's approvals. In OctoSense that is always the shell. @@ -164,16 +164,16 @@ Each run records what it distilled into the **app's memory namespace** (octos Re Every app with an agent gets a **built-in conversation with its own agent**, drawn by the shell (the same component in every app, like a host sheet) and backed by the app's peer: the same context, history, memory, `AGENT.md`, skills and tools. It is where the person steps into the loop: -- **Approvals.** When a background run reaches an outward or destructive tool, it pauses and the shell posts an **approval request** into the app's conversation: what it wants to do, the exact arguments (the reply text, the event change, the recipients), why, and what happens if the person declines. The person can **approve, edit the arguments, or decline**; the run continues from where it paused, or stops and records the decision. Requests expire, and an expired request is declined. +- **Approvals.** When a background run reaches an outward or destructive tool, it pauses and the shell posts an **approval request** into the app's conversation (for a `confirm: app` tool, the owning app raises its own sheet instead; section 12): what it wants to do, the exact arguments (the reply text, the event change, the recipients), why, and what happens if the person declines. The person can **approve, edit the arguments, or decline**; the run continues from where it paused, or stops and records the decision. Requests expire, and an expired request is declined. - **Questions.** An agent that cannot decide on its own (which of two meetings to keep, which topic the person meant) asks in the conversation instead of guessing, and the run waits or continues without that step, as its `AGENT.md` says. - **Follow-ups and steering.** The person can ask about a card ("why is this here?", "tell me more"), correct the agent ("less of this topic"), or change what it collects. Corrections become data (topics, filters, preferences) or memory, and feed the system agent's tuning of the app agent (section 11); they never edit the pinned `AGENT.md`. - **One thread per run.** Each background run that needs the person has its own thread, so its card, the approval and the outcome stay together. -**The shell renders every approval** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §8)*. Every agent's tool calls pass through the shell, so the shell draws every approval sheet, whether it appears in the app's conversation or batched in the system chat. **An agent's own text is never an approval surface**: a "yes" typed to an agent that asked in prose approves nothing. Each line of a sheet shows the **owning app, the tool and the exact arguments**; for a cross-app call it also shows the **calling app** (or the system agent). +**The shell is the single path for every tool call; it draws every approval except `confirm: app`** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §8)*. Every app tool call, whoever makes it (the owning app's own agent, another app's agent or the system agent), goes from the kernel's `peer/tool/call` to the shell's host connection and on to the owning app's executor; an app's agent has no direct way to call its own tools. So the shell sees, authorizes (grants, budgets), audits and routes every call. For a `confirm: host` tool the shell draws the approval sheet, in the app's conversation or batched in the system chat. For a `confirm: app` tool the shell hands the confirmation to the owning app, which shows its **own sheet** (for Rinx, its send sheet) for callers of every kind; the request says who is calling (which app or agent, and its context), so the app's sheet can show it (section 12). **An agent's own text is never an approval surface**: a "yes" typed to an agent that asked in prose approves nothing. Each line of a sheet, the shell's or the app's, shows the **owning app, the tool and the exact arguments**; for a cross-app call it also shows the **calling app** (or the system agent). -**Where requests surface.** A pending approval shows on the app's glance card ("Needs you: approve reply to Alice") and as a notification; tapping either opens the app at that thread. An outward or destructive tool runs only on the person's decision: a **live approval** on a shell-drawn sheet, or a **standing rule** the person made in advance, keyed to (owning app, tool) and evaluated by the shell on the exact arguments (ADR 0004 §8). A card or notification never approves on its own. The system agent may batch one request's pending approvals into one shell-drawn sheet in its chat and order them across apps, but it never approves on the person's behalf. +**Where requests surface.** A pending approval shows on the app's glance card ("Needs you: approve reply to Alice") and as a notification; tapping either opens the app at that thread. An outward or destructive tool runs only on the person's decision: a **live approval** on a shell-drawn sheet (the owning app's own sheet for a `confirm: app` tool), or a **standing rule** the person made in advance, keyed to (owning app, tool) and evaluated by the shell on the exact arguments (ADR 0004 §8). A card or notification never approves on its own. The system agent may batch one request's pending `confirm: host` approvals into one shell-drawn sheet in its chat and order them across apps, but it never approves on the person's behalf. -**Person present versus absent.** With the person in the conversation, the agent may propose an outward action and run it after the person's explicit confirmation on the shell's sheet there. Without the person, it only queues the request. The approval record (request, arguments, decision, time) goes into the run's audit log. +**Person present versus absent.** With the person in the conversation, the agent may propose an outward action and run it after the person's explicit confirmation on the shell's sheet there, or on the owning app's own sheet for a `confirm: app` tool. Without the person, it only queues the request; a `confirm: app` call waits or is refused visibly, as octos#2567 specifies. The approval record (request, arguments, decision, time) goes into the run's audit log. **Built on the UI Protocol.** The conversation uses the kernel's existing approval and question messages over the app peer's context, so octos, the shell's conversation component and other clients (octoscode-web, the TUI) handle approvals the same way. @@ -199,15 +199,15 @@ Everything above applies the same way to **contained script apps** (run by App H | **Tools** | `tools.json`; implemented by the app's host service or the app | `tools.json` is the only declaration. The app-peers broker loads it and builds its tool definitions from it; the module's Rust code only implements executors keyed by tool name. A build check requires every declared tool to have an executor and every executor to be declared. | | **Registration with the kernel** | the shell (`crates/ai-host`) calls `peer/tools/register` for the app's peer | the broker, which owns the host-owned peer (Rinx ADR 0007), calls `peer/tools/register`; the module never talks to the kernel | | **Enforcement** | kernel per peer, plus the Card runner's isolate and the app's grants | kernel per peer, plus the module's own code (trusted tier) | -| **Approvals** | the shell's approval sheet, in the shell's shared conversation component in the app | the shell's approval sheet, shown in the module's conversation (for Rinx, its chat); only a `confirm: app` sheet (below) is the module's own | +| **Approvals** | the shell's approval sheet, in the shell's shared conversation component in the app | the shell's approval sheet, shown in the module's conversation (for Rinx, its chat); for a `confirm: app` tool (below), the module's own sheet, whoever calls | | **Data service** | a host service (e.g. the News service) | the module's own sync (for Rinx, Matrix sync stays Rinx's code); the shell supplies the wake and the schedule | **Who confirms a destructive tool.** `risk` and `confirm` are separate fields; confirmation is never derived from risk: -- `risk: destructive`, `confirm: host`: the kernel's approval path. Person present or absent, the call waits for approval in the owning app's conversation. -- `risk: destructive`, `confirm: app`: the app confirms it itself. With the person present, the app's own confirmation sheet is the only confirmation (for example, Rinx's send-message sheet), and the kernel does not prompt again. With the person absent, it becomes an approval request in the owning app's conversation. The person is never asked twice. The app's own sheet is the app's UI drawn by its trusted code, never the agent's text; a call to the tool from another app's agent or from the system agent is confirmed on the shell's sheet, which shows the calling app. +- `risk: destructive`, `confirm: host`: the kernel's approval path and the shell's sheet. Person present or absent, the call waits for approval in the owning app's conversation (or batched in the system chat). +- `risk: destructive`, `confirm: app`: the owning app confirms it itself, whoever calls it: its own agent, another app's agent or the system agent. The shell authorizes the call as for any other, then hands the confirmation to the owning app with who is calling (which app or agent, and its context); the app's own confirmation sheet (for example, Rinx's send-message sheet) shows the caller and is the only confirmation, and the kernel does not prompt again. The person is never asked twice. The app's own sheet is the app's UI drawn by its trusted code, never the agent's text. When the owning app is not running, or nobody is present, the call waits or is refused visibly, as octos#2567 specifies. -In every case approvals go through the kernel's existing approval path to a shell-drawn sheet, in the owning app's conversation or batched in the system chat (section 10), deduplicated by `//`, and the system agent cannot answer approvals for host-bound peers (octos#2560). +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). It never gets command execution. 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. @@ -224,12 +224,12 @@ Least privilege, declared by the app, checked by App Hub, granted by the host, e | Layer | Restriction | Enforced by | |---|---|---| -| **Tools** | only the app's own `tools.json` plus the generic tools its manifest names (`agent.tools`); tools of other apps only where the manifest asks, the shell granted them and the owning app marks them shareable, registered by the shell and marked with the owning app; nothing else is visible to the model | kernel, per peer context (script apps and native modules alike); the shell checks every cross-app call against the grants | -| **Network** | only the manifest's declared hosts; wider information only through granted system toolbox tools (`research`, `crawl`), within the app's declared scope | host network policy on every fetch; the toolbox checks every call against the scope | +| **Tools** | only the app's own `tools.json` plus the generic tools its manifest names (`agent.tools`) and the person granted at install, with no fixed exclusions; tools of other apps only where the manifest asks, the shell granted them and the owning app marks them shareable, registered by the shell and marked with the owning app; nothing else is visible to the model | kernel, per peer context (script apps and native modules alike); the shell checks every cross-app call against the grants | +| **Network** | only the manifest's declared hosts; wider information only through granted tools: the system toolbox (`research`, `crawl`) within the app's declared scope, or octos's own search tools where declared and granted | host network policy on every fetch; the toolbox checks every call against the scope | | **Files** | only the app's folder | app jail and the kernel's per-app workspace | | **Memory** | only `app//…`; promotion by rule or approval | kernel memory namespaces | | **Secrets** | none; keys and passwords stay in host services | host services | -| **Risk** | each tool declares Read, Act or Destructive. Read and in-app Act run unattended; anything outward (send, post, share, buy, delete) pauses as an **approval request** on a shell-drawn sheet in the app's conversation, surfaced on its card and as a notification, unless a standing rule keyed to (owning app, tool) answers it | kernel approval gate; the shell's sheets and approval router | +| **Risk** | each tool declares Read, Act or Destructive. Read and in-app Act run unattended; anything outward (send, post, share, buy, delete) pauses as an **approval request** on a shell-drawn sheet in the app's conversation (the owning app's own sheet for a `confirm: app` tool), surfaced on its card and as a notification, unless a standing rule keyed to (owning app, tool) answers it | kernel approval gate; the shell's sheets and approval router | | **Model** | chosen by the host from the person's providers to meet the app's declared requirements; policy may lower the tier or force local models; the person may override | host, system agent, Settings | | **Budgets** | tokens, run time, research depth, browser pages, runs per day, Wi-Fi/charging conditions | system agent and kernel limits | | **Output** | cards only through `glance.publish`, only L0/L1, checked and pinned | shell | @@ -271,7 +271,7 @@ The answer is `{output, meta}`: `output` is the validated JSON; `meta` is `{clas ### Examples -The same loop serves every app; only the data service, `AGENT.md` and skills, the model requirements and the app's tools differ. A shareable tool in the table (for example `calendar.free_busy`) may also be called by another app's agent or by the system agent when it is granted to that caller (section 1); an outward one (`mail.send`, `rinx.message.send`) still waits for the person's approval on the shell's sheet, which then names the calling app. +The same loop serves every app; only the data service, `AGENT.md` and skills, the model requirements and the app's tools differ. A shareable tool in the table (for example `calendar.free_busy`) may also be called by another app's agent or by the system agent when it is granted to that caller (section 1); an outward one (`mail.send`, `rinx.message.send`) still waits for the person's approval, on the shell's sheet or, for a `confirm: app` tool such as `rinx.message.send`, the owning app's own sheet, which then names the calling app. | App | Data service (no model) | App tools (examples) | Agent (on event or schedule) | Card | Needs confirmation | |---|---|---|---|---|---| @@ -280,7 +280,7 @@ The same loop serves every app; only the data service, `AGENT.md` and skills, th | **Calendar / travel** | calendar sync; flight or transit status | `calendar.list`, `calendar.free_busy` (shareable), `calendar.move` (Act, confirm) | sees a delay or conflict, works out consequences | "Your 9:00 is at risk: flight +40 min" with options | changing or declining an event | | **Weather** | forecast and warnings feed | `weather.forecast`, `weather.alerts` | relates warnings to the person's plans and places | "Storm at 17:00 near your commute" | none | | **Health** | the device's health store | `health.summary`, `health.trend` (Read, never shareable) | notices a trend against the person's baseline | weekly summary; a gentle flag | sharing with anyone | -| **Rinx** (Matrix; native module) | Rinx's own Matrix sync, woken by the shell (follow-up) | `rinx.rooms.list`, `rinx.unread`, `rinx.message.draft`, `rinx.message.send` (Destructive, `confirm: app`) | summarizes mentions and decisions, drafts replies | "3 mentions need you" | sending (Rinx's own sheet if present, else an approval request in Rinx) | +| **Rinx** (Matrix; native module) | Rinx's own Matrix sync, woken by the shell (follow-up) | `rinx.rooms.list`, `rinx.unread`, `rinx.message.draft`, `rinx.message.send` (Destructive, `confirm: app`) | summarizes mentions and decisions, drafts replies | "3 mentions need you" | sending (Rinx's own sheet, whoever calls; waits or is refused visibly when Rinx is not running or nobody is present) | ``` data service (timer or notification, no model) ──▶ app folder + ledger @@ -319,10 +319,10 @@ system agent: policy and budgets; rank the glance screen; cross-app insights; In order; each step usable on its own. 1. **Kernel (octos):** host-registered tools per peer (schema, risk, routing to the host; additive, with their own origin kept out of external turns; octos#2567, #2601); the system agent's input to a host-owned peer delivered to the host as `peer/input`; enforce a peer's tool list and tool risk levels; the system agent's defined tool set (section 12); peers own schedules; a host-authorised "wake peer with event" call; memory ingestion from runs. -2. **Shell (`crates/ai-host`, `crates/app-peers`):** the built-in per-app conversation (threads per run; approvals with approve, edit and decline; questions; expiry; deep links from cards and notifications); installing each app's `AGENT.md` and skills into its peer workspace; selecting each peer's model from the person's providers under policy (`peer/model/set`), with per-app override in Settings; background peer handles for host services under host policy; `events` from data services to peers; registering each app's tools with its peer and routing calls to the host service or the app; registering, routing and checking granted cross-app tools, for app agents and the system agent; handling `peer/input`; drawing every approval sheet; budgets, kill switch and audit log in Settings. +2. **Shell (`crates/ai-host`, `crates/app-peers`):** the built-in per-app conversation (threads per run; approvals with approve, edit and decline; questions; expiry; deep links from cards and notifications); installing each app's `AGENT.md` and skills into its peer workspace; selecting each peer's model from the person's providers under policy (`peer/model/set`), with per-app override in Settings; background peer handles for host services under host policy; `events` from data services to peers; registering each app's tools with its peer and routing calls to the host service or the app; registering, routing and checking granted cross-app tools, for app agents and the system agent; handling `peer/input`; drawing every `confirm: host` approval sheet and handing `confirm: app` confirmations, with the caller, to the owning app; budgets, kill switch and audit log in Settings. 3. **App Hub:** the bundle gains `AGENT.md`, the app's skills, its model requirements and `tools.json` (schemas, risk, `confirm`, background and shareable flags), and the manifest a background permission; admission checks and pins them. The same parser and checks are a library the app-peers broker uses for native modules' `tools.json`. 4. **Data services:** a common shape (collect, ledger, emit events) and the first services for the system apps that need them. -5. **System toolbox:** the research engine in octos (structured items, `lang`, `since`, per-domain limits, free providers and SearXNG, browser reading, robots.txt as an off-by-default operator setting, no disguised scraping; octos#2568) exposed as host-executed toolbox tools (`toolbox.search`, `toolbox.web_read`, `toolbox.deep_crawl`, and deep research as a template through `workflow.run`; not octos's `deep_research`); App Hub `research` and `crawl` capabilities with a scope; per-app budgets and queueing by the system agent. +5. **System toolbox:** the research engine in octos (structured items, `lang`, `since`, per-domain limits, free providers and SearXNG, browser reading, robots.txt as an off-by-default operator setting, no disguised scraping; octos#2568) exposed as host-executed toolbox tools (`toolbox.search`, `toolbox.web_read`, `toolbox.deep_crawl`, and deep research as a template through `workflow.run`; octos's `deep_research` only where declared and granted); App Hub `research` and `crawl` capabilities with a scope; per-app budgets and queueing by the system agent. 6. **Outer loop:** per-app overlays for `AGENT.md` and skills (versioned, applied on top of the pinned base), run metrics and feedback signals, offline evaluation by replay or split trials, adoption and rollback, and the overlay history in Settings. 7. **Cards:** a `card-studio` skill (render in `card-host --remote`, measured checks, vision critique, revise within budget); `glance.publish` and the glance screen's curation. 8. **A first app end to end** through steps 1–7 (News; see *First slice: News*), then the other system apps. @@ -360,8 +360,8 @@ ADR 0004 supplies the hosting and transport this ADR assumes and changes it in t - **§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 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`; not octos's `deep_research`. Registration is additive, host-routed tools are kept out of external turns, and the system agent may receive granted tools too. +- **§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". The shell renders every approval sheet, in the app's conversation or batched in the system chat; 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 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. ADR 0004 §8. +- **§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. 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 defined and enforced, in octos or host config, with a test; today its turns get octos's full default tool set, shell included, which must change (ADR 0004 §12). - **§13 (autonomy).** Adds the `auto_approvable` flag and the incoming-content rule. diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md index 79412ade..4dc36c96 100644 --- a/docs/adr/0004-native-apps-hosting-and-peers.md +++ b/docs/adr/0004-native-apps-hosting-and-peers.md @@ -59,7 +59,7 @@ 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). 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** once the host narrows them with `generic_tools`, which it must to keep the shell out (section 12). +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`. 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. @@ -141,9 +141,9 @@ The shell is always the kernel's host connection: it holds the host token, regis It rides the process's hub socket as its **own channel**, addressed to that app's peer and never registered with the AI services bus: - 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 }`, `PeerToolCancel { call_id }`. +- 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`; 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). 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.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. 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. @@ -151,7 +151,7 @@ The shell stamps the app's identity from the socket, enforces #2567's host oblig - 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. - **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 (no shell, section 12), which also drops `ask_user_question`, 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 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. - **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. @@ -161,7 +161,7 @@ The shell stamps the app's identity from the socket, enforces #2567's host oblig - **Granted.** An app declares in `tools.json` which of its tools are shareable; a calling app's manifest asks for others' shareable tools. The shell grants them when a script app is installed; for a native app the grant is part of its reviewed entry in `native-apps.json`. The system agent's grants are part of its defined tool set (section 12). - **Registered, routed and checked by the shell.** The shell registers each granted tool in the caller's tool list as a host-routed tool (as `mail.send`, marked as Mail's), receives the call like any other, checks the grant on every call and hands it to the owning app's host service or process. It uses #2567's additive registration and host-routed origin, so no granted tool reaches a Talk to Octos external client; it may move further into octos once proven. -- **Authorized by the shell.** A call from app A's agent, or from the system agent, to app B's tool is checked by the shell against the manifest grants; no second, agent-level consent is needed. Outward and destructive tools still follow section 8: a live approval on the shell's sheet, or a standing rule keyed to (owning app, tool), with the calling app shown on the sheet. +- **Authorized by the shell.** A call from app A's agent, or from the system agent, to app B's tool is checked by the shell against the manifest grants; no second, agent-level consent is needed. Outward and destructive tools still follow section 8: a live approval (on the shell's sheet, or on the owning app's own sheet for a `confirm: app` tool), or a standing rule keyed to (owning app, tool), with the calling app shown on the sheet. - **Who acts.** The system agent plans. A bounded call it is granted (a read such as "what's on my calendar today?", or a single action) it may make directly. Work that needs an app's own context and judgement goes to that app's agent through `peer/input`: "schedule a meeting" goes to Calendar, which calls Mail's `mail.send` for the invitations and records them on the event. - **Partial failure is reported, not hidden**: which step succeeded, which did not. A call whose outcome is unknown is never retried without the person (octos marks it `outcome_unknown`). - **Ambiguity is asked**, not guessed (two Edwards, no free slot). @@ -171,8 +171,9 @@ The shell stamps the app's identity from the socket, enforces #2567's host oblig Only the person approves. The system agent never does, and cannot be talked into it: it reads untrusted text, and rules are checked mechanically. -- **The shell renders every approval UI (decided on #110).** Every agent's tool calls pass through the shell, so the shell draws every approval sheet, whether it appears in the app's conversation or batched in the system chat. **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** (another app's agent, or the system agent). -- **Live.** Pending approvals surface on a shell-drawn sheet in the app's conversation, on its card and as a notification; the system agent may batch those of one request into one shell-drawn sheet in its chat (a plan plus its outward actions, with exact arguments). The shell answers each approval with the person's decision. +- **The shell is the single path and the authority for every tool call (decided on #110).** Every app tool call, whoever makes it (the owning app's own agent, another app's agent or the system agent), goes kernel `peer/tool/call` → the shell's host connection → the owning app's executor; an app's agent has no direct way to call its own tools. So the shell sees, authorizes (grants, budgets), audits and routes every call. +- **Who draws the sheet.** For a `confirm: host` tool the shell draws it, in the app's conversation or batched in the system chat. For a `confirm: app` tool the shell hands the confirmation to the owning app, which shows its **own sheet** (for Rinx, its send sheet) for callers of every kind; the call carries who is calling (`caller`, with its context; section 5), so the app's sheet shows it. When the owning app is not running, or nobody is present, the call waits or is refused visibly, as octos#2567 specifies. **An agent's own text is never an approval surface.** Each line, on either sheet, shows the **owning app, the tool and the exact arguments**, and for a cross-app call the **calling app** (another app's agent, or the system agent). +- **Live.** Pending approvals surface on a shell-drawn sheet in the app's conversation (on the owning app's own sheet for a `confirm: app` tool), on its card and as a notification; the system agent may batch the `confirm: host` approvals of one request into one shell-drawn sheet in its chat (a plan plus its outward actions, with exact arguments). The shell answers each approval with the person's decision. - **In advance: standing approvals.** The person sets rules; the shell's **approval router** evaluates each approval request against them on the exact arguments and answers it, or surfaces it: - scope: keyed to **(owning app, tool)**, whoever calls it, so a cross-app call is covered by the owning app's rule and the sheet or notification shows the calling app; with conditions (recipients in contacts or in the thread, no attachments, triggered by the person, amount or count limits) and a daily cap; - created in Settings → Assistant → Approvals, or from an approval sheet ("always for people in my contacts", "allow for 1 hour"); the system agent may suggest a rule, only the person creates one; @@ -185,7 +186,7 @@ Octos needs no change: the host already answers approvals, and it answers each o ### 9. Rinx -One agent for Rinx and one request context per mini app is enough while mini apps are clients, not agents (Rinx ADR 0007). Because #2567 registers tools per peer, **Rinx checks each tool call's `client` (the mini app, stamped by the shell with its `context_id`, section 5) against that mini app's grants** (rooms, Matrix actions) and shares one budget fairly between contexts. If mini apps ever become autonomous (their own triggers, instructions, model or budget), each needs its own peer, and the shell creates it for Rinx (peers cannot create peers). Its agent reads the files Rinx writes into its account folder (section 11); the Matrix store and keys live under `secrets/rinx/`, and room data the agent needs beyond those files comes through Rinx's tools, with its per-room checks. +One agent for Rinx and one request context per mini app is enough while mini apps are clients, not agents (Rinx ADR 0007). Because #2567 registers tools per peer, **Rinx checks each tool call's `client` (the mini app, stamped by the shell with its `context_id`, section 5) against that mini app's grants** (rooms, Matrix actions) and shares one budget fairly between contexts. If mini apps ever become autonomous (their own triggers, instructions, model or budget), each needs its own peer, and the shell creates it for Rinx (peers cannot create peers). Rinx's send sheet confirms `rinx.message.send` (`confirm: app`) for every caller and shows who is calling (section 8). Its agent reads the files Rinx writes into its account folder (section 11); the Matrix store and keys live under `secrets/rinx/`, and room data the agent needs beyond those files comes through Rinx's tools, with its per-room checks. ### 10. The terminal @@ -242,22 +243,23 @@ Enforcement: | Secrets out of reach of the agent | host services hold them | host secrets API; startup check | host secrets API; startup check | | Quotas | the isolate's quota | the shell measures and warns; the sandbox cannot count bytes | the shell measures and warns | -### 12. Toolbox grants: per capability, and no command execution +### 12. Agent tools: what the manifest declares and the person grants -An app agent gets toolbox tools only for the capabilities its manifest declares and the person grants (ADR 0002 §6; the registration in [#108](https://github.com/OctoSense-org/OctoSense/pull/108)). They are host-routed tools registered additively on its peer, like granted cross-app tools (section 7): +An app agent gets **whatever tools its manifest declares, once the person grants them at install**: its own tools, octos's generic tools (`web_search`, `deep_search` and the rest), toolbox tools, other apps' shareable tools (section 7) and command execution. OctoSense hard-codes no exclusions. The shell registers them (host-routed tools additively on the peer, octos's generic tools through the turn's `generic_tools` list), authorizes and audits every call, and a destructive or outward call still needs the person, live or by a standing rule (section 8). Talk to Octos external clients still get only their fixed allowlist ([ADR 0003](0003-shared-octos-client-access.md)). Toolbox tools follow ADR 0002 §6 (the registration in [#108](https://github.com/OctoSense-org/OctoSense/pull/108)): -| Toolbox | Who gets it | Runs | +| Tool | Who gets it | Runs | | --- | --- | --- | -| web search and page reading (`toolbox.search`, `toolbox.web_read`; deep research as a template through `workflow.run`, not octos's `deep_research`) | apps granted `research` | host | +| web search and page reading (`toolbox.search`, `toolbox.web_read`; deep research as a template through `workflow.run`) | apps granted `research` | host | | crawling (`toolbox.deep_crawl`, with depth and page limits) | apps granted `crawl` | host | | the app's own files | every app agent | octos, fenced to its account folder | | memory | every app agent, its own namespace | octos | | one-shot model calls (`model`) | apps granted `model` | host service | -| **command execution** | **no app agent, and not the system agent** | – | +| 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, each call under section 8; **not the system agent** (below) | host tool (for example `terminal.run`) | **The system agent's tool set is defined and enforced (decided on #110).** "Not the system agent" is not true today: the system agent's turns get octos's full default tool set, shell included. 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, and the cross-app tools granted to it (section 7); no command execution. The list is enforced in octos or in the shell's host configuration, and a test starts a system-agent turn and checks it is offered exactly that list (in particular, no `shell`). This is a plan step (step 4), and it lands before any cross-app grant reaches the system agent. -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. octos already refuses it to app peers (#2567's generic-tool allowlist) and to external clients (#98). An app that needs computation gets a narrow tool instead (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`, granted only to an app that asks for it, each command approved live with the exact command shown, run in a terminal the person sees. +**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. 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. ### 13. Developer mode: everything granted, for building apps fast @@ -299,7 +301,7 @@ How it is turned on and kept from leaking into normal use: - On macOS, Windows and Vulkan Linux a native crash no longer takes the shell down; mobile and non-Vulkan Linux depend on the robustness bar. - 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). -- The person decides every outward action, on a sheet only the shell draws, now or through narrow, time-boxed, audited rules keyed to (owning app, tool). +- 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 defined, tested tool set. - 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. @@ -310,8 +312,8 @@ How it is turned on and kept from leaking into normal use: 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`. 4. **The system agent's tool set** (section 12): define it (supervision tools, granted toolbox tools, granted cross-app tools; no shell), enforce it in octos or in the shell's host configuration, and add 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 (section 5), 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. -7. Shell-drawn approval sheets everywhere (owning app, tool, exact arguments, calling app); the approval router, standing approvals keyed to (owning app, tool) and their Settings; consent at first use. +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. +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. 10. The storage layout and contract (section 11): App Hub's `storage` fields; the host paths and secrets APIs; Rinx and the host services move under `apps/` and `secrets/` (the Rinx move, #101, and any App Hub pin move through the tagged-Rinx rule and the lockstep fix, hagency-org/Rinx#37); the startup check. From 38c021efabab413b539db222802d54e470e9c08d Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:18:00 -0700 Subject: [PATCH 6/8] docs(adr): grantable system-agent commands, Terminal AI types, mail.send confirm: host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three maintainer answers on #110: - The system agent may be granted command execution, like app agents: off unless the person grants it in Settings, each command approved live through the shell. Its tool set is its grants, enforced by the shell and tested (ADR 0002 §12 and amendment; ADR 0004 §12, Consequences, plan 4). - The Terminal's AI may type commands; each goes through the shell with a live approval of the exact command and stays auto_approvable: false (ADR 0004 §10; consistent with makepad#41). - mail.send is confirm: host: the shell draws its sheet, may batch it, and standing rules may answer it (ADR 0002 §12 and Mail row; ADR 0004 §8 and the Calendar/Mail example). Co-Authored-By: Claude Opus 5.5 --- docs/adr/0002-event-driven-app-agents.md | 8 ++++---- docs/adr/0004-native-apps-hosting-and-peers.md | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/adr/0002-event-driven-app-agents.md b/docs/adr/0002-event-driven-app-agents.md index 25be157d..2b9da07c 100644 --- a/docs/adr/0002-event-driven-app-agents.md +++ b/docs/adr/0002-event-driven-app-agents.md @@ -204,12 +204,12 @@ Everything above applies the same way to **contained script apps** (run by App H **Who confirms a destructive tool.** `risk` and `confirm` are separate fields; confirmation is never derived from risk: -- `risk: destructive`, `confirm: host`: the kernel's approval path and the shell's sheet. Person present or absent, the call waits for approval in the owning app's conversation (or batched in the system chat). +- `risk: destructive`, `confirm: host` (for example `mail.send`): the kernel's approval path and the shell's sheet, which may batch it and which a standing rule may answer. Person present or absent, the call waits for approval in the owning app's conversation (or batched in the system chat). - `risk: destructive`, `confirm: app`: the owning app confirms it itself, whoever calls it: its own agent, another app's agent or the system agent. The shell authorizes the call as for any other, then hands the confirmation to the owning app with who is calling (which app or agent, and its context); the app's own confirmation sheet (for example, Rinx's send-message sheet) shows the caller and is the only confirmation, and the kernel does not prompt again. The person is never asked twice. The app's own sheet is the app's UI drawn by its trusted code, never the agent's text. When the owning app is not running, or nobody is present, the call waits or is refused visibly, as octos#2567 specifies. 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). It never gets command execution. 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). 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. **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. @@ -276,7 +276,7 @@ The same loop serves every app; only the data service, `AGENT.md` and skills, th | App | Data service (no model) | App tools (examples) | Agent (on event or schedule) | Card | Needs confirmation | |---|---|---|---|---|---| | **News** | feeds, RSSHub, topic feeds, with a seen-items ledger | `news.list`, `news.read`, `news.topics.set`, `news.digest.write` | clusters new stories, researches the top topics across languages, writes a cited digest, proposes topics | morning/evening digest | none | -| **Mail** | mail sync | `mail.list`, `mail.read`, `mail.draft_reply`, `mail.send` (Destructive) | spots what needs a reply or a decision, drafts replies, extracts dates and tasks | "3 need you today" with drafts | sending a reply | +| **Mail** | mail sync | `mail.list`, `mail.read`, `mail.draft_reply`, `mail.send` (Destructive, `confirm: host`) | spots what needs a reply or a decision, drafts replies, extracts dates and tasks | "3 need you today" with drafts | sending a reply | | **Calendar / travel** | calendar sync; flight or transit status | `calendar.list`, `calendar.free_busy` (shareable), `calendar.move` (Act, confirm) | sees a delay or conflict, works out consequences | "Your 9:00 is at risk: flight +40 min" with options | changing or declining an event | | **Weather** | forecast and warnings feed | `weather.forecast`, `weather.alerts` | relates warnings to the person's plans and places | "Storm at 17:00 near your commute" | none | | **Health** | the device's health store | `health.summary`, `health.trend` (Read, never shareable) | notices a trend against the person's baseline | weekly summary; a gentle flag | sharing with anyone | @@ -363,5 +363,5 @@ ADR 0004 supplies the hosting and transport this ADR assumes and changes it in t - **§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. 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 defined and enforced, in octos or host config, 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). - **§13 (autonomy).** Adds the `auto_approvable` flag and the incoming-content rule. diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md index 4dc36c96..cb3a1f4f 100644 --- a/docs/adr/0004-native-apps-hosting-and-peers.md +++ b/docs/adr/0004-native-apps-hosting-and-peers.md @@ -172,7 +172,7 @@ The shell stamps the app's identity from the socket, enforces #2567's host oblig Only the person approves. The system agent never does, and cannot be talked into it: it reads untrusted text, and rules are checked mechanically. - **The shell is the single path and the authority for every tool call (decided on #110).** Every app tool call, whoever makes it (the owning app's own agent, another app's agent or the system agent), goes kernel `peer/tool/call` → the shell's host connection → the owning app's executor; an app's agent has no direct way to call its own tools. So the shell sees, authorizes (grants, budgets), audits and routes every call. -- **Who draws the sheet.** For a `confirm: host` tool the shell draws it, in the app's conversation or batched in the system chat. For a `confirm: app` tool the shell hands the confirmation to the owning app, which shows its **own sheet** (for Rinx, its send sheet) for callers of every kind; the call carries who is calling (`caller`, with its context; section 5), so the app's sheet shows it. When the owning app is not running, or nobody is present, the call waits or is refused visibly, as octos#2567 specifies. **An agent's own text is never an approval surface.** Each line, on either sheet, shows the **owning app, the tool and the exact arguments**, and for a cross-app call the **calling app** (another app's agent, or the system agent). +- **Who draws the sheet.** For a `confirm: host` tool (for example `mail.send`) the shell draws it, in the app's conversation or batched in the system chat, and a standing rule may answer it. For a `confirm: app` tool the shell hands the confirmation to the owning app, which shows its **own sheet** (for Rinx, its send sheet) for callers of every kind; the call carries who is calling (`caller`, with its context; section 5), so the app's sheet shows it. When the owning app is not running, or nobody is present, the call waits or is refused visibly, as octos#2567 specifies. **An agent's own text is never an approval surface.** Each line, on either sheet, shows the **owning app, the tool and the exact arguments**, and for a cross-app call the **calling app** (another app's agent, or the system agent). - **Live.** Pending approvals surface on a shell-drawn sheet in the app's conversation (on the owning app's own sheet for a `confirm: app` tool), on its card and as a notification; the system agent may batch the `confirm: host` approvals of one request into one shell-drawn sheet in its chat (a plan plus its outward actions, with exact arguments). The shell answers each approval with the person's decision. - **In advance: standing approvals.** The person sets rules; the shell's **approval router** evaluates each approval request against them on the exact arguments and answers it, or surfaces it: - scope: keyed to **(owning app, tool)**, whoever calls it, so a cross-app call is covered by the owning app's rule and the sheet or notification shows the calling app; with conditions (recipients in contacts or in the thread, no attachments, triggered by the person, amount or count limits) and a daily cap; @@ -191,7 +191,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 tools stay read-only in every hosting (`read_screen`, `read_scrollback`); the standalone app's `run` is not offered when hosted. +- 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). - 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,9 +255,9 @@ 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, each call under section 8; **not the system agent** (below) | 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`) | -**The system agent's tool set is defined and enforced (decided on #110).** "Not the system agent" is not true today: the system agent's turns get octos's full default tool set, shell included. 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, and the cross-app tools granted to it (section 7); no command execution. The list is enforced in octos or in the shell's host configuration, and a test starts a system-agent turn and checks it is offered exactly that list (in particular, no `shell`). 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. 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. 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. @@ -292,7 +292,7 @@ How it is turned on and kept from leaking into normal use: 1. The person asks the system agent (chat or voice). It asks Calendar's agent who "the group" is and when they are free; Calendar resolves Edward (from contacts, or Mail's shareable `mail.lookup_contact`). 2. The system agent proposes "Tue 3–4 pm, Ana, Bo, Chen and Edward (edward@…); book and send invitations?" 3. On yes, it delegates to Calendar's agent, which calls `calendar.create_event` and Mail's `mail.send` four times (routed by the shell). -4. The four sends are outward: one batched sheet, drawn by the shell in the chat, shows each invitation's owning app and tool (Mail, `mail.send`), the calling app (Calendar) and the exact arguments, unless a standing rule on (Mail, `mail.send`) ("send to my contacts") answers them. The shell had already checked that Calendar's manifest was granted `mail.send`; no agent is asked to consent. +4. The four sends are outward, and `mail.send` is `confirm: host`: one batched sheet, drawn by the shell in the chat, shows each invitation's owning app and tool (Mail, `mail.send`), the calling app (Calendar) and the exact arguments, unless a standing rule on (Mail, `mail.send`) ("send to my contacts") answers them. The shell had already checked that Calendar's manifest was granted `mail.send`; no agent is asked to consent. 5. Calendar's UI shows the event; the system app notifies "Meeting booked Tue 3 pm; invitations sent to 4." A failed invitation is named and not retried without the person. ## Consequences @@ -302,7 +302,7 @@ 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 defined, tested tool set. +- 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. - 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 @@ -310,7 +310,7 @@ How it is turned on and kept from leaking into normal use: 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`. -4. **The system agent's tool set** (section 12): define it (supervision tools, granted toolbox tools, granted cross-app tools; no shell), enforce it in octos or in the shell's host configuration, and add a test that a system-agent turn is offered exactly that list. Before any cross-app grant reaches the system agent. +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. 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. From 99569a1bd26b674ff562913a700d7837c527fce9 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:21:04 -0700 Subject: [PATCH 7/8] docs(adr): developer mode overrides every approval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer decision on #110: developer mode overrides all approvals, auto_approvable: false included (Terminal commands, granted command execution, deletes, payments); there is no live sheet in developer mode. - ADR 0004 §13 says so explicitly and restates the guardrails that stay: banner, audit of every call and command, never for external clients, development-build or explicit-flag gate, developer profile. - ADR 0004 §8, §10 and §12 and ADR 0002 §10, §12 and the §10 amendment qualify live approval and auto_approvable: false with "outside developer mode" and a reference to ADR 0004 §13. Co-Authored-By: Claude Opus 5.5 --- docs/adr/0002-event-driven-app-agents.md | 8 ++++---- docs/adr/0004-native-apps-hosting-and-peers.md | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/adr/0002-event-driven-app-agents.md b/docs/adr/0002-event-driven-app-agents.md index 2b9da07c..cf2bed93 100644 --- a/docs/adr/0002-event-driven-app-agents.md +++ b/docs/adr/0002-event-driven-app-agents.md @@ -171,7 +171,7 @@ Every app with an agent gets a **built-in conversation with its own agent**, dra **The shell is the single path for every tool call; it draws every approval except `confirm: app`** *(amended 2026-09-28, [ADR 0004](0004-native-apps-hosting-and-peers.md) §8)*. Every app tool call, whoever makes it (the owning app's own agent, another app's agent or the system agent), goes from the kernel's `peer/tool/call` to the shell's host connection and on to the owning app's executor; an app's agent has no direct way to call its own tools. So the shell sees, authorizes (grants, budgets), audits and routes every call. For a `confirm: host` tool the shell draws the approval sheet, in the app's conversation or batched in the system chat. For a `confirm: app` tool the shell hands the confirmation to the owning app, which shows its **own sheet** (for Rinx, its send sheet) for callers of every kind; the request says who is calling (which app or agent, and its context), so the app's sheet can show it (section 12). **An agent's own text is never an approval surface**: a "yes" typed to an agent that asked in prose approves nothing. Each line of a sheet, the shell's or the app's, shows the **owning app, the tool and the exact arguments**; for a cross-app call it also shows the **calling app** (or the system agent). -**Where requests surface.** A pending approval shows on the app's glance card ("Needs you: approve reply to Alice") and as a notification; tapping either opens the app at that thread. An outward or destructive tool runs only on the person's decision: a **live approval** on a shell-drawn sheet (the owning app's own sheet for a `confirm: app` tool), or a **standing rule** the person made in advance, keyed to (owning app, tool) and evaluated by the shell on the exact arguments (ADR 0004 §8). A card or notification never approves on its own. The system agent may batch one request's pending `confirm: host` approvals into one shell-drawn sheet in its chat and order them across apps, but it never approves on the person's behalf. +**Where requests surface.** A pending approval shows on the app's glance card ("Needs you: approve reply to Alice") and as a notification; tapping either opens the app at that thread. An outward or destructive tool runs only on the person's decision: a **live approval** on a shell-drawn sheet (the owning app's own sheet for a `confirm: app` tool), or a **standing rule** the person made in advance, keyed to (owning app, tool) and evaluated by the shell on the exact arguments (ADR 0004 §8). A card or notification never approves on its own. All of this holds outside developer mode, which overrides every approval, `auto_approvable: false` included (ADR 0004 §13). The system agent may batch one request's pending `confirm: host` approvals into one shell-drawn sheet in its chat and order them across apps, but it never approves on the person's behalf. **Person present versus absent.** With the person in the conversation, the agent may propose an outward action and run it after the person's explicit confirmation on the shell's sheet there, or on the owning app's own sheet for a `confirm: app` tool. Without the person, it only queues the request; a `confirm: app` call waits or is refused visibly, as octos#2567 specifies. The approval record (request, arguments, decision, time) goes into the run's audit log. @@ -207,9 +207,9 @@ Everything above applies the same way to **contained script apps** (run by App H - `risk: destructive`, `confirm: host` (for example `mail.send`): the kernel's approval path and the shell's sheet, which may batch it and which a standing rule may answer. Person present or absent, the call waits for approval in the owning app's conversation (or batched in the system chat). - `risk: destructive`, `confirm: app`: the owning app confirms it itself, whoever calls it: its own agent, another app's agent or the system agent. The shell authorizes the call as for any other, then hands the confirmation to the owning app with who is calling (which app or agent, and its context); the app's own confirmation sheet (for example, Rinx's send-message sheet) shows the caller and is the only confirmation, and the kernel does not prompt again. The person is never asked twice. The app's own sheet is the app's UI drawn by its trusted code, never the agent's text. When the owning app is not running, or nobody is present, the call waits or is refused visibly, as octos#2567 specifies. -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). +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). 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. **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. @@ -362,6 +362,6 @@ ADR 0004 supplies the hosting and transport this ADR assumes and changes it in t - **§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. ADR 0004 §8. +- **§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). - **§13 (autonomy).** Adds the `auto_approvable` flag and the incoming-content rule. diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md index cb3a1f4f..dc66c3af 100644 --- a/docs/adr/0004-native-apps-hosting-and-peers.md +++ b/docs/adr/0004-native-apps-hosting-and-peers.md @@ -169,7 +169,7 @@ The shell stamps the app's identity from the socket, enforces #2567's host oblig ### 8. Approvals: by the person, live or in advance -Only the person approves. The system agent never does, and cannot be talked into it: it reads untrusted text, and rules are checked mechanically. +Only the person approves. The system agent never does, and cannot be talked into it: it reads untrusted text, and rules are checked mechanically. Everything in this section holds **outside developer mode**; developer mode overrides every approval, `auto_approvable: false` included (section 13). - **The shell is the single path and the authority for every tool call (decided on #110).** Every app tool call, whoever makes it (the owning app's own agent, another app's agent or the system agent), goes kernel `peer/tool/call` → the shell's host connection → the owning app's executor; an app's agent has no direct way to call its own tools. So the shell sees, authorizes (grants, budgets), audits and routes every call. - **Who draws the sheet.** For a `confirm: host` tool (for example `mail.send`) the shell draws it, in the app's conversation or batched in the system chat, and a standing rule may answer it. For a `confirm: app` tool the shell hands the confirmation to the owning app, which shows its **own sheet** (for Rinx, its send sheet) for callers of every kind; the call carries who is calling (`caller`, with its context; section 5), so the app's sheet shows it. When the owning app is not running, or nobody is present, the call waits or is refused visibly, as octos#2567 specifies. **An agent's own text is never an approval surface.** Each line, on either sheet, shows the **owning app, the tool and the exact arguments**, and for a cross-app call the **calling app** (another app's agent, or the system agent). @@ -191,7 +191,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). +- 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). - 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. @@ -259,14 +259,15 @@ An app agent gets **whatever tools its manifest declares, once the person grants **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. -**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. 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. 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. ### 13. Developer mode: everything granted, for building apps fast Developers need to try an agent with every capability without granting, consenting and approving each step. **Developer mode** does that in one switch: - **Grants**, for all apps or chosen ones: every capability an app could declare, every app's shareable tools to every other app, and a host tool **`dev.run {command, cwd?, timeout_ms?}`** that the shell executes (in the agent's workspace by default, with an output cap, every command logged). octos needs no change: `dev.run` is a host-registered tool like any app tool, and it is withdrawn the moment developer mode ends. -- **Approvals**: every action is approved automatically, including destructive, outward and `auto_approvable: false` tools; the cap of section 8 does not apply. +- **Approvals: none (decided on #110).** Developer mode **overrides every approval**: the shell answers each one itself, with no live sheet, no standing rule and no app's own `confirm: app` sheet, including destructive, outward and `auto_approvable: false` tools (Terminal commands, granted command execution, `dev.run`, deletes, payments); the cap of section 8 does not apply. The live approvals and `auto_approvable: false` rules of sections 8, 10 and 12 hold only outside developer mode. +- **What stays in place**: the developer banner, the audit of every call and command, the exclusion of Talk to Octos external clients, the development-build or explicit-flag gate, and the developer profile, each below. - **Consent**: no first-use prompts. How it is turned on and kept from leaking into normal use: From b3721b6700b4a847f2c175a21a74730d2d5d8678 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:27:44 -0700 Subject: [PATCH 8/8] docs(adr): accept ADR 0004 Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/adr/0004-native-apps-hosting-and-peers.md | 2 +- docs/adr/README.md | 2 +- docs/adr/README.zh-CN.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md index dc66c3af..31f4c882 100644 --- a/docs/adr/0004-native-apps-hosting-and-peers.md +++ b/docs/adr/0004-native-apps-hosting-and-peers.md @@ -1,7 +1,7 @@ # 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:** Proposed +- **Status:** Accepted (2026-09-28) - **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). diff --git a/docs/adr/README.md b/docs/adr/README.md index c5235372..7ba2864f 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -9,7 +9,7 @@ Decisions for the OctoSense repository: the shell, its services, the system apps | [0001](0001-one-octosense-repository.md) | One OctoSense repository for the shell, its services, the system apps and both packagings | Accepted | | [0002](0002-event-driven-app-agents.md) | Event-driven app agents: apps think on their own triggers and publish cards to the glance screen | Proposed | | [0003](0003-shared-octos-client-access.md) | Talk to Octos: one kernel for native and external clients (opt-in) | Implemented; Android unverified | -| [0004](0004-native-apps-hosting-and-peers.md) | Native apps, app agents and cross-app work: one manifest, hosting per target, an agent for every app, approvals by the person | Proposed | +| [0004](0004-native-apps-hosting-and-peers.md) | Native apps, app agents and cross-app work: one manifest, hosting per target, an agent for every app, approvals by the person | Accepted | ## Home (phone shell) decisions, 2026-09-16 to 2026-09-25 diff --git a/docs/adr/README.zh-CN.md b/docs/adr/README.zh-CN.md index f41b4cda..4d4b03f2 100644 --- a/docs/adr/README.zh-CN.md +++ b/docs/adr/README.zh-CN.md @@ -9,7 +9,7 @@ OctoSense 仓库的决策:Shell、Shell 服务、系统应用,以及桌面 | [0001](0001-one-octosense-repository.md)(英文) | 用一个 OctoSense 仓库承载 Shell、Shell 服务、系统应用和两种打包形态 | 已接受 | | [0002](0002-event-driven-app-agents.md)(英文) | 事件驱动的应用智能体:应用按自己的触发条件思考,并把卡片发布到一览屏 | 提议中 | | [0003](0003-shared-octos-client-access.md)(英文) | Talk to Octos:原生与外部客户端共用一个内核(需手动开启) | 已实现;Android 未验证 | -| [0004](0004-native-apps-hosting-and-peers.md)(英文) | 原生应用、应用智能体与跨应用协作:一份清单、按目标平台托管、每个应用都有智能体、由本人批准 | 提议中 | +| [0004](0004-native-apps-hosting-and-peers.md)(英文) | 原生应用、应用智能体与跨应用协作:一份清单、按目标平台托管、每个应用都有智能体、由本人批准 | 已接受 | ## Home(手机 Shell)的决策,2026-09-16 至 2026-09-25