From f19f9c23f59f9fbb2e02b398193de9d86a45aae6 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:19:11 -0700 Subject: [PATCH 1/3] docs: bring docs/ up to date with the code Five reviewers audited every file under docs/ against the code at 89e6c42f. Each finding was confirmed in the code, git log or gh before it was applied. A final review of this diff found 11 more problems, which are fixed. - Native agents (#309, #329, #330): ai-services, architecture and the walkthrough now describe: - the five Makepad apps' agents, through OctosPeer and serve_tools with no workspace, and their system_tools; - the Terminal's own_tools; - native peers living with the open window. They no longer say that Rinx is the only native agent or that nothing uses the peer link. - terminal.run exists only while the Terminal runs as its own sandboxed process. Release packages ship no process-app binary, so there the Terminal runs in-process and Task is absent. Task is named as a process app. - Admitted AGENT.md and skill text go with every turn. Mail has eight tools and an incoming-mail trigger. agents.provision and agents.status accept only os.mail. - Smaller fixes: keys on Windows, the window-manager override path, phone glance cards, unsandboxed catalog apps, octos_peer frame names, TurnFrom (Person), the default build's octos-core, the packaged kernel, the octos child's runtime, and local-ci's merge scope, clone lock and rom.yml paths. - ADRs get dated notes: 0002 is partly implemented; 0004 has three plan items open and notes on Task, the Terminal's agent, host tools and #238; 0005 has notes on app-contract 1.1.0; Home 0001 and 0002 get notes. The ADR index statuses are updated, and the Photos and icons plans are marked done. - Chinese parity: mail-agent-events regains its sequence diagram. ai-services, architecture, the walkthrough and the mail-events report regain their missing rows and details. - written outside code is escaped (GitHub stripped it). ai-services-processes.png and ai-services-cross-app-call.png show the shipped peer link and peer/input as solid, and mark the mail.send hop as planned. Co-Authored-By: Claude Opus 5.5 --- README.md | 14 ++-- README.zh-CN.md | 14 ++-- docs/adr/0002-event-driven-app-agents.md | 4 +- .../adr/0004-native-apps-hosting-and-peers.md | 39 +++++----- docs/adr/0005-app-contract.md | 6 +- docs/adr/README.md | 6 +- docs/adr/README.zh-CN.md | 8 +-- ...brid-android-launcher-and-system-bridge.md | 1 + .../home/0002-agentic-app-security-model.md | 2 +- docs/ai-services.md | 36 +++++----- docs/ai-services.zh-CN.md | 48 +++++++------ docs/architecture-walkthrough.md | 62 +++++++--------- docs/architecture-walkthrough.zh-CN.md | 40 ++++++----- docs/architecture.md | 64 ++++++++--------- docs/architecture.zh-CN.md | 66 +++++++++-------- docs/images/ai-services-cross-app-call.png | Bin 461783 -> 461298 bytes docs/images/ai-services-processes.png | Bin 468166 -> 466700 bytes docs/local-ci.md | 12 ++-- docs/mail-agent-events.md | 18 ++--- docs/mail-agent-events.zh-CN.md | 67 ++++++++++++------ docs/plans/2026-09-30-photos-memories.md | 2 + docs/plans/2026-10-03-platform-app-icons.md | 2 + docs/testing/mail-card-actions-2026-10-04.md | 2 + .../mail-card-actions-2026-10-04.zh-CN.md | 1 + docs/testing/mail-events-2026-10-04.zh-CN.md | 14 ++-- 25 files changed, 274 insertions(+), 254 deletions(-) diff --git a/README.md b/README.md index ff4d6cc6..e8fd2754 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ flowchart LR - **The shell** hosts the window manager, the native apps, the Card runner with the script apps, the system chat, the approval router and the host-tool relay. Its AI side is [`crates/ai-host`](crates/ai-host/README.md), with the [app-peers broker](crates/app-peers/README.md) that drives each app agent. - **The octos kernel** ([`crates/kernel`](crates/kernel/README.md)) starts on its first connection and exits with the shell. On the desktop and Android it is a child process that speaks OUP over stdio; on OpenHarmony it runs inside the shell; iOS has none. The person picks models and enters keys in the **AI providers** app, on host sheets. Keys stay with the shell (in the macOS keychain, or in an owner-only file elsewhere) and never reach an app. -- **Process apps** run outside the shell in an OS sandbox (Seatbelt on macOS, Landlock and seccomp on Linux, none yet on Windows). Today that is only the Terminal on the desktop. It sends its frames over the shell's local hub and reaches its agent over the peer link on the same connection. +- **Process apps** run outside the shell in an OS sandbox (Seatbelt on macOS, Landlock and seccomp on Linux, none yet on Windows). Today those are the Terminal and Task (Task Manager, which has no agent), on a desktop built from a checkout; release packages don't ship their binaries yet, so there the Terminal runs inside the shell and Task is absent. The Terminal sends its frames over the shell's local hub and reaches its agent over the peer link on the same connection. - **External clients** (Talk to Octos, opt-in) can use the system conversation from a browser or a terminal with a limited token. They get no app agent, no `peer/*` method and no host tool. The opt-in AppCard prototype is the one exception to all this: it opens its own kernel connection instead of going through the broker. For the full picture with code paths, read [docs/architecture.md](docs/architecture.md); for the trust model and how to test the AI services locally, [docs/ai-services.md](docs/ai-services.md); for the decisions behind it, [ADR 0004](docs/adr/0004-native-apps-hosting-and-peers.md). @@ -91,13 +91,13 @@ The opt-in AppCard prototype is the one exception to all this: it opens its own **The system agent** is the octos session `_main:api:octosense#system`. The person talks to it in the system chat: F8 or the dock's Assistant icon on the desktop, the Assistant tile on the phone. It has two sets of tools: - **Its own kernel tools**, the fixed list in [`SYSTEM_AGENT_TOOLS`](crates/kernel/src/system_tools.rs): the `peer_*` tools to supervise app agents, plus files in its own workspace, memory, questions to the person and web search. octos's own shell tools are never on it. -- **Host tools from the shell**: `agents.list` and `agents.ask` to find app agents and ask the person to allow one, `agents.provision` and `agents.status` to run Mail's new-mail automation, `terminal.run` while Setup's Command execution switch is on, and the read tools native apps share with it (the table's last column). +- **Host tools from the shell**: `agents.list` and `agents.ask` to find app agents and ask the person to allow one, `agents.provision` and `agents.status` to run Mail's new-mail automation, `terminal.run` while Setup's Command execution switch is on and the Terminal runs as its own sandboxed process, and the read tools native apps share with it (the table's last column). The system agent can never approve a tool call; only the person can. **An app agent** exists once the person allows it, on a sheet shown once per app. When it runs depends on the kind of app: -- A **script app's** agent is prepared at shell startup with the app's tools registered, so the system agent's `peer_list` sees it even while the app is closed. +- A **script app's** agent is prepared as soon as it is allowed, and again at each startup, with the app's tools registered, so the system agent's `peer_list` sees it even while the app is closed. - A **native app's** agent belongs to the app's open window and is live only while the app is open; its memory and transcript persist between openings. Its tools run in that window too: with Notes closed, a call answers "Open Notes first". Signing out keeps an agent. Removing the account or uninstalling the app erases its transcripts and memory. @@ -107,7 +107,7 @@ These apps have an agent: | App | Kind | Its agent's own tools | What the system agent may call | | --- | --- | --- | --- | | Rinx (Matrix chat) | native, in the shell | octos's file, memory and web tools | – | -| Terminal (desktop) | native, its own process | `terminal.read_screen`, `terminal.read_scrollback` | `terminal.run`, behind Setup's switch, approved per command | +| Terminal (desktop) | native; its own process in a checkout build, inside the shell in a release package | `terminal.read_screen`, `terminal.read_scrollback` | `terminal.run`, only while it runs as its own sandboxed process, behind Setup's switch, approved per command | | Calculator, Clock, Notes, Reminders, Weather | native, in the shell | each app's read tools | the same read tools | | Mail | script app | `mail.*` tools scoped to the signed-in account, including `mail.publish_card` | – | | Calendar | script app | `calendar.events`, `calendar.add_event`, `calendar.remove_event` (asks first), `calendar.notify`, `calendar.agenda` | – | @@ -251,13 +251,13 @@ In a shell with a kernel, the first call asks the person to allow the app's agen An agent can only work with what its app hands it. A script app declares all of this in its bundle; a native app declares it in its `native-apps.json` entry. -- **A declaration.** The manifest's `agent` block names the kernel tools the agent may use (the system apps ask only for `ask_user_question`), the model features it needs (`tool_calling`) and, optionally, an `AGENT.md` with instructions. A native app's entry also says which of its tools its own agent may call (`own_tools`) and which the system agent may call (`system_tools`). +- **A declaration.** The manifest's `agent` block names the kernel tools the agent may use (the system apps ask only for `ask_user_question`), the model features it needs (`tool_calling`) and, optionally, an `AGENT.md` with instructions and skills, which the shell sends with every turn. A native app's entry also says which of its tools its own agent may call (`own_tools`) and which the system agent may call (`system_tools`). - **Tools.** `tools.json` describes each tool, named `.`: its input schema, its `risk` (`read`, `act` or `destructive`), who confirms it (`confirm: host` for a shell sheet, `app` for the app's own sheet) and whether other apps' agents may use it (`shareable`). - **Something to run the tools.** A declared tool needs an executor: the app's host service (Mail, Calendar, News), the shell's notice service (`.notify` for the other system apps) or a native app's open window. Store apps have no host service, and a tool marked `implemented_by: "app"` has no executor in the Card runner yet. So today a store app's agent can talk, ask questions and read its folder, but cannot act through tools of its own. - **Data.** By default the agent works in its account's folder, `apps//accounts//` (a single `device` folder for an app without accounts), and reads it with the host's read-only `files.list`, `files.read` and `files.search` (on Unix). An app can declare `storage.agent_workspace: "none"` instead; Calculator, Clock, Notes, Reminders, Weather and the Terminal do, so their agents see only what their tools return. No agent sees another account's folder. - **Memory.** Each agent has its own memory namespace, `app//acct-`, erased with the account. - **A way to reach the person.** With the `glance` permission, its tools can publish cards. -- **Events** (only Mail, for now). A `triggers.events` entry, a skill and `AGENT.md` let Mail's agent react to new mail without being asked; the shell sends the admitted instructions and skill text with each of those turns. +- **Events** (only Mail, for now). A `triggers.events` entry lets Mail's agent react to new mail without being asked, guided by its `AGENT.md` and a triage skill. The steps for adding a tool (manifest, `tools.json`, grant, handler, approval path) are in [AGENTS.md](AGENTS.md#architecture-documentation-and-code-walkthroughs), and the design is [ADR 0002](docs/adr/0002-event-driven-app-agents.md). @@ -328,7 +328,7 @@ A phone runs the shell, the kernel, up to a dozen app agents and their apps at t - **One connection to the kernel.** The system chat and every app agent's broker share a single stream to the kernel. A small router ([`crates/kernel/src/router.rs`](crates/kernel/src/router.rs)) gives each request a unique id and sends each notification to the consumers of its session. - **Threads per service, not per turn.** Turns are Tokio tasks. In the shell, the kernel service runs on one two-worker Tokio runtime, each live agent's broker on a one-thread runtime of its own (so a slow agent cannot stall another agent or the UI), and the system chat on one thread that wakes the UI through Makepad's `SignalToUI` only when something changed. The octos child process (desktop and Android) uses Tokio's default runtime, one worker per CPU core. - **Started on demand, stopped when idle.** The kernel starts on its first connection: at startup if the person has already allowed a script app's agent, otherwise when an agent or the system chat first needs it. The system chat connects only while its pane is open or a turn is running. With Talk to Octos off, the kernel stops when its last connection closes. An app with no granted agent gets no broker and no peer. -- **Apps share the shell process.** Script apps are isolates in one Card runner and native apps are modules, so most apps cost no process of their own. A script error stays inside its isolate, and a native module's panic is caught at the module boundary. Only an app that needs an OS sandbox runs as a process. +- **Apps share the shell process.** Script apps are isolates in one Card runner and native apps are modules, so most apps cost no process of their own. A script error stays inside its isolate, and a native module's panic is caught at the module boundary. Only an app declared as its own process (the Terminal, for its OS sandbox, and Task, which has no module) runs as one, where its binary is available. - **Zero-copy frames for process apps.** The Terminal's frames reach the shell as shared GPU surfaces: IOSurface on macOS, D3D11 shared handles on Windows, DMA_BUF on Linux with Vulkan. Where only a CPU copy would work (Linux with OpenGL), every app runs inside the shell instead. ## Layout diff --git a/README.zh-CN.md b/README.zh-CN.md index f65cd1f1..6a5ac130 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -81,7 +81,7 @@ flowchart LR - **Shell** 承载窗口管理器、原生应用、运行脚本应用的 Card runner、系统对话、审批路由和宿主工具中转。它的 AI 部分是 [`crates/ai-host`](crates/ai-host/README.md),其中的 [app-peers 代理](crates/app-peers/README.md)负责驱动每个应用 Agent。 - **octos 内核**([`crates/kernel`](crates/kernel/README.zh-CN.md))在第一个连接到来时启动,随 Shell 一起退出。桌面端和 Android 上,它是通过 stdio 讲 OUP 的子进程;OpenHarmony 上,它在 Shell 进程内运行;iOS 上没有内核。用户在 **AI providers** 应用中、在宿主面板上选择模型并输入密钥。密钥留在 Shell 一侧(macOS 上存入钥匙串,其他平台存入仅所有者可读的文件),永远不会到达应用。 -- **进程应用**在 Shell 之外运行,处在系统沙箱中(macOS 上是 Seatbelt,Linux 上是 Landlock 和 seccomp,Windows 上尚未实现)。目前只有桌面端的 Terminal 是进程应用。它通过 Shell 的本地 hub 发送画面,并通过同一连接上的 peer link 使用自己的 Agent。 +- **进程应用**在 Shell 之外运行,处在系统沙箱中(macOS 上是 Seatbelt,Linux 上是 Landlock 和 seccomp,Windows 上尚未实现)。目前的进程应用是 Terminal 和 Task(Task Manager,没有 Agent),而且只在从源码检出构建的桌面端上;发布包还不附带它们的二进制,所以发布包里 Terminal 运行在 Shell 内,也没有 Task。Terminal 通过 Shell 的本地 hub 发送画面,并通过同一连接上的 peer link 使用自己的 Agent。 - **外部客户端**(Talk to Octos,需手动开启)可以凭受限的 token 从浏览器或终端使用系统对话,但拿不到任何应用 Agent、`peer/*` 方法或宿主工具。 可选的 AppCard 原型是唯一的例外:它不经过代理,而是自己打开内核连接。想看带代码路径的完整说明,请读 [docs/architecture.zh-CN.md](docs/architecture.zh-CN.md);信任模型以及如何在本地测试 AI 服务,见 [docs/ai-services.zh-CN.md](docs/ai-services.zh-CN.md);背后的决策见 [ADR 0004(英文)](docs/adr/0004-native-apps-hosting-and-peers.md)。 @@ -91,13 +91,13 @@ flowchart LR **系统 Agent** 是 octos 会话 `_main:api:octosense#system`。用户在系统对话中与它交谈:桌面端按 F8 或点 Dock 上的 Assistant 图标,手机上点 Assistant 磁贴。它有两组工具: - **它自己的内核工具**,即 [`SYSTEM_AGENT_TOOLS`](crates/kernel/src/system_tools.rs) 中固定的列表:用于监督应用 Agent 的 `peer_*` 工具,以及自己工作区里的文件、记忆、向用户提问和网页搜索。octos 自带的 shell 工具永远不在其中。 -- **Shell 为它注册的宿主工具**:`agents.list` 和 `agents.ask` 用来查找应用 Agent、请用户允许某个 Agent;`agents.provision` 和 `agents.status` 用来运行邮件的新邮件自动处理;Setup 中的 Command execution 开关打开时还有 `terminal.run`;以及原生应用共享给它的只读工具(见下表最后一列)。 +- **Shell 为它注册的宿主工具**:`agents.list` 和 `agents.ask` 用来查找应用 Agent、请用户允许某个 Agent;`agents.provision` 和 `agents.status` 用来运行邮件的新邮件自动处理;Setup 中的 Command execution 开关打开、且 Terminal 作为独立的沙箱进程运行时,还有 `terminal.run`;以及原生应用共享给它的只读工具(见下表最后一列)。 系统 Agent 永远不能批准工具调用,只有用户可以。 **应用 Agent** 要等用户允许后才会存在,每个应用只问一次。它何时运行取决于应用的类型: -- **脚本应用**的 Agent 在 Shell 启动时就准备好,并注册了应用的工具,所以即使应用没有打开,系统 Agent 的 `peer_list` 也能看到它。 +- **脚本应用**的 Agent 在获准时立即准备好,此后每次启动时也会准备,并注册应用的工具,所以即使应用没有打开,系统 Agent 的 `peer_list` 也能看到它。 - **原生应用**的 Agent 属于应用已打开的窗口,只在应用打开期间运行;它的记忆和对话记录在两次打开之间会保留。它的工具也在那个窗口里运行:Notes 没打开时,调用会回答 “Open Notes first”。 退出登录会保留 Agent;删除账号或卸载应用会清除它的对话记录和记忆。 @@ -107,7 +107,7 @@ flowchart LR | 应用 | 类型 | 它的 Agent 自己的工具 | 系统 Agent 可以调用 | | --- | --- | --- | --- | | Rinx(Matrix 聊天) | 原生,在 Shell 内 | octos 的文件、记忆和网页工具 | – | -| Terminal(桌面端) | 原生,独立进程 | `terminal.read_screen`、`terminal.read_scrollback` | `terminal.run`,受 Setup 开关控制,每条命令都要批准 | +| Terminal(桌面端) | 原生;源码检出构建中为独立进程,发布包中在 Shell 内 | `terminal.read_screen`、`terminal.read_scrollback` | `terminal.run`,只在它作为独立的沙箱进程运行时提供,受 Setup 开关控制,每条命令都要批准 | | Calculator、Clock、Notes、Reminders、Weather | 原生,在 Shell 内 | 各自的只读工具 | 同样的只读工具 | | 邮件 | 脚本应用 | 绑定当前登录账号的 `mail.*` 工具,包括 `mail.publish_card` | – | | 日历 | 脚本应用 | `calendar.events`、`calendar.add_event`、`calendar.remove_event`(先问用户)、`calendar.notify`、`calendar.agenda` | – | @@ -251,13 +251,13 @@ fn ask(){ Agent 能做什么,取决于应用交给它什么。脚本应用把这些都声明在应用包里;原生应用则声明在 `native-apps.json` 的条目里。 -- **声明。** manifest 的 `agent` 块列出 Agent 可用的内核工具(系统应用只申请了 `ask_user_question`)、需要的模型能力(`tool_calling`),以及可选的、写有指令的 `AGENT.md`。原生应用的条目还会说明它自己的 Agent 可以调用它的哪些工具(`own_tools`),系统 Agent 又可以调用哪些(`system_tools`)。 +- **声明。** manifest 的 `agent` 块列出 Agent 可用的内核工具(系统应用只申请了 `ask_user_question`)、需要的模型能力(`tool_calling`),以及可选的、写有指令的 `AGENT.md` 和技能,Shell 会随每一轮发送它们。原生应用的条目还会说明它自己的 Agent 可以调用它的哪些工具(`own_tools`),系统 Agent 又可以调用哪些(`system_tools`)。 - **工具。** `tools.json` 描述每个工具(命名为 `.`):输入 schema、`risk`(`read`、`act` 或 `destructive`)、由谁确认(`confirm: host` 用 Shell 面板,`app` 用应用自己的面板),以及其他应用的 Agent 能否使用(`shareable`)。 - **执行工具的地方。** 声明了的工具还需要执行者:应用的宿主服务(邮件、日历、新闻)、Shell 的通知服务(其他系统应用的 `.notify`),或原生应用已打开的窗口。商店应用没有宿主服务,而标为 `implemented_by: "app"` 的工具在 Card runner 中还没有执行器。所以目前商店应用的 Agent 能对话、能提问、能读取自己的文件夹,但还不能通过自己的工具做事。 - **数据。** 默认情况下,Agent 在它所属账号的文件夹 `apps//accounts//` 中工作(不区分账号的应用只有一个 `device` 文件夹),并用宿主的只读工具 `files.list`、`files.read` 和 `files.search`(Unix 上)读取它。应用也可以声明 `storage.agent_workspace: "none"`;Calculator、Clock、Notes、Reminders、Weather 和 Terminal 就是这样,它们的 Agent 只能看到自己的工具返回的内容。任何 Agent 都看不到别的账号的文件夹。 - **记忆。** 每个 Agent 有自己的记忆命名空间 `app//acct-`,随账号一起清除。 - **联系用户的方式。** 有了 `glance` 权限,它的工具就能发布卡片。 -- **事件**(目前只有邮件)。一个 `triggers.events` 条目、一项技能和 `AGENT.md`,让邮件的 Agent 不等人开口就能处理新邮件;在这些回合里,Shell 会附上已接纳的指令和技能文本。 +- **事件**(目前只有邮件)。一个 `triggers.events` 条目让邮件的 Agent 不等人开口就能处理新邮件,并由它的 `AGENT.md` 和一项分拣技能指导。 新增一个工具的步骤(manifest、`tools.json`、授权、处理代码、审批路径)见 [AGENTS.md(英文)](AGENTS.md#architecture-documentation-and-code-walkthroughs),设计见 [ADR 0002(英文)](docs/adr/0002-event-driven-app-agents.md)。 @@ -328,7 +328,7 @@ Agent 的 `ask_user_question` 出现在这一轮的来处:用户和应用自 - **到内核只有一条连接。** 系统对话和每个应用 Agent 的代理共用一条通往内核的流。一个小小的路由器([`crates/kernel/src/router.rs`](crates/kernel/src/router.rs))为每个请求分配唯一的 id,并把每条通知只发给关心该会话的使用方。 - **线程按服务分配,而不是按回合。** 回合是 Tokio 任务。在 Shell 中,内核服务运行在一个有两个工作线程的 Tokio 运行时上,每个活跃应用 Agent 的代理有自己的单线程运行时(所以一个慢吞吞的 Agent 拖不住别的 Agent,也拖不住界面),系统对话则用一个线程,只在有变化时才通过 Makepad 的 `SignalToUI` 唤醒界面。octos 子进程(桌面端和 Android)使用 Tokio 的默认运行时,每个 CPU 核心一个工作线程。 - **按需启动,空闲即停。** 内核在第一个连接到来时启动:如果用户已经允许过某个脚本应用的 Agent,就在 Shell 启动时;否则在某个 Agent 或系统对话第一次需要它时。系统对话只在面板打开或有回合运行时才保持连接。Talk to Octos 关闭时,最后一个连接断开,内核就停止。没有获准 Agent 的应用既没有代理,也没有 peer。 -- **应用共享 Shell 进程。** 脚本应用是同一个 Card runner 中的隔离环境,原生应用是模块,所以大多数应用不单独占用进程。脚本出错只影响它自己的隔离环境,原生模块的 panic 在模块边界被捕获。只有需要系统沙箱的应用才作为独立进程运行。 +- **应用共享 Shell 进程。** 脚本应用是同一个 Card runner 中的隔离环境,原生应用是模块,所以大多数应用不单独占用进程。脚本出错只影响它自己的隔离环境,原生模块的 panic 在模块边界被捕获。只有声明为独立进程的应用(为了系统沙箱的 Terminal,以及没有模块的 Task)才作为进程运行,前提是有它的二进制。 - **进程应用的画面零拷贝。** Terminal 的画面以共享 GPU 表面的形式到达 Shell:macOS 上是 IOSurface,Windows 上是 D3D11 共享句柄,Linux 配 Vulkan 时是 DMA_BUF。在只能靠 CPU 拷贝的环境(Linux 配 OpenGL)中,所有应用都改在 Shell 内运行。 ## 目录结构 diff --git a/docs/adr/0002-event-driven-app-agents.md b/docs/adr/0002-event-driven-app-agents.md index afdf924c..f3299a26 100644 --- a/docs/adr/0002-event-driven-app-agents.md +++ b/docs/adr/0002-event-driven-app-agents.md @@ -1,7 +1,7 @@ # ADR 0002: Event-driven app agents: apps think on their own triggers and publish cards to the glance screen - **Date:** 2026-09-27 -- **Status:** Proposed; amended 2026-09-28 by [ADR 0004](0004-native-apps-hosting-and-peers.md) (sections 1, 4, 6, 10, 12 and 13; see the amendment at the end); notes dated 2026-09-29 record what has since changed; amended 2026-10-03 by [ADR 0006](0006-app-studio-on-the-phone.md) (sections 6 and 7; see the amendment at the end) +- **Status:** Proposed; partly implemented (2026-10-04: §6's toolbox, §7's `sys.digest` and `glance.publish`, §14's `model.complete`, News M1 and the Mail incoming-event slice are on `main`; see the implementation amendment of 2026-10-03); amended 2026-09-28 by [ADR 0004](0004-native-apps-hosting-and-peers.md) (sections 1, 4, 6, 10, 12 and 13; see the amendment at the end); notes dated 2026-09-29 record what has since changed; amended 2026-10-03 by [ADR 0006](0006-app-studio-on-the-phone.md) (sections 6 and 7; 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). @@ -244,7 +244,7 @@ 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`) 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 | +| **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 *(2026-10-04: narrower in what ships; see ADR 0004 §12's note)*; 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 | diff --git a/docs/adr/0004-native-apps-hosting-and-peers.md b/docs/adr/0004-native-apps-hosting-and-peers.md index 0af01898..bb461d8a 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:** Accepted (2026-09-28); amended 2026-09-29 (section 6: `host.ask` dropped for octos's `ask_user_question`; sections 10 and 12: `terminal.run` only where the Terminal runs as a process; factual corrections from the gap review, marked where made; sections 4 and 8: interactive app cards; section 6: one shared conversation per app agent, then the same day two parallel lanes that share history; follow-ups from the 2026-09-29 code review; section 4: which apps have an agent, peers prepared at consent, the shell's "Ask " panel); **Implemented** (2026-09-29): every plan step and every gap from the 2026-09-28 review is on `main`, and the full CI (Desktop, Phone, Apps and services, ROM) passed on `74e1d713`. The follow-ups below remain open work, not part of this decision's implementation. +- **Status:** Accepted (2026-09-28); amended 2026-09-29 (section 6: `host.ask` dropped for octos's `ask_user_question`; sections 10 and 12: `terminal.run` only where the Terminal runs as a process; factual corrections from the gap review, marked where made; sections 4 and 8: interactive app cards; section 6: one shared conversation per app agent, then the same day two parallel lanes that share history; follow-ups from the 2026-09-29 code review; section 4: which apps have an agent, peers prepared at consent, the shell's "Ask <app>" panel); **Implemented** (2026-09-29): every plan step and every gap from the 2026-09-28 review is on `main`, and the full CI (Desktop, Phone, Apps and services, ROM) passed on `74e1d713`. The follow-ups below remain open work, not part of this decision's implementation. *(2026-10-04: three plan items are not on `main`: release packages ship no process app's `bin` (step 2), so a packaged desktop runs the Terminal in-process and read-only; the Windows AppContainer sandbox (step 8, [#137](https://github.com/OctoSense-org/OctoSense/issues/137)); Rinx's data under `apps/rinx/` (step 10, hagency-org/Rinx#37).)* - **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, merged; OctoSense pins its follow-ups, now octos 056173e8 (4a3ec9f9 plus the security fixes #2653, #2655 and #2656, and #2675, hydrate rows that name their tool calls) with octos#2621, #2626, #2636, #2644, #2652, #2654, #2657, #2658 and the UPCR additions #2647 (`read_parent`), #2649 (`peer/purge`) and #2648 (`session/tool_list/set`)) 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). @@ -11,7 +11,7 @@ | | Native app | Script system app | | --- | --- | --- | -| Today | App Hub (the store and the Card runner), Rinx, Terminal; opt-in Sheets, Reference, AppCard; the native News, Maps and Photos crates once kept "for comparison" were deleted by [#113](https://github.com/OctoSense-org/OctoSense/pull/113) (the script apps are the only versions) | News, Maps, Photos, Camera, Mail, AI providers | +| Today | App Hub (the store and the Card runner), Rinx, Terminal; opt-in Sheets, Reference, AppCard; the native News, Maps and Photos crates once kept "for comparison" were deleted by [#113](https://github.com/OctoSense-org/OctoSense/pull/113) (the script apps are the only versions). *(2026-10-04: also Calculator, Clock, Notes, Reminders and Weather in both shells ([#309](https://github.com/OctoSense-org/OctoSense/pull/309), [#329](https://github.com/OctoSense-org/OctoSense/pull/329)), and Task (Task Manager), a process-only desktop app.)* | 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 | @@ -34,7 +34,7 @@ | 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 built from a checkout (`cargo build --release`, since 2026-09-30; `cargo run` before), then their binary is started; without a checkout the sibling binary is; release packaging does not ship them yet ([#94](https://github.com/OctoSense-org/OctoSense/pull/94) is open). +`host::processes_available()` is false for native mobile targets and wasm. Mobile GL contexts are per process. Process apps are built from a checkout (`cargo build --release`, since 2026-09-30; `cargo run` before), then their binary is started; without a checkout the sibling binary is; release packaging does not ship them yet ([#94](https://github.com/OctoSense-org/OctoSense/pull/94) is open). *(2026-10-04: #94 was closed unmerged on 2026-10-01; release packages ship only `octosense` and the kernel.)* ### Two AI models @@ -110,7 +110,9 @@ Script system apps stay in `desktop/system-apps.json` and `phone/system-apps.jso - **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`. +*(2026-10-04: since [#309](https://github.com/OctoSense-org/OctoSense/pull/309) a native app may have no module (`"module": null`): it runs only as its own desktop process, with hosting `none` where there are no processes. Task (Task Manager) is the first: no shell links it, and the desktop catalog starts it as a process.)* + +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`. *(2026-10-04: not yet; see the Status.)* **How a process app is built and started** (from the 2026-09-30 code review; `crates/shell/src/clients.rs`, `launch_plan`). In a checkout, the shell first builds the app from the OctoSense workspace, held to its `Cargo.lock`: `cargo build --release --locked --manifest-path /Cargo.toml -p octosense-process-apps -p --bin `, with the `crate` and `bin` of the app's `native-apps.json` entry, so it builds with the versions the shell pins. `crates/process-apps` is a workspace member generated from `native-apps.json` that depends on every app with a `bin`, with the features its binary needs (`bin_features`: Sheets' `standalone`); cargo takes no features for a crate outside the workspace, and without it `-p makepad-sheets` made cargo itself panic. Its own empty library is never built (`--bin`). Cargo runs in the workspace, outside any sandbox, with the shell's allow-listed environment and a `PATH` without entries under the person's home other than the cargo and rustup homes. Then the shell starts the built binary itself under the app's sandbox; cargo never starts the app. The directory the app opens in (the Terminal's inherited working directory) is given to the app alone and never decides which `.cargo/config.toml` cargo reads. The desktop catalog row (`desktop/config/apps.json`) only names and places the app; `tools/native_apps.py --check` fails when a native app's row names another package, binary or source than its entry, or when an app that runs as a process has no row. *(Before 2026-09-30 the launch was `cargo run --release` in the app's working directory, `--locked` only where that checkout had a lock (Makepad's has none), and only the app was sandboxed, as cargo's runner: a `.cargo/config.toml` the Terminal wrote could run a build wrapper unsandboxed at the next launch, confirmed on macOS by a reviewer.)* @@ -122,7 +124,7 @@ In-process modules (all of mobile, and Linux without Vulkan) must meet the bar a - **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. -- **Nothing an app can write decides what the shell builds or runs next** (2026-09-30). The build runs outside the sandbox (section 2), so whatever it reads or runs is read-only to every app, after every grant (the Terminal's `home:rw` included): the checkout and its target dir, the cargo and rustup homes (`~/.cargo`, `~/.rustup` by default), every `.cargo/` and `rust-toolchain(.toml)` from the checkout up to the root, and the shell's own directory (`sandbox::Policy::read_only`). The macOS profile applies it; **the Linux Landlock rules do not read `read_only` yet** (`sandbox/linux.rs`, owned by the Linux sandbox work), so on Linux the Terminal's `home:rw` still reaches these paths: open. On macOS the profile also keeps `~/Library/LaunchAgents` unwritable (login items run outside any sandbox), and the generated profiles and per-app logs live in `/sandbox/` and `/logs/clients/` (owner-only, closed to every app), not the shared temp dir. **Consequence for the person: the OctoSense Terminal can no longer write `~/.cargo` or `~/.rustup`, so cargo and rustup updates must run outside it**, in another terminal. Checked live on macOS (2026-09-30, a hidden shell with the Terminal as a sandboxed process): from its shell, writes into `~/.cargo`, `~/.rustup`, the checkout, its `target/` and its `.cargo/`, and `~/Library/LaunchAgents` were refused ("Operation not permitted"), `cargo fetch` of a new crate failed writing `~/.cargo/registry`, and a file in the home was written as before. `cargo install` and `rustup update` write the same folders, so they fail the same way (**unverified**: not run); so does building in the OctoSense checkout. Pointing `CARGO_HOME` elsewhere inside the Terminal should work for other projects (**unverified**). The test `inside_the_terminals_sandbox_the_build_toolchain_and_login_items_are_out_of_reach` proves the refusals on macOS. The keychains are not closed: closing their files broke `git`'s `osxkeychain` credentials in the Terminal (tried 2026-09-30), and the Keychain's own access prompts guard the items. +- **Nothing an app can write decides what the shell builds or runs next** (2026-09-30). The build runs outside the sandbox (section 2), so whatever it reads or runs is read-only to every app, after every grant (the Terminal's `home:rw` included): the checkout and its target dir, the cargo and rustup homes (`~/.cargo`, `~/.rustup` by default), every `.cargo/` and `rust-toolchain(.toml)` from the checkout up to the root, and the shell's own directory (`sandbox::Policy::read_only`). The macOS profile applies it; **the Linux Landlock rules do not read `read_only` yet** (`sandbox/linux.rs`, owned by the Linux sandbox work), so on Linux the Terminal's `home:rw` still reaches these paths: open. *(2026-10-04: closed by [#238](https://github.com/OctoSense-org/OctoSense/pull/238) on 2026-09-30: the Linux Landlock rules grant these paths read and execute only.)* On macOS the profile also keeps `~/Library/LaunchAgents` unwritable (login items run outside any sandbox), and the generated profiles and per-app logs live in `/sandbox/` and `/logs/clients/` (owner-only, closed to every app), not the shared temp dir. **Consequence for the person: the OctoSense Terminal can no longer write `~/.cargo` or `~/.rustup`, so cargo and rustup updates must run outside it**, in another terminal. Checked live on macOS (2026-09-30, a hidden shell with the Terminal as a sandboxed process): from its shell, writes into `~/.cargo`, `~/.rustup`, the checkout, its `target/` and its `.cargo/`, and `~/Library/LaunchAgents` were refused ("Operation not permitted"), `cargo fetch` of a new crate failed writing `~/.cargo/registry`, and a file in the home was written as before. `cargo install` and `rustup update` write the same folders, so they fail the same way (**unverified**: not run); so does building in the OctoSense checkout. Pointing `CARGO_HOME` elsewhere inside the Terminal should work for other projects (**unverified**). The test `inside_the_terminals_sandbox_the_build_toolchain_and_login_items_are_out_of_reach` proves the refusals on macOS. The keychains are not closed: closing their files broke `git`'s `osxkeychain` credentials in the Terminal (tried 2026-09-30), and the Keychain's own access prompts guard the items. - **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. @@ -133,11 +135,11 @@ An app that declares `agent.octos` (native) or `octos.*` capabilities (script) a **Which apps have an agent (amended 2026-09-29).** From the manifests and grants, never from names (`crates/shell/src/agents.rs`, `apps::agent_apps`): a native app whose `native-apps.json` entry grants `agent.octos`; a script app whose manifest declares `octos.*` or an `agent` block, **or whose admitted bundle ships `tools.json`** (App Hub's `AgentBundle::load`). News ships `tools.json` (`news.list`, `news.read`) and declares no `octos.*`: it has an agent. Settings › Assistant › Approvals lists every one of them ("Not asked yet", on, off) before any has asked. An app's own `host.request("octos.*")` stays limited to the services its manifest declares. -**Peers exist once the person allows them.** The first use that asks may be the person opening the app's "Ask " panel, the app's own `octos` call, or the system agent's `agents.ask` (the first-use sheet then shows over the system chat); developer mode asks nobody (section 13), and there is no other bypass. From the moment an agent is allowed (and at startup for the ones allowed before), the shell **prepares its peer**: `peer/prepare`, its tools and `generic_tools` registered (G3), its session open, so the system agent's `peer_list` lists it and `peer_send_input` reaches it without the app ever calling `octos`. A script app's peer is the contained service's `card.`, one per app, shared by the app, its cards, the panel and the system agent. A native app's peer is its instance's and exists while the app is open (a native app with accounts cannot be prepared without its signed-in account). Turning the agent off releases the peer and its contexts at once; the shell refuses the system agent's input to it (the kernel may still list the retired peer). octos lists only prepared peers and offers no hook for an app whose agent is not allowed, so the shell tells the system agent itself: a short note with the system chat's turn whenever the apps' agents changed (each app, and whether it is allowed, not yet allowed or off), and two host tools on the system session, `agents.list` and `agents.ask`, answered by the shell (registered once a host credential exists, that is, once one app's agent has been prepared). +**Peers exist once the person allows them.** The first use that asks may be the person opening the app's "Ask <app>" panel, the app's own `octos` call, or the system agent's `agents.ask` (the first-use sheet then shows over the system chat); developer mode asks nobody (section 13), and there is no other bypass. From the moment an agent is allowed (and at startup for the ones allowed before), the shell **prepares its peer**: `peer/prepare`, its tools and `generic_tools` registered (G3), its session open, so the system agent's `peer_list` lists it and `peer_send_input` reaches it without the app ever calling `octos`. A script app's peer is the contained service's `card.`, one per app, shared by the app, its cards, the panel and the system agent. A native app's peer is its instance's and exists while the app is open (a native app with accounts cannot be prepared without its signed-in account). Turning the agent off releases the peer and its contexts at once; the shell refuses the system agent's input to it (the kernel may still list the retired peer). octos lists only prepared peers and offers no hook for an app whose agent is not allowed, so the shell tells the system agent itself: a short note with the system chat's turn whenever the apps' agents changed (each app, and whether it is allowed, not yet allowed or off), and two host tools on the system session, `agents.list` and `agents.ask`, answered by the shell (registered once a host credential exists, that is, once one app's agent has been prepared). *(2026-10-04: also `agents.provision` and `agents.status` for Mail's incoming-mail agent ([#321](https://github.com/OctoSense-org/OctoSense/pull/321)), and each native app's `agent.system_tools`, shareable read tools lent to the system agent: `calculator.eval`, `clock.now`, `notes.search`, `notes.read`, `reminders.due`, `reminders.list`, `weather.current` ([#309](https://github.com/OctoSense-org/OctoSense/pull/309)); a closed app answers that it is not running.)* -**"Ask " (the person's lane, drawn by the shell).** For every app with an agent the shell draws a panel with the app's conversation (section 6): it opens a sharing context with `open_conversation` (the contained service's for a script app), shows both lanes merged with their speakers ("You", "System agent", "'s agent", "'s agent, to the system agent"), has a composer whose Send stays available whenever the person's own lane is idle (the lanes are independent, so the person can send while the system agent's turn runs) and whose Stop stops only the person's own running turn; the system agent's running turn has its own row with an explicit "Stop the system agent's task" (`broker::interrupt_lane_where`), never in the Send button's place. It shows and answers the app's own questions (G11), and leaves approvals to the shell's sheets. It opens from the bar's "Ask " beside the focused window's title, Shift+F8, or Setup › Assistant › Ask this app's agent; on a desktop it stands beside the system chat, on a phone it is a full-screen sheet. It is the system chat's pane (`crates/shell/src/system_chat/view.rs`) in its app mode (`crates/shell/src/app_chat/`). +**"Ask <app>" (the person's lane, drawn by the shell).** For every app with an agent the shell draws a panel with the app's conversation (section 6): it opens a sharing context with `open_conversation` (the contained service's for a script app), shows both lanes merged with their speakers ("You", "System agent", "<App>'s agent", "<App>'s agent, to the system agent"), has a composer whose Send stays available whenever the person's own lane is idle (the lanes are independent, so the person can send while the system agent's turn runs) and whose Stop stops only the person's own running turn; the system agent's running turn has its own row with an explicit "Stop the system agent's task" (`broker::interrupt_lane_where`), never in the Send button's place. It shows and answers the app's own questions (G11), and leaves approvals to the shell's sheets. It opens from the bar's "Ask <app>" beside the focused window's title, Shift+F8, or Setup › Assistant › Ask this app's agent; on a desktop it stands beside the system chat, on a phone it is a full-screen sheet. It is the system chat's pane (`crates/shell/src/system_chat/view.rs`) in its app mode (`crates/shell/src/app_chat/`). -Three flows: +Four flows: | Flow | Native module | Native app in its own process | Script app | | --- | --- | --- | --- | @@ -146,6 +148,8 @@ Three flows: | 3. The system agent talks to the agent: the system agent's lane, in parallel with flow 1, each lane seeing the other's recent turns | 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 | | 4. The person acts in one of the app's cards (glance screen, notification) | as in the app's UI: the card runs under the app's own policy and its calls take the app's path of flows 1 and 2 (the person's lane) | the same | the Card runner's service gate, as in the app's UI (`host.request` under the manifest's grants, then each host service's own checks) | +*(2026-10-04: the native-module column describes Rinx's injected service; every other native agent uses the peer link in process (section 5, [#142](https://github.com/OctoSense-org/OctoSense/issues/142)).)* + **App cards are fully interactive (decided 2026-09-29: "the app card can do everything, for now").** An app's cards on the glance screen and behind notifications run under the publishing app's own policy, the same as the app's UI in the Card runner: typing, buttons, `host.request`, chatting with the app's agent (section 6). For example, the agent drafts an email and publishes it as an editable Mail card; the person edits it and taps Send in the card. A card's calls go through the app's existing path, not the host-tool relay, so the relay's schema checks and budgets (section 3) do not cover them. What the person does in a card is the app's own action (section 8). *(Until 2026-09-29 a glance card was contained: its isolate ran under an empty policy with no host requests, took no input, and the whole tile was one shell tap target that opened the app (`crates/shell/src/glance_card.rs`). Since the branch `feat/interactive-glance-cards`, a tile runs under the publishing app's policy and takes input, an app may publish a `script` card (a Splash program) besides an L0 `source` card, and `glance.publish` with `notify: true` posts a notification that opens the card.)* 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. @@ -173,12 +177,12 @@ The shell stamps the app's identity from the socket, enforces #2567's host oblig - **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. - **Two parallel lanes that share history, per app agent (decided 2026-09-29).** Each app agent (a host-owned app peer) has two sessions that run **in parallel** and **share history read-only**. *(This supersedes, the same day, the single shared conversation of OctoSense [#166](https://github.com/OctoSense-org/OctoSense/pull/166) and octos [#2626](https://github.com/octos-org/octos/pull/2626): one peer session that the person's turns and the system agent's `peer/input`s both drove, queued one turn at a time. The system agent's long work blocked the person, and the person's chat blocked the system agent.)* - **The system agent's lane** is the peer's own session (`…#peer-`), driven by `peer/input` (the system agent's `peer_send_input`) as before, with its own queue. - - **The person's lane** is a request context (`…#peerctx-.`) that the app opens with `share_history` (octos UPCR-2026-034, "Parallel person context with shared history", [#2636](https://github.com/octos-org/octos/pull/2636), pinned e045c727). A native module opens it with `OctosAppService::open_conversation`, a process app with `octos.session.open` over the peer link, and a script app or card with `host.request("octos.session.open")`. No system agent is involved. The broker opens a new context id for every handle (a closed id is never reopened); the history of a handle still includes the person's lane of the earlier handles of the same account and instance (the shell's "Ask " panel reopened), kept in memory per broker and cleared when the account changes or the contexts are revoked. It starts the person's message there with `origin: {kind: "person", label: }`, or `app` when the app says it started the run itself (G2's `trigger: app | schedule | background | incoming`). G2's trigger stays the authority for approval rules: an unsaid turn is `person` for the label and `Unknown` for rules. Only a shell surface makes a turn the person's for rules (the shell's own composer: the "Ask " panel, the system chat; an in-process module's own UI): an app's `"trigger": "person"` over the wire is only its word (`TurnTrigger::AppSaysPerson`), labelled the person's in the transcript and the app's run for rules, so "when I start it" never answers it (2026-09-30). A person's turn waits only for its own context's previous turn. + - **The person's lane** is a request context (`…#peerctx-.`) that the app opens with `share_history` (octos UPCR-2026-034, "Parallel person context with shared history", [#2636](https://github.com/octos-org/octos/pull/2636), pinned e045c727). A native module opens it with `OctosAppService::open_conversation`, a process app with `octos.session.open` over the peer link, and a script app or card with `host.request("octos.session.open")`. No system agent is involved. The broker opens a new context id for every handle (a closed id is never reopened); the history of a handle still includes the person's lane of the earlier handles of the same account and instance (the shell's "Ask <app>" panel reopened), kept in memory per broker and cleared when the account changes or the contexts are revoked. It starts the person's message there with `origin: {kind: "person", label: }`, or `app` when the app says it started the run itself (G2's `trigger: app | schedule | background | incoming`). G2's trigger stays the authority for approval rules: an unsaid turn is `person` for the label and `Unknown` for rules. Only a shell surface makes a turn the person's for rules (the shell's own composer: the "Ask <app>" panel, the system chat; an in-process module's own UI): an app's `"trigger": "person"` over the wire is only its word (`TurnTrigger::AppSaysPerson`), labelled the person's in the transcript and the app's run for rules, so "when I start it" never answers it (2026-09-30). A person's turn waits only for its own context's previous turn. - **Shared history, read-only.** At the start of each turn in one lane, the kernel shows the model the other lane's last N user and assistant text rows as a labelled, read-only block: "Recent turns in the system agent's conversation with this app" or "Recent turns in the person's conversation with this app". The defaults are N = 20 (at most 50) within 16 KiB. The block is read under the other session's persist lock, drops tool rows, names each row's speaker and is never written into the reader's transcript. A turn still running in the other lane is shown too, after the finished rows (octos [#2644](https://github.com/octos-org/octos/pull/2644)): its request with its speaker, the text it has streamed so far (`[the app agent] (in progress) …`) and a `[turn status]` line (`still running`, `waiting for approval: ` or `waiting for an answer`, never tool arguments). **No shared transcript**: two writers on one transcript are rejected, because they break tool-call pairing and compaction. - **What the system agent sees.** Each of the person's turns also leaves a round on the peer's blackboard (`result-.md` with `origin: person` and `context: `), so `peer_gather` shows it. A person's round alone never fires a fleet synthesis. - **Who speaks, and what the app sees.** #166's speaker labels stay. The kernel's `[from the person: Rinx] …` or `[from the system agent] …` marker leads the user message. The app's follower (`OctosContext::subscribe`, or `conversation` frames on the peer link) gets both lanes' events: its own context's and the system agent's turns on the peer session. Each event carries its `lane` (`person` or `system_agent`), its `speaker`, and a user message's `display_text`; a turn's caller hears only its own turn. `octos.session.history` returns both transcripts merged by `persisted_at`, each row with `lane` and, for a marked user message, `speaker` and `display_text`. Approvals and questions keep going to the shell's router and request model, routed by the turn's origin: the person's and the app's turns to the app's conversation, the system agent's to the system chat. - **Which API opens which.** `open_conversation` opens the person's lane. `open_context(client)` stays a plain request context, with no shared history, no origin and no blackboard round: Rinx's mini apps, and `octos.session.open` with a `client`. - - **Nothing blocks a lane for long, and Stop covers both.** #167's deadlines hold in both lanes. An approval or question nobody answers expires at the prompt deadline (section 8), and a turn still running 30 s later is interrupted (its late calls refused); on the peer session, the next queued input then starts. **Stop from the app stops both lanes**: the person's running turn and the system agent's (the person owns the device, and one Stop is the simplest clear behaviour). This is `octos.turn.interrupt` (`ContextOp::Interrupt`) on a conversation, and the reply lists each stopped turn with its lane. "Stop 's agent" on the shell's app-conversation sheet and question card (`broker::interrupt_where`) does the same. The shell's "Ask " panel is different: its Stop is the person's own lane only, and the system agent's turn is stopped only from its own row (see "Ask " above). + - **Nothing blocks a lane for long, and Stop covers both.** #167's deadlines hold in both lanes. An approval or question nobody answers expires at the prompt deadline (section 8), and a turn still running 30 s later is interrupted (its late calls refused); on the peer session, the next queued input then starts. **Stop from the app stops both lanes**: the person's running turn and the system agent's (the person owns the device, and one Stop is the simplest clear behaviour). This is `octos.turn.interrupt` (`ContextOp::Interrupt`) on a conversation, and the reply lists each stopped turn with its lane. "Stop <app>'s agent" on the shell's app-conversation sheet and question card (`broker::interrupt_where`) does the same. The shell's "Ask <app>" panel is different: its Stop is the person's own lane only, and the system agent's turn is stopped only from its own row (see "Ask <app>" above). - **Accepted hazards.** The two lanes run at the same time on one peer: - File edits are not locked across lanes. Each lane edits its own folder (a context works in `/contexts/`), and a lane that writes the other's folder can race it. - The unknown-outcome marks of host tool calls are shared by both lanes. @@ -207,7 +211,7 @@ Only the person approves. The system agent never does, and cannot be talked into - **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. The audit (2026-09-30): the relay records each call when it arrives and when it ends, answered, refused or cancelled (caller, owning app, tool, a SHA-256 digest of the exact arguments, never the arguments, and the outcome), one JSON line each in `logs/tool-calls.jsonl` under the OctoSense home, owner-only; `terminal.run` and `dev.run` are calls like any other. Approvals have their own audit beside it (`logs/approvals-audit.jsonl`). - **Agent tool calls, not the person's own actions (decided 2026-09-29).** The approval router and the shell's sheets govern **agent tool calls** (`peer/tool/call`). **Actions the person takes in an app's own UI, its cards included (section 4), are the app's own actions**: they run under the app's policy and host services' own checks, and the shell draws no extra approval for them. An app can draw something that looks like a shell approval card, but a fake approves nothing: only the shell's host connection answers `approval/respond` (octos approvals are once-only and owned per connection). -- **Hardening deferred (2026-09-29).** OctoSense is at a very early stage; we see how interactive cards work and improve security later. Deferred: a user-gesture check on card actions; letting only the owning app's card act on its tools; a shell-only "Needs you" area; "from " labels on app tiles; a personal anti-phishing mark; filtering mirrored Android notification actions named like approvals; budgets on card-initiated actions. +- **Hardening deferred (2026-09-29).** OctoSense is at a very early stage; we see how interactive cards work and improve security later. Deferred: a user-gesture check on card actions; letting only the owning app's card act on its tools; a shell-only "Needs you" area; "from <app>" labels on app tiles; a personal anti-phishing mark; filtering mirrored Android notification actions named like approvals; budgets on card-initiated actions. - **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 (2026-09-30: as data too, `ConfirmRequest::caller`, so the app can check its own grants; a call cancelled while on the app's sheet is withdrawn from 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). On the shell's sheet that means every argument (2026-09-30): long rows wrap, past twelve rows the argument area scrolls (the wheel, or "More ↓"), control and format characters are shown as their code point (`⟨U+202E⟩`), and Approve once and "Always …" stay disabled until every row has been on screen. A command tool's `command` (`terminal.run`, `dev.run`) is shown one numbered row per line, so a second command after a newline is in plain sight; newlines are allowed, not refused. - **Whose turns the system session relays (2026-10-01).** octos routes a call of the host session's tool set only for a turn the registering connection drove itself (`apply_session_owned_host_tools`, octos ae230ce0): a kernel continuation or wake-up, a Talk to Octos client and another device's connection (ADR 0003) get no host-session tools, so their calls never reach the shell. The system chat therefore relays every call octos sends it for the system session: a turn it started itself is the person's (`trigger: person`), any other turn on its own connection is the system agent's own work (`system_agent`: never "when I start it"), and a call for any other session or an app peer's set is refused at once (`not_this_hosts_session`), not left to time out. An app agent's question routed to the system chat stays listed while it is open; at most 16 open ones are shown, and past that a new one waits, said in the conversation ("N more app questions waiting"), until one settles; none is dropped. - **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. @@ -231,7 +235,7 @@ One agent for Rinx and one request context per mini app is enough while mini app - 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`) in every hosting, and **type commands only when the Terminal runs as its own sandboxed process**: macOS, Windows, and Linux with Vulkan and Wayland. *(2026-09-30: "sandboxed" is now checked, not assumed: `terminal.run` is registered only after the Terminal's newest launch reported its sandbox applied (`sandbox::launch_sandboxed`), and withdrawn when a launch runs unsandboxed. So on Windows, until its AppContainer exists, there is no `terminal.run`, and none before the Terminal's first launch. Its description no longer says the command runs "unsandboxed": it runs inside the Terminal's own sandbox, which reaches the person's files and the network but not OctoSense's private data or the build toolchain.)* The in-process Terminal (Linux without Vulkan, phones) is read-only: its module offers only the read tools (Makepad `apps/terminal/src/module.rs`, `read_only_manifest`). *(Decided 2026-09-29; this bullet said "type commands, in every hosting".)* Each typed command goes through the shell with a live approval showing the exact command, and is `auto_approvable: false`, so no standing rule approves it (consistent with makepad#41, which already requires confirmation for each run). Outside developer mode, that is: developer mode runs them without a sheet (section 13). -- An agent for the terminal is optional and later. Its output is untrusted text: anything that types into a shell stays behind the person's confirmation and is `auto_approvable: false`. +- An agent for the terminal is optional and later. *(2026-10-04: since [#330](https://github.com/OctoSense-org/OctoSense/pull/330) the Terminal has its own agent, narrowed by `agent.own_tools` to `terminal.read_screen` and `terminal.read_scrollback` over its peer link; `terminal.run` stays the system agent's, behind Command execution.)* 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 @@ -296,6 +300,8 @@ An app agent gets **whatever tools its manifest declares, once the person grants | octos's generic tools (`web_search`, `deep_search`, `deep_research` and the rest) | apps that declare them and are granted them | octos | | **command execution** | apps that declare it and are granted it, and the system agent when the person grants it in Settings (below); each command approved live under section 8 | host tool (for example `terminal.run`, only where a process Terminal exists: section 10) | +*(2026-10-04: what ships is narrower: App Hub admits only `ask_user_question` as a script app's kernel tool (`KERNEL_TOOLS`), and a native agent keeps exactly its entry's `generic_tools`. Only Rinx lists file and memory tools; the Terminal's, Calculator's, Clock's, Notes', Reminders' and Weather's agents use their own tools only.)* + **The system agent's tool set is its grants, enforced by the shell (decided on #110).** Today the system agent's turns get octos's full default tool set, shell included, granted or not. *(Since [#117](https://github.com/OctoSense-org/OctoSense/pull/117), 2026-09-28: the shell writes a `tool_policy` denying `group:runtime` into the `_main` profile, so octos's shell is gone; that policy is a ceiling. Since 2026-10-01 (octos 4a3ec9f9) every kernel start also sets the system session's exact kernel tool list with octos's durable, host-only `session/tool_list/set` (octos#2648); see the follow-ups.)* The system agent gets an explicit list instead: its supervision tools (the peer tools, glance curation, policy and budgets), the toolbox tools it is granted, the cross-app tools granted to it (section 7), and command execution only when the person grants it in Settings, off by default, each command going through the shell with a live approval (section 8). The shell enforces the list (with octos's help where needed), and a test starts a system-agent turn and checks it is offered exactly that list (in particular, no `shell` unless granted). This is a plan step (step 4), and it lands before any cross-app grant reaches the system agent. **Command execution is a grantable capability like any other, and the widest.** A shell escapes every other boundary: the workspace fence (`cat` reads anywhere, including `secrets/`), the network policy (`curl` reaches any host), budgets and audit (one call does anything), and it turns prompt injection into code execution. So the install-time grant says so plainly, and each destructive or outward call goes through section 8. A narrow tool often serves better (for example a sandboxed `toolbox.run_python`: no network, only its folder, time and memory limits). Running real commands on the person's machine is the Terminal app's shareable `terminal.run`: destructive, `auto_approvable: false`, each command approved live with the exact command shown, run in a terminal the person sees. `terminal.run` exists only where the Terminal runs as its own sandboxed process and its launch reported the sandbox applied (macOS and Linux with Vulkan and Wayland today; Windows once its sandbox exists; section 10), so a grant of command execution, the system agent's Settings switch included, takes effect only there; on Linux without Vulkan and on phones it has no target (decided 2026-09-29). Every live approval and `auto_approvable: false` in this section holds outside developer mode, which approves them all (section 13). octos refuses its own `shell` to external clients (#98). For app peers and the system agent, octos's `shell` is kept out by the `_main` profile's `tool_policy`, which denies `group:runtime` (#117), so a granted app reaches command execution through a host tool. *(Corrected 2026-09-29: this sentence said octos refuses `shell` to app peers through #2567's generic-tool allowlist. It does not: with `generic_tools` omitted, as the broker registered every peer until G3, a peer keeps octos's whole roster, `shell` included (octos test `should_offer_exactly_the_registered_tools_and_refuse_an_unlisted_one`); the profile's `tool_policy` is the only barrier (and it failed open until G13: now a policy that cannot be enforced starts no kernel) until each peer's `generic_tools` is set from its grants, plan step 6 (done: each peer is registered with exactly its granted `generic_tools`).)* @@ -322,7 +328,7 @@ How it is turned on and kept from leaking into normal use: ### 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. +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 <id>"), 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)`), and publishes it with `glance.publish`: glance screen and notification. 4. The person acts in the card itself (section 4): asks a follow-up in it, which goes to Mail's agent in the person's lane of its conversation (section 6), which sees the recent turns of the system agent's lane, so it sees the email and the findings; or taps the card's own actions, which are Mail's own actions (section 8). Opening Mail at that email's fact-check thread is still possible, and shows the same conversation, the system agent's exchanges included. *(Until 2026-09-29 tapping the card was the only action: it opened Mail at a thread on a request context per email, whose transcript Mail reloaded with `octos.session.history`, step 3 stored that context's id with the card, and deleting the email closed that context.)* @@ -362,10 +368,10 @@ How it is turned on and kept from leaking into normal use: ### Follow-ups -- [x] **An agent for every app that declares one** (section 4, 2026-09-29): a live run on `625d6cc` found News (tools.json, no `octos.*`) without a peer, an empty `peer_list` for the system agent, Settings listing only Rinx, and no place for the person to talk to an app's agent. Now the shell lists every app with an agent, prepares a consented app's peer, tells the system agent about the ones it cannot list, and draws "Ask ". Still open: a native app's peer exists only while the app is open; the speaker label of a script app's person turns is its app id (the peer's name, kept for resume), not its display name; prepared peers hold a kernel connection, so the kernel's idle stop waits for them. -- [x] **The "Ask " panel's live run, follow-ups** (2026-09-29, [#189](https://github.com/OctoSense-org/OctoSense/pull/189) and the App Hub pin after it): the panel orders both lanes by one clock (each lane's `cursor.seq` is its own; a turn's request line stands where the turn started, handed over by the broker with `turn/started`, since octos writes a turn's user message only when it ends and never for a stopped turn); Stop says "interrupted" once; the system agent is given each peer's slug for `peer_send_input`; and a script app's agent may keep octos's `ask_user_question` (App Hub's one kernel tool for contained apps, `KERNEL_TOOLS`, "Ask you questions" in the store), handed to its peer's `generic_tools` by G3. News's agent keeps it, and its questions of the person's lane are asked in "Ask News". +- [x] **An agent for every app that declares one** (section 4, 2026-09-29): a live run on `625d6cc` found News (tools.json, no `octos.*`) without a peer, an empty `peer_list` for the system agent, Settings listing only Rinx, and no place for the person to talk to an app's agent. Now the shell lists every app with an agent, prepares a consented app's peer, tells the system agent about the ones it cannot list, and draws "Ask <app>". Still open: a native app's peer exists only while the app is open; the speaker label of a script app's person turns is its app id (the peer's name, kept for resume), not its display name; prepared peers hold a kernel connection, so the kernel's idle stop waits for them. +- [x] **The "Ask <app>" panel's live run, follow-ups** (2026-09-29, [#189](https://github.com/OctoSense-org/OctoSense/pull/189) and the App Hub pin after it): the panel orders both lanes by one clock (each lane's `cursor.seq` is its own; a turn's request line stands where the turn started, handed over by the broker with `turn/started`, since octos writes a turn's user message only when it ends and never for a stopped turn); Stop says "interrupted" once; the system agent is given each peer's slug for `peer_send_input`; and a script app's agent may keep octos's `ask_user_question` (App Hub's one kernel tool for contained apps, `KERNEL_TOOLS`, "Ask you questions" in the store), handed to its peer's `generic_tools` by G3. News's agent keeps it, and its questions of the person's lane are asked in "Ask News". -- [x] **Process launch review (2026-09-30)**: done in this change. Process apps are built from the workspace with `--locked` and started directly (no `cargo run` in the app's directory; section 2); the build's paths are read-only in every app's sandbox, macOS profiles and client logs moved into the OctoSense home, login items unwritable (section 3); the desktop catalog rows are checked against `native-apps.json`; a slot whose child was reaped never signals that process group again; the third close within 5 s ends an app that keeps refusing (section 2); `terminal.run` needs a launch that reported its sandbox (sections 10, 12). Still open from it: the Linux Landlock rules do not apply `read_only` yet; AppCard opened with `MAKEPAD_WM_TEST_APP=appcard` in a hidden shell leaves the shell with no window, on `main` too (not caused by this change; not investigated). +- [x] **Process launch review (2026-09-30)**: done in this change. Process apps are built from the workspace with `--locked` and started directly (no `cargo run` in the app's directory; section 2); the build's paths are read-only in every app's sandbox, macOS profiles and client logs moved into the OctoSense home, login items unwritable (section 3); the desktop catalog rows are checked against `native-apps.json`; a slot whose child was reaped never signals that process group again; the third close within 5 s ends an app that keeps refusing (section 2); `terminal.run` needs a launch that reported its sandbox (sections 10, 12). Still open from it: ~~the Linux Landlock rules do not apply `read_only` yet~~ (done, [#238](https://github.com/OctoSense-org/OctoSense/pull/238)); AppCard opened with `MAKEPAD_WM_TEST_APP=appcard` in a hidden shell leaves the shell with no window, on `main` too (not caused by this change; not investigated). *Still open after the implementation (2026-09-29):* the Windows AppContainer sandbox ([#137](https://github.com/OctoSense-org/OctoSense/issues/137)); ~~the Linux sandbox has run only in CI~~ (it had never run: no CI job runs the shell's tests on Linux; run on Linux 7.0 with Landlock ABI 8, with Landlock hidden, and with the Terminal sandboxed next to a real octos kernel on Wayland + Vulkan on 2026-09-30, which found and fixed a program reached through a link that could not start (a `cargo run` launch that sandboxed its build with the checkout, the shell's binary and `~/.cargo` writable was found too, and went with the process-launch review's direct launch), [#138](https://github.com/OctoSense-org/OctoSense/issues/138), [#199](https://github.com/OctoSense-org/OctoSense/pull/199)); on Linux a `network: none` process app still reaches local Unix sockets by path (the session bus among them) and TCP to the hub's port on any address, but no other IP or socket family since [#202](https://github.com/OctoSense-org/OctoSense/pull/202); ~~process apps see the system agent's lane only through history until Makepad's peer client reads `conversation` frames; a second message on one conversation handle is refused rather than queued~~ (done in Makepad's client, [makepad#77](https://github.com/OctoSense-org/makepad/pull/77), in the runtime since the makepad e29a0eaa pin; see the gaps below); Rinx still has to send turn triggers and hand over its send sheet; ~~the OpenHarmony build with octos#2619's BoringSSL (`btls-sys`) is untested~~ (2026-09-30: no OctoSense build links it. octos-cli is taken with `default-features = false` and `api,git,ast`, so octos-research's `impersonate` (wreq, BoringSSL) is not in the graph: `cargo tree -p octosense-home --target aarch64-unknown-linux-ohos -i wreq` finds nothing, and the Android kernel artifact builds with the same features. The OpenHarmony Home HAP built with `rom/scripts/build-home-ohos.py` at the octos pin fe08d8e6, which includes #2619. Installing and running it is **unverified**: no OpenHarmony device was connected. A consequence to know: phones and the in-process kernel have no browser-like client for Google's results page). @@ -386,7 +392,6 @@ Gaps between this decision and `main` found after it was accepted (2026-09-28; s - [x] **The `peer/input` route belongs to whichever connection registered the peer's tools last**: octos `set_host_route` (`crates/octos-cli/src/peers/host_tools.rs`) replaces any earlier route. Untested with more than one instance of an app. Done: one instance drives an app's peer (section 5, "One driver per app peer"). In the shell every broker shares the kernel's one connection, and its router hands a peer session's frames to every consumer that named the session. So before this, two instances of one app (two windows of one module) each started the system agent's `peer/input` and each ran the peer's tool calls, and each drew its approvals. Tested in `crates/app-peers/tests/broker.rs` (`two_instances_of_one_app_drive_the_peer_once_and_hand_over_on_close`, scripted kernel, two connections); a real-kernel run with two instances is **unverified**. Not changed: the relay keeps one executor per app (`set_executor`), so an in-process module with two instances that each set one keeps the last. Only Rinx sets one, and it runs one instance per process. - [x] **Script apps never take a native app's name; App Hub's manifest carries ADR 0004's fields** (2026-09-30, from the ADR 0004 code review; [App-Hub#44](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/44) and the App Hub pin after it). A store app could use a native app's id (`terminal`, `rinx`, `apphub`; App Hub reserved only `os.`), and the shell keys a script app's jail and storage folders, spec, tool declarations, executor and consent by that id, and its tools by the id's last segment: it stood in for the Terminal or Rinx. Now App Hub refuses an id, or an id's last segment, that is a native app's or a name the shell acts under (`RESERVED_NAMES`: `system`, `toolbox`, `workflow`, `dev`, `agents`, `octos`, `shell`, `card`, `os`) at its gate, install and admission (`policy::resolve`, `AgentBundle::load`), and the shell checks again against `native-apps.json` (`apps::check_script_app_id`) before it opens a card, loads or installs a script app's tools, sets its storage spec or deletes its folders on uninstall; a shell test fails if `RESERVED_NAMES` lacks a native app. App Hub's `storage` block takes section 11's fields, and its `tools.json` takes `outward` (waits for the person like a destructive tool) and `auto_approvable` (`false`: no standing rule), which the shell passes on (`outward` to the kernel, `auto_approvable` to the approval router). Still open: a store app may still share a system app's namespace (`dev.example.mail` declares `mail.*`), and the relay resolves a dotted grant by name across every owner (`Catalog::owner_of`); ~~Mail's bundle declares no `storage.accounts` yet, and contained peers still use the account `device`~~ (done: Mail declares `storage.accounts: true`, and each Mail account has its own agent identity: its memory namespace, its folder as workspace, its suspension, its startup-check refusal and its conversation's `read_parent` are all keyed by the account, through one rule in `crates/shell/src/host_tools/mod.rs` (`keeps_accounts`/`account_key`: a native entry's `accounts`, or a script app's manifest `storage.accounts`). **One account is active at a time**: a contained app has one live peer, bound to the account the person signed in to last (Mail's `signed_in` time; an account kept before that field counts as the oldest), and rebound when Mail adds or removes an account; rebinding revokes the previous account's open contexts, and that account's agent resumes, with its own memory, when it is active again. Why not one live agent per account: a script app's octos calls name no account (the Card runner has none), so the shell could not tell which agent a call is for, and every live peer holds a kernel connection. The storage block is read from the manifest before any agent is prepared (`lifecycle::prepare_agent_with`), so an agent prepared before the app was opened binds the right account. Removing a Mail account suspends that account's agent and deletes its folder; `should_suspend_mails_agent_and_delete_its_folder_when_its_account_is_removed` runs it with the shipped manifest.) - [x] **In-process modules use the peer-link client** (section 5, 2026-09-30, [#142](https://github.com/OctoSense-org/OctoSense/issues/142)): the module host picks up Makepad's parked peer links and the shell serves them with the process apps' peer link, so an app that uses `makepad_ai_services::peer::OctosPeer` works the same hosted in-process or as a process. The relay runs an app's tools on its executor when it has one (a module that opens the link only to talk keeps its tools there) and down its link otherwise. A module granted no agent holds no link; its requests are answered `no_agent`. A link opened inside a nested call into another module stays the outer module's. Still open: Rinx uses the injected `OctosAppService` until it moves to the client (a Rinx change); the bridge passes the shell's `conversation` frames to a module as to a process, so a module follows both lanes wherever the pinned Makepad client reads them. -- [x] **A shell-native chat with the system agent**: done. `crates/shell/src/system_chat/` (Setup → Assistant → Assistant chat, F8; full screen on a phone) talks to `_main:api:octosense#system` over the shell's own kernel connection, and its approvals go through the approval router, batched per request. - [x] **A shell-native chat with the system agent**: done. `crates/shell/src/system_chat/` (Setup → Assistant → Assistant chat, F8, the desktop dock's Assistant icon right after Applications, or the phone home's Assistant tile beside the group tiles ([#143](https://github.com/OctoSense-org/OctoSense/issues/143)); full screen on a phone) talks to `_main:api:octosense#system` over the shell's own kernel connection, and its approvals go through the approval router, batched per request. ## Open questions diff --git a/docs/adr/0005-app-contract.md b/docs/adr/0005-app-contract.md index e2fd2dfe..2660eb5b 100644 --- a/docs/adr/0005-app-contract.md +++ b/docs/adr/0005-app-contract.md @@ -62,8 +62,8 @@ Within `1.x`: - `octosense-app-contract` is published to a registry, so consumers write `octosense-app-contract = "1"` and Cargo resolves one `1.x` for the whole build. No lockstep, no duplicate copies, no host-alias tricks. - **Registry: crates.io (decided 2026-09-30).** App Hub's source is already public, and Rinx, OctoSense and third-party apps can depend on it with no credentials or setup. A published version can never be deleted, only yanked, so releases go through review. A private registry was the alternative, rejected because every consumer's CI and machine would need a token and outside app developers could not use it. Plain git dependencies cannot work: Cargo never unifies two git commits. -- OctoSense's root `Cargo.toml` states `octosense-app-contract = "1"`, and its `Cargo.lock` records the exact version it ships with (1.0.0 today); apps state the lowest `1.x` they need. Inside OctoSense, OctoSense's lock is what links. -- Rinx's CI should build against the lowest and the highest `1.x` it claims. *Not yet in place (2026-10-01): with only 1.0.0 published both are the same version; the check joins Rinx's CI (`main.yml`) with the first 1.1.0.* +- OctoSense's root `Cargo.toml` states `octosense-app-contract = "1"`, and its `Cargo.lock` records the exact version it ships with (1.0.0 today; *2026-10-04: 1.1.0 since [#312](https://github.com/OctoSense-org/OctoSense/pull/312)*); apps state the lowest `1.x` they need. Inside OctoSense, OctoSense's lock is what links. +- Rinx's CI should build against the lowest and the highest `1.x` it claims. *Not yet in place (2026-10-01): with only 1.0.0 published both are the same version; the check joins Rinx's CI (`main.yml`) with the first 1.1.0.* *(2026-10-04: 1.1.0 is published; the check is still not in Rinx's CI.)* ### 4. Breaking changes @@ -71,7 +71,7 @@ A change that cannot be additive becomes `octosense-app-contract 2.0`. Hosts sup ### 5. Who changes the contract -The contract crate lives in the App Hub repository under `crates/app-contract`, with its own changelog. A pull request that touches it needs review from App Hub and from one app owner (Rinx). *Not yet enforced (2026-10-01): App Hub has no CODEOWNERS; the rule is followed by hand until a CODEOWNERS entry for `crates/app-contract` is added.* CI runs: the fixture corpus, a public-API diff (`cargo public-api` or `cargo semver-checks`) that fails on any non-additive change within `1.x`, and Rinx's build against the new version. +The contract crate lives in the App Hub repository under `crates/app-contract`, with its own changelog. A pull request that touches it needs review from App Hub and from one app owner (Rinx). *Not yet enforced (2026-10-01): App Hub has no CODEOWNERS; the rule is followed by hand until a CODEOWNERS entry for `crates/app-contract` is added.* CI runs: the fixture corpus, a public-API diff (`cargo public-api` or `cargo semver-checks`) that fails on any non-additive change within `1.x`, and Rinx's build against the new version. *(2026-10-04: not in place for Rinx: App Hub's CI runs the fixtures and `cargo semver-checks`, but builds no Rinx.)* ## Consequences diff --git a/docs/adr/README.md b/docs/adr/README.md index 42a5d345..a71b1c87 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -9,16 +9,16 @@ How these decisions fit together in the code on `main`, and which parts are stil | ADR | Title | Status | | --- | --- | --- | | [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 | +| [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; partly implemented | | [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 | Implemented | +| [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 | Implemented; three plan items open | | [0005](0005-app-contract.md) | The app contract: one small, versioned interface between App Hub and every app | Implemented | | [0006](0006-app-studio-on-the-phone.md) | App Studio on the phone | Accepted | | [0007](0007-composable-mail-action-cards.md) | Composable Mail cards with editing, chat and approved actions | Implementation in progress; phone acceptance pending | ## Home (phone shell) decisions, 2026-09-16 to 2026-09-25 -Written in OctoSense-ROM (retired; merged into this repository) `home/docs/adr/` before the repositories merged, and kept here unchanged as history under [`home/`](home/). They keep their own numbers; cite them as "Home ADR 0004". Where one names a path such as `home/src/` or `home/apps/`, read `crates/shell/src/` (the shell; Settings is in `phone/src/`) and `apps/` (see ADR 0001). Their status is as they recorded it. +Written in OctoSense-ROM (retired; merged into this repository) `home/docs/adr/` and kept here as history under [`home/`](home/); on 2026-09-28 Home 0002 and 0004 gained dated amendments and Home 0004's implementation-status line and last Consequences bullet were updated; on 2026-10-04 Home 0001 and 0002 gained dated notes. Cite them as "Home ADR 0004"; inside them "ADR 000N" means a Home ADR, except in those 2026-09-28 additions (this repository's ADR 0004). Paths are relative to the old `home/` or belong to other repositories: `src/` → `crates/shell/src/` (Settings: `phone/src/`), `resources/` and `android/` → `phone/resources/` and `phone/android/`, `octosense-rom/` → `rom/`, OctoSense-System-Apps `apps/` → `apps/` (ADR 0001); `crates/app-policy` and `crates/app-hub-app` are App Hub's; `apps/calendar/cards/` was Octoscript-AppCard's. Their status is as they recorded it. | Home ADR | Title | Date | Status | | --- | --- | --- | --- | diff --git a/docs/adr/README.zh-CN.md b/docs/adr/README.zh-CN.md index 1757d921..4bd8c8d4 100644 --- a/docs/adr/README.zh-CN.md +++ b/docs/adr/README.zh-CN.md @@ -9,16 +9,16 @@ OctoSense 仓库的决策:Shell、Shell 服务、系统应用,以及桌面 | ADR | 标题 | 状态 | | --- | --- | --- | | [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)(英文) | 原生应用、应用智能体与跨应用协作:一份清单、按目标平台托管、每个应用都有智能体、由本人批准 | 已实施 | +| [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)(英文) | 原生应用、应用智能体与跨应用协作:一份清单、按目标平台托管、每个应用都有智能体、由本人批准 | 已实施;三项计划未完成 | | [0005](0005-app-contract.md)(英文) | 应用契约:App Hub 与每个应用之间一个小而带版本的接口 | 已实施 | | [0006](0006-app-studio-on-the-phone.md)(英文) | 手机上的 App Studio | 已接受 | | [0007](0007-composable-mail-action-cards.zh-CN.md) | 可组合的 Mail 卡片:编辑、聊天与经批准的操作 | 实现中;手机验收待完成 | ## Home(手机 Shell)的决策,2026-09-16 至 2026-09-25 -这些记录在仓库合并前写于 OctoSense-ROM(已停用,并入本仓库)的 `home/docs/adr/`,现原样作为历史保存在 [`home/`](home/) 下。它们保留原编号,引用时写作“Home ADR 0004”。文中出现 `home/src/`、`home/apps/` 等路径时,对应现在的 `crates/shell/src/`(Shell;设置应用在 `phone/src/`)和 `apps/`(见 ADR 0001)。状态为当时记录的状态。 +这些记录写于 OctoSense-ROM(已停用,并入本仓库)的 `home/docs/adr/`,现作为历史保存在 [`home/`](home/) 下;2026-09-28 Home 0002 和 0004 增加了注明日期的修订,Home 0004 的实施状态行和最后一条 Consequences 也已更新;2026-10-04 Home 0001 和 0002 增加了注明日期的说明。引用时写作“Home ADR 0004”;文中的“ADR 000N”指 Home ADR,2026-09-28 增补内容中的除外(指本仓库 ADR 0004)。路径相对于原 `home/` 目录或属于其他仓库:`src/` → `crates/shell/src/`(设置应用在 `phone/src/`),`resources/`、`android/` → `phone/resources/`、`phone/android/`,`octosense-rom/` → `rom/`,OctoSense-System-Apps 的 `apps/` → `apps/`(见 ADR 0001);`crates/app-policy`、`crates/app-hub-app` 属于 App Hub;`apps/calendar/cards/` 曾属于 Octoscript-AppCard。状态为当时记录的状态。 | Home ADR | 标题 | 日期 | 状态 | | --- | --- | --- | --- | diff --git a/docs/adr/home/0001-hybrid-android-launcher-and-system-bridge.md b/docs/adr/home/0001-hybrid-android-launcher-and-system-bridge.md index 7fbcea18..f7a8ffca 100644 --- a/docs/adr/home/0001-hybrid-android-launcher-and-system-bridge.md +++ b/docs/adr/home/0001-hybrid-android-launcher-and-system-bridge.md @@ -2,6 +2,7 @@ - **Date:** 2026-09-16 - **Status:** Accepted +- **Note (2026-10-04):** Since this record, the OctoSense ROM ships Home, the System Bridge and the native Quickstep fork as platform-signed privileged apps without Trebuchet (`rom/vendor/octosense/octosense.mk`); see the [implementation record](../../../phone/docs/android/adr-0001-implementation-record.md). The implementation status below and the platform-key assumption are history. - **Implementation status:** In progress; Home/bridge prototypes and the separate Quickstep layout-service prototype are installed on the approved OnePlus 6. Native API/Binder checks, Android/hosted icon placement storage/UI, public launch with the bridge service unavailable, activity/process recreation, package replacement/reconnection, widget pages, native Done input, current-user shortcut pin/icon/launch, synthetic notification label/icon rendering and cleanup have phone evidence. Home's visible-icon geometry is acknowledged across the authenticated service, with revision/invalidation and same-signer foreign-package rejection checks passing. The ROM-matched native Quickstep/Recents APK now compiles and passes packaging inspection; a deployment module is prepared. Native installation, app-to-Home transitions, broader lifecycle validation and performance parity remain pending; no milestone exit gate is complete. See the [implementation record](../../../phone/docs/android/adr-0001-implementation-record.md). - **Scope:** OctoSense-mobile Android architecture; native-launcher parity and optional system controls - **Initial target:** OnePlus 6, Android 15 / API 35, LineageOS 22.2 diff --git a/docs/adr/home/0002-agentic-app-security-model.md b/docs/adr/home/0002-agentic-app-security-model.md index e699b9d9..2f156846 100644 --- a/docs/adr/home/0002-agentic-app-security-model.md +++ b/docs/adr/home/0002-agentic-app-security-model.md @@ -2,7 +2,7 @@ - **Date:** 2026-09-19 - **Status:** Proposed -- **Implementation status:** Phases 1 to 4 built on 19–20 Sep 2026; phase 5 partly. **Phase 1**: `octosense-app-policy` (`crates/app-policy` in the OctoSense-App-Hub repository) — the manifest, its admission and its resolution into both containers, 27 tests. **Phases 2–4, in the runtime** (Makepad fork, uncommitted): `widgets/src/splash_policy.rs` holds a per-heap policy the host sets once; the `host` bridge now REFUSES a `host.request` outside the granted capabilities before it is queued (§2); a URL gate in `makepad-script-std` is consulted by all three ways out of an isolate — `net.http_request`, `res.http_resource` (artwork) and the `sys.*` data fetches — against the app's exact host list, with `host:port` entries so a host can admit its own asset origin and no other loopback service (§3); a cumulative instruction budget is charged after every evaluation, callback and hook, and an exhausted isolate runs nothing further; the heap ceiling is applied to the isolate's script heap (§4). An isolate the host never put under a policy behaves exactly as before. Verified on a real card: a bundle whose artwork pointed at an outside server drew without its icons and logged nine refusals with zero outbound requests; the same card with bundled artwork drew fully. **Phase 5**: every agent session profile carries the app id and version as provenance; ledger-side tagging and cross-app reads as a granted capability are not built, because no shared ledger exists in these trees yet. Still open: per-app widget vocabulary (registration is process-wide), and a real signature verifier lives in the hub crate rather than here. +- **Implementation status:** Phases 1 to 4 built on 19–20 Sep 2026; phase 5 partly. **Phase 1**: `octosense-app-policy` (`crates/app-policy` in the OctoSense-App-Hub repository) — the manifest, its admission and its resolution into both containers, 27 tests. **Phases 2–4, in the runtime** (Makepad fork, uncommitted) *(2026-10-04: committed in the fork as 03a0362fe on 2026-09-20.)*: `widgets/src/splash_policy.rs` holds a per-heap policy the host sets once; the `host` bridge now REFUSES a `host.request` outside the granted capabilities before it is queued (§2); a URL gate in `makepad-script-std` is consulted by all three ways out of an isolate — `net.http_request`, `res.http_resource` (artwork) and the `sys.*` data fetches — against the app's exact host list, with `host:port` entries so a host can admit its own asset origin and no other loopback service (§3); a cumulative instruction budget is charged after every evaluation, callback and hook, and an exhausted isolate runs nothing further; the heap ceiling is applied to the isolate's script heap (§4). An isolate the host never put under a policy behaves exactly as before. Verified on a real card: a bundle whose artwork pointed at an outside server drew without its icons and logged nine refusals with zero outbound requests; the same card with bundled artwork drew fully. **Phase 5**: every agent session profile carries the app id and version as provenance; ledger-side tagging and cross-app reads as a granted capability are not built, because no shared ledger exists in these trees yet. Still open: per-app widget vocabulary (registration is process-wide), and a real signature verifier lives in the hub crate rather than here. - **Scope:** What may run inside the OctoSense shell, what contains it, and what contains the agent that acts on its behalf. Covers the phone (one shell process, modules linked in) and the desktop (same shell, process hosting available). Does not cover the ROM's own privilege model, which is [ADR 0001](0001-hybrid-android-launcher-and-system-bridge.md). - **Initial target:** OnePlus 6, Android 15 / API 35, LineageOS 22.2; macOS and Linux desktop. diff --git a/docs/ai-services.md b/docs/ai-services.md index df066a4f..f7653291 100644 --- a/docs/ai-services.md +++ b/docs/ai-services.md @@ -4,11 +4,9 @@ English | [简体中文](ai-services.zh-CN.md) How the assistant is wired into OctoSense: the octos kernel, provider configuration, app peers and tools. Use the [Cargo.toml](../Cargo.toml) dependency pins when following external code. For calls and the execution model, read the [architecture walkthrough](architecture-walkthrough.md). Status descriptions refer to this checkout. Dated runs below are historical validation records. -Earlier source reviews took place on 2026-09-28 (OctoSense `ad0d738`) and 2026-09-29 (`baa90bd`). Those dates do not date the current status table. - This page is about the assistant *inside* OctoSense. Building an app needs no AI service and no particular coding agent: the app harness, [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow), works with any agent or none. Its [AI-SERVICES](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/AI-SERVICES.md) page is the app developer's short version of this one. -For the whole system around it (processes per platform, agents, protocols, tools and grants, approvals, storage and trust boundaries), see [OctoSense architecture](architecture.md). This page does not repeat it: the native app manifest (`native-apps.json`), the system agent's tool set, the approval router, first-use consent and developer mode are described there and only linked from here. +For the whole system around it (processes per platform, agents, protocols, tools and grants, approvals, storage and trust boundaries), see [OctoSense architecture](architecture.md). This page summarizes the parts the AI services touch, such as the system agent's tool set and first-use consent; the native app manifest (`native-apps.json`), the approval router and developer mode are described there. ## Contents @@ -27,17 +25,17 @@ For the whole system around it (processes per platform, agents, protocols, tools | One octos kernel per shell process, started on first use, restarted after a provider change | Works today (desktop with its packaged `octos-kernel` or `OCTOS_APP_CORE_BIN`, Android, OpenHarmony; none on iOS) | [`crates/kernel`](../crates/kernel/README.md) | | AI providers: the person's model providers and keys, keys only on host sheets | Works today | [`apps/ai-providers`](../apps/ai-providers/host-service/README.md) | | The shell's AI entry point (`start`, policy, per-instance offer, QR import) | Works today | [`crates/ai-host`](../crates/ai-host/README.md) | -| Host-owned app peers: one octos peer per granted app/account, owned by the system agent | Implemented for native modules (Rinx) and contained script apps after per-app consent | [`crates/app-peers`](../crates/app-peers/README.md), [Rinx ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md) | +| Host-owned app peers: one octos peer per granted app/account, owned by the system agent | Implemented, after per-app consent, for Rinx (injected service); the Terminal, Calculator, Clock, Notes, Reminders and Weather (peer link; the peer belongs to the open instance); and contained script apps (prepared once allowed, and at each later startup) | [`crates/app-peers`](../crates/app-peers/README.md), [Rinx ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md) | | AppCard ("Ask anything") on the shell's kernel | Works today, opt-in (`--features app-appcard`), not shipped | [`apps/appcard`](../apps/appcard) | | A contained script app (system or store) asking the assistant | Implemented through exact declared `octos.*` services, with a kernel and first-use consent by default. Host tools and kernel-tool approvals route through the shell. `OCTOSENSE_CONTAINED_APPS=0` disables access; `1` is a developer consent bypass, not normal setup | [below](#contained-script-apps-system-and-store) | | One-shot model calls for contained apps (`model`, `model.complete`) | Works today ([#95](https://github.com/OctoSense-org/OctoSense/pull/95)): registered by `crates/ai-host` with the `llm` service, for apps granted `model` ([App-Hub#24](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/24), in the shells' App Hub pin) | [below](#contained-script-apps-system-and-store) | | News data service (`news`, no model) | Merged ([#69](https://github.com/OctoSense-org/OctoSense/pull/69)); answers `os.*` apps only | [`apps/news/host-service`](../apps/news/host-service/README.md) | -| `glance.publish`: L0/L1 source cards or Splash script cards | Merged ([#72](https://github.com/OctoSense-org/OctoSense/pull/72)); contained apps granted the `glance` capability publish ([#86](https://github.com/OctoSense-org/OctoSense/pull/86)). `sys.digest` integration is a separate runtime path; [#87](https://github.com/OctoSense-org/OctoSense/pull/87) is historical tracking, not a current PR-status claim | [`crates/shell/src/glance.rs`](../crates/shell/src/glance.rs) | +| `glance.publish`: L0/L1 source cards or Splash script cards | Merged ([#72](https://github.com/OctoSense-org/OctoSense/pull/72)); contained apps granted the `glance` capability publish ([#86](https://github.com/OctoSense-org/OctoSense/pull/86)). `sys.digest` integration is a separate runtime path | [`crates/shell/src/glance.rs`](../crates/shell/src/glance.rs) | | Approvals, first-use consent, developer mode | Works today ([#120](https://github.com/OctoSense-org/OctoSense/pull/120), [#118](https://github.com/OctoSense-org/OctoSense/pull/118)): the shell's approval router, standing rules and sheets, fed by the AI services bus and by the kernel's `host_tool` approvals through the host-tool relay ([#145](https://github.com/OctoSense-org/OctoSense/pull/145)); consent before an app's agent first runs; developer mode turned on only by the person | [architecture § Approvals](architecture.md#5-approvals) | -| The system agent's tool set | Works today, enforced: octos's shell is never offered (the `_main` profile's `tool_policy`, [#117](https://github.com/OctoSense-org/OctoSense/pull/117)), and every kernel start sets the system session's exact kernel tool list (`session/tool_list/set`, octos#2648). Command execution is the Setup → Assistant → Command execution switch ([#132](https://github.com/OctoSense-org/OctoSense/pull/132)): `terminal.run`, each command approved live, in both module and process hosting where Terminal is available | [architecture § Tools and grants](architecture.md#4-tools-and-grants) | +| The system agent's tool set | Works today, enforced: octos's shell is never offered (the `_main` profile's `tool_policy`, [#117](https://github.com/OctoSense-org/OctoSense/pull/117)), and every kernel start sets the system session's exact kernel tool list (`session/tool_list/set`, octos#2648). Command execution is the Setup → Assistant → Command execution switch ([#132](https://github.com/OctoSense-org/OctoSense/pull/132)): `terminal.run`, each command approved live, offered only while the Terminal runs as its own sandboxed process | [architecture § Tools and grants](architecture.md#4-tools-and-grants) | | Native apps declared once (`native-apps.json`), hosting per target; the Terminal a system app in its own process on the desktop | Works today ([#113](https://github.com/OctoSense-org/OctoSense/pull/113)) | [architecture § Native apps](architecture.md#native-apps-in-process-or-their-own-process) | -| An app's own agent: declarations versus execution | `tools.json`, generic-tool grants, peers and host-service executors are implemented for News/Mail/Calendar/Photos/Maps/Camera/YouTube. `AGENT.md` prompt loading, bundle skills/model selection and automatic triggers remain gaps | [below](#planned-event-driven-app-agents-adr-0002) | -| Native apps in their own process reaching their agent (the peer link, ADR 0004 §5) | Makepad’s client and the shell’s `crates/shell/src/peer_link/` are implemented. No process app currently requests its own agent; Terminal declares tools but does not request a peer. | [architecture § An app and its own agent](architecture.md#an-app-and-its-own-agent) | +| An app's own agent: declarations versus execution | `tools.json`, generic-tool grants, peers and host-service executors are implemented for News/Mail/Calendar/Photos/Maps/Camera/YouTube. Admitted `AGENT.md` and skill text reach every turn as host text, and Mail's `mail.messages.new` trigger runs; model selection and other apps' triggers remain gaps | [below](#planned-event-driven-app-agents-adr-0002) | +| Native apps reaching their agent through the peer link (ADR 0004 §5) | Makepad’s client and the shell’s `crates/shell/src/peer_link/` are implemented. Calculator, Clock, Notes, Reminders and Weather open their agent with `OctosPeer` in process; the Terminal opens it through the runtime patch `makepad-terminal-own-agent`. Its agent may call only `terminal.read_screen` and `terminal.read_scrollback` (`agent.own_tools`); `terminal.run` stays the system agent's. | [architecture § An app and its own agent](architecture.md#an-app-and-its-own-agent) | ## Architecture @@ -50,16 +48,18 @@ flowchart TB llm["llm host service
writes the provider profile"] aihost["crates/ai-host
start, policy, offer"] broker["crates/app-peers broker
one peer per granted app/account"] - runner["Card runner (App Hub)
host services: mail, news, glance, llm, model, octos"] + runner["Card runner (App Hub)
host services: mail, calendar, news, glance, llm, model, octos"] router["Approval router
crates/shell/src/approvals"] relay["Shell host-tool relay
declared tools and caller checks"] bus["AI services bus
crates/shell/src/ai_bus.rs"] rinx["Rinx (native module)"] + natives["Calculator, Clock, Notes,
Reminders, Weather (native modules)"] appcard["AppCard (native, opt-in)"] scripts["Script apps (system and store)"] end vault[("Platform secret store
or owner-only profile")] kernel[("octos kernel, one per shell
profile _main, system agent session")] + term["Terminal (process app on the desktop)"] person --> providers person --> sheet providers -- "llm.*" --> llm @@ -70,6 +70,8 @@ flowchart TB aihost --> broker broker -- "peer/prepare, peer/context/open, turn/start" --> kernel rinx -- "scoped OctosAppService" --> broker + natives -- "OctosPeer, peer link" --> broker + term -- "OctosPeer, peer link" --> broker appcard -- "its own connection" --> kernel scripts -- "host.request" --> runner runner -- "octos.* service
broker identity card.<app id>" --> broker @@ -103,7 +105,7 @@ With Talk to Octos enabled, the kernel uses host-managed loopback WebSocket tran It is a contained script app like any other; the privileged half is the **`llm` host service** ([`apps/ai-providers/host-service`](../apps/ai-providers/host-service/README.md)): - It writes the kernel's profile, `/profiles/_main.json` (`config.llm` and the key variables in `config.env_vars`), then restarts the kernel. -- Keys go to the macOS login keychain (service `octos`), on desktop Linux to `/secrets/` (0600), on Android, iOS and HarmonyOS into the app-private profile (0600): octos has no secret store on Android and iOS, and HarmonyOS's embedded kernel would look for one under the shell's own `HOME`. `OCTOSENSE_LLM_VAULT=file` keeps them in the profile for development. +- Keys go to the macOS login keychain (service `octos`), on desktop Linux to `/secrets/` (0600), on Android, iOS, HarmonyOS and Windows into the app-private profile (0600): octos has no secret store on Android and iOS, and HarmonyOS's embedded kernel would look for one under the shell's own `HOME`. `OCTOSENSE_LLM_VAULT=file` keeps them in the profile for development. - Keys, PINs and QR codes are typed, drawn and scanned only on the service's **host sheets**. Only a sheet may call `llm.sheet.*`; the app sees masked status (`"set ••••1234"`, `"missing"`). - It serves **`os.*` apps only** (`"llm is for OctoSense's own apps."`). It manages providers; it has no method that sends a prompt to a model. @@ -111,7 +113,7 @@ It is a contained script app like any other; the privileged half is the **`llm` [`crates/ai-host`](../crates/ai-host/README.md) (`octosense-ai-host`) is what both shells call: `start(Host::platform(data_dir))` at startup (configures the kernel, installs the host policy, registers `llm` with the platform's QR import), `handle_event` every event, `offer(module, scope)` / `finish()` around a native module's `create`, and `shutdown()`. -The host **policy** grants exact native `octos.*` services from the generated `native-apps.json` declarations (currently Rinx). +The host **policy** grants exact native `octos.*` services from the generated `native-apps.json` declarations (Rinx, the Terminal, Calculator, Clock, Notes, Reminders and Weather). #### Contained-app consent @@ -143,7 +145,7 @@ The system agent owns the app peers and uses `peer_send_input` to delegate, then The person reaches the system chat through F8 or the shell's Assistant entry (`crates/shell/src/system_chat/`), and can talk directly to an app through the desktop “Ask <app>” panel or a card's `sys.chat`. Phone touch navigation has no Ask-app panel entry yet. -The system agent has a restricted kernel roster (`system_tools.rs`) and selected shell tools such as opt-in `terminal.run`; it does not inherit every app tool and cannot approve for the person. Talk to Octos is a separate client-access option. Automatic trigger orchestration, learning overlays and richer glance ranking remain planned; cards currently sort by priority and recency. +The system agent has a restricted kernel roster (`system_tools.rs`) and the host tools `agents.list`, `agents.ask`, `agents.provision` and `agents.status` (the last two accept only `os.mail`), opt-in `terminal.run`, and native apps' shared read tools (`agent.system_tools`); it does not inherit every app tool and cannot approve for the person. Talk to Octos is a separate client-access option. Trigger orchestration beyond Mail, learning overlays and richer glance ranking remain planned; cards currently sort by priority and recency. ### Other assistants in the shell @@ -165,7 +167,7 @@ The system agent has a restricted kernel roster (`system_tools.rs`) and selected ### Native modules (Rinx-style) -A native module is trusted Rust linked into the shell. **Rinx** ([hagency-org/Rinx](https://github.com/hagency-org/Rinx)) is the reference and, under `Policy::shipped()`, the only one granted the assistant. +A native module is trusted Rust linked into the shell. **Rinx** ([hagency-org/Rinx](https://github.com/hagency-org/Rinx)) is the reference, and the only module that claims the injected `OctosAppService`; the other native apps with an agent use the peer link. 1. The module lists the exact services in `capabilities()`, for example all four of `octosense_app_peers::OCTOS_SERVICES`. 2. The shell calls `ai_host::offer(module, &scope)` before `module.create` and `offer.finish()` after it; the returned `Assistant` lives with the instance and releases it when dropped. @@ -207,7 +209,7 @@ The four `octos.*` calls have separate argument, origin, approval and reply rule - **Arguments.** `octos.turn.start` requires non-blank `text` of at most 32 KiB and accepts optional `trigger` (`person`, `app` or `incoming`) and `from`. The other calls accept only `{}`. - **Origin.** `trigger: "person"` is classified as `AppSaysPerson`. It is an app assertion, not a trusted human gesture, and cannot bypass approval. -- **Approvals.** Both host-tool and other peer approvals route to the shell. Existing grants and approval rules still apply; developer mode answers for the apps it covers. A host without a router declines requests and lists them in `denied_approvals`. See [`host_approvals.rs`](../crates/app-peers/src/host_approvals.rs). +- **Approvals.** Both host-tool and other peer approvals route to the shell. Existing grants and approval rules still apply; developer mode answers for the apps it covers. A host without a router declines requests and lists them in `denied_approvals`. See [`contained.rs`](../crates/ai-host/src/contained.rs) for `denied_approvals` and [`host_approvals.rs`](../crates/app-peers/src/host_approvals.rs) for developer mode's override. - **Replies.** The host refuses replies larger than 2 MiB. The response table below lists successful calls and common errors. A script's own assistant calls use declared `octos.*` services. Separately, the shell can drive an app peer from `agent`/`tools.json` without the script declaring those UI calls. Replies on the script service path: @@ -226,7 +228,7 @@ A script's own assistant calls use declared `octos.*` services. Separately, the **Historical validation (not rerun for this revision).** The first row, the `llm` row and the `card-host` row were run in `card-host` (App Hub `362d832`) on 2026-09-27. The reply, the unsupported-arguments answer and the missing-kernel answer were run on macOS on 2026-09-28 in a release desktop with hidden windows, through a system app declaring `octos.session.open` and `octos.turn.start` opened from the launcher; with a kernel built at the then-pinned octos `7bec0918` and the person's provider, the reply came from the peer `card.`, whose memory namespace `app/card./…` appeared in the kernel's data. The text-length and switch-off answers are covered by `cargo test -p octosense-ai-host`, through the same dispatch. OctoScript-App-Design-Flow's [AI-SERVICES](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/AI-SERVICES.md) has the example app and the argument and answer shapes of the four `octos.*` calls as Rinx serves them. -The manifest's `agent` and admitted `tools.json` participate in shell agent discovery, tool loading and grants. News/Mail/Calendar/Photos/Maps/Camera/YouTube have working host-service-backed declarations. The `profile`/model requirements, `AGENT.md` prompt content, bundle skills and automatic triggers are not fully consumed; `implemented_by: "app"` has no script executor. Mail currently declares only `mail.notify`, not its UI's read/send methods. The agent workspace does not automatically expose host-service databases. Rinx mini-app admission is a separate contract; do not assume App Hub metadata is supported there. +The manifest's `agent` and admitted `tools.json` participate in shell agent discovery, tool loading and grants. News/Mail/Calendar/Photos/Maps/Camera/YouTube have working host-service-backed declarations. The `profile`/model requirements and triggers other than Mail's are not consumed; `implemented_by: "app"` has no script executor. Mail declares `mail.accounts`, `mail.folders`, `mail.list`, `mail.sync`, `mail.peek`, `mail.notify`, `mail.publish_card` and `mail.skip_event`; it has no send tool. The agent workspace does not automatically expose host-service databases. Rinx mini-app admission is a separate contract; do not assume App Hub metadata is supported there. ## Planned: event-driven app agents (ADR 0002) @@ -235,13 +237,13 @@ The manifest's `agent` and admitted `tools.json` participate in shell agent disc | Piece | ADR | Status | | --- | --- | --- | | `tools.json`: names, input/output schemas, risk, confirmation, sharing and implementation | §4, §12 | Loaded by `host_tools/script_apps.rs`, registered on peers and routed to host-service executors. News, Mail, Calendar, Photos, Maps, Camera and YouTube ship declarations; AI providers has no app agent. Arbitrary `implemented_by: "app"` script execution remains unavailable. | -| `AGENT.md`, data-only skills, model requirements, `background`, triggers | §2, §3 | Admitted metadata. Prompt/skill loading, model selection from these requirements and trigger scheduling remain unimplemented in the shell. Tool background eligibility alone does not schedule a turn. | +| `AGENT.md`, data-only skills, model requirements, `background`, triggers | §2, §3 | Admitted metadata. `AGENT.md` and skill text are sent with each turn as host text (`guidance.rs`, not kernel skills), and `agent_events.rs` schedules Mail's trigger; model selection from these requirements and other triggers remain unimplemented. Tool background eligibility alone does not schedule a turn. | | Kernel: host-registered tools per peer (`peer/tools/register`, `peer/tool/call`/`result`/`cancel`), tool-list and risk enforcement, approvals only on the host connection, an allowlist of generic tools, `peer/input` | §4, §13 | Kernel side merged: [octos#2567](https://github.com/octos-org/octos/pull/2567) (UPCR-2026-035), in the pin with its follow-ups (octos#2616). The shell side is on main: [#145](https://github.com/OctoSense-org/OctoSense/pull/145) (tool registration, the relay, `peer/input`) and G3 (per-app `generic_tools` from the manifests, `tools.json` loading, grants, executors for script apps' host services, schema and budget checks; `peer/input/reject`, octos#2621). | | Tool approval policy | §4, §12 | Destructive and outward calls enter the shell’s approval router. It applies standing rules, host or registered app confirmation surfaces, and developer mode. Grants, schemas and caller budgets are checked separately. See [Approvals](architecture.md#5-approvals). | | News M1: the `news` data service (no model) | First slice | Merged ([#69](https://github.com/OctoSense-org/OctoSense/pull/69)); issue [#60](https://github.com/OctoSense-org/OctoSense/issues/60). | | News M2: `os.news` gets a peer and tools | First slice | Implemented: manifest `agent`, `news.list`/`news.read`/`news.notify` tools, shell peer preparation and relay. | | News M3: triggers and an unattended run | First slice | Planned: [#62](https://github.com/OctoSense-org/OctoSense/issues/62). | -| News M4: glance cards (`glance.publish`, the glance page and desktop panel) | §7, §8 | `glance.publish` ([#72](https://github.com/OctoSense-org/OctoSense/pull/72)) and the `glance` capability for contained apps ([#86](https://github.com/OctoSense-org/OctoSense/pull/86), with [App-Hub#22](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/22)) merged; `sys.digest` integration has separate runtime/host requirements; [#87](https://github.com/OctoSense-org/OctoSense/pull/87) is historical tracking; issue [#63](https://github.com/OctoSense-org/OctoSense/issues/63). | +| News M4: glance cards (`glance.publish`, the glance page and desktop panel) | §7, §8 | `glance.publish` ([#72](https://github.com/OctoSense-org/OctoSense/pull/72)) and the `glance` capability for contained apps ([#86](https://github.com/OctoSense-org/OctoSense/pull/86), with [App-Hub#22](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/22)) merged; `sys.digest` integration has separate runtime/host requirements; issue [#63](https://github.com/OctoSense-org/OctoSense/issues/63). | | M5: the system toolbox as granted tools | §6 | Implemented behind `toolbox-peers`: `crates/ai-host/src/toolbox_peers.rs` and the shell toolbox executor. Home enables the feature by default; desktop opts in. Tools still require admission/grants and consent; this is not a general conversation with the system agent. | | M6: render and critique (`card-studio`) | §7 | App Hub's `card-studio` crate merged ([App-Hub#19](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/19)); the skill is planned ([#65](https://github.com/OctoSense-org/OctoSense/issues/65)). | | M7: app conversations, questions and memory | §9, §10 | Implemented human/system lanes, Ask-app panel, `sys.chat`, host-routed questions/approvals and scoped peer/context memory. See the [walkthrough](architecture-walkthrough.md). | diff --git a/docs/ai-services.zh-CN.md b/docs/ai-services.zh-CN.md index 0d9db4ba..2c886597 100644 --- a/docs/ai-services.zh-CN.md +++ b/docs/ai-services.zh-CN.md @@ -4,11 +4,9 @@ 本文介绍 OctoSense 的 octos 内核、提供方配置、应用 peer 和工具。阅读外部源码时使用 [Cargo.toml](../Cargo.toml) 的依赖锁定版本。调用链与执行模型见[架构导读](architecture-walkthrough.zh-CN.md)。状态说明以当前检出为准。下方带日期的运行信息是历史验证记录。 -历史源码评审分别在 2026-09-28(OctoSense `ad0d738`)和 2026-09-29(`baa90bd`)进行;这些日期不代表当前状态表的更新时间。 - 本文讨论的是 OctoSense *内部*的助手。开发应用不需要任何 AI 服务,也不需要特定的编程 Agent:应用开发工具集 [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) 适用于任何 Agent,也可以不用 Agent。它的 [AI-SERVICES](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/AI-SERVICES.zh-CN.md) 页面是本文面向应用开发者的简短版本。 -它所处的整个系统(各平台的进程、Agent、协议、工具与授权、审批、存储和信任边界)见 [OctoSense 架构](architecture.zh-CN.md)。本文不重复那些内容:原生应用清单(`native-apps.json`)、系统 Agent 的工具集、审批路由、首次使用同意和开发者模式都在那里描述,这里只给出链接。 +它所处的整个系统(各平台的进程、Agent、协议、工具与授权、审批、存储和信任边界)见 [OctoSense 架构](architecture.zh-CN.md)。本文只概述 AI 服务涉及的部分,例如系统 Agent 的工具集和首次使用同意;原生应用清单(`native-apps.json`)、审批路由和开发者模式在那里描述。 ## 目录 @@ -27,17 +25,17 @@ | 每个 Shell 进程一个 octos 内核,首次使用时启动,提供方变更后重启 | 目前可用(有随附 `octos-kernel` 或设置了 `OCTOS_APP_CORE_BIN` 的桌面端、Android、OpenHarmony;iOS 没有) | [`crates/kernel`](../crates/kernel/README.md) | | AI providers:用户的模型提供方和密钥,密钥只在宿主面板上输入 | 目前可用 | [`apps/ai-providers`](../apps/ai-providers/host-service/README.md) | | Shell 的 AI 入口(`start`、策略、按实例提供服务、QR 导入) | 目前可用 | [`crates/ai-host`](../crates/ai-host/README.md) | -| 宿主拥有的应用 peer:每个获授权的应用/账号一个 octos peer,归系统 Agent 所有 | 已实现:原生模块(Rinx)及经过按应用同意的隔离脚本应用 | [`crates/app-peers`](../crates/app-peers/README.md)、[Rinx ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md) | +| 宿主拥有的应用 peer:每个获授权的应用/账号一个 octos peer,归系统 Agent 所有 | 已实现(均需按应用同意):Rinx(注入的服务);Terminal、Calculator、Clock、Notes、Reminders、Weather(peer link,peer 属于打开的实例);隔离脚本应用(获准后即准备,此后每次启动时也会准备) | [`crates/app-peers`](../crates/app-peers/README.md)、[Rinx ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md) | | 在 Shell 内核上运行的 AppCard("Ask anything") | 目前可用,需主动开启(`--features app-appcard`),不随产品发布 | [`apps/appcard`](../apps/appcard) | -| 隔离脚本应用向助手提问 | 已实现:精确声明的 `octos.*` 服务、内核与默认的首次使用同意;宿主工具和内核工具审批都交给 Shell。`OCTOSENSE_CONTAINED_APPS=0` 禁用;`1` 是跳过同意的开发覆盖,不是正常设置步骤 | [见下文](#隔离运行的脚本应用系统应用和商店应用) | +| 隔离脚本应用(系统应用或商店应用)向助手提问 | 已实现:精确声明的 `octos.*` 服务、内核与默认的首次使用同意;宿主工具和内核工具审批都交给 Shell。`OCTOSENSE_CONTAINED_APPS=0` 禁用;`1` 是跳过同意的开发覆盖,不是正常设置步骤 | [见下文](#隔离运行的脚本应用系统应用和商店应用) | | 供隔离应用使用的一次性模型调用(`model`,`model.complete`) | 目前可用([#95](https://github.com/OctoSense-org/OctoSense/pull/95)):由 `crates/ai-host` 与 `llm` 服务一同注册,供获得 `model` 权限的应用使用([App-Hub#24](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/24),已在 Shell 锁定的 App Hub 中) | [见下文](#隔离运行的脚本应用系统应用和商店应用) | | News 数据服务(`news`,不使用模型) | 已合入([#69](https://github.com/OctoSense-org/OctoSense/pull/69));只响应 `os.*` 应用 | [`apps/news/host-service`](../apps/news/host-service/README.md) | -| `glance.publish`:L0/L1 source 卡片或 Splash script 卡片 | 已合入([#72](https://github.com/OctoSense-org/OctoSense/pull/72));获得 `glance` 权限的隔离应用可以发布([#86](https://github.com/OctoSense-org/OctoSense/pull/86))。`sys.digest` 有单独的运行时/宿主要求;[#87](https://github.com/OctoSense-org/OctoSense/pull/87) 是历史跟踪链接,不表示当前 PR 状态 | [`crates/shell/src/glance.rs`](../crates/shell/src/glance.rs) | -| 审批、首次使用同意、开发者模式 | 已实现:Shell 审批路由、常设规则及面板接收 AI 服务总线和内核工具审批;首次运行先征求同意,开发者模式由用户开启 | [架构 § 审批](architecture.zh-CN.md#5-审批) | -| 系统 Agent 的工具集 | 已强制:不提供 octos 内置 shell;精确设置系统会话内核工具列表。用户开启命令执行后通过 `terminal.run`,每次命令实时确认;Terminal 可用时模块和进程托管均支持 | [架构 § 工具与授权](architecture.zh-CN.md#4-工具与授权) | +| `glance.publish`:L0/L1 source 卡片或 Splash script 卡片 | 已合入([#72](https://github.com/OctoSense-org/OctoSense/pull/72));获得 `glance` 权限的隔离应用可以发布([#86](https://github.com/OctoSense-org/OctoSense/pull/86))。`sys.digest` 有单独的运行时/宿主要求 | [`crates/shell/src/glance.rs`](../crates/shell/src/glance.rs) | +| 审批、首次使用同意、开发者模式 | 目前可用([#120](https://github.com/OctoSense-org/OctoSense/pull/120)、[#118](https://github.com/OctoSense-org/OctoSense/pull/118)):Shell 的审批路由、常设规则和面板,接收 AI 服务总线的调用和内核经宿主工具 relay 送来的 `host_tool` 审批([#145](https://github.com/OctoSense-org/OctoSense/pull/145));应用 Agent 首次运行前征求同意;开发者模式只能由用户开启 | [架构 § 审批](architecture.zh-CN.md#5-审批) | +| 系统 Agent 的工具集 | 已强制:从不提供 octos 的 shell(`_main` 配置的 `tool_policy`,[#117](https://github.com/OctoSense-org/OctoSense/pull/117));每次内核启动都精确设置系统会话的内核工具列表(`session/tool_list/set`,octos#2648)。命令执行是 Setup → Assistant → Command execution 开关([#132](https://github.com/OctoSense-org/OctoSense/pull/132)):`terminal.run`,每条命令实时确认,只在 Terminal 作为独立的沙箱进程运行时提供 | [架构 § 工具与授权](architecture.zh-CN.md#4-工具与授权) | | 原生应用只声明一次(`native-apps.json`),按目标平台决定托管方式;Terminal 作为系统应用在桌面端以独立进程运行 | 目前可用([#113](https://github.com/OctoSense-org/OctoSense/pull/113)) | [架构 § 原生应用](architecture.zh-CN.md#原生应用进程内还是独立进程) | -| 应用自己的 Agent:声明与执行 | News/Mail/Calendar/Photos/Maps/Camera/YouTube 的 `tools.json`、通用工具授权、peer 和宿主服务执行器已实现。`AGENT.md` 提示词加载、包内 skills/模型选择和自动触发器仍有缺口 | [见下文](#规划中事件驱动的应用-agentadr-0002) | -| 独立进程中的原生应用访问自己的 Agent(peer link) | Makepad 客户端与 Shell `crates/shell/src/peer_link/` 均已实现。当前没有进程应用申请自己的 Agent;Terminal 声明了工具但未申请 peer | [架构 § 应用与它自己的 Agent](architecture.zh-CN.md#应用与它自己的-agent) | +| 应用自己的 Agent:声明与执行 | News/Mail/Calendar/Photos/Maps/Camera/YouTube 的 `tools.json`、通用工具授权、peer 和宿主服务执行器已实现。已接纳的 `AGENT.md` 和技能文本作为宿主文本随每轮发送,Mail 的 `mail.messages.new` 触发器已运行;模型选择和其他应用的触发器仍有缺口 | [见下文](#规划中事件驱动的应用-agentadr-0002) | +| 原生应用经 peer link 访问自己的 Agent(ADR 0004 §5) | Makepad 客户端与 Shell `crates/shell/src/peer_link/` 均已实现。Calculator、Clock、Notes、Reminders、Weather 在进程内用 `OctosPeer` 打开自己的 Agent;Terminal 通过运行时补丁 `makepad-terminal-own-agent` 打开。它的 Agent 只能调用 `terminal.read_screen`、`terminal.read_scrollback`(`agent.own_tools`);`terminal.run` 仍归系统 Agent。 | [架构 § 应用与它自己的 Agent](architecture.zh-CN.md#应用与它自己的-agent) | ## 架构 @@ -50,16 +48,18 @@ flowchart TB llm["llm 宿主服务
写入提供方配置"] aihost["crates/ai-host
start、策略、offer"] broker["crates/app-peers broker
每个获授权的应用/账号一个 peer"] - runner["Card runner(App Hub)
宿主服务:mail、news、glance、llm、model、octos"] + runner["Card runner(App Hub)
宿主服务:mail、calendar、news、glance、llm、model、octos"] router["审批路由
crates/shell/src/approvals"] relay["Shell 宿主工具 relay
声明和调用者检查"] bus["AI 服务总线
crates/shell/src/ai_bus.rs"] rinx["Rinx(原生模块)"] + natives["Calculator、Clock、Notes、
Reminders、Weather(原生模块)"] appcard["AppCard(原生,需主动开启)"] scripts["脚本应用(系统和商店)"] end vault[("平台密钥存储
或仅所有者可读的配置")] kernel[("octos 内核,每个 Shell 一个
配置 _main,系统 Agent 会话")] + term["Terminal(桌面端进程应用)"] person --> providers person --> sheet providers -- "llm.*" --> llm @@ -70,6 +70,8 @@ flowchart TB aihost --> broker broker -- "peer/prepare、peer/context/open、turn/start" --> kernel rinx -- "受限的 OctosAppService" --> broker + natives -- "OctosPeer,peer link" --> broker + term -- "OctosPeer,peer link" --> broker appcard -- "自己的连接" --> kernel scripts -- "host.request" --> runner runner -- "octos.* 服务
broker 身份 card.<应用 id>" --> broker @@ -103,7 +105,7 @@ flowchart TB 它和其他应用一样是隔离运行的脚本应用;有特权的一半是 **`llm` 宿主服务**([`apps/ai-providers/host-service`](../apps/ai-providers/host-service/README.md)): - 它写入内核的配置 `/profiles/_main.json`(`config.llm` 和 `config.env_vars` 中的密钥变量),然后重启内核。 -- 密钥存入 macOS 登录钥匙串(服务名 `octos`);桌面 Linux 上存入 `/secrets/`(0600);Android、iOS 和 HarmonyOS 上存入应用私有的配置文件(0600):octos 在 Android 和 iOS 上没有密钥存储,而 HarmonyOS 上内嵌的内核会到 Shell 自己的 `HOME` 下找密钥存储。开发时可用 `OCTOSENSE_LLM_VAULT=file` 把密钥留在配置文件中。 +- 密钥存入 macOS 登录钥匙串(服务名 `octos`);桌面 Linux 上存入 `/secrets/`(0600);Android、iOS、HarmonyOS 和 Windows 上存入应用私有的配置文件(0600):octos 在 Android 和 iOS 上没有密钥存储,而 HarmonyOS 上内嵌的内核会到 Shell 自己的 `HOME` 下找密钥存储。开发时可用 `OCTOSENSE_LLM_VAULT=file` 把密钥留在配置文件中。 - 密钥、PIN 和 QR 码只在服务自己的**宿主面板**上输入、绘制和扫描。只有面板能调用 `llm.sheet.*`;应用只看到掩码后的状态(`"set ••••1234"`、`"missing"`)。 - 它**只服务 `os.*` 应用**(`"llm is for OctoSense's own apps."`)。它管理提供方;没有任何向模型发送提示词的方法。 @@ -111,7 +113,7 @@ flowchart TB [`crates/ai-host`](../crates/ai-host/README.md)(`octosense-ai-host`)是两个 Shell 都调用的入口:启动时调用 `start(Host::platform(data_dir))`(配置内核、安装宿主策略、注册带平台 QR 导入能力的 `llm`),每个事件调用 `handle_event`,在原生模块 `create` 前后调用 `offer(module, scope)` / `finish()`,退出时调用 `shutdown()`。 -宿主**策略**从 `native-apps.json` 生成的声明读取原生 `octos.*` 精确授权(目前是 Rinx)。 +宿主**策略**从 `native-apps.json` 生成的声明读取原生 `octos.*` 精确授权(Rinx、Terminal、Calculator、Clock、Notes、Reminders、Weather)。 #### 隔离应用的首次使用同意 @@ -143,7 +145,7 @@ flowchart TB 用户可用 F8 或 Shell 的 Assistant 入口打开系统聊天(`crates/shell/src/system_chat/`),也可通过桌面 “Ask <app>” 面板或卡片 `sys.chat` 直接与应用 Agent 对话;手机尚无打开 Ask-app 面板的触控入口。 -系统 Agent 使用受限内核工具列表和审核过的 Shell 工具(例如需开启的 `terminal.run`),不会继承所有应用工具,也不能替用户审批。Talk to Octos 是另一种客户端入口。自动触发调度、学习 overlay 和更丰富的 glance 排序仍在规划中;卡片目前按优先级和时间排序。 +系统 Agent 使用受限内核工具列表(`system_tools.rs`)和宿主工具 `agents.list`、`agents.ask`、`agents.provision`、`agents.status`(后两个只接受 `os.mail`)、需开启的 `terminal.run`,以及原生应用共享的只读工具(`agent.system_tools`),不会继承所有应用工具,也不能替用户审批。Talk to Octos 是另一种客户端入口。Mail 以外的触发调度、学习 overlay 和更丰富的 glance 排序仍在规划中;卡片目前按优先级和时间排序。 ### Shell 中的其他助手 @@ -165,7 +167,7 @@ flowchart TB ### 原生模块(Rinx 这类) -原生模块是链接进 Shell 的受信任 Rust 代码。**Rinx**([hagency-org/Rinx](https://github.com/hagency-org/Rinx))是参考实现,也是 `Policy::shipped()` 下唯一获得助手授权的模块。 +原生模块是链接进 Shell 的受信任 Rust 代码。**Rinx**([hagency-org/Rinx](https://github.com/hagency-org/Rinx))是参考实现,也是唯一领取注入的 `OctosAppService` 的模块;其他带 Agent 的原生应用使用 peer link。 1. 模块在 `capabilities()` 中列出精确的服务名,例如 `octosense_app_peers::OCTOS_SERVICES` 中的全部四个。 2. Shell 在 `module.create` 之前调用 `ai_host::offer(module, &scope)`,之后调用 `offer.finish()`;返回的 `Assistant` 与实例同生命周期,被丢弃时释放。 @@ -183,7 +185,7 @@ flowchart TB 4. `availability()` 报告 `Unavailable`(没有内核、未授权、未登录)、`Idle`、`Ready` 或 `Failed`;在任何状态下应用的普通界面都照常工作。`settings_entry()` 为 `Host`:应用不提供自己的提供方表单,而是引导用户去 AI providers。 -**工具。** Rinx 在 `src/assistant/mod.rs` 定义其 UI 助手工具,通过 AI 服务总线上的 `ServiceExecutor` 执行,读取和发送仍走自己的确认界面。内核宿主注册工具及 Shell relay 已实现,缺口不是等待 octos#2567 合并:当前 Rinx 原生清单未声明这些 peer 工具,也尚未通过 `OctosAppService::set_confirm_sheet` 将发送确认面板交给 Shell。因此不要把总线上能调用的工具当作 Rinx peer 已拥有的工具。 +**工具。** Rinx 在 `src/assistant/mod.rs` 定义其 UI 助手工具,通过 AI 服务总线上的 `ServiceExecutor` 执行,读取和发送仍走自己的确认界面。内核支持宿主注册工具,Shell 已实现它们的 relay。Rinx 尚未在原生清单中声明这些 peer 工具,也尚未通过 `OctosAppService::set_confirm_sheet` 将发送确认面板交给 Shell。因此不要把总线上能调用的工具当作 Rinx peer 已拥有的工具。 **Rinx 迷你应用。** Rinx 托管经过审核的 OctoScript 迷你应用,并向它们提供同样的四个 `octos.*` 服务,每个运行中的实例使用 Rinx peer 的一个独立请求上下文([示例](https://github.com/hagency-org/Rinx/tree/main/examples/miniapps/matrix-octos-script))。这是 Rinx 自己的迷你应用宿主,适用于用户审核后导入 Rinx 的应用包;它不是 App Hub 的安装路径。 @@ -205,7 +207,7 @@ flowchart TB - **参数。** `octos.turn.start` 必须提供非空白、最多 32 KiB 的 `text`,还可以提供 `trigger`(`person`、`app` 或 `incoming`)和 `from`。其余调用只接受 `{}`。 - **来源。** `trigger: "person"` 会得到 `AppSaysPerson` 分类。它只是应用的声明,不是受信任的人类操作,也不能绕过审批。 -- **审批。** 宿主工具审批和其他 peer 审批都交给 Shell;工具仍受已有授权与审批规则约束,开发者模式会为它覆盖的应用作答。没有审批路由的宿主会拒绝请求,并在 `denied_approvals` 中列出。详见 [`host_approvals.rs`](../crates/app-peers/src/host_approvals.rs)。 +- **审批。** 宿主工具审批和其他 peer 审批都交给 Shell;工具仍受已有授权与审批规则约束,开发者模式会为它覆盖的应用作答。没有审批路由的宿主会拒绝请求,并在 `denied_approvals` 中列出。`denied_approvals` 见 [`contained.rs`](../crates/ai-host/src/contained.rs),开发者模式的覆盖见 [`host_approvals.rs`](../crates/app-peers/src/host_approvals.rs)。 - **回复。** 宿主拒绝超过 2 MiB 的回复。下表列出成功调用及常见错误的返回内容。 脚本自己的助手调用使用已声明的 `octos.*`;Shell 也可以根据 `agent`/`tools.json` 驱动 peer,而不要求脚本声明这些 UI 调用。脚本服务调用的回复: @@ -222,9 +224,9 @@ flowchart TB | 获得 `llm` 授权的商店应用调用 `llm.*` | `llm is for OctoSense's own apps.` | | 在 App Hub 的 `card-host` 中调用任何服务 | `no service answers "" on this device`(`card-host` 不注册任何服务) | -**历史验证。** 第一行、`llm` 一行和 `card-host` 一行已于 2026-09-27 在 `card-host`(App Hub `362d832`)中运行验证。回复、参数不合规的返回和缺少内核的返回已于 2026-09-28 在 macOS 上运行验证:release 版桌面端、隐藏窗口,经启动器打开一个声明了 `octos.session.open` 和 `octos.turn.start` 的系统应用;使用按当时锁定的 octos `7bec0918` 构建的内核和用户自己的提供方时,回复来自 peer `card.<应用 id>`,内核数据中出现了它的记忆命名空间 `app/card.<应用 id>/…`。文本长度和开关关闭的返回由 `cargo test -p octosense-ai-host` 覆盖,走的是同一个分发函数。OctoScript-App-Design-Flow 的 [AI-SERVICES](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/AI-SERVICES.zh-CN.md) 给出了示例应用,以及 Rinx 提供的四个 `octos.*` 调用的参数和返回形状。 +**历史验证(本版本未重新运行)。** 第一行、`llm` 一行和 `card-host` 一行已于 2026-09-27 在 `card-host`(App Hub `362d832`)中运行验证。回复、参数不合规的返回和缺少内核的返回已于 2026-09-28 在 macOS 上运行验证:release 版桌面端、隐藏窗口,经启动器打开一个声明了 `octos.session.open` 和 `octos.turn.start` 的系统应用;使用按当时锁定的 octos `7bec0918` 构建的内核和用户自己的提供方时,回复来自 peer `card.<应用 id>`,内核数据中出现了它的记忆命名空间 `app/card.<应用 id>/…`。文本长度和开关关闭的返回由 `cargo test -p octosense-ai-host` 覆盖,走的是同一个分发函数。OctoScript-App-Design-Flow 的 [AI-SERVICES](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/AI-SERVICES.zh-CN.md) 给出了示例应用,以及 Rinx 提供的四个 `octos.*` 调用的参数和返回形状。 -manifest 的 `agent` 和已准入 `tools.json` 已参与 Shell 的 Agent 发现、工具加载与授权;News/Mail/Calendar/Photos/Maps/Camera/YouTube 已有宿主服务实现。`profile`/模型需求、`AGENT.md` 提示词、包内 skills 和自动触发器尚未完整消费,`implemented_by: "app"` 没有脚本执行器。Mail 当前只声明 `mail.notify`,不包含 UI 的读信/发信方法;Agent 工作目录不会自动暴露宿主数据库。Rinx 小程序准入是另一套约定,不能默认它支持 App Hub 的所有元数据。 +manifest 的 `agent` 和已准入 `tools.json` 已参与 Shell 的 Agent 发现、工具加载与授权;News/Mail/Calendar/Photos/Maps/Camera/YouTube 已有宿主服务实现。`profile`/模型需求和 Mail 以外的触发器尚未消费,`implemented_by: "app"` 没有脚本执行器。Mail 声明 `mail.accounts`、`mail.folders`、`mail.list`、`mail.sync`、`mail.peek`、`mail.notify`、`mail.publish_card`、`mail.skip_event`,没有发信工具;Agent 工作目录不会自动暴露宿主数据库。Rinx 小程序准入是另一套约定,不能默认它支持 App Hub 的所有元数据。 ## 规划中:事件驱动的应用 Agent(ADR 0002) @@ -233,13 +235,13 @@ manifest 的 `agent` 和已准入 `tools.json` 已参与 Shell 的 Agent 发现 | 组成部分 | ADR | 状态 | | --- | --- | --- | | `tools.json`:名称、输入输出 schema、风险、确认、共享和实现 | §4、§12 | `host_tools/script_apps.rs` 已加载、注册到 peer 并转发到宿主服务执行器;News/Mail/Calendar/Photos/Maps/Camera/YouTube 已声明工具。任意 `implemented_by: "app"` 脚本工具执行仍不可用。 | -| `AGENT.md`、只含数据的 skills、模型需求、`background` 和触发器 | §2、§3 | 已准入的元数据;Shell 尚未实现提示词/skill 加载、按这些需求选模型和触发调度。工具可后台运行不等于会自动启动回合。 | -| 内核:宿主注册工具、风险/列表检查、宿主审批、通用工具白名单和 `peer/input` | §4、§13 | 已实现并在锁定版本中;Shell 注册工具、接收输入、检查 schema/预算并通过 relay 调用执行器。见 `crates/app-peers` 和 `crates/shell/src/host_tools/`。 | +| `AGENT.md`、只含数据的 skills、模型需求、`background` 和触发器 | §2、§3 | 已准入的元数据。`AGENT.md`/技能文本作为宿主文本随每轮发送(`guidance.rs`,不是内核技能),Mail 的触发器由 `agent_events.rs` 调度;按这些需求选模型和其他触发器仍未实现。工具可后台运行不等于会自动启动回合。 | +| 内核:每个 peer 的宿主注册工具(`peer/tools/register`、`peer/tool/call`/`result`/`cancel`)、工具列表与风险检查、只在宿主连接上的审批、通用工具白名单、`peer/input` | §4、§13 | 内核一侧已合入:[octos#2567](https://github.com/octos-org/octos/pull/2567)(UPCR-2026-035),连同后续修复(octos#2616)在锁定版本中。Shell 一侧已在 main:[#145](https://github.com/OctoSense-org/OctoSense/pull/145)(工具注册、relay、`peer/input`)和 G3(按 manifest 的 `generic_tools`、`tools.json` 加载、授权、脚本应用宿主服务的执行器、schema 与预算检查;`peer/input/reject`,octos#2621)。 | | 工具审批策略 | §4、§12 | 破坏性和对外调用进入 Shell 的审批路由,由它应用常设规则、宿主或已注册的应用确认界面,以及开发者模式。授权、schema 和调用方预算另行检查。详见[审批](architecture.zh-CN.md#5-审批)。 | | News M1:`news` 数据服务(不使用模型) | 第一个切片 | 已合入([#69](https://github.com/OctoSense-org/OctoSense/pull/69));issue [#60](https://github.com/OctoSense-org/OctoSense/issues/60)。 | | News M2:`os.news` peer 及工具 | 第一个切片 | 已实现:manifest `agent`、`news.list`/`news.read`/`news.notify`、Shell peer 准备与 relay。 | | News M3:触发器和无人值守运行 | 第一个切片 | 规划中:[#62](https://github.com/OctoSense-org/OctoSense/issues/62)。 | -| News M4:glance 卡片(`glance.publish`、glance 页面和桌面面板) | §7、§8 | `glance.publish`([#72](https://github.com/OctoSense-org/OctoSense/pull/72))和供隔离应用使用的 `glance` 权限([#86](https://github.com/OctoSense-org/OctoSense/pull/86),配合 [App-Hub#22](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/22))已合入;`sys.digest` 有单独的运行时/宿主要求;[#87](https://github.com/OctoSense-org/OctoSense/pull/87) 是历史跟踪链接,不表示当前 PR 状态;issue [#63](https://github.com/OctoSense-org/OctoSense/issues/63)。 | +| News M4:glance 卡片(`glance.publish`、glance 页面和桌面面板) | §7、§8 | `glance.publish`([#72](https://github.com/OctoSense-org/OctoSense/pull/72))和供隔离应用使用的 `glance` 权限([#86](https://github.com/OctoSense-org/OctoSense/pull/86),配合 [App-Hub#22](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/22))已合入;`sys.digest` 有单独的运行时/宿主要求;issue [#63](https://github.com/OctoSense-org/OctoSense/issues/63)。 | | M5:作为获授权工具的系统工具箱 | §6 | 已在 `toolbox-peers` feature 下实现,见 `crates/ai-host/src/toolbox_peers.rs` 和 Shell 工具箱执行器。Home 默认开启,桌面需显式开启;仍需准入、授权和同意,不是与系统 Agent 的通用对话。 | | M6:渲染与评审(`card-studio`) | §7 | App Hub 的 `card-studio` crate 已合入([App-Hub#19](https://github.com/OctoSense-org/OctoSense-App-Hub/pull/19));对应 skill 规划中([#65](https://github.com/OctoSense-org/OctoSense/issues/65))。 | | M7:应用对话、问题和记忆 | §9、§10 | 已实现人类/系统通道、Ask-app 面板、`sys.chat`、宿主路由的问题/审批及 peer/context 记忆。见[导读](architecture-walkthrough.zh-CN.md)。 | @@ -276,7 +278,7 @@ manifest 的 `agent` 和已准入 `tools.json` 已参与 Shell 的 Agent 发现 3. 打开 **Start → Settings → AI providers**,添加一个模型(family、模型、路由、密钥、**Test connection**、保存)。配置文件是 `$T/octos-home/.octos/profiles/_main.json`;使用文件密钥库时密钥就在其中,用完后请删除 `$T`。 4. 通过某个使用者来使用助手:Rinx(默认链接并在进程内运行;从启动器打开它;登录 Matrix,在首次使用面板上允许它的 Agent,然后使用它的助手),或 AppCard(`--features app-appcard`)。隔离运行的应用通过 `octos` 宿主服务访问它([见上文](#隔离运行的脚本应用系统应用和商店应用));保持 `OCTOSENSE_CONTAINED_APPS` 未设置,并在 Shell 询问时同意。F8 也可打开系统聊天;`agents.ask` 用于同意/准备,`peer_send_input` 才委派任务。 -**隐藏窗口。** 加上 `MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=`,即可通过远程控制桥操作 Shell 而不占用屏幕([桌面端 README § Remote-control bridge](../desktop/README.md#remote-control-bridge))。`desktop/scripts/ai_providers_remote.sh` 以这种方式端到端运行 AI providers,使用假密钥并禁止出站 HTTPS;`desktop/scripts/glance_remote.sh` 对 glance 面板做同样的事。 +**隐藏窗口。** 加上 `MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=`,即可通过远程控制桥操作 Shell 而不占用屏幕([桌面端 README § 远程控制桥](../desktop/README.zh-CN.md#远程控制桥))。`desktop/scripts/ai_providers_remote.sh` 以这种方式端到端运行 AI providers,使用假密钥并禁止出站 HTTPS;`desktop/scripts/glance_remote.sh` 对 glance 面板做同样的事。 **测试**(在仓库根目录): diff --git a/docs/architecture-walkthrough.md b/docs/architecture-walkthrough.md index 33bd3786..6038449e 100644 --- a/docs/architecture-walkthrough.md +++ b/docs/architecture-walkthrough.md @@ -32,7 +32,7 @@ The launch choices come from [native-apps.json](../native-apps.json), [AppRegist | What you launch | Follow the code | What is actually running | | --- | --- | --- | | Native Rust module, such as Reference or Rinx | [module_host.rs](../crates/shell/src/module_host.rs): module creation, scoped handles, assistant/storage offers | Rust code and UI in the shell process; each instance gets a script isolate. The isolate does not isolate arbitrary Rust memory. | -| Native process app, currently desktop Terminal where supported | [clients.rs](../crates/shell/src/clients.rs), [hub.rs](../crates/shell/src/hub.rs), [process-apps](../crates/process-apps/src/lib.rs) | A child executable exchanges frames, input and AI-bus messages over the authenticated Makepad hub socket. An `octos_peer` envelope on that socket also supports its assistant link; see §5. Shipped process apps currently request no `agent.octos` services. | +| Native process app, currently the desktop Terminal and Task where supported | [clients.rs](../crates/shell/src/clients.rs), [hub.rs](../crates/shell/src/hub.rs), [process-apps](../crates/process-apps/src/lib.rs) | A child executable exchanges frames, input and AI-bus messages over the authenticated Makepad hub socket. An `octos_peer` envelope on that socket also supports its assistant link; see §5. The Terminal requests `agent.octos` services for its own agent, which only reads the screen and scrollback. | | Contained script app | App Hub's `CARD_MODULE`, reached through `apps.rs` | The Card runner admits the bundle and makes a restricted, nested Splash VM; capabilities limit access to host services and platform APIs. No Rust executable is compiled per bundle. | | Glance card | [glance.rs](../crates/shell/src/glance.rs), [glance_chat.rs](../crates/shell/src/glance_chat.rs) | An L0/L1 `source` card is checked and lowered with host data; a Splash `script` card runs interactive handlers under its publisher's policy. `notify` adds a toast/shade entry. UI dismissal calls `glance::dismiss`; apps use `glance.withdraw`. Chat reaches the publisher's peer. | @@ -44,12 +44,12 @@ python3 tools/setup.py --check --cargo cargo run --release -p octosense # An opt-in, in-process Rust example: MAKEPAD_WM_TEST_APP=reference cargo run --release -p octosense --features app-reference -- --module reference -# A shell with the kernel integration; point at a binary built from Cargo.toml's octos rev: +# The default build has the kernel service; point it at a kernel built from Cargo.toml's octos rev: OCTOS_APP_CORE_BIN=/path/to/pinned/octos \ - cargo run --release -p octosense --features octos-core + cargo run --release -p octosense ``` -The AI providers host-owned sheet must also configure a provider/model before a real conversation can run. A Rust build does not provision those credentials. `OCTOS_APP_CORE_BIN` names a file; this kernel launcher does not search `PATH`. For automated UI work use the existing hidden-window instructions in the product README. +Before a real conversation can run, set a provider and model on the AI providers app's host sheet. A Rust build does not provision those credentials. `OCTOS_APP_CORE_BIN` names a file; this kernel launcher does not search `PATH`. For automated UI work use the existing hidden-window instructions in the product README. For a script app, Design Flow's Python `tools/octo` CLI wraps App Hub's `card-host` and `hub` commands. Run a bundle with `tools/octo run /path/to/bundle` from the Design Flow checkout (unverified launch recipe). The standalone runner exercises contained UI and policy. Use a shell to test Mail, Calendar, provider and peer services; those are registered by the shell. A `hub check` pass validates the bundle's admission contract. @@ -59,7 +59,7 @@ Read [ai-host/src/lib.rs](../crates/ai-host/src/lib.rs), then [kernel/src/lib.rs 1. The shell calls `ai_host::start`, registering host services and configuring the kernel source. `model.complete` is a one-shot provider request; it does not create a peer or run a tool loop. 2. The first authorized consumer calls `Core::connect`. It obtains a logical `Connection`; subsequent consumers share the same kernel generation. -3. `launch::resolve` selects a desktop child (`OCTOS_APP_CORE_BIN` or an explicit program), Android's executable packaged as `liboctos.so`, or an embedded OpenHarmony service. iOS has no kernel here. +3. `launch::resolve` selects a desktop child (an explicit program, `OCTOS_APP_CORE_BIN`, or the packaged `octos-kernel` beside the shell, checked against the pinned revision), Android's executable packaged as `liboctos.so`, or an embedded OpenHarmony service. iOS has no kernel here. 4. `kernel::supervise` owns the running process/task and frame pump. `Router` correlates consumer request IDs and session events over the one physical protocol connection. 5. The protocol is OUP, JSON-RPC messages and asynchronous notifications. Ordinary mode uses newline-delimited JSON over stdio. Enabling Talk to Octos selects a host-managed loopback WebSocket; it does not start an extra kernel for each client. 6. Provider changes restart the generation. Consumers must reconnect and rebind. A peer's memory outlives any `Connection`. @@ -72,9 +72,9 @@ Open [system_chat/mod.rs](../crates/shell/src/system_chat/mod.rs), [session.rs]( The assistant pane sends a `Command` to its worker. The `Driver` opens `_main:api:octosense#system`, reads history and starts a turn. Incoming events update a chat model, and `SignalToUI` wakes the Makepad event loop to draw a snapshot. -The system agent is a session on the `_main` profile. The host narrows its kernel tools using `session/tool_list/set`; `SYSTEM_AGENT_TOOLS` includes `peer_list`, `peer_send_input`, `peer_gather` and `peer_respond`. Its normal command-execution route, if the person enables it, is the shell's `terminal.run` tool and approval UI. It is not octos's builtin `shell` tool. +The system agent is a session on the `_main` profile. The host narrows its kernel tools using `session/tool_list/set`; `SYSTEM_AGENT_TOOLS` includes `peer_list`, `peer_send_input`, `peer_gather` and `peer_respond`. Its normal command-execution route, if the person enables it, is the shell's `terminal.run` tool and approval UI. It is not octos's builtin `shell` tool. It also gets the read tools native apps share (`agent.system_tools`, such as `calculator.eval`), which the app's open instance answers. -[agents.rs](../crates/shell/src/agents.rs) adds two useful host tools: `agents.list` discovers app agents and their availability; `agents.ask` opens first-use consent and waits for a ready peer slug—the identifier issued by the kernel for that peer. On success the system agent uses `peer_send_input` to submit the task; the ask call itself only prepares access. The person controls first-use consent. +[agents.rs](../crates/shell/src/agents.rs) adds two useful host tools: `agents.list` discovers app agents and their availability; `agents.ask` opens first-use consent and waits for a ready peer slug—the identifier issued by the kernel for that peer. On success the system agent uses `peer_send_input` to submit the task; the ask call itself only prepares access. The person controls first-use consent. For a native app, `agents.ask` returns as soon as the agent is allowed: its peer belongs to the app's open instance, which the shell does not prepare, so the system agent reaches it only while the app is open. A script app's peer is prepared as soon as it is allowed (and at each later startup). ## 5. Prepare one app peer, then give it two lanes @@ -95,7 +95,7 @@ Process death cancels outstanding work and closes request contexts while keeping `card.os.news` is the broker's app identity, not necessarily the kernel's generated peer slug. `peer/prepare` returns that slug, the peer session and a host credential; use the returned values. Peers are keyed by app and account. Accountless apps use `device`; Mail uses the host-reported signed-in account. `ensure_peer` resumes the recorded peer, verifies its namespace, and registers tools before a turn can run. -In the Calendar example, delegation runs on the system lane; “Ask Calendar” runs on a human lane. The **blackboard** below is the kernel’s shared record of peer work and results, which the system agent can read with `peer_gather`. +In the Calendar example, delegation runs on the system agent's lane; “Ask Calendar” runs on a person's lane. The **blackboard** below is the kernel’s shared record of peer work and results, which the system agent can read with `peer_gather`. ```mermaid sequenceDiagram @@ -109,46 +109,31 @@ sequenceDiagram K->>B: peer/input notification B->>B: check account, consent, tool registration; queue input B->>K: turn/start on peer session, with input id - K->>A: execute system lane turn - H->>B: Ask app: ContextOp::Turn + K->>A: execute system agent's lane turn + H->>B: Ask app: ContextOp::TurnFrom (Person) B->>K: peer/context/open with share_history; turn/start - K->>C: execute human lane turn + K->>C: execute person's lane turn A-->>K: completed turn / peer result K-->>S: blackboard result available to peer_gather C-->>B: streamed events and completion B-->>H: app conversation / in-card chat reply ``` -The app peer session is the **system lane**. `open_conversation` creates a request context with `share_history` for a **human lane**. They have separate transcripts and turn state; bounded recent text from the other lane is supplied read-only. Multiple human surfaces may create separate conversation contexts of the same peer. `open_context` is the non-sharing path for per-client work, such as Rinx mini apps. A request context is its own kernel session, but is not another app peer. +The app peer session is the **system agent's lane**. `open_conversation` creates a request context with `share_history` for a **person's lane**. They have separate transcripts and turn state; bounded recent text from the other lane is supplied read-only. Several of the person's surfaces may create separate conversation contexts of the same peer. `open_context` is the non-sharing path for per-client work, such as Rinx mini apps. A request context is its own kernel session, but is not another app peer. -The broker's `driver_of` and `take_over` handle multiple native instances sharing a peer: one broker drives the system-lane input queue. This prevents every open app window from independently running the same `peer/input`. Human-context work is separate and can run while that queue's current turn is active. +The broker's `driver_of` and `take_over` handle multiple native instances sharing a peer: one broker drives the input queue of the system agent's lane. This prevents every open app window from independently running the same `peer/input`. Work in the person's context is separate and can run while that queue's current turn is active. ## 6. Where a person talks, and where the answer goes | Surface | Implementation and behavior | | --- | --- | | System assistant pane, F8 | `system_chat`: system session; routes app questions raised during system-delegated work here too. | -| “Ask ”, Shift+F8 | [app_chat/mod.rs](../crates/shell/src/app_chat/mod.rs): `agents::conversation`, subscription to both lanes and merged history. Send starts the person's context turn. | +| “Ask <app>”, Shift+F8 | [app_chat/mod.rs](../crates/shell/src/app_chat/mod.rs): `agents::conversation`, subscription to both lanes and merged history. Send starts the person's context turn. | | An app's own native chat | Injected `OctosAppService::open_conversation`; the app renders its events. | | A script app's own chat | Exact granted `octos.session.open`, `octos.session.history`, `octos.turn.start`, `octos.turn.interrupt` calls through `host.request`. The shipped system-app agents are shell-driven without their scripts declaring these calls. | | A published card's chat | `sys.chat` → [l0-chat](../crates/l0-chat/src/lib.rs) and [glance_chat.rs](../crates/shell/src/glance_chat.rs). Publisher checks bind it to the card's owning app. | -Phone Glance renders compact summaries without executing generated UI. A tap uses that summary's screen rectangle to reveal `glance_sheet.rs` above the feed at full viewport size. `card_presentation.rs` owns the reversible Makepad frame animation; it does not spawn Tokio tasks, launch an app or change the app peer. Email and Chat share one row. The original email and directly editable draft are in `mail_clip.rs`; `card_chat.rs` virtualizes the transcript and pins the native composer above the keyboard. Both panes use the same authoritative draft. `chat_submit` checks the declared `sys.chat` source, publisher, account and thread, then issues a one-use editing capability for the saved draft revision. Mail consumes it through `mail.suggest_reply`; without it, the tool only proposes a suggestion. This never authorizes sending. Dismissal preserves the resident widgets/session and revokes pending review authority; retained clean workspaces are bounded, and invalidated accounts/publications are retired. Final sending still requires physical approval of the exact host review. - -The shared workspace also hosts Calendar, News, Photos, YouTube and other publishers. When an agent-enabled publisher omits `sys.chat`, `L0Session::for_card` adds a host-owned Card / Chat conversation without rewriting its L0 or Splash source. `ContextKind::Card` carries bounded publication data and current L0 state, scoped to the original publication account and a stable card thread. It grants no Mail edit token or additional tools. First use opens the existing permission sheet through Enable assistant; a publisher without an agent has no Chat tab. Existing explicit `sys.chat` declarations keep their threads. Thus shipped notice and Calendar cards can use native workspace chat as well as desktop “Ask ”. The explicit `OCTOSENSE_GLANCE_DEMO=mail` chat remains canned. Local L0 changes and interacted Splash isolates are exempt from clean-cache eviction; stale accounts cannot reopen their old publications. - -A bound Mail reply card adds a durable host path: `mail.propose_reply` creates -an account/email-bound draft, and `mail.publish_card` carries its `draft_id`. -`mail_card.rs` answers `sys.mail_draft` and checks Field-origin edits against the -displayed revision. Its `sys.mail_review` write requests the host's -`mail_review.rs` UI, never approval. `drafts.rs` owns the immutable attempt and -SMTP receipt; the agent cannot send directly. Chat includes the bound email, -acknowledged draft and bounded history in the existing Mail peer. Only verified -Android physical touch can approve in this iteration; desktop/accessibility -approval and complete paired-model phone acceptance remain pending. Follow the -[implementation and gate table](mail-composable-cards.md) before treating the -new flow as validated. - +The shipped `.notify`, `calendar.notify` and `calendar.agenda` templates contain no `sys.chat`; their agents are reached through “Ask <app>”. The `OCTOSENSE_GLANCE_DEMO=mail` demonstration card has chat but uses canned replies (`glance_chat::HostResponder`). The card-chat route above applies to a card that declares `sys.chat`. Phone touch navigation does not yet expose a control to open the Ask-app panel. App-owned chat and published-card chat remain separate surfaces. @@ -156,8 +141,8 @@ Stopping work depends on the entry point: | Action | What it interrupts | | --- | --- | -| Ask-app panel’s Stop | The human lane | -| “Stop the system agent’s task” | The system lane | +| Ask-app panel’s Stop | The person's lane | +| “Stop the system agent’s task” | The system agent's lane | | Lower-level `ContextOp::Interrupt` | Can interrupt both lanes | Hiding the Ask panel keeps its context and subscription. Changing app or revoking access closes them. @@ -187,7 +172,7 @@ The executor depends on the tool's owner. [host_tools/mod.rs](../crates/shell/sr | Tool route | Executor boundary | | --- | --- | | Contained app, `implemented_by: "host-service"` | `HostServiceExecutor` invokes the admitted owner's Rust service. | -| Native module | Its `OctosAppService::set_tool_executor` callback handles the call. | +| Native module | The open instance answers over its peer link with Makepad's `OctosPeer::serve_tools` (Calculator, Clock, Notes, Reminders, Weather), or else on its AI bus service. An installed `OctosAppService::set_tool_executor` callback takes precedence. | | Native process | `peer_link` sends a tool request to the client over its authenticated hub socket; registration and grants are still required. | | System `terminal.run` | The shell types an approved command into the visible Terminal through its AI bus; enabled by the Command execution setting. | | `files.list/read/search` | Shell executor over the caller's permitted account workspace, on Unix. | @@ -225,8 +210,10 @@ For the Calendar question, the executor reads `calendar/events.json` under Calen | App | Current data/tool boundary | | --- | --- | | News | `news.list`, `news.read` and `news.notify` | -| Mail | Account-scoped accounts/folders/sync/list/peek, notify/publish_card/skip_event, plus propose_reply/draft/suggest_reply/propose_send; no send, approve or credential tool | +| Mail | Account-scoped accounts/folders/sync/list/peek, notify/publish_card and explicit event skip; no send or credential tool | | Photos, Maps, Camera, YouTube | Only their own `.notify`, publishing a shared notice card through [glance_notice.rs](../crates/shell/src/glance_notice.rs) | +| Calculator, Clock, Notes, Reminders, Weather | Their own read tools, which the system agent may also call; no workspace (`agent_workspace: "none"`) | +| Terminal | `terminal.read_screen` and `terminal.read_scrollback`; typing a command stays the system agent's `terminal.run` | | AI providers | No app agent | The older AppCard personal-data importer is not automatically synchronized with current Mail’s store. @@ -259,9 +246,9 @@ Follow the direct “Ask Calendar” question across those boundaries: 1. The Makepad UI submits a context request to Calendar’s broker. 2. The broker’s runtime sends `turn/start` through a shared kernel `Connection`; the kernel-service supervisor carries it across the transport. 3. octos spawns a turn orchestration task. It waits at a start barrier until the active-turn registry accepts it, then starts agent processing. -4. If the model calls `calendar.events`, its tool future waits while the broker and shell relay deliver the request to Calendar’s host service. The returned data lets the model continue; reply events travel back to the human context and wake the UI. +4. If the model calls `calendar.events`, its tool future waits while the broker and shell relay deliver the request to Calendar’s host service. The returned data lets the model continue; reply events travel back to the person's context and wake the UI. -These are scheduling boundaries, not a new thread or process for every peer. Use this table to locate their owners: +These are scheduling boundaries: the kernel adds no thread or process per peer, but each live peer's broker in the shell has one worker thread. Use this table to locate their owners: | Layer | Actual execution model | Source | | --- | --- | --- | @@ -270,6 +257,7 @@ These are scheduling boundaries, not a new thread or process for every peer. Use | Shell kernel service | Lazily built Tokio runtime: **2 worker threads**, **8 MiB stacks**. One generation supervisor task owns transport and process lifecycle, with auxiliary I/O tasks | `kernel/src/lib.rs::Inner::runtime`, `kernel.rs::supervise` | | App broker | **Each `Broker::new` builds a runtime with 1 worker thread**. Link pump, request futures, retries and deadline tasks run there. Multiple brokers may share one durable peer | `app-peers/src/broker.rs` | | Embedded OpenHarmony kernel | `serve_io` is spawned on the host runtime over `tokio::io::duplex`; no child executable | `kernel.rs::start` | +| octos child (desktop, Android) | Its own process: `ServeCommand::execute` builds Tokio's default multi-thread runtime, one worker per CPU core | pinned octos `crates/octos-cli/src/commands/serve.rs` | | octos OUP turn | A spawned orchestration task waits on a `oneshot` start barrier until active-turn admission succeeds; `run_standalone_turn` then spawns agent processing and auxiliary tasks | pinned octos `crates/octos-cli/src/api/ui_protocol_transport.rs` | | octos transport output | WebSocket has an async writer task; embedded/stdio uses a bounded synchronous queue and an ordinary writer thread. Transport variants are not identical | same octos file | | Host service / file executor | Depends on the service: UI pump, callback/reply queues, worker threads for blocking operations. Not universally a Tokio task per service or app | `host_tools/files.rs`, App Hub `services.rs`, app host services | @@ -283,7 +271,7 @@ flowchart LR C --> SUP["Kernel-service supervisor task"] SUP <-->|"stdio or host WebSocket"| OUP["octos protocol dispatcher"] OUP --> T1["System-lane turn task"] - OUP --> T2["Human-context turn task"] + OUP --> T2["Person's-lane turn task"] T1 --> TOOL["tool future waits for host reply"] TOOL --> B B --> Q["Shell relay queue"] diff --git a/docs/architecture-walkthrough.zh-CN.md b/docs/architecture-walkthrough.zh-CN.md index bd80474b..e387a9dc 100644 --- a/docs/architecture-walkthrough.zh-CN.md +++ b/docs/architecture-walkthrough.zh-CN.md @@ -18,7 +18,7 @@ | Agent | 读取消息、选择工具、消费结果并回答的模型工作单元。实际操作由工具执行器完成。 | | Session / turn | Session 保存一次对话的身份与状态;turn 是响应一次输入的执行过程,可包含多次模型请求与工具调用。 | | Peer / request context | Peer 是持久的协作 Agent 身份;context 是属于该 peer 的另一个 session,有独立记录、工作目录,以及 peer 记忆命名空间下的独立存储区域。 | -| Tool / host service | Tool 是模型可选择的操作;host service 是应用或执行器调用的 Rust 服务。分别检查它们的授权与入口。 | +| Tool / host service | Tool 是模型可选择的操作;host service 是应用或执行器调用的 Rust 服务。开放其中一个,并不会自动开放另一个。 | | `AGENTS.md` / `AGENT.md` | 前者指导仓库贡献者;后者是应用包的 Agent 指令文件。App Hub 准入后者,Shell 每轮提供其宿主指令文本;这不是内核原生技能安装。 | 相关仓库的导读:[Design Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/218b25d2460d64f843932f67d419467618464fb9/docs/CODE-WALKTHROUGH.md)、[App Hub](https://github.com/OctoSense-org/OctoSense-App-Hub/blob/19bb52d402e80e89e085dea989615e3ec612d359/docs/CODE-WALKTHROUGH.md)、[Octoscript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad/blob/fb29b6b1cb6e16f38d99a8aef60431a9565dfd59/docs/architecture-walkthrough.md)、[octos](https://github.com/octos-org/octos/blob/82900bf149d3a53016c1c1492ffc075d2d4fb0ed/docs/octosense-integration-walkthrough.md)。这些链接指向已发布的文档版本;运行时版本仍由使用方的依赖锁定文件控制。 @@ -32,7 +32,7 @@ | 启动对象 | 代码路径 | 实际运行方式 | | --- | --- | --- | | 原生 Rust 模块,如 Reference、Rinx | [module_host.rs](../crates/shell/src/module_host.rs):创建模块,注入作用域句柄、助手和存储接口 | Rust 代码与 UI 在 Shell 进程内运行;每实例有脚本 isolate,Rust 内存仍在同一进程。 | -| 原生进程应用,如受支持桌面上的 Terminal | [clients.rs](../crates/shell/src/clients.rs)、[hub.rs](../crates/shell/src/hub.rs)、[process-apps](../crates/process-apps/src/lib.rs) | 子进程经认证的 Makepad hub socket 交换帧、输入与 AI bus 消息。同一 socket 的 `octos_peer` 封装还支持助手连接(第 5 节);当前随产品提供的进程应用未请求 `agent.octos` 服务。 | +| 原生进程应用,如受支持桌面上的 Terminal 和 Task | [clients.rs](../crates/shell/src/clients.rs)、[hub.rs](../crates/shell/src/hub.rs)、[process-apps](../crates/process-apps/src/lib.rs) | 子进程经认证的 Makepad hub socket 交换帧、输入与 AI bus 消息。同一 socket 的 `octos_peer` 封装还支持助手连接(第 5 节);Terminal 为自己的 Agent 请求了 `agent.octos` 服务,该 Agent 只读取屏幕和回滚内容。 | | 隔离脚本应用 | `apps.rs` 进入 App Hub 的 `CARD_MODULE` | Card runner 准入应用包,创建受限的嵌套 Splash VM;capability 限制宿主服务和平台 API。每个包无需编译 Rust 可执行文件。 | | Glance 卡片 | [glance.rs](../crates/shell/src/glance.rs)、[glance_chat.rs](../crates/shell/src/glance_chat.rs) | L0/L1 `source` 卡片经检查并结合宿主数据 lower 到运行时;Splash `script` 卡片在发布者策略下运行交互逻辑。`notify` 增加通知,UI 用 `glance::dismiss` 移除,应用用 `glance.withdraw` 撤回。聊天通向发布者的 peer。 | @@ -44,9 +44,9 @@ python3 tools/setup.py --check --cargo cargo run --release -p octosense # 可选的进程内 Rust 示例: MAKEPAD_WM_TEST_APP=reference cargo run --release -p octosense --features app-reference -- --module reference -# 启用内核集成;二进制应来自 Cargo.toml 锁定的 octos 版本: +# 默认构建已含内核服务;指定按 Cargo.toml 锁定的 octos 版本构建的内核: OCTOS_APP_CORE_BIN=/path/to/pinned/octos \ - cargo run --release -p octosense --features octos-core + cargo run --release -p octosense ``` 真实对话还需要在 AI providers 的宿主确认面板配置提供方、模型与凭据。`OCTOS_APP_CORE_BIN` 指定一个文件;此启动器不会搜索 `PATH`。自动 UI 工作使用产品 README 中的隐藏窗口方式。 @@ -59,7 +59,7 @@ OCTOS_APP_CORE_BIN=/path/to/pinned/octos \ 1. Shell 调用 `ai_host::start` 注册服务、配置内核来源。`model.complete` 是一次提供方请求;peer 与工具循环走另一条路径。 2. 首个获授权的使用方调用 `Core::connect`,得到逻辑 `Connection`;后续使用方共享同一内核代际。 -3. `launch::resolve` 选择桌面子进程(`OCTOS_APP_CORE_BIN` 或显式程序)、Android 打包的 `liboctos.so` 可执行文件,或 OpenHarmony 内嵌服务。iOS 在这里没有内核。 +3. `launch::resolve` 选择桌面子进程(显式程序、`OCTOS_APP_CORE_BIN`,或 Shell 旁按锁定版本校验的打包 `octos-kernel`)、Android 打包的 `liboctos.so` 可执行文件,或 OpenHarmony 内嵌服务。iOS 在这里没有内核。 4. `kernel::supervise` 持有运行中的进程/任务和帧循环。`Router` 在一条物理连接上关联使用方请求 ID 与 session 事件。 5. OUP 协议使用 JSON-RPC 与异步通知。普通模式经 stdio 传输逐行 JSON;启用 Talk to Octos 后改用宿主管理的回环 WebSocket,客户端共享该内核。 6. 提供方变化会重启代际,使用方需重连并重新绑定。Peer 记忆的生命周期长于 `Connection`。 @@ -72,9 +72,9 @@ ROM 的特权 Android 服务也称“agent”,负责通过 Android bridge 执 助手面板向工作线程发送 `Command`。`Driver` 打开 `_main:api:octosense#system`、读取历史并启动回合。收到的事件更新聊天模型,`SignalToUI` 唤醒 Makepad 事件循环绘制快照。 -系统 Agent 是 `_main` profile 的一个 session。宿主通过 `session/tool_list/set` 限定其内核工具;`SYSTEM_AGENT_TOOLS` 包含 `peer_list`、`peer_send_input`、`peer_gather` 和 `peer_respond`。用户开启命令执行后,它通过 Shell 的 `terminal.run` 与审批 UI 执行命令;octos 内置 `shell` 不在其工具列表中。 +系统 Agent 是 `_main` profile 的一个 session。宿主通过 `session/tool_list/set` 限定其内核工具;`SYSTEM_AGENT_TOOLS` 包含 `peer_list`、`peer_send_input`、`peer_gather` 和 `peer_respond`。用户开启命令执行后,它通过 Shell 的 `terminal.run` 与审批 UI 执行命令;octos 内置 `shell` 不在其工具列表中。它还获得原生应用共享的只读工具(`agent.system_tools`,如 `calculator.eval`),由应用打开的实例回答。 -[agents.rs](../crates/shell/src/agents.rs) 提供两个宿主工具:`agents.list` 查询应用 Agent 及可用状态;`agents.ask` 打开首次同意面板并等待可用的 peer slug(内核为该 peer 分配的标识符)。成功后系统 Agent 用 `peer_send_input` 提交任务;ask 调用只准备访问。首次同意由用户控制。 +[agents.rs](../crates/shell/src/agents.rs) 提供两个宿主工具:`agents.list` 查询应用 Agent 及可用状态;`agents.ask` 打开首次同意面板并等待可用的 peer slug(内核为该 peer 分配的标识符)。成功后系统 Agent 用 `peer_send_input` 提交任务;ask 调用只准备访问。首次同意由用户控制。对原生应用,`agents.ask` 在获准后立即返回:它的 peer 属于应用打开的实例,Shell 不预先准备,所以系统 Agent 只有在应用打开时才能联系它。脚本应用的 peer 在获准时立即准备(此后每次启动时也会准备)。 ## 5. 一个应用 peer,两条对话通道 @@ -110,7 +110,7 @@ sequenceDiagram B->>B: 检查账号、同意与工具;输入排队 B->>K: peer session 上 turn/start,携带 input id K->>A: 执行系统通道回合 - H->>B: Ask app:ContextOp::Turn + H->>B: Ask app:ContextOp::TurnFrom(Person) B->>K: peer/context/open,share_history;turn/start K->>C: 执行用户通道回合 A-->>K: 回合完成 / peer 结果 @@ -128,17 +128,12 @@ sequenceDiagram | 入口 | 实现与行为 | | --- | --- | | 系统助手面板,F8 | `system_chat` 使用系统 Agent 的 session;系统委派工作产生的问题也在这里显示。 | -| “Ask ”,Shift+F8 | [app_chat/mod.rs](../crates/shell/src/app_chat/mod.rs) 经 `agents::conversation` 订阅两条通道并合并历史;发送消息启动用户 context 回合。 | +| “Ask <app>”,Shift+F8 | [app_chat/mod.rs](../crates/shell/src/app_chat/mod.rs) 经 `agents::conversation` 订阅两条通道并合并历史;发送消息启动用户 context 回合。 | | 原生应用自己的聊天 | 调用注入的 `OctosAppService::open_conversation`,由应用绘制事件。 | | 脚本应用自己的聊天 | 经 `host.request` 调用确切获授权的 `octos.session.open`、`octos.session.history`、`octos.turn.start`、`octos.turn.interrupt`。随产品提供的系统应用 Agent 由 Shell 驱动,其脚本无需声明这些调用。 | | 已发布卡片中的聊天 | `sys.chat` 经 [l0-chat](../crates/l0-chat/src/lib.rs) 与 [glance_chat.rs](../crates/shell/src/glance_chat.rs) 到达发布者的 Agent;宿主核对卡片归属。 | -手机 Glance 只绘制紧凑摘要,不执行生成 UI。点按使用摘要的屏幕矩形,让 `glance_sheet.rs` 在信息流上方展开至完整可用视口。`card_presentation.rs` 在 Makepad 帧循环中管理可反向切换的动画,不创建 Tokio 任务、不启动应用,也不改变应用 peer。Email 与 Chat 位于同一行。`mail_clip.rs` 提供原邮件和可直接编辑的草稿;`card_chat.rs` 虚拟化消息并将原生输入框固定在键盘上方。两页共享权威草稿。`chat_submit` 校验声明的 `sys.chat` 源、发布者、账户和线程,然后签发绑定已保存修订号的一次性编辑凭证。Mail 通过 `mail.suggest_reply` 消费凭证;没有凭证时只提出建议,不授权发送。关闭展示时保留常驻控件/会话并撤销待批准审核;干净工作区缓存有数量限制,账户或发布失效时清理对应工作区。最终发送仍需实体输入批准准确的宿主审核内容。 - -共享工作区同样承载 Calendar、News、Photos、YouTube 等发布者。声明了 agent 的发布者未提供 `sys.chat` 时,`L0Session::for_card` 会添加宿主拥有的 Card / Chat 会话,不改写 L0 或 Splash 源码。`ContextKind::Card` 携带有大小限制的发布数据及当前 L0 状态,并绑定原发布账户和稳定的卡片线程;它不签发 Mail 编辑凭证,也不新增工具。首次使用可通过 Enable assistant 打开现有权限面板;没有 agent 的发布者不显示 Chat。显式 `sys.chat` 保留原线程。因此内置通知和 Calendar 卡片既可使用原生工作区聊天,也可使用桌面的 “Ask ”。显式 `OCTOSENSE_GLANCE_DEMO=mail` 聊天仍返回预设答案。L0 本地修改和已交互的 Splash 实例不参与干净缓存淘汰;账户失效后不能重开旧发布。 - -绑定草稿的 Mail 回复卡片新增持久化宿主路径:`mail.propose_reply` 创建账户/邮件绑定草稿,`mail.publish_card` 携带其 `draft_id`。`mail_card.rs` 回答 `sys.mail_draft`,按显示中的修订号校验 Field 来源的编辑。`sys.mail_review` 写入只请求 `mail_review.rs` 宿主 UI,不构成批准。`drafts.rs` 拥有不可变尝试和 SMTP 回执,agent 不能直接发送。聊天在已有 Mail peer 中包含绑定邮件、已确认保存的草稿和有限历史。本轮只有经过验证的 Android 实体触摸可批准;桌面/无障碍审批及完整双模型手机验收仍待完成。将新路径视为已验证前,请看[实现与验证表](mail-composable-cards.zh-CN.md)。 - +随产品提供的 `.notify`、`calendar.notify` 与 `calendar.agenda` 模板不含 `sys.chat`,通过 “Ask <app>” 与其 Agent 对话。`OCTOSENSE_GLANCE_DEMO=mail` 演示卡片带有聊天,但由 `glance_chat::HostResponder` 返回预设答案。上表的卡片聊天路线适用于声明了 `sys.chat` 的卡片。 手机触控导航尚无打开 Ask-app 面板的对应控件。应用自己的聊天与卡片聊天仍是独立入口。 @@ -175,7 +170,7 @@ Shell 将聊天输入动作标为 `TurnTrigger::Person`。脚本调用 `octos.tu | 工具路线 | 执行边界 | | --- | --- | | 隔离应用,`implemented_by: "host-service"` | `HostServiceExecutor` 调用准入的所属应用 Rust 服务。 | -| 原生模块 | `OctosAppService::set_tool_executor` 注册的回调。 | +| 原生模块 | 打开的实例经 peer link 用 Makepad 的 `OctosPeer::serve_tools` 回答(Calculator、Clock、Notes、Reminders、Weather),否则经其 AI bus 服务回答;已安装的 `OctosAppService::set_tool_executor` 回调优先。 | | 原生进程 | `peer_link` 经认证的 hub socket 发送工具请求;仍需注册与授权。 | | 系统 `terminal.run` | 开启 Command execution 后,Shell 经 AI bus 在可见 Terminal 中输入已批准的命令。 | | `files.list/read/search` | Unix 上的 Shell 执行器,范围为调用方允许访问的账号工作目录。 | @@ -197,6 +192,8 @@ Shell 将聊天输入动作标为 `TurnTrigger::Person`。脚本调用 `octos.tu ## 8. “访问应用数据”对应哪些存储 +数据存放在几处,各有所有者: + | 数据 | 应用 Agent 的访问路线 | | --- | --- | | 应用账号目录 | [app_storage](../crates/shell/src/app_storage/mod.rs) 与 `ToolHost::agent_workspace` 将新 peer 的 cwd 绑定到允许的目录;内核文件工具仍需授权。 | @@ -211,8 +208,10 @@ Shell 将聊天输入动作标为 `TurnTrigger::Person`。脚本调用 `octos.tu | 应用 | 当前数据/工具边界 | | --- | --- | | News | `news.list`、`news.read`、`news.notify` | -| Mail | 绑定账户的 accounts/folders/sync/list/peek、notify/publish_card/skip_event,以及 propose_reply/draft/suggest_reply/propose_send;不暴露发送、批准或凭据工具 | +| Mail | 绑定账户的 accounts/folders/sync/list/peek,notify/publish_card,以及事件无操作决策 skip_event;不暴露发信或密码 | | Photos、Maps、Camera、YouTube | 仅各自的 `.notify`,经 [glance_notice.rs](../crates/shell/src/glance_notice.rs) 发布共享通知卡片 | +| Calculator、Clock、Notes、Reminders、Weather | 各自的只读工具,系统 Agent 也可调用;没有工作区(`agent_workspace: "none"`) | +| Terminal | `terminal.read_screen`、`terminal.read_scrollback`;输入命令仍是系统 Agent 的 `terminal.run` | | AI providers | 没有应用 Agent | 旧 AppCard 个人数据导入器不会自动同步当前 Mail 存储。 @@ -221,7 +220,9 @@ Shell 将聊天输入动作标为 `TurnTrigger::Person`。脚本调用 `octos.tu ## 9. 跨应用工作与求助 -**委派任务:**系统 Agent 用 `peer_send_input` 请求现有应用 peer 工作,再获取结果。Shell 驱动该应用回合,peer 身份可以跨进程生命周期保留。 +以下三种操作容易混淆: + +**委派任务:**系统 Agent 用 `peer_send_input` 请求现有应用 peer 工作,再获取结果。Shell 驱动该应用回合,peer 不是新启动的进程。 **直接调用另一应用的 API 工具:**这条路线直接调用所属应用的执行器,无需经过它的模型。例如,另一应用要调用可共享的 Calendar 工具,必须满足以下条件: @@ -245,7 +246,7 @@ Peer 是持久身份与状态。**Tokio task** 是由 runtime 工作线程调度 3. octos spawn 回合编排 task;它先在启动屏障等待活跃回合注册成功,再开始 Agent 处理。 4. 若模型调用 `calendar.events`,工具 future 等待 broker 与 Shell relay 将请求交给 Calendar 宿主服务。返回的数据让模型继续;回复事件经用户 context 返回并唤醒 UI。 -这些是调度边界;每个 peer 并不各自创建线程或进程。下表列出各部分的所有者: +这些是调度边界:内核不为每个 peer 创建线程或进程,但 Shell 中每个活动 peer 的 broker 各有一个工作线程。下表列出各部分的所有者: | 层 | 实际执行模型 | 源码 | | --- | --- | --- | @@ -254,6 +255,7 @@ Peer 是持久身份与状态。**Tokio task** 是由 runtime 工作线程调度 | Shell 内核服务 | 惰性创建 Tokio runtime:**2 个工作线程,8 MiB 栈**;每代一个 supervisor task 持有传输与进程生命周期,另有 I/O task | `kernel/src/lib.rs::Inner::runtime`、`kernel.rs::supervise` | | 应用 broker | **每次 `Broker::new` 创建 1 个工作线程的 runtime**,运行连接泵送、请求 future、重试和截止任务;多个 broker 可共享一个 peer | `app-peers/src/broker.rs` | | OpenHarmony 内嵌内核 | 在宿主 runtime 上 spawn `serve_io`,经 `tokio::io::duplex` 通信 | `kernel.rs::start` | +| octos 子进程(桌面、Android) | 独立进程:`ServeCommand::execute` 创建 Tokio 默认多线程 runtime,每个 CPU 核心一个工作线程 | 锁定 octos 的 `crates/octos-cli/src/commands/serve.rs` | | octos OUP 回合 | 已 spawn 的编排任务先在 `oneshot` 启动屏障等待活跃回合准入;成功后 `run_standalone_turn` 再 spawn Agent 处理与辅助任务 | 锁定 octos 的 `crates/octos-cli/src/api/ui_protocol_transport.rs` | | octos 传输出口 | WebSocket 使用异步 writer task;内嵌/stdio 使用有界同步队列和普通 writer 线程 | 同一 octos 文件 | | 宿主服务/文件执行器 | 按服务使用 UI 泵送、回调/回复队列或处理阻塞操作的工作线程 | `host_tools/files.rs`、App Hub `services.rs`、各应用服务 | diff --git a/docs/architecture.md b/docs/architecture.md index 80c06a82..968ee1dc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,15 +4,13 @@ English | [简体中文](architecture.zh-CN.md) How OctoSense fits together: processes, agents, tools, approvals, storage and trust boundaries. Follow the [code walkthrough](architecture-walkthrough.md) for calls and ownership. Dependency versions come from [Cargo.toml](../Cargo.toml) and [native-runtime.lock.json](../native-runtime.lock.json); the dated design notes below record earlier decisions. -**Mail reply implementation:** [ADR0007](adr/0007-composable-mail-action-cards.md) now has draft/editor/chat/review adapters in source. Mail agents can propose drafts and review, but cannot approve or send. Even developer mode requires the host-owned review control; currently only physical Android touch can authorize it. The [current code and verification gates](mail-composable-cards.md) supersede older Mail demo/send-path descriptions below. Scoped OnePlus 6 tests with DeepSeek and MiniMax are recorded there; the full device/UX matrix and desktop/accessibility send approval remain incomplete. - Every statement carries its status: - **On main**: merged, read in this repository's code (a path is given). - **In progress**: an open pull request, linked. - **Planned**: decided in an ADR (linked, with its plan step) and not built yet. -The decisions behind it are [ADR 0001](adr/0001-one-octosense-repository.md) (one repository), [ADR 0002](adr/0002-event-driven-app-agents.md) (app agents, Proposed, as amended by 0004), [ADR 0003](adr/0003-shared-octos-client-access.md) (Talk to Octos) and [ADR 0004](adr/0004-native-apps-hosting-and-peers.md) (native apps, app agents, cross-app work and approvals, Accepted, and Implemented on 2026-09-29 apart from its follow-ups). The assistant's services, what each kind of app can call today and how to run them locally are in [ai-services.md](ai-services.md); this page does not repeat them. +The decisions behind it are [ADR 0001](adr/0001-one-octosense-repository.md) (one repository), [ADR 0002](adr/0002-event-driven-app-agents.md) (app agents, Proposed and partly implemented, as amended by 0004 and 0006), [ADR 0003](adr/0003-shared-octos-client-access.md) (Talk to Octos) and [ADR 0004](adr/0004-native-apps-hosting-and-peers.md) (native apps, app agents, cross-app work and approvals, Accepted, and Implemented on 2026-09-29 apart from its follow-ups and three plan items). The assistant's services, what each kind of app can call today and how to run them locally are in [ai-services.md](ai-services.md); this page does not repeat them. ## Contents @@ -41,7 +39,7 @@ flowchart LR person(["The person"]) subgraph shellp["OctoSense shell process (desktop, or Home on a phone)"] wm["Window manager, launcher, sheets,
system chat, glance panel
crates/shell"] - mods["In-process native modules
App Hub, Rinx, (AppCard)"] + mods["In-process native modules
App Hub, Rinx, Notes, Clock, …, (AppCard)"] runner["App Hub Card runner
script apps in isolates"] router["Approval router
crates/shell/src/approvals"] aihost["ai-host + app-peers broker
host connection to the kernel"] @@ -59,7 +57,7 @@ flowchart LR wm --- mods wm --- runner term <-->|"hub (loopback WebSocket)"| wm - mods -- "injected OctosAppService" --> aihost + mods -- "injected OctosAppService or peer link" --> aihost runner -- "host.request(octos.*)" --> aihost aihost <-->|"OUP over stdio
(or host-token WebSocket)"| kern sys -- "peer_send_input / blackboard" --> peers @@ -77,6 +75,7 @@ flowchart LR +--------------------------- OctoSense shell process ----------------------------+ | window manager / launcher / sheets approval router AI services bus | | in-process modules: App Hub (+ Card runner with script apps), Rinx, (AppCard) | +| and Calculator, Clock, Notes, Reminders, Weather | | host-tool relay -> app host services (mail, calendar, news, notices) | | ai-host + app-peers broker == host connection ==+ | +---------------------------------------------------|-----------------------------+ @@ -127,11 +126,12 @@ Native apps are first-party Rust crates declared only in [`native-apps.json`](.. | Terminal | **own process** | own process with a Vulkan build and a Wayland session, else in process | in process | default / off | its own agent, with its read tools only (`own_tools`); typing a command is the system agent's `terminal.run` (`confirm: host`, `auto_approvable: false`) | | Calculator, Clock, Notes, Reminders, Weather | in process | in process | in process | default / default | the four `octos.*` services; their read tools also go to the system agent (`system_tools`) | | Sheets, Reference | in process | in process | in process | opt-in / `mobile-apps` | – | +| Task (Task Manager) | **own process** | own process (the shell does not apply `process-if-vulkan` to an app without a module) | – (not built) | from the desktop catalog; no module | – | | AppCard | in process | in process | in process | opt-in / opt-in | its own kernel connection | -So **only the Terminal is a process app**, and only on the desktop; `tools/native_apps.py` refuses plain `process` on Linux (`process-if-vulkan` only), so Linux without Vulkan and Wayland runs every app in process. App Hub stays in process because it hosts the Card runner every script app runs in. Rinx stays in process: ADR 0004 §2 moves it, by a reviewed change to its `hosting`, once the peer link and the process sandboxes exist. The peer link's shell side and the macOS and Linux sandboxes are on main now, but Rinx at its pin reaches its agent through the injected service ([section 3](#an-app-and-its-own-agent)), not the peer link, and its `hosting` is still `module` everywhere. +So on the desktop **the Terminal and Task are the process apps** (Task has no module); `tools/native_apps.py` refuses plain `process` on Linux (`process-if-vulkan` only), so Linux without Vulkan and Wayland runs every linked app in process. App Hub stays in process because it hosts the Card runner every script app runs in. Rinx stays in process: ADR 0004 §2 moves it, by a reviewed change to its `hosting`, once the peer link and the process sandboxes exist. The peer link's shell side and the macOS and Linux sandboxes are on main now, but Rinx at its pin reaches its agent through the injected service ([section 3](#an-app-and-its-own-agent)), not the peer link, and its `hosting` is still `module` everywhere. -How the shell decides at run time (`crates/shell/src/apps.rs`, `AppRegistry::hosting`): if the target cannot run processes (`host::processes_available()` is false on wasm and native mobile), every app is a module; App Hub and Settings are always modules; otherwise a native app follows its manifest entry, and runs as a process only when a process form exists (a checkout to build it from, or a sibling binary); without one it falls back to in-process (`manifest_default`). Desktop release packages are not on main yet ([#257](https://github.com/OctoSense-org/OctoSense/pull/257), in progress, which replaces the closed #94). A per-app override (`~/.makepad/wm/apps.splash`, `--module `) switches an app between module and process. +How the shell decides at run time (`crates/shell/src/apps.rs`, `AppRegistry::hosting`): if the target cannot run processes (`host::processes_available()` is false on wasm and native mobile), every app is a module; App Hub and Settings are always modules; otherwise a native app follows its manifest entry, and runs as a process only when a process form exists (a checkout to build it from, or a sibling binary); without one it falls back to in-process (`manifest_default`). Desktop release packages ([#257](https://github.com/OctoSense-org/OctoSense/pull/257)) ship no process app's binary, so there the Terminal runs in process and Task is absent. A per-app override (`~/.octosense/wm/apps.splash`, `--module `) switches an app between module and process. **Process hosting** is Makepad's window-manager hosting (`crates/shell/src/clients.rs`, `hub.rs`): @@ -144,19 +144,19 @@ How the shell decides at run time (`crates/shell/src/apps.rs`, `AppRegistry::hos ### Script apps -Script apps (the system apps News, Photos, Maps, Mail, Calendar, AI providers and YouTube, plus Camera on phones, from `desktop/system-apps.json` and `phone/system-apps.json`, plus store apps) are OctoScript bundles. They all run in process, inside App Hub's **Card runner** (`CARD_MODULE` of `octosense-app-hub-app`): one nested isolate per app instance with `mod.res` and `mod.run` stripped, a jail and a quota. An app reaches the shell only through `host.request(".", …)` for the families its manifest was granted. A script bug fails inside its isolate. **On main.** +Script apps (the system apps News, Photos, Maps, Mail, AI providers and YouTube, plus Calendar on the desktop and Camera on phones, from `desktop/system-apps.json` and `phone/system-apps.json`, plus store apps) are OctoScript bundles. They all run in process, inside App Hub's **Card runner** (`CARD_MODULE` of `octosense-app-hub-app`): one nested isolate per app instance with `mod.res` and `mod.run` stripped, a jail and a quota. An app reaches the shell only through `host.request(".", …)` for the families its manifest was granted. A script bug fails inside its isolate. **On main.** ```mermaid flowchart TB subgraph desktop["Desktop (macOS)"] - ds["OctoSense process
shell + App Hub + Card runner + Rinx"] + ds["OctoSense process
shell + App Hub + Card runner + Rinx + Notes, Clock, …"] dk["octos child
octos-kernel serve --stdio"] dt["Terminal child
--stdin-loop"] ds -- "stdin/stdout" --> dk dt -- "hub WebSocket, IOSurface frames" --> ds end subgraph android["Android (Home)"] - as["Home process
shell + App Hub + Card runner + Rinx (all in process)"] + as["Home process
shell + App Hub + Card runner + Rinx + Notes, Clock, … (all in process)"] ak["liboctos.so child
serve --stdio"] as -- "stdin/stdout" --> ak end @@ -174,16 +174,16 @@ flowchart TB | Agent | What it is | Status | | --- | --- | --- | -| **The system agent** | the session `_main:api:octosense#system` on the `_main` profile (`SYSTEM_SESSION` in `crates/kernel/src/network.rs`). It owns every app peer and supervises them. The person reaches it in the shell's **system chat** (`crates/shell/src/system_chat/`, [#132](https://github.com/OctoSense-org/OctoSense/pull/132): Setup → Assistant → Assistant chat, F8, the dock's Assistant icon or the phone home's Assistant tile; on a desktop a medium pane at the left that the person can move and resize, full screen on a phone), or through a paired Talk to Octos client | On main | -| **An app agent** | one host-owned octos **peer** per (app, account), owned by the system agent (octos UPCR-2026-034, Rinx [ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md)). The person talks to it in the app's own UI, or in the shell's **"Ask " panel** (`crates/shell/src/app_chat/`: the bar's "Ask ", Shift+F8; beside the system chat on a desktop, a full-screen sheet on a phone), which shows both lanes with their speakers and a composer: Send while the person's lane is idle (even while the system agent works), Stop for the person's own turn, and "Stop the system agent's task" on the system agent's running row | On main for the native apps with an agent (Rinx, the Terminal, Calculator, Clock, Notes, Reminders, Weather) and every script app with an agent (today the system apps News, Mail, Photos, Maps, YouTube, Calendar on the desktop and Camera on phones), after first-use consent; see below | +| **The system agent** | the session `_main:api:octosense#system` on the `_main` profile (`SYSTEM_SESSION` in `crates/kernel/src/network.rs`). It owns every app peer and supervises them. The person reaches it in the shell's **system chat** (`crates/shell/src/system_chat/`: F8, the dock's Assistant icon or the phone home's Assistant tile), or through a paired Talk to Octos client | On main | +| **An app agent** | one host-owned octos **peer** per (app, account), owned by the system agent (octos UPCR-2026-034, Rinx [ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md)). The person talks to it in the app's own UI, or in the shell's **"Ask <app>" panel** (`crates/shell/src/app_chat/`: the bar's "Ask <app>", Shift+F8; beside the system chat on a desktop, a full-screen sheet on a phone), which shows both lanes with their speakers and a composer: Send while the person's lane is idle (even while the system agent works), Stop for the person's own turn, and "Stop the system agent's task" on the system agent's running row | On main for the native apps with an agent (Rinx, the Terminal, Calculator, Clock, Notes, Reminders, Weather) and every script app with an agent (today the system apps News, Mail, Photos, Maps, YouTube, Calendar on the desktop and Camera on phones), after first-use consent; see below | Each app peer has, separately from every other: - a **workspace**: the folder the peer's session is bound to. A peer created since the octos 665209e5 pin gets its account folder (`apps//accounts//`, ADR 0004 §11) as `peer/prepare`'s `cwd` (`host_tools::agent_workspace`), recorded beside its host token so a resume names the same folder; a peer created before keeps the kernel-provisioned one, since octos refuses to resume a peer under another workspace (see [the disagreements](#where-the-code-and-the-adrs-disagree)); - a **memory namespace** `app//acct-` (`app_namespace()` in `crates/app-peers/src/broker.rs`); a kernel that does not return it is refused; - its own **transcript** and **model** lane (the host sets the model with `peer/prepare` / `peer/model/set`); -- its own **tool list**: exactly the kernel tools its manifest grants (`generic_tools`, never octos's `shell`) plus the tools the shell registers (octos [#2567](https://github.com/octos-org/octos/pull/2567), UPCR-2026-035): after every `peer/prepare` and reconnect the broker registers, on the connection that drives the peer's turns, the app's declared tools and the other apps' shareable tools granted to it, each marked with its owning app (`crates/shell/src/host_tools/`), with `generic_tools` set to exactly the kernel tools the app's manifest declares and the person granted (a native app's `native-apps.json` `agent.generic_tools`, a script app's plain names in its manifest's `agent.tools`; an app that names none keeps none). The Terminal declares `terminal.run`, `terminal.read_screen` and `terminal.read_scrollback`. The system apps declare theirs in `apps//bundle/tools.json`, each run on a host service: News `news.list`, `news.read` and `news.notify` (the `news` service), Mail `mail.notify` (the `mail` service), Calendar `calendar.events`, `calendar.add_event`, `calendar.remove_event`, `calendar.notify` and `calendar.agenda` (the `calendar` service), and Photos, Maps, YouTube and Camera `.notify` (the shell's notice service, `crates/shell/src/glance_notice.rs`, since they have no service of their own). A peer that cannot register runs no turn; -- **request contexts** (`peer/context/open`): one per client instance (a Rinx mini app), each with its own transcript, folder `contexts//` inside the peer's workspace and child memory namespace. Such a client's context cannot read the files beside it directly; it reads its account's data through the shell's host read tools `files.list`, `files.read` and `files.search` (ADR 0004 §11, `crates/shell/src/host_tools/files.rs`), which never show another context's folder. Besides those per-client contexts (`OctosAppService::open_context`: Rinx's mini apps, a process app's `octos.session.open` with a `client`), each app conversation is one more request context: the person's own conversation with an app agent, from the shell's "Ask " panel, the app's UI or a card's in-card chat, runs in the person's lane, a context opened with `share_history` that runs in parallel with the peer's own session (the system agent's lane) and shows each lane the other's recent turns (ADR 0004 §6, decided 2026-09-29; below). That conversation context is opened with octos's `read_parent` where the manifest says the agent works in the account folder (`storage.agent_workspace: "account"`, octos#2647): it reads the account folder read-only, never another context's folder, and writes only its own. +- its own **tool list**: exactly the kernel tools its manifest grants (`generic_tools`, never octos's `shell`) plus the tools the shell registers (octos [#2567](https://github.com/octos-org/octos/pull/2567), UPCR-2026-035): after every `peer/prepare` and reconnect the broker registers, on the connection that drives the peer's turns, the app's declared tools and the other apps' shareable tools granted to it, each marked with its owning app (`crates/shell/src/host_tools/`), with `generic_tools` set to exactly the kernel tools the app's manifest declares and the person granted (a native app's `native-apps.json` `agent.generic_tools`, a script app's plain names in its manifest's `agent.tools`; an app that names none keeps none). The Terminal declares `terminal.run`, `terminal.read_screen` and `terminal.read_scrollback`, and its own agent gets only the two reads (`agent.own_tools`); Calculator, Clock, Notes, Reminders and Weather declare their read tools (`calculator.eval`, `clock.now`, `notes.search`, `notes.read`, `reminders.due`, `reminders.list`, `weather.current`). The system apps declare theirs in `apps//bundle/tools.json`, each run on a host service: News `news.list`, `news.read` and `news.notify` (the `news` service), Mail `mail.accounts`, `mail.folders`, `mail.sync`, `mail.list`, `mail.peek`, `mail.notify`, `mail.publish_card` and `mail.skip_event` (the `mail` service), Calendar `calendar.events`, `calendar.add_event`, `calendar.remove_event`, `calendar.notify` and `calendar.agenda` (the `calendar` service), and Photos, Maps, YouTube and Camera `.notify` (the shell's notice service, `crates/shell/src/glance_notice.rs`, since they have no service of their own). A peer that cannot register runs no turn; +- **request contexts** (`peer/context/open`): one per client instance (a Rinx mini app), each with its own transcript, folder `contexts//` inside the peer's workspace and child memory namespace. Such a client's context cannot read the files beside it directly; it reads its account's data through the shell's host read tools `files.list`, `files.read` and `files.search` (ADR 0004 §11, `crates/shell/src/host_tools/files.rs`), which never show another context's folder. Besides those per-client contexts (`OctosAppService::open_context`: Rinx's mini apps, a process app's `octos.session.open` with a `client`), each app conversation is one more request context: the person's own conversation with an app agent, from the shell's "Ask <app>" panel, the app's UI or a card's in-card chat, runs in the person's lane, a context opened with `share_history` that runs in parallel with the peer's own session (the system agent's lane) and shows each lane the other's recent turns (ADR 0004 §6, decided 2026-09-29; below). That conversation context is opened with octos's `read_parent` where the manifest says the agent works in the account folder (`storage.agent_workspace: "account"`, octos#2647): it reads the account folder read-only, never another context's folder, and writes only its own. Who gets a peer today (`crates/ai-host/src/lib.rs`, `Policy::shipped()`; `crates/app-peers/src/hosted.rs`, `effective_services` = declared ∩ supported ∩ policy): @@ -241,14 +241,14 @@ The kernel speaks the **octos UI protocol** (OUP; `octos-ui/v1alpha1`, JSON-RPC octos's peer mechanism, depth 1 (peers cannot create, steer or close peers). **On main** in octos; the tools are in octos `crates/octos-agent/src/tools/`. - **System agent → app agent.** `peer_send_input` (originator only, at most 64 KB) delivers text as the peer's next user turn, through the peer's inbox (a durable queue in serve, drained every few seconds, at least once). -- **Which apps' agents the system agent can reach.** `peer_list` lists the prepared peers: the shell prepares every allowed app's agent. For the ones it cannot list (not yet allowed, or off) the shell tells it: a note with the system chat's turn whenever the apps' agents changed, and the host tools `agents.list` and `agents.ask` on the system session (`crates/shell/src/agents.rs`). `agents.ask` shows the first-use sheet, which only the person answers, and waits for them: it is declared `risk: act`, `outward: true`, `confirm: "app"`, so the kernel holds the call as it holds an approval, and the system chat answers it once the person has answered and the app's peer is ready, with the peer's slug, so the system agent goes on with the request in the same turn (after 10 min without an answer it says the person has not answered yet, and the sheet stays up; [#274](https://github.com/OctoSense-org/OctoSense/pull/274)). +- **Which apps' agents the system agent can reach.** `peer_list` lists the prepared peers: the shell prepares every allowed script app's agent; a native app's peer exists while the app is open. For the ones it cannot list (not yet allowed, off, or a closed native app) the shell tells it: a note with the system chat's turn whenever the apps' agents changed, and the host tools `agents.list` and `agents.ask` on the system session (`crates/shell/src/agents.rs`). `agents.ask` shows the first-use sheet, which only the person answers, and waits for them: it is declared `risk: act`, `outward: true`, `confirm: "app"`, so the kernel holds the call as it holds an approval, and the system chat answers it once the person has answered and the app's peer is ready, with the peer's slug, so the system agent goes on with the request in the same turn (after 10 min without an answer it says the person has not answered yet, and the sheet stays up; [#274](https://github.com/OctoSense-org/OctoSense/pull/274)). - **App agent → system agent: the blackboard.** Each peer turn writes `peers//result.md` (plus `result-.md`) and a line in `turns.txt`; the system agent reads them with `peer_gather` and `peer_list` (`awaiting_input` shows a peer waiting on a question). It is the only cross-peer channel. -- **Questions.** A peer asks with `ask_user_question`; the system agent answers with `peer_respond`. `peer_respond` never answers approvals (octos refuses). octos keeps `ask_user_question` on host-driven turns (`generic_tools` is an exact list the host sets, and omitted keeps the whole roster). ADR 0004 §6 (decided 2026-09-29) has the shell route each question to the right conversation: the app's own conversation, or the system chat for system-agent turns, `peer/input` turns included; there is no `host.ask`. **On main**: the system chat answers the system agent's own questions on its session; the broker hands an app peer's `user_question/requested` (its own session, a request context, a `peer/input` turn) to the shell's request model (`crates/shell/src/questions/`) with the turn's origin (reported by octos once it carries it, else derived: the turns the broker started for `peer/input` are the system agent's), and the model routes it by that origin: a person's or the app's turn to the app's conversation (its "Ask " panel while that is open, otherwise the question card in the shell's approvals overlay, `crates/shell/src/approvals/view.rs`: one surface at a time, [#279](https://github.com/OctoSense-org/OctoSense/pull/279)), a system-agent turn to the system chat. Only the person answers, on a shell surface (`user_question/respond` on the broker's link); the app's context hears `user_question/handled_by_host`, and the broker refuses an app's attempt to answer an id the host holds. A question closes when its turn ends. -- **Two parallel lanes per app agent, sharing history read-only.** ADR 0004 §6 (decided 2026-09-29, replacing the same day's single queued conversation of [#166](https://github.com/OctoSense-org/OctoSense/pull/166) and octos [#2626](https://github.com/octos-org/octos/pull/2626)): the system agent talks to the peer's own session (`peer/input`), and the person (the app's UI, its cards, a process app without a `client`) in a request context the broker opens with `share_history` (octos [#2636](https://github.com/octos-org/octos/pull/2636), UPCR-2026-034), a new context id per handle (its history also holds the person's rows of the earlier handles of the same account and instance). The two run in parallel: a person's message waits only for its own context's previous turn, and `peer/input` keeps its own queue on the peer's session. At each turn's start the kernel shows the model the other lane's last user/assistant text rows (20 by default, at most 50, within 16 KiB; tool rows dropped) as a read-only block that is never written into the reader's transcript; there is no shared transcript. Every turn keeps its `origin` (`person`, `system_agent`, `app`; the kernel's `[from …]` marker; the host cannot relabel a system-agent turn), and a person's turn leaves a round on the blackboard (`origin: person`, `context: `) that the system agent reads with `peer_gather` and that fires no fleet synthesis on its own. `OctosAppService::open_conversation` is the person's lane; `open_context(client)` stays a plain request context (Rinx's mini apps). The app's follower (`OctosContext::subscribe`; `conversation` frames on the peer link) gets both lanes' events, each with `lane` and `speaker`; `octos.session.history` is both transcripts merged by `persisted_at`, each row with `lane` and, for a marked user message, `speaker` and `display_text`. Accepted hazards: file edits are not locked across lanes (each lane edits its own folder, a context's is `contexts/`), the unknown-outcome marks are shared, and parallel turns can overshoot the peer's token budget slightly. +- **Questions.** A peer asks with `ask_user_question`; the system agent answers with `peer_respond`. `peer_respond` never answers approvals (octos refuses). octos keeps `ask_user_question` on host-driven turns (`generic_tools` is an exact list the host sets, and omitted keeps the whole roster). ADR 0004 §6 (decided 2026-09-29) has the shell route each question to the right conversation: the app's own conversation, or the system chat for system-agent turns, `peer/input` turns included; there is no `host.ask`. **On main**: the system chat answers the system agent's own questions on its session; the broker hands an app peer's `user_question/requested` (its own session, a request context, a `peer/input` turn) to the shell's request model (`crates/shell/src/questions/`) with the turn's origin (reported by octos once it carries it, else derived: the turns the broker started for `peer/input` are the system agent's), and the model routes it by that origin: a person's or the app's turn to the app's conversation (its "Ask <app>" panel while that is open, otherwise the question card in the shell's approvals overlay, `crates/shell/src/approvals/view.rs`: one surface at a time, [#279](https://github.com/OctoSense-org/OctoSense/pull/279)), a system-agent turn to the system chat. Only the person answers, on a shell surface (`user_question/respond` on the broker's link); the app's context hears `user_question/handled_by_host`, and the broker refuses an app's attempt to answer an id the host holds. A question closes when its turn ends. +- **Two parallel lanes per app agent, sharing history read-only.** ADR 0004 §6: the system agent talks to the peer's own session (`peer/input`), and the person (the app's UI, its cards, a process app without a `client`) in a request context the broker opens with `share_history` (octos [#2636](https://github.com/octos-org/octos/pull/2636), UPCR-2026-034), a new context id per handle (its history also holds the person's rows of the earlier handles of the same account and instance). The two run in parallel: a person's message waits only for its own context's previous turn, and `peer/input` keeps its own queue on the peer's session. At each turn's start the kernel shows the model the other lane's last user/assistant text rows (20 by default, at most 50, within 16 KiB; tool rows dropped) as a read-only block that is never written into the reader's transcript; there is no shared transcript. Every turn keeps its `origin` (`person`, `system_agent`, `app`; the kernel's `[from …]` marker; the host cannot relabel a system-agent turn), and a person's turn leaves a round on the blackboard (`origin: person`, `context: `) that the system agent reads with `peer_gather` and that fires no fleet synthesis on its own. `OctosAppService::open_conversation` is the person's lane; `open_context(client)` stays a plain request context (Rinx's mini apps). The app's follower (`OctosContext::subscribe`; `conversation` frames on the peer link) gets both lanes' events, each with `lane` and `speaker`; `octos.session.history` is both transcripts merged by `persisted_at`, each row with `lane` and, for a marked user message, `speaker` and `display_text`. Accepted hazards: file edits are not locked across lanes (each lane edits its own folder, a context's is `contexts/`), the unknown-outcome marks are shared, and parallel turns can overshoot the peer's token budget slightly. ### The host-owned path: `peer/input` -For a host-owned peer, a plain `peer_send_input` runs as a kernel continuation without the app's tools or memory. ADR 0004 §6 fixes that: octos delivers the system agent's input to the host's driving connection as **`peer/input {peer, session_id, input_id, turn_id, text}`**, and the shell starts the turn itself (`turn/start` with the kernel's `turn_id`), so it runs with the app's tools, memory and context and its approvals surface in the app. If no host connection holds the peer, octos tells the system agent the app is not connected. **On main**: octos sends it since the 665209e5 pin (octos [#2567](https://github.com/octos-org/octos/pull/2567)); the broker (`crates/app-peers/src/broker.rs`) starts the turn on its own link with the kernel's `turn_id`, drops a repeated `input_id`, and starts nothing for a signed-out account or an app the person has not allowed (`host_tools::ShellToolHost::admit_input`), refusing it with `peer/input/reject` and the reason (octos [#2621](https://github.com/octos-org/octos/pull/2621)); a turn that fails to start is refused the same way. With several instances of one app on the kernel, the oldest live one for the account drives the peer (registers its tools, takes its `peer/input`), and the next takes over when it closes ([#201](https://github.com/OctoSense-org/OctoSense/pull/201)). The kernel admits one turn per session and queues none (`turn_in_progress`), so the broker keeps one queue per peer for the system agent's inputs on the peer's session, one turn at a time (a start refused `turn_in_progress` is retried with the same turn id); when 8 wait, an input is refused `busy`. The person's messages run in their own lane and never wait in it. Nothing holds a lane for long: an approval or question on the peer's session or a context expires after 10 min (`OCTOSENSE_PROMPT_DEADLINE_SECS`; the approval router denies it, the request model declines the question with free text, never approving, and both show "Expired: no answer in 10 min" until dismissed), and a turn still running 30 s later is interrupted by the broker so the next queued turn starts. The person's Stop ends the running turns of both lanes, the person's and the system agent's: `octos.turn.interrupt` on a conversation (apps, cards, the peer link) and "Stop 's agent" on the shell's approval sheet and question card (`approvals::stop_agent`, `broker::interrupt_where`). The "Ask " panel stops one lane at a time (`broker::interrupt_lane_where`): its Stop ends the person's own turn, and "Stop the system agent's task" the system agent's. The shell's kernel consumers share one kernel connection (`crates/kernel/src/router.rs`), so every broker and the system chat are the same host connection to octos; each answers only the calls of its own peer or session. +For a host-owned peer, a plain `peer_send_input` runs as a kernel continuation without the app's tools or memory. ADR 0004 §6 fixes that: octos delivers the system agent's input to the host's driving connection as **`peer/input {peer, session_id, input_id, turn_id, text}`**, and the shell starts the turn itself (`turn/start` with the kernel's `turn_id`), so it runs with the app's tools, memory and context and its approvals surface in the app. If no host connection holds the peer, octos tells the system agent the app is not connected. **On main**: octos sends it since the 665209e5 pin (octos [#2567](https://github.com/octos-org/octos/pull/2567)); the broker (`crates/app-peers/src/broker.rs`) starts the turn on its own link with the kernel's `turn_id`, drops a repeated `input_id`, and starts nothing for a signed-out account or an app the person has not allowed (`host_tools::ShellToolHost::admit_input`), refusing it with `peer/input/reject` and the reason (octos [#2621](https://github.com/octos-org/octos/pull/2621)); a turn that fails to start is refused the same way. With several instances of one app on the kernel, the oldest live one for the account drives the peer (registers its tools, takes its `peer/input`), and the next takes over when it closes ([#201](https://github.com/OctoSense-org/OctoSense/pull/201)). The kernel admits one turn per session and queues none (`turn_in_progress`), so the broker keeps one queue per peer for the system agent's inputs on the peer's session, one turn at a time (a start refused `turn_in_progress` is retried with the same turn id); when 8 wait, an input is refused `busy`. The person's messages run in their own lane and never wait in it. Nothing holds a lane for long: an approval or question on the peer's session or a context expires after 10 min (`OCTOSENSE_PROMPT_DEADLINE_SECS`; the approval router denies it, the request model declines the question with free text, never approving, and both show "Expired: no answer in 10 min" until dismissed), and a turn still running 30 s later is interrupted by the broker so the next queued turn starts. The person's Stop ends the running turns of both lanes, the person's and the system agent's: `octos.turn.interrupt` on a conversation (apps, cards, the peer link) and "Stop <app>'s agent" on the shell's approval sheet and question card (`approvals::stop_agent`, `broker::interrupt_where`). The "Ask <app>" panel stops one lane at a time (`broker::interrupt_lane_where`): its Stop ends the person's own turn, and "Stop the system agent's task" the system agent's. The shell's kernel consumers share one kernel connection (`crates/kernel/src/router.rs`), so every broker and the system chat are the same host connection to octos; each answers only the calls of its own peer or session. ### An app and its own agent @@ -256,8 +256,8 @@ For a host-owned peer, a plain `peer_send_input` runs as a kernel continuation w | --- | --- | --- | | In-process native module, through the peer link | Makepad's `OctosPeer::open` parks a channel pair; the module host claims it for the instance whose code opened it and the shell serves it with the process apps' **peer link** (`peer_link::module_connected`, `octosense_ai_host::module_peer`), so the app does not know how it is hosted | On main ([#142](https://github.com/OctoSense-org/OctoSense/issues/142)); used by Calculator, Clock, Notes, Reminders and Weather, and by the Terminal where it runs in process | | In-process native module (Rinx) | the **injected service**: `ai_host::offer` before `create`, `octosense_app_peers::injection::claim` inside it, giving a scoped `OctosAppService` (`Open`, `History`, `Turn`, `Interrupt`, `Approval`); the module never sees the protocol | On main | -| Script app | `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt", …)` to the `octos` host service (`crates/ai-host/src/contained.rs`); gated by the manifest, `Policy::contained_gate` (`ContainedGate::Consent` by default) and first-use consent; tool approvals its peer raises go to the shell's router. None of the system apps calls `octos.*` or draws a chat: the person talks to their agents in the shell's "Ask " panel, on the same peer, and the shell drives them for the system agent | On main ([#106](https://github.com/OctoSense-org/OctoSense/pull/106), consent from [#120](https://github.com/OctoSense-org/OctoSense/pull/120)) | -| Native app in its own process | the **peer link**: its own channel on the app's hub connection (`PeerRequest`, `PeerReply`, `PeerToolCall` with the identity and caller stamped by the shell), never registered with the AI bus; client API in Makepad's `makepad-ai-services` | Shell side on main ([#130](https://github.com/OctoSense-org/OctoSense/pull/130), `crates/shell/src/peer_link/`, frames routed from the hub connection in `lib.rs`); used by the Terminal | +| Script app | `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt", …)` to the `octos` host service (`crates/ai-host/src/contained.rs`); gated by the manifest, `Policy::contained_gate` (`ContainedGate::Consent` by default) and first-use consent; tool approvals its peer raises go to the shell's router. None of the system apps calls `octos.*` or draws a chat: the person talks to their agents in the shell's "Ask <app>" panel, on the same peer, and the shell drives them for the system agent | On main ([#106](https://github.com/OctoSense-org/OctoSense/pull/106), consent from [#120](https://github.com/OctoSense-org/OctoSense/pull/120)) | +| Native app in its own process | the **peer link**: its own channel on the app's hub connection (`octos_peer` requests, replies and `PeerToolCall` with the identity and caller stamped by the shell), never registered with the AI bus; client API in Makepad's `makepad-ai-services` | Shell side on main ([#130](https://github.com/OctoSense-org/OctoSense/pull/130), `crates/shell/src/peer_link/`, frames routed from the hub connection in `lib.rs`); used by the Terminal | ### Makepad's AI services bus vs OctoSense's app agents @@ -280,22 +280,20 @@ Where an agent's tools come from: | Source | Example | Runs | Status | | --- | --- | --- | --- | -| The app's own tools (`tools.json`: name `.`, schemas, `risk`, `confirm: host` or `app`, `shareable`) | `news.list`, `terminal.read_screen`, `mail.notify`, `calendar.add_event` | the app's host service, module or process (or the shell's notice service), called by the shell | On main (`crates/shell/src/host_tools/`): declared from `native-apps.json` `agent.tools` (native) or the admitted bundle's `tools.json` (script, `host_tools/script_apps.rs`, with App Hub's loader), authorized by (owning app, tool) and caller, arguments and results checked against the declared schemas, budgeted, stamped with account, context and client, and routed to a process app's peer link, an in-process module's executor (`OctosAppService::set_tool_executor`), a script app's host service (`HostServiceExecutor`: the service of the tool's namespace, which the app's manifest must be granted unless it is a system app's own, as `calendar` is Calendar's) or the Terminal's AI bus service. News offers `news.list`, `news.read` and `news.notify`; Mail its account-scoped reads, draft/proposal tools, `mail.notify` and `mail.publish_card` (see `apps/mail/bundle/tools.json`); Calendar `calendar.events`, `calendar.add_event`, `calendar.remove_event` (destructive, `confirm: host`), `calendar.notify` and `calendar.agenda`; Photos, Maps, YouTube and Camera `.notify`, which the shell's notice service answers; the Terminal `terminal.run` and its read tools | +| The app's own tools (`tools.json`: name `.`, schemas, `risk`, `confirm: host` or `app`, `shareable`) | `news.list`, `terminal.read_screen`, `mail.notify`, `calendar.add_event` | the app's host service, module or process (or the shell's notice service), called by the shell | On main (`crates/shell/src/host_tools/`): declared from `native-apps.json` `agent.tools` (native) or the admitted bundle's `tools.json` (script, `host_tools/script_apps.rs`, with App Hub's loader), authorized by (owning app, tool) and caller, arguments and results checked against the declared schemas, budgeted, stamped with account, context and client, and routed to the app's peer link (a process app, or a module serving its tools there), an in-process module's executor (`OctosAppService::set_tool_executor`), a script app's host service (`HostServiceExecutor`: the service of the tool's namespace, which the app's manifest must be granted unless it is a system app's own, as `calendar` is Calendar's) or the Terminal's AI bus service. News offers `news.list`, `news.read` and `news.notify`; Mail `mail.list`, `mail.peek`, `mail.publish_card` and its other read and card tools; Calendar `calendar.events`, `calendar.add_event`, `calendar.remove_event` (destructive, `confirm: host`), `calendar.notify` and `calendar.agenda`; Photos, Maps, YouTube and Camera `.notify`, which the shell's notice service answers; the Terminal `terminal.run` and its read tools | | The system toolbox, by capability (`research`, `crawl`) | `toolbox.search`, `toolbox.web_read`, `toolbox.deep_crawl`, `workflow.run` | host (`crates/toolbox`) | On main behind the feature `toolbox-peers` ([#151](https://github.com/OctoSense-org/OctoSense/pull/151); in the phone build by default, off on the desktop; `crates/ai-host/src/toolbox_peers.rs`): `research` gives `workflow.run`, `workflow.fork`, `toolbox.search` and `toolbox.web_read`, `crawl` gives `toolbox.deep_crawl`, offered only after consent. Until App Hub checks these capabilities, a script app's declaration counts only for a system app, and no app declares either yet | | octos's generic tools | file reads fenced to the workspace, memory, `web_search`, `deep_search` | octos | On main per app: each peer is registered with exactly its granted `generic_tools` (Rinx: its workspace files, `ask_user_question`, memory, the web); an app granted none keeps none; octos's `shell` is never among them (the generator, the catalog and the script loader drop it), and the `_main` profile's `tool_policy` denies it too | | Other apps' shareable tools, routed by the shell | Calendar's agent calling Mail's `mail.send` (planned: Mail declares no such tool yet) | the owning app, via the shell | On main (`relay::Catalog`): granted by (owning app, tool), from `native-apps.json` `agent.grants` (native) or the dotted names in a script app's `agent.tools` (at install), registered marked with the owner, checked on every call. News's `news.list` and `news.read` are shareable, but no app asks for another app's tool yet | | Command execution | `terminal.run` (the Terminal's shareable tool: `confirm: host`, `auto_approvable: false`) | host tool, in a terminal the person sees | On main for the system agent: registered on its session while Setup › Assistant › Command execution is on and the Terminal runs as its own sandboxed process (its newest launch ran inside its OS sandbox: macOS, and Linux with Vulkan and Wayland; not Windows, whose sandbox is not built), each call approved live through the router as a command, typed into the running Terminal. The in-process Terminal offers its reads only and, by ADR 0004 §10 (decided 2026-09-29), stays read-only. Granting it to app agents is step 11 | -**What an agent puts on the glance screen.** The `.notify` tools fill the shell's fixed notice template (`crates/shell/resources/glance/notice.card`); Calendar's `notify` and `agenda` fill its event/agenda templates. The model supplies text for those tools. Mail additionally offers `mail.publish_card`, which accepts model-authored L0 source and can attach a host-issued draft binding. Publication uses the calling app's admitted `glance` grant; repeated card IDs replace that app's publication. - -A phone notification opens its exact card workspace above the current screen; a stale card falls back to Glance. The compact Glance feed executes no generated UI. Tapping a summary expands the resident workspace to the root safe viewport without launching another app. Agent-enabled publishers get native Card / Chat even when their L0 or Splash source omits `sys.chat`; the host binds bounded publication and L0 state context to the original account. Mail's specialized Email / Chat reads one durable draft and uses separate host review for sending. Explicit `sys.chat` retains its declared thread; the explicit Mail demo still uses canned replies. Desktop toasts, the Glance panel and dismiss/undo keep their existing entry points. See [the card workspace walkthrough](mail-composable-cards.md#shared-workspaces-for-all-card-publishers) for code and device evidence. +**What an agent puts on the glance screen** ([#267](https://github.com/OctoSense-org/OctoSense/pull/267), [#273](https://github.com/OctoSense-org/OctoSense/pull/273), [#274](https://github.com/OctoSense-org/OctoSense/pull/274)). **On main.** Every `.notify` fills the shell's one notice card (`crates/shell/resources/glance/notice.card`, `crates/shell/src/glance_notice.rs`): the app's icon and name come from the shell, the title and text from the call, and the same `card_id` replaces the app's earlier notice. Mail's and News's host services hand `notify` to it; the shell's notice service answers the apps without a service of their own. `calendar.notify` and `calendar.agenda` fill Calendar's own `event.card` and `agenda.card` (`apps/calendar/host-service/resources/`). For notices the model writes only text; Mail's `mail.publish_card` takes model-written L0, which the shell checks. Each card is published through the glance service (`crates/shell/src/glance.rs`) as the calling app, only when its manifest was granted `glance` (`glance::publish_for`), and posts a notification. On a desktop that is a toast, which opens the card in its own card window (`glance_sheet.rs`); every new card also opens the glance panel (`glance_panel.rs`, the bar's bell, F9) unless a card window is up, and the toasts stack left of the open panel (`crates/shell/src/shell/notifications.rs`, `keep_clear_of`). The hovered card shows a dismiss button (`glance::dismiss`), and a dismissal can be undone ([#290](https://github.com/OctoSense-org/OctoSense/pull/290)). On a phone it is a shade notification that opens the glance page. None of these cards declares an in-card chat (`sys.chat`); the only shipped card with one is the `OCTOSENSE_GLANCE_DEMO=mail` demo card (`crates/shell/resources/glance/mail-request.card`), whose chat answers with a canned reply. **The system agent's tool set** (`crates/kernel/src/system_tools.rs`, [#117](https://github.com/OctoSense-org/OctoSense/pull/117)). **On main**, enforced: - Its default list, `SYSTEM_AGENT_TOOLS`: supervision (`peer_send_input`, `peer_gather`, `peer_list`, `peer_respond`; not `peer_handoff`, and not `peer_close`, which the `_main` profile's `tool_policy` denies to every agent), its workspace's file tools, `ask_user_question` and media viewing, memory, `web_search` / `web_fetch`, `tool_search`. Grants add to it (`SystemAgentTools`: toolbox tools, other apps' shareable tools, command execution); only command execution has a switch today. - **octos's own shell is never offered.** Before every kernel start `enforce` writes the `_main` profile's `tool_policy`, denying `group:runtime` (`shell`, `bash`, `exec_command`, `write_stdin`). It replaces only a policy OctoSense wrote and refuses the person's own octos home. - **Exactly its list.** Every kernel start sets the system session's kernel tools to `SystemAgentTools::kernel_tools` with octos's durable, host-only `session/tool_list/set` (octos#2648), on the host's own connection before any consumer's frame; a grant change sets it again. Every turn on the session is narrowed to it, whoever starts it; host tools (`terminal.run`) are registered beside it and the `spawn` family is never on it. Real-kernel test: `a_system_agent_turn_is_offered_exactly_the_system_agent_tools`. -- **Command execution from Settings** (off by default, each command approved live): Setup › Assistant › Command execution sets the grant; while it is on, the system chat registers `terminal.run` on the system session over its own connection (`peer/tools/register` without `peer`), and withdraws it the moment it is turned off. The chat's connection is the host's own, so octos needs no credential for it (octos#2657, [#146](https://github.com/OctoSense-org/OctoSense/issues/146)): the tool is offered at once, before any app's agent has started. The grant takes effect only where the Terminal runs as its own process and its newest launch ran inside its OS sandbox (ADR 0004 §10 and §12, decided 2026-09-29; `system_chat::grants::terminal_target`): on Linux without Vulkan, on Windows (no sandbox yet) and on phones `terminal.run` is not registered, and Setup says it needs the Terminal as a sandboxed process. +- **Command execution from Settings** (off by default, each command approved live): Setup › Assistant › Command execution sets the grant; while it is on, the system chat registers `terminal.run` on the system session over its own connection (`peer/tools/register` without `peer`), and withdraws it the moment it is turned off. The chat's connection is the host's own, so octos needs no credential for it (octos#2657, [#146](https://github.com/OctoSense-org/OctoSense/issues/146)): the tool is offered at once, before any app's agent has started. The grant takes effect only where the Terminal runs as its own process and its newest launch ran inside its OS sandbox (ADR 0004 §10 and §12, decided 2026-09-29; `system_chat::grants::terminal_target`): on Linux without Vulkan, on Windows (no sandbox yet), in release packages and on phones `terminal.run` is not registered, and Setup says it needs the Terminal as a sandboxed process. **External clients** get only octos's fixed allowlist ([section 3](#oup-between-the-kernel-and-its-clients)); host-routed tools, developer grants and `dev.run` never reach them. @@ -303,7 +301,7 @@ A phone notification opens its exact card workspace above the current screen; a **A grant is not an approval.** A grant says an agent may *have* a tool; an approval says *this* call, with these exact arguments, may run. Read and in-app act tools run once granted; an outward or destructive call (send, post, share, buy, delete, run a command) needs the person, live or by a standing rule. Only the person approves; the system agent never does, and an agent's own text is never an approval surface (ADR 0004 §8). -**Agent tool calls and the person's actions.** The router governs `peer/tool/call`; ordinary app UI actions use the publishing app's policy through the Card runner's service gate and the host service's checks. Mail sending additionally requires a host-owned exact-message review and physical Android approval, including for its legacy composer; generated buttons, developer mode and standing rules cannot authorize SMTP. A look-alike card cannot answer `approval/respond` or obtain the Mail review capability. Each generated card runs in an isolate under its publisher's policy. L0 actions dispatch through `glance_card::LiveCards`; on the phone they execute inside the expanded workspace, while collapsed summaries stay inert. +**Agent tool calls, not the person's own actions.** The router and the shell's sheets govern agent tool calls (`peer/tool/call`). What the person does in an app's own UI, its cards on the glance screen and behind notifications included, is the app's own action, run under the app's policy through the Card runner's service gate and each host service's own checks, with no extra shell approval (ADR 0004 §4 and §8, decided 2026-09-29; hardening of card actions deferred). An app can draw a look-alike approval card, but only the shell's host connection answers `approval/respond`. **On main** since [#153](https://github.com/OctoSense-org/OctoSense/pull/153): every glance tile runs in its own isolate under the publishing app's resolved policy and takes input (`crates/shell/src/glance_card.rs`), and its `host.request` calls leave through the Card runner's gate as that app. A script card's handlers run wherever it is drawn. An L0 card's taps and field edits run in the desktop's card window (`glance_sheet.rs`, opened from the card's toast), the glance panel and the phone's glance page, one path for all three (`glance_card.rs` `LiveCards`). **The approval router** (`crates/shell/src/approvals/router.rs`, [#120](https://github.com/OctoSense-org/OctoSense/pull/120)) is the shell's one place that answers approval requests, on their exact arguments. **On main.** For each request, in order: @@ -399,7 +397,7 @@ One host-owned layout for every app, declared by its manifest (`storage` block) | --- | --- | --- | | Script app ↔ shell | App Hub's nested isolate: no `mod.res` / `mod.run`, a jail and quota, only `host.request` for granted families; password fields inert; secrets on host sheets | On main | | Native module ↔ shell | none in memory: reviewed first-party code only (`native-apps.json`); **panic containment** at the module boundary (`catch_unwind`, [#114](https://github.com/OctoSense-org/OctoSense/pull/114)); limits: a panic while unwinding, `panic = "abort"`, FFI | On main | -| Process app ↔ shell | its own address space; an **OS sandbox** from the manifest's `sandbox` and `storage` (macOS sandbox profile, Linux Landlock and seccomp, Windows AppContainer) | macOS on main (`crates/shell/src/sandbox/macos.rs`, a Seatbelt profile, built and tested; [#130](https://github.com/OctoSense-org/OctoSense/pull/130)); Linux Landlock and seccomp on main (`crates/shell/src/sandbox/linux.rs`), validated on a real kernel on 2026-09-30 ([#138](https://github.com/OctoSense-org/OctoSense/issues/138), fixes [#199](https://github.com/OctoSense-org/OctoSense/pull/199), [#200](https://github.com/OctoSense-org/OctoSense/pull/200), [#202](https://github.com/OctoSense-org/OctoSense/pull/202)); their tests run in local CI's Linux-host job `linux-host / sandbox` ([local-ci.md](local-ci.md)), not in GitHub CI; Windows planned ([#137](https://github.com/OctoSense-org/OctoSense/issues/137)): a process app there runs with the user's rights | +| Process app ↔ shell | its own address space; for native apps (the catalog's upstream Makepad apps, such as Browser and Files, run unsandboxed), an **OS sandbox** from the manifest's `sandbox` and `storage` (macOS sandbox profile, Linux Landlock and seccomp, Windows AppContainer) | macOS on main (`crates/shell/src/sandbox/macos.rs`, a Seatbelt profile, built and tested; [#130](https://github.com/OctoSense-org/OctoSense/pull/130)); Linux Landlock and seccomp on main (`crates/shell/src/sandbox/linux.rs`), validated on a real kernel on 2026-09-30 ([#138](https://github.com/OctoSense-org/OctoSense/issues/138), fixes [#199](https://github.com/OctoSense-org/OctoSense/pull/199), [#200](https://github.com/OctoSense-org/OctoSense/pull/200), [#202](https://github.com/OctoSense-org/OctoSense/pull/202)); their tests run in local CI's Linux-host job `linux-host / sandbox` ([local-ci.md](local-ci.md)), not in GitHub CI; Windows planned ([#137](https://github.com/OctoSense-org/OctoSense/issues/137)): a process app there runs with the user's rights | | App ↔ kernel | apps never speak the kernel protocol and **never see the host token**: modules get the peer link or a scoped `OctosAppService`, script apps a host service, process apps the peer link | On main | | Peer ↔ peer | octos: separate workspace (overlapping workspaces refused), memory namespace, transcript; request contexts fenced to `contexts//` | On main (octos UPCR-2026-034) | | Agent ↔ secrets | secrets outside every jail and workspace; startup check | On main | @@ -411,7 +409,7 @@ One host-owned layout for every app, declared by its manifest (`storage` block) ## 8. Worked example: emailing a meeting invite -This **historical ADR 0004 sketch is not an implemented send workflow**. Calendar ships `calendar.add_event`; Mail now provides account-scoped reads, draft/proposal operations and card publication, but declares no shareable agent tool `mail.send`. ADR0007 supersedes the sketch's hypothetical standing-rule sending: current Mail requires physical approval of the exact host review. The diagram below illustrates the proposed cross-app relay shape, not today's Mail approval contract. For the implemented reply flow, see [Composed Mail cards](mail-composable-cards.md). +This ADR 0004 example combines an implemented Calendar action with a **planned Mail send tool**. Calendar (desktop only) ships `calendar.add_event`; Mail currently gives its agent read and card tools, but no `mail.send`. Completing this workflow requires a declared/shareable `mail.send`, admission and caller grants, and its executor. The broker, relay and approval mechanisms below already exist. ```mermaid sequenceDiagram @@ -447,12 +445,12 @@ sequenceDiagram | Step | What happens | Status | | --- | --- | --- | | 1 | The person asks the system agent, in the system conversation `_main:api:octosense#system`. | On main: the shell's system chat ([#132](https://github.com/OctoSense-org/OctoSense/pull/132)), or a paired Talk to Octos client (the system assistant pane is separate from an app's Makepad `aichat` bus interface) | -| 2 | The system agent plans. Ambiguity is asked, not guessed ("two Edwards?"); for a bounded read it may call a granted tool directly. Work that needs Calendar's judgement goes to Calendar's agent with `peer_send_input`; if the person has not allowed Calendar's agent yet, `agents.ask` shows them the first-use sheet and waits for the answer. | `peer_send_input`, `agents.list` and `agents.ask` on main, and relay grants implemented; the system agent's host tools are described in section 4; this example's Mail tool and grant remain unimplemented | +| 2 | The system agent plans. Ambiguity is asked, not guessed ("two Edwards?"); for a bounded read it may call a granted tool directly. Work that needs Calendar's judgement goes to Calendar's agent with `peer_send_input`; if the person has not allowed Calendar's agent yet, `agents.ask` shows them the first-use sheet and waits for the answer. | `peer_send_input`, `agents.list` and `agents.ask` on main, and relay grants implemented; the system agent's host tools are `agents.*`, the native apps' read tools and, when on, `terminal.run`, and this example's Mail tool and grant are still planned | | 3 | octos delivers the input to the shell as `peer/input`; the shell starts the turn on Calendar's peer with `turn/start`, so it has Calendar's tools, memory and account context. If Calendar's peer has no host connection, the system agent is told the app is not connected. | On main ([octos#2567](https://github.com/octos-org/octos/pull/2567) in the pin; the broker starts the turn) | -| 4 | Calendar's agent calls `calendar.add_event` (its own tool, `act`: it runs without a sheet) and Mail's shareable `mail.send` once per invitee. Each call reaches the shell as `peer/tool/call`; the shell stamps the caller (Calendar's agent), account and context. | Calendar action ( the `calendar` host service keeps the events in `/calendar/events.json`) and relay implemented; `mail.send` planned | +| 4 | Calendar's agent calls `calendar.add_event` (its own tool, `act`: it runs without a sheet) and Mail's shareable `mail.send` once per invitee. Each call reaches the shell as `peer/tool/call`; the shell stamps the caller (Calendar's agent), account and context. | Calendar action (desktop only; the `calendar` host service keeps the events in `/calendar/events.json`) and relay implemented; `mail.send` planned | | 5 | The shell checks that Calendar's manifest was granted `mail.send` (at install for a script app); no second, agent-level consent. | Relay and App Hub admission checks implemented; this specific grant/tool is absent | | 6 | `mail.send` is outward and `confirm: host`, so the approval router takes it: developer mode off; not `confirm: app`; not `auto_approvable: false`; a standing rule on (Mail, `mail.send`) such as "send to my contacts" approves it (notified and audited; the contacts condition holds only once the person turned on "Use my contacts in approval rules"), else one batched shell sheet shows each invitation: owning app Mail, tool `mail.send`, calling app Calendar, the exact arguments. | Router, rules, sheets and audit on main ([#120](https://github.com/OctoSense-org/OctoSense/pull/120)); fed by kernel `host_tool` approvals through the relay, the path Calendar's destructive `calendar.remove_event` takes today; `mail.send` planned | -| 7 | On approval the shell hands each call to Mail's host service, which sends with the account the person signed in to on Mail's host sheet (the password never reaches the agent), and returns the result to the kernel as `peer/tool/result`. | Mail host service implemented (its legacy `mail.send` path also requires host review); its agent has `mail.notify`, while `mail.send` as an agent tool remains planned | +| 7 | On approval the shell hands each call to Mail's host service, which sends with the account the person signed in to on Mail's host sheet (the password never reaches the agent), and returns the result to the kernel as `peer/tool/result`. | Mail host service implemented (its `mail.send` method serves Mail's own UI); its agent has read and card tools, while `mail.send` as an agent tool remains planned | | 8 | Calendar's turn ends and octos writes `peers//result.md` and `turns.txt`; the system agent reads it with `peer_gather`. A failed invitation is named, and one with an unknown outcome is never retried without the person. | Blackboard on main (octos) | | 9 | The system agent announces "Booked Tue 3 pm; invitations sent to 3". Calendar's and Mail's own UIs show the change because their data changed. | Planned: Calendar's own window cannot list events yet (it needs an App Hub `calendar` capability); `calendar.notify` can put the event on the glance screen today | @@ -472,7 +470,7 @@ Found while writing this page (2026-09-28), rechecked against `main` on 2026-09- 10. **Developer mode, ADR 0004 §13.** Fixed: `dev.run` is registered on covered apps' peers and run by the shell. Fixed: Settings chooses the apps it covers (the desktop's Setup › Developer options, a phone's About phone › Developer options), and a phone reveals Developer options with seven taps on Build number, then turns it on there with a confirmation. Open: in-process modules still show their own confirmation sheets. 11. **Questions, ADR 0004 §6 (decided 2026-09-29).** Agents ask with octos's `ask_user_question` and the shell routes each question to the app's conversation or the system chat; `host.ask` is dropped. Fixed: the broker forwards an app peer's `user_question/requested` to the shell's request model (`crates/shell/src/questions/`), which routes it by the turn's origin to the app's conversation or the system chat; only the person answers, on a shell surface (cards never answer). An agent asks only when its `generic_tools` list `ask_user_question`. 12. **Process sandboxes, ADR 0004 §3.** Fixed for Linux: Landlock and seccomp were validated on a real kernel on 2026-09-30 (#138, with the fixes #199, #200 and #202), and their tests run in local CI's `linux-host / sandbox` job, not in GitHub CI. Open: Windows is not built (#137). -13. **App-agent follow-ups from the 2026-09-29 review, rechecked 2026-10-01.** The old blanket claims that approvals are dropped, every app uses `device`, and failed input starts are only logged no longer describe the implementation. `broker.rs::on_user_question`/approval routing hand prompts to the host; `contained.rs::account_of` consults the host account source; input-start failures use `peer/input/reject`; `driver_of`/`take_over` coordinate multiple brokers for one peer. The `broker` tests exercise these paths. The contained `host.request` adapter still returns a collected turn reply rather than a script-visible streaming subscription; native contexts and the shell's chat surfaces have event subscriptions. `implemented_by: "app"` script tool dispatch, bundle `AGENT.md` prompt loading and automatic skills/triggers remain unimplemented. Read the [walkthrough](architecture-walkthrough.md) for the exact current boundaries. +13. **App-agent follow-ups from the 2026-09-29 review, rechecked 2026-10-01.** The old blanket claims that approvals are dropped, every app uses `device`, and failed input starts are only logged no longer describe the implementation. `broker.rs::on_user_question`/approval routing hand prompts to the host; `contained.rs::account_of` consults the host account source; input-start failures use `peer/input/reject`; `driver_of`/`take_over` coordinate multiple brokers for one peer. The `broker` tests exercise these paths. The contained `host.request` adapter still returns a collected turn reply rather than a script-visible streaming subscription; native contexts and the shell's chat surfaces have event subscriptions. `implemented_by: "app"` script tool dispatch remains unimplemented; bundle `AGENT.md` and skills go with each turn, and only Mail has a trigger (new mail). Read the [walkthrough](architecture-walkthrough.md) for the exact current boundaries. ## Source map @@ -491,7 +489,7 @@ Found while writing this page (2026-09-28), rechecked against `main` on 2026-09- | App peers: broker, hosted launch, storage contract, injection | [`crates/app-peers/src/`](../crates/app-peers/README.md) (`broker.rs`, `hosted.rs`, `storage.rs`, `injection.rs`, `host_approvals.rs`) | | Script system apps per shell | [`desktop/system-apps.json`](../desktop/system-apps.json), [`phone/system-apps.json`](../phone/system-apps.json) | | Which apps have an agent; the system agent's `agents.list` and `agents.ask` | [`crates/shell/src/apps.rs`](../crates/shell/src/apps.rs) (`agent_apps`), [`crates/shell/src/agents.rs`](../crates/shell/src/agents.rs) | -| The system chat and the "Ask " panel | [`crates/shell/src/system_chat/`](../crates/shell/src/system_chat/mod.rs), [`crates/shell/src/app_chat/`](../crates/shell/src/app_chat/mod.rs) | +| The system chat and the "Ask <app>" panel | [`crates/shell/src/system_chat/`](../crates/shell/src/system_chat/mod.rs), [`crates/shell/src/app_chat/`](../crates/shell/src/app_chat/mod.rs) | | Host-tool relay: app tools, script apps' executor, host read tools, toolbox | [`crates/shell/src/host_tools/`](../crates/shell/src/host_tools/mod.rs) (`relay.rs`, `script_apps.rs`, `files.rs`, `toolbox.rs`), [`crates/ai-host/src/toolbox_peers.rs`](../crates/ai-host/src/toolbox_peers.rs), [`crates/toolbox`](../crates/toolbox/README.md) | | Agents' questions | [`crates/shell/src/questions/`](../crates/shell/src/questions/mod.rs) | | Glance cards: the service, notices, tiles, panel, card window, in-card chat, toasts | [`crates/shell/src/glance.rs`](../crates/shell/src/glance.rs), [`glance_notice.rs`](../crates/shell/src/glance_notice.rs), [`glance_card.rs`](../crates/shell/src/glance_card.rs), [`glance_panel.rs`](../crates/shell/src/glance_panel.rs), [`glance_sheet.rs`](../crates/shell/src/glance_sheet.rs), [`glance_chat.rs`](../crates/shell/src/glance_chat.rs), [`shell/notifications.rs`](../crates/shell/src/shell/notifications.rs); [`crates/l0-chat`](../crates/l0-chat/README.md) | diff --git a/docs/architecture.zh-CN.md b/docs/architecture.zh-CN.md index ba59d3e4..6683772b 100644 --- a/docs/architecture.zh-CN.md +++ b/docs/architecture.zh-CN.md @@ -4,15 +4,13 @@ OctoSense 的进程、Agent、工具、审批、存储与信任边界。具体调用和所有权见[代码导读](architecture-walkthrough.zh-CN.md)。依赖版本以 [Cargo.toml](../Cargo.toml) 和 [native-runtime.lock.json](../native-runtime.lock.json) 为准;下方带日期的设计记录保留早期决策。 -**Mail 回复实现:** [ADR0007](adr/0007-composable-mail-action-cards.zh-CN.md) 的草稿/编辑/聊天/审核适配器已在源码中实现。Mail Agent 可以提议草稿与审核,不能批准或发送。开发者模式也必须经过宿主审核控件;当前只有 Android 实体触摸能授权。[当前代码与验证条件](mail-composable-cards.zh-CN.md)取代下文较早的 Mail 演示/发送路径描述。其中记录了 OnePlus 6 上 DeepSeek 与 MiniMax 的限定范围测试;完整设备/UX 矩阵和桌面/无障碍发送审批仍未完成。 - 每条陈述都标明状态: - **已在 main**:已合入,在本仓库代码中读过(给出路径)。 - **进行中**:有未合并的 pull request,附链接。 - **规划中**:已在 ADR 中决定(附链接和计划步骤),尚未实现。 -背后的决策是 [ADR 0001](adr/0001-one-octosense-repository.md)(英文,一个仓库)、[ADR 0002](adr/0002-event-driven-app-agents.md)(英文,应用 Agent,Proposed,经 0004 修订)、[ADR 0003](adr/0003-shared-octos-client-access.md)(英文,Talk to Octos)和 [ADR 0004](adr/0004-native-apps-hosting-and-peers.md)(英文,原生应用、应用 Agent、跨应用协作与审批,已接受,并已于 2026-09-29 实现,后续事项除外)。助手的各项服务、各类应用目前能调用什么、如何在本地运行,见 [ai-services.zh-CN.md](ai-services.zh-CN.md);本文不再重复。 +背后的决策是 [ADR 0001](adr/0001-one-octosense-repository.md)(英文,一个仓库)、[ADR 0002](adr/0002-event-driven-app-agents.md)(英文,应用 Agent,Proposed,部分已实施,经 0004 和 0006 修订)、[ADR 0003](adr/0003-shared-octos-client-access.md)(英文,Talk to Octos)和 [ADR 0004](adr/0004-native-apps-hosting-and-peers.md)(英文,原生应用、应用 Agent、跨应用协作与审批,已接受,并已于 2026-09-29 实现,后续事项和三项计划除外)。助手的各项服务、各类应用目前能调用什么、如何在本地运行,见 [ai-services.zh-CN.md](ai-services.zh-CN.md);本文不再重复。 ## 目录 @@ -41,7 +39,7 @@ flowchart LR person(["用户"]) subgraph shellp["OctoSense Shell 进程(桌面端,或手机上的 Home)"] wm["窗口管理器、启动器、面板、
系统对话、glance 面板
crates/shell"] - mods["进程内原生模块
App Hub、Rinx、(AppCard)"] + mods["进程内原生模块
App Hub、Rinx、Notes、Clock 等、(AppCard)"] runner["App Hub Card runner
脚本应用各在隔离环境中"] router["审批路由
crates/shell/src/approvals"] aihost["ai-host + app-peers broker
到内核的宿主连接"] @@ -59,7 +57,7 @@ flowchart LR wm --- mods wm --- runner term <-->|"hub(回环 WebSocket)"| wm - mods -- "注入的 OctosAppService" --> aihost + mods -- "注入的 OctosAppService 或 peer link" --> aihost runner -- "host.request(octos.*)" --> aihost aihost <-->|"OUP over stdio
(或宿主 token 的 WebSocket)"| kern sys -- "peer_send_input / 黑板" --> peers @@ -77,6 +75,7 @@ flowchart LR +--------------------------- OctoSense Shell 进程 -------------------------------+ | 窗口管理器 / 启动器 / 面板 审批路由 AI 服务总线 | | 进程内模块:App Hub(+ 运行脚本应用的 Card runner)、Rinx、(AppCard) | +| 以及 Calculator、Clock、Notes、Reminders、Weather | | 宿主工具中继 -> 应用宿主服务(mail、calendar、news、通知) | | ai-host + app-peers broker == 宿主连接 ==+ | +--------------------------------------------|-----------------------------------+ @@ -127,11 +126,12 @@ flowchart LR | Terminal | **独立进程** | Vulkan 构建且在 Wayland 会话中时为独立进程,否则进程内 | 进程内 | 默认 / 关闭 | 有自己的 Agent,只有只读工具(`own_tools`);输入命令是系统 Agent 的 `terminal.run`(`confirm: host`、`auto_approvable: false`) | | Calculator、Clock、Notes、Reminders、Weather | 进程内 | 进程内 | 进程内 | 默认 / 默认 | 获授权四个 `octos.*` 服务;它们的只读工具也提供给系统 Agent(`system_tools`) | | Sheets、Reference | 进程内 | 进程内 | 进程内 | 可选 / `mobile-apps` | – | +| Task(Task Manager) | **独立进程** | 独立进程(Shell 对没有模块的应用不套用 `process-if-vulkan`) | –(不构建) | 由桌面目录启动;没有模块 | – | | AppCard | 进程内 | 进程内 | 进程内 | 可选 / 可选 | 自己的内核连接 | -因此**只有 Terminal 是进程应用**,而且只在桌面端;`tools/native_apps.py` 拒绝 Linux 上的普通 `process`(只允许 `process-if-vulkan`),所以没有 Vulkan 和 Wayland 的 Linux 上所有应用都在进程内运行。App Hub 留在进程内,因为它承载所有脚本应用运行所在的 Card runner。Rinx 留在进程内:ADR 0004 §2 规定,等 peer link 和进程沙箱就绪后,再通过一次经评审的 `hosting` 改动把它移出。如今 peer link 的 Shell 一侧以及 macOS 和 Linux 的沙箱已在 main 上,但所锁定版本的 Rinx 通过注入的服务([第 3 节](#应用与它自己的-agent))而不是 peer link 访问它的 Agent,它的 `hosting` 在所有平台上仍是 `module`。 +因此在桌面端,**Terminal 和 Task 是进程应用**(Task 没有模块);`tools/native_apps.py` 拒绝 Linux 上的普通 `process`(只允许 `process-if-vulkan`),所以没有 Vulkan 和 Wayland 的 Linux 上所有已链接的应用都在进程内运行。App Hub 留在进程内,因为它承载所有脚本应用运行所在的 Card runner。Rinx 留在进程内:ADR 0004 §2 规定,等 peer link 和进程沙箱就绪后,再通过一次经评审的 `hosting` 改动把它移出。如今 peer link 的 Shell 一侧以及 macOS 和 Linux 的沙箱已在 main 上,但所锁定版本的 Rinx 通过注入的服务([第 3 节](#应用与它自己的-agent))而不是 peer link 访问它的 Agent,它的 `hosting` 在所有平台上仍是 `module`。 -Shell 在运行时如何决定(`crates/shell/src/apps.rs`,`AppRegistry::hosting`):如果目标平台不能运行进程(在 wasm 和原生移动平台上 `host::processes_available()` 为 false),所有应用都作为模块;App Hub 和 Settings 始终是模块;其他情况下原生应用按清单条目托管,并且只有存在进程形态(可以从中构建它的源码检出,或同目录下的二进制)时才作为进程运行;没有进程形态时回退到进程内(`manifest_default`)。桌面端发布包尚未在 main 上([#257](https://github.com/OctoSense-org/OctoSense/pull/257),进行中,它取代已关闭的 #94)。按应用的覆盖设置(`~/.makepad/wm/apps.splash`、`--module `)可以在模块和进程之间切换一个应用。 +Shell 在运行时如何决定(`crates/shell/src/apps.rs`,`AppRegistry::hosting`):如果目标平台不能运行进程(在 wasm 和原生移动平台上 `host::processes_available()` 为 false),所有应用都作为模块;App Hub 和 Settings 始终是模块;其他情况下原生应用按清单条目托管,并且只有存在进程形态(可以从中构建它的源码检出,或同目录下的二进制)时才作为进程运行;没有进程形态时回退到进程内(`manifest_default`)。桌面端发布包([#257](https://github.com/OctoSense-org/OctoSense/pull/257))不含进程应用的二进制,因此其中 Terminal 在进程内运行,没有 Task。按应用的覆盖设置(`~/.octosense/wm/apps.splash`、`--module `)可以在模块和进程之间切换一个应用。 **进程托管**使用 Makepad 窗口管理器的托管机制(`crates/shell/src/clients.rs`、`hub.rs`): @@ -144,19 +144,19 @@ Shell 在运行时如何决定(`crates/shell/src/apps.rs`,`AppRegistry::host ### 脚本应用 -脚本应用(系统应用 News、Photos、Maps、Mail、Calendar、AI providers 和 YouTube,加上仅手机上的 Camera,来自 `desktop/system-apps.json` 和 `phone/system-apps.json`,以及商店应用)是 OctoScript 应用包。它们都在进程内、在 App Hub 的 **Card runner**(`octosense-app-hub-app` 的 `CARD_MODULE`)中运行:每个应用实例一个嵌套隔离环境,去掉了 `mod.res` 和 `mod.run`,有 jail 和配额。应用只能通过 `host.request(".", …)` 调用其清单获授权的服务族来访问 Shell。脚本的错误只会在它自己的隔离环境中失败。**已在 main。** +脚本应用(系统应用 News、Photos、Maps、Mail、AI providers 和 YouTube,加上仅桌面端的 Calendar 和仅手机上的 Camera,来自 `desktop/system-apps.json` 和 `phone/system-apps.json`,以及商店应用)是 OctoScript 应用包。它们都在进程内、在 App Hub 的 **Card runner**(`octosense-app-hub-app` 的 `CARD_MODULE`)中运行:每个应用实例一个嵌套隔离环境,去掉了 `mod.res` 和 `mod.run`,有 jail 和配额。应用只能通过 `host.request(".", …)` 调用其清单获授权的服务族来访问 Shell。脚本的错误只会在它自己的隔离环境中失败。**已在 main。** ```mermaid flowchart TB subgraph desktop["桌面端(macOS)"] - ds["OctoSense 进程
Shell + App Hub + Card runner + Rinx"] + ds["OctoSense 进程
Shell + App Hub + Card runner + Rinx + Notes、Clock 等"] dk["octos 子进程
octos-kernel serve --stdio"] dt["Terminal 子进程
--stdin-loop"] ds -- "stdin/stdout" --> dk dt -- "hub WebSocket、IOSurface 画面" --> ds end subgraph android["Android(Home)"] - as["Home 进程
Shell + App Hub + Card runner + Rinx(全部进程内)"] + as["Home 进程
Shell + App Hub + Card runner + Rinx + Notes、Clock 等(全部进程内)"] ak["liboctos.so 子进程
serve --stdio"] as -- "stdin/stdout" --> ak end @@ -174,16 +174,16 @@ flowchart TB | Agent | 是什么 | 状态 | | --- | --- | --- | -| **系统 Agent** | `_main` profile 上的会话 `_main:api:octosense#system`(`crates/kernel/src/network.rs` 中的 `SYSTEM_SESSION`)。它拥有并监督所有应用 peer。用户在 Shell 的**系统对话**中与它对话(`crates/shell/src/system_chat/`,[#132](https://github.com/OctoSense-org/OctoSense/pull/132):Setup → Assistant → Assistant chat、F8、桌面 Dock 上的 Assistant 图标或手机主屏的 Assistant 磁贴;桌面上是左侧一个中等大小、用户可以移动和调整大小的面板,手机上为全屏),或通过已配对的 Talk to Octos 客户端 | 已在 main | -| **应用 Agent** | 每个(应用,账号)一个由宿主拥有的 octos **peer**,归系统 Agent 所有(octos UPCR-2026-034,Rinx [ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md))。用户可在应用自己的界面中,或在 Shell 的 **“Ask ” 面板**(`crates/shell/src/app_chat/`:顶栏的 “Ask ”、Shift+F8;桌面上位于系统对话旁边,手机上为全屏面板)与它对话。面板显示两条通道及其发言者,以及一个输入框:用户的通道空闲时可以发送(即使系统 Agent 正在工作),用户自己的回合运行时可以停止,系统 Agent 正在运行的那一行上有 “Stop the system agent's task” | 带 Agent 的原生应用(Rinx、Terminal、Calculator、Clock、Notes、Reminders、Weather)和每个带 Agent 的脚本应用(目前是系统应用 News、Mail、Photos、Maps、YouTube、仅桌面端的 Calendar 和仅手机上的 Camera)已在 main,需首次使用同意;见下文 | +| **系统 Agent** | `_main` profile 上的会话 `_main:api:octosense#system`(`crates/kernel/src/network.rs` 中的 `SYSTEM_SESSION`)。它拥有并监督所有应用 peer。用户在 Shell 的**系统对话**中与它对话(`crates/shell/src/system_chat/`:F8、桌面 Dock 上的 Assistant 图标或手机主屏的 Assistant 磁贴),或通过已配对的 Talk to Octos 客户端 | 已在 main | +| **应用 Agent** | 每个(应用,账号)一个由宿主拥有的 octos **peer**,归系统 Agent 所有(octos UPCR-2026-034,Rinx [ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md))。用户可在应用自己的界面中,或在 Shell 的 **“Ask <app>” 面板**(`crates/shell/src/app_chat/`:顶栏的 “Ask <app>”、Shift+F8;桌面上位于系统对话旁边,手机上为全屏面板)与它对话。面板显示两条通道及其发言者,以及一个输入框:用户的通道空闲时可以发送(即使系统 Agent 正在工作),用户自己的回合运行时可以停止,系统 Agent 正在运行的那一行上有 “Stop the system agent's task” | 带 Agent 的原生应用(Rinx、Terminal、Calculator、Clock、Notes、Reminders、Weather)和每个带 Agent 的脚本应用(目前是系统应用 News、Mail、Photos、Maps、YouTube、仅桌面端的 Calendar 和仅手机上的 Camera)已在 main,需首次使用同意;见下文 | 每个应用 peer 都独立拥有: - **工作区**:peer 会话所绑定的目录。自锁定 octos 665209e5 起新建的 peer 以账号目录(`apps//accounts//`,ADR 0004 §11)作为 `peer/prepare` 的 `cwd`(`host_tools::agent_workspace`),并记录在其宿主令牌旁边,恢复时使用同一目录;之前创建的 peer 保留内核分配的目录,因为 octos 不允许在另一个工作区下恢复 peer(见[不一致之处](#代码与-adr-不一致之处)); - **记忆命名空间** `app//acct-`(`crates/app-peers/src/broker.rs` 中的 `app_namespace()`);不返回它的内核会被拒绝; - 自己的**对话记录**和**模型**通道(宿主通过 `peer/prepare` / `peer/model/set` 设置模型); -- 自己的**工具列表**:内核工具恰好是其清单授予的那些(`generic_tools`,从不包括 octos 的 `shell`),加上 Shell 注册的工具(octos [#2567](https://github.com/octos-org/octos/pull/2567),UPCR-2026-035):每次 `peer/prepare` 和重连之后,broker 在驱动该 peer 回合的连接上注册应用声明的工具,以及授予它的其他应用可共享工具,每个都标明所属应用(`crates/shell/src/host_tools/`),并把 `generic_tools` 设为恰好是应用清单声明且用户授予的内核工具(原生应用为 `native-apps.json` 的 `agent.generic_tools`,脚本应用为其清单 `agent.tools` 中不带点的名称;一个也没写的应用就一个也没有)。Terminal 声明 `terminal.run`、`terminal.read_screen` 和 `terminal.read_scrollback`。系统应用在 `apps//bundle/tools.json` 中声明自己的工具,各自在一个宿主服务上运行:News 的 `news.list`、`news.read` 和 `news.notify`(`news` 服务),Mail 的 `mail.notify`(`mail` 服务),Calendar 的 `calendar.events`、`calendar.add_event`、`calendar.remove_event`、`calendar.notify` 和 `calendar.agenda`(`calendar` 服务),以及 Photos、Maps、YouTube 和 Camera 的 `.notify`(Shell 的通知服务,`crates/shell/src/glance_notice.rs`,因为它们没有自己的服务)。注册失败的 peer 不运行任何回合; -- **请求上下文**(`peer/context/open`):每个客户端实例一个(一个 Rinx 小程序),各有自己的对话记录、peer 工作区内的目录 `contexts//` 和子记忆命名空间。这类客户端上下文不能直接读旁边的文件;它通过 Shell 的宿主读取工具 `files.list`、`files.read` 和 `files.search`(ADR 0004 §11,`crates/shell/src/host_tools/files.rs`)读取账户数据,这些工具从不显示其他上下文的目录。除了这些按客户端划分的上下文(`OctosAppService::open_context`:Rinx 小程序,以及带 `client` 的进程应用 `octos.session.open`),每个应用对话也是一个请求上下文:用户与应用 Agent 的对话(来自 Shell 的 “Ask ” 面板、应用界面或卡片的卡内对话)在用户通道中运行,这是一个以 `share_history` 打开的上下文,与 peer 自己的会话(系统 Agent 的通道)并行,并让每条通道看到另一条通道最近的回合(ADR 0004 §6,2026-09-29 决定;见下文)。在清单声明 Agent 工作在账号文件夹中时(`storage.agent_workspace: "account"`,octos#2647),该对话上下文以 octos 的 `read_parent` 打开:它只读地读取账号文件夹,从不读取其他上下文的文件夹,且只写入自己的文件夹。 +- 自己的**工具列表**:内核工具恰好是其清单授予的那些(`generic_tools`,从不包括 octos 的 `shell`),加上 Shell 注册的工具(octos [#2567](https://github.com/octos-org/octos/pull/2567),UPCR-2026-035):每次 `peer/prepare` 和重连之后,broker 在驱动该 peer 回合的连接上注册应用声明的工具,以及授予它的其他应用可共享工具,每个都标明所属应用(`crates/shell/src/host_tools/`),并把 `generic_tools` 设为恰好是应用清单声明且用户授予的内核工具(原生应用为 `native-apps.json` 的 `agent.generic_tools`,脚本应用为其清单 `agent.tools` 中不带点的名称;一个也没写的应用就一个也没有)。Terminal 声明 `terminal.run`、`terminal.read_screen` 和 `terminal.read_scrollback`,它自己的 Agent 只有两个读取工具(`agent.own_tools`);Calculator、Clock、Notes、Reminders 和 Weather 声明各自的只读工具(`calculator.eval`、`clock.now`、`notes.search`、`notes.read`、`reminders.due`、`reminders.list`、`weather.current`)。系统应用在 `apps//bundle/tools.json` 中声明自己的工具,各自在一个宿主服务上运行:News 的 `news.list`、`news.read` 和 `news.notify`(`news` 服务),Mail 的 `mail.accounts`、`mail.folders`、`mail.sync`、`mail.list`、`mail.peek`、`mail.notify`、`mail.publish_card` 和 `mail.skip_event`(`mail` 服务),Calendar 的 `calendar.events`、`calendar.add_event`、`calendar.remove_event`、`calendar.notify` 和 `calendar.agenda`(`calendar` 服务),以及 Photos、Maps、YouTube 和 Camera 的 `.notify`(Shell 的通知服务,`crates/shell/src/glance_notice.rs`,因为它们没有自己的服务)。注册失败的 peer 不运行任何回合; +- **请求上下文**(`peer/context/open`):每个客户端实例一个(一个 Rinx 小程序),各有自己的对话记录、peer 工作区内的目录 `contexts//` 和子记忆命名空间。这类客户端上下文不能直接读旁边的文件;它通过 Shell 的宿主读取工具 `files.list`、`files.read` 和 `files.search`(ADR 0004 §11,`crates/shell/src/host_tools/files.rs`)读取账户数据,这些工具从不显示其他上下文的目录。除了这些按客户端划分的上下文(`OctosAppService::open_context`:Rinx 小程序,以及带 `client` 的进程应用 `octos.session.open`),每个应用对话也是一个请求上下文:用户与应用 Agent 的对话(来自 Shell 的 “Ask <app>” 面板、应用界面或卡片的卡内对话)在用户通道中运行,这是一个以 `share_history` 打开的上下文,与 peer 自己的会话(系统 Agent 的通道)并行,并让每条通道看到另一条通道最近的回合(ADR 0004 §6,2026-09-29 决定;见下文)。在清单声明 Agent 工作在账号文件夹中时(`storage.agent_workspace: "account"`,octos#2647),该对话上下文以 octos 的 `read_parent` 打开:它只读地读取账号文件夹,从不读取其他上下文的文件夹,且只写入自己的文件夹。 目前谁有 peer(`crates/ai-host/src/lib.rs`,`Policy::shipped()`;`crates/app-peers/src/hosted.rs`,`effective_services` = 声明 ∩ 支持 ∩ 策略): @@ -241,14 +241,14 @@ flowchart LR 使用 octos 的 peer 机制,深度为 1(peer 不能创建、引导或关闭 peer)。octos 中**已在 main**;这些工具位于 octos `crates/octos-agent/src/tools/`。 - **系统 Agent → 应用 Agent。** `peer_send_input`(只有发起者可用,最多 64 KB)经 peer 的收件箱把文字作为 peer 的下一个用户回合送达(在 serve 中是持久队列,每隔几秒处理一次,至少送达一次)。 -- **系统 Agent 能联系到哪些应用的 Agent。** `peer_list` 列出已准备好的 peer:Shell 会准备每个已允许应用的 Agent。对它列不出来的(尚未允许或已关闭),Shell 会告诉它:应用的 Agent 有变化时,在系统对话的回合中附上一条说明;以及系统会话上的宿主工具 `agents.list` 和 `agents.ask`(`crates/shell/src/agents.rs`)。`agents.ask` 显示首次使用面板,只有用户能回答,并且它会等用户作答:它被声明为 `risk: act`、`outward: true`、`confirm: "app"`,所以内核像持有审批一样持有这次调用;用户作答且该应用的 peer 就绪后,系统对话带着 peer 的 slug 回答它,系统 Agent 就能在同一个回合里继续处理请求(10 分钟仍无回答时,它回答用户还没有作答,面板保持显示;[#274](https://github.com/OctoSense-org/OctoSense/pull/274))。 +- **系统 Agent 能联系到哪些应用的 Agent。** `peer_list` 列出已准备好的 peer:Shell 会准备每个已允许的脚本应用的 Agent,原生应用的 peer 只在应用打开时存在。对它列不出来的(尚未允许、已关闭或原生应用未打开),Shell 会告诉它:应用的 Agent 有变化时,在系统对话的回合中附上一条说明;以及系统会话上的宿主工具 `agents.list` 和 `agents.ask`(`crates/shell/src/agents.rs`)。`agents.ask` 显示首次使用面板,只有用户能回答,并且它会等用户作答:它被声明为 `risk: act`、`outward: true`、`confirm: "app"`,所以内核像持有审批一样持有这次调用;用户作答且该应用的 peer 就绪后,系统对话带着 peer 的 slug 回答它,系统 Agent 就能在同一个回合里继续处理请求(10 分钟仍无回答时,它回答用户还没有作答,面板保持显示;[#274](https://github.com/OctoSense-org/OctoSense/pull/274))。 - **应用 Agent → 系统 Agent:黑板。** peer 的每个回合写入 `peers//result.md`(以及 `result-.md`),并在 `turns.txt` 中追加一行;系统 Agent 用 `peer_gather` 和 `peer_list` 读取(`awaiting_input` 表示 peer 正在等待回答问题)。这是 peer 之间唯一的通道。 -- **提问。** peer 用 `ask_user_question` 提问;系统 Agent 用 `peer_respond` 回答。`peer_respond` 从不回答审批(octos 会拒绝)。octos 在宿主驱动的回合中保留 `ask_user_question`(`generic_tools` 是宿主设定的精确列表,省略时保留全部工具)。ADR 0004 §6(2026-09-29 决定)由 Shell 把每个问题转到正确的对话:应用自己的对话,或者对系统 Agent 的回合(包括 `peer/input` 回合)转到系统对话;没有 `host.ask`。**已在 main**:系统对话回答系统 Agent 自己会话上的问题;broker 把应用 peer 的 `user_question/requested`(其自身会话、请求上下文或 `peer/input` 回合)连同回合的发起者(octos 报告时用它,否则推断:broker 为 `peer/input` 发起的回合属于系统 Agent)交给 Shell 的请求模型(`crates/shell/src/questions/`),请求模型按发起者路由:用户或应用的回合转到应用的对话(该应用的 “Ask ” 面板打开时在面板中,否则在 Shell 审批浮层的问题卡片中,`crates/shell/src/approvals/view.rs`:同一时间只有一个界面,[#279](https://github.com/OctoSense-org/OctoSense/pull/279)),系统 Agent 的回合转到系统对话。只有用户能在 Shell 界面上回答(在 broker 的连接上发送 `user_question/respond`);应用的上下文只收到 `user_question/handled_by_host`,broker 拒绝应用回答宿主持有的 id。回合结束时问题关闭。 -- **每个应用 Agent 两条并行通道,只读共享历史。** ADR 0004 §6(2026-09-29 决定,取代同日 [#166](https://github.com/OctoSense-org/OctoSense/pull/166) 与 octos [#2626](https://github.com/octos-org/octos/pull/2626) 的单一排队对话):系统 Agent 与 peer 自己的会话对话(`peer/input`),用户(应用界面、其卡片、不带 `client` 的进程应用)在 broker 以 `share_history` 打开的请求上下文中对话(octos [#2636](https://github.com/octos-org/octos/pull/2636),UPCR-2026-034),每个句柄一个新的上下文 id(其历史也包含同一账号、同一实例之前各个句柄中用户的消息)。两条通道并行运行:用户的消息只等自己上下文的上一轮,`peer/input` 在 peer 会话上保留自己的队列。每一轮开始时,内核把另一条通道最近的用户/助手文本(默认 20 条,最多 50 条,16 KiB 以内;去掉工具行)作为只读块给模型看,这个块从不写入读取方的对话记录;没有共享的对话记录。每一轮都保留 `origin`(`person`、`system_agent`、`app`;内核的 `[from …]` 标记;宿主不能改写系统 Agent 回合的标记),用户的回合在黑板上留下一轮结果(`origin: person`、`context: `),系统 Agent 用 `peer_gather` 读取,它本身不会触发 fleet 汇总。`OctosAppService::open_conversation` 是用户的通道;`open_context(client)` 仍是普通请求上下文(Rinx 小程序)。应用的跟随者(`OctosContext::subscribe`;peer link 上的 `conversation` 帧)收到两条通道的事件,每个都带 `lane` 与 `speaker`;`octos.session.history` 按 `persisted_at` 合并两份对话记录,每行带 `lane`,带标记的用户消息另附 `speaker` 与 `display_text`。接受的风险:跨通道的文件编辑不加锁(每条通道编辑自己的文件夹,上下文的是 `contexts/`),未知结果标记是共享的,并行回合可能略微超出 peer 的 token 预算。 +- **提问。** peer 用 `ask_user_question` 提问;系统 Agent 用 `peer_respond` 回答。`peer_respond` 从不回答审批(octos 会拒绝)。octos 在宿主驱动的回合中保留 `ask_user_question`(`generic_tools` 是宿主设定的精确列表,省略时保留全部工具)。ADR 0004 §6(2026-09-29 决定)由 Shell 把每个问题转到正确的对话:应用自己的对话,或者对系统 Agent 的回合(包括 `peer/input` 回合)转到系统对话;没有 `host.ask`。**已在 main**:系统对话回答系统 Agent 自己会话上的问题;broker 把应用 peer 的 `user_question/requested`(其自身会话、请求上下文或 `peer/input` 回合)连同回合的发起者(octos 报告时用它,否则推断:broker 为 `peer/input` 发起的回合属于系统 Agent)交给 Shell 的请求模型(`crates/shell/src/questions/`),请求模型按发起者路由:用户或应用的回合转到应用的对话(该应用的 “Ask <app>” 面板打开时在面板中,否则在 Shell 审批浮层的问题卡片中,`crates/shell/src/approvals/view.rs`:同一时间只有一个界面,[#279](https://github.com/OctoSense-org/OctoSense/pull/279)),系统 Agent 的回合转到系统对话。只有用户能在 Shell 界面上回答(在 broker 的连接上发送 `user_question/respond`);应用的上下文只收到 `user_question/handled_by_host`,broker 拒绝应用回答宿主持有的 id。回合结束时问题关闭。 +- **每个应用 Agent 两条并行通道,只读共享历史。** ADR 0004 §6:系统 Agent 与 peer 自己的会话对话(`peer/input`),用户(应用界面、其卡片、不带 `client` 的进程应用)在 broker 以 `share_history` 打开的请求上下文中对话(octos [#2636](https://github.com/octos-org/octos/pull/2636),UPCR-2026-034),每个句柄一个新的上下文 id(其历史也包含同一账号、同一实例之前各个句柄中用户的消息)。两条通道并行运行:用户的消息只等自己上下文的上一轮,`peer/input` 在 peer 会话上保留自己的队列。每一轮开始时,内核把另一条通道最近的用户/助手文本(默认 20 条,最多 50 条,16 KiB 以内;去掉工具行)作为只读块给模型看,这个块从不写入读取方的对话记录;没有共享的对话记录。每一轮都保留 `origin`(`person`、`system_agent`、`app`;内核的 `[from …]` 标记;宿主不能改写系统 Agent 回合的标记),用户的回合在黑板上留下一轮结果(`origin: person`、`context: `),系统 Agent 用 `peer_gather` 读取,它本身不会触发 fleet 汇总。`OctosAppService::open_conversation` 是用户的通道;`open_context(client)` 仍是普通请求上下文(Rinx 小程序)。应用的跟随者(`OctosContext::subscribe`;peer link 上的 `conversation` 帧)收到两条通道的事件,每个都带 `lane` 与 `speaker`;`octos.session.history` 按 `persisted_at` 合并两份对话记录,每行带 `lane`,带标记的用户消息另附 `speaker` 与 `display_text`。接受的风险:跨通道的文件编辑不加锁(每条通道编辑自己的文件夹,上下文的是 `contexts/`),未知结果标记是共享的,并行回合可能略微超出 peer 的 token 预算。 ### 宿主拥有的路径:`peer/input` -对宿主拥有的 peer,普通的 `peer_send_input` 会作为内核续跑执行,没有应用的工具或记忆。ADR 0004 §6 解决了这个问题:octos 把系统 Agent 的输入以 **`peer/input {peer, session_id, input_id, turn_id, text}`** 的形式送到宿主的驱动连接,由 Shell 自己启动回合(用内核的 `turn_id` 调用 `turn/start`),这样回合就带有应用的工具、记忆和上下文,其审批也出现在应用中。如果没有宿主连接持有该 peer,octos 会告诉系统 Agent 该应用未连接。**已在 main**:自锁定 665209e5 起 octos 会发送它(octos [#2567](https://github.com/octos-org/octos/pull/2567));broker(`crates/app-peers/src/broker.rs`)用内核的 `turn_id` 在自己的连接上启动回合,丢弃重复的 `input_id`,对已登出的账号或用户未允许的应用不启动任何回合(`host_tools::ShellToolHost::admit_input`),而是用 `peer/input/reject` 连同原因拒绝它(octos [#2621](https://github.com/octos-org/octos/pull/2621));启动失败的回合也以同样方式拒绝。同一个应用在内核上有多个实例时,该账号最早仍在运行的实例驱动这个 peer(注册其工具、接收其 `peer/input`),它关闭时由下一个实例接管([#201](https://github.com/OctoSense-org/OctoSense/pull/201))。内核每个会话只接受一个回合,且不排队(`turn_in_progress`),所以 broker 为系统 Agent 在 peer 会话上的输入每个 peer 维护一个队列,一次一个回合(被以 `turn_in_progress` 拒绝的启动会用同一个回合 id 重试);已有 8 个在等待时,新的输入以 `busy` 被拒绝。用户的消息在自己的通道里运行,从不在这个队列里等待。任何通道都不会被长时间占住:peer 会话或上下文上的审批或问题 10 分钟后过期(`OCTOSENSE_PROMPT_DEADLINE_SECS`;审批路由拒绝该审批,请求模型以自由文本婉拒该问题,从不批准,两者都显示 “Expired: no answer in 10 min”,直到被关闭),30 秒后仍在运行的回合会被 broker 中断,以便队列中的下一个回合开始。用户的 Stop 结束两条通道上正在运行的回合,用户的和系统 Agent 的:对话上的 `octos.turn.interrupt`(应用、卡片、peer link),以及 Shell 审批面板和问题卡片上的 “Stop 's agent”(`approvals::stop_agent`、`broker::interrupt_where`)。“Ask ” 面板一次只停一条通道(`broker::interrupt_lane_where`):它的 Stop 结束用户自己的回合,“Stop the system agent's task” 结束系统 Agent 的回合。Shell 的各个内核使用方共用一个内核连接(`crates/kernel/src/router.rs`),因此每个 broker 和系统对话对 octos 来说是同一个宿主连接;各自只回答自己的 peer 或会话的调用。 +对宿主拥有的 peer,普通的 `peer_send_input` 会作为内核续跑执行,没有应用的工具或记忆。ADR 0004 §6 解决了这个问题:octos 把系统 Agent 的输入以 **`peer/input {peer, session_id, input_id, turn_id, text}`** 的形式送到宿主的驱动连接,由 Shell 自己启动回合(用内核的 `turn_id` 调用 `turn/start`),这样回合就带有应用的工具、记忆和上下文,其审批也出现在应用中。如果没有宿主连接持有该 peer,octos 会告诉系统 Agent 该应用未连接。**已在 main**:自锁定 665209e5 起 octos 会发送它(octos [#2567](https://github.com/octos-org/octos/pull/2567));broker(`crates/app-peers/src/broker.rs`)用内核的 `turn_id` 在自己的连接上启动回合,丢弃重复的 `input_id`,对已登出的账号或用户未允许的应用不启动任何回合(`host_tools::ShellToolHost::admit_input`),而是用 `peer/input/reject` 连同原因拒绝它(octos [#2621](https://github.com/octos-org/octos/pull/2621));启动失败的回合也以同样方式拒绝。同一个应用在内核上有多个实例时,该账号最早仍在运行的实例驱动这个 peer(注册其工具、接收其 `peer/input`),它关闭时由下一个实例接管([#201](https://github.com/OctoSense-org/OctoSense/pull/201))。内核每个会话只接受一个回合,且不排队(`turn_in_progress`),所以 broker 为系统 Agent 在 peer 会话上的输入每个 peer 维护一个队列,一次一个回合(被以 `turn_in_progress` 拒绝的启动会用同一个回合 id 重试);已有 8 个在等待时,新的输入以 `busy` 被拒绝。用户的消息在自己的通道里运行,从不在这个队列里等待。任何通道都不会被长时间占住:peer 会话或上下文上的审批或问题 10 分钟后过期(`OCTOSENSE_PROMPT_DEADLINE_SECS`;审批路由拒绝该审批,请求模型以自由文本婉拒该问题,从不批准,两者都显示 “Expired: no answer in 10 min”,直到被关闭),30 秒后仍在运行的回合会被 broker 中断,以便队列中的下一个回合开始。用户的 Stop 结束两条通道上正在运行的回合,用户的和系统 Agent 的:对话上的 `octos.turn.interrupt`(应用、卡片、peer link),以及 Shell 审批面板和问题卡片上的 “Stop <app>'s agent”(`approvals::stop_agent`、`broker::interrupt_where`)。“Ask <app>” 面板一次只停一条通道(`broker::interrupt_lane_where`):它的 Stop 结束用户自己的回合,“Stop the system agent's task” 结束系统 Agent 的回合。Shell 的各个内核使用方共用一个内核连接(`crates/kernel/src/router.rs`),因此每个 broker 和系统对话对 octos 来说是同一个宿主连接;各自只回答自己的 peer 或会话的调用。 ### 应用与它自己的 Agent @@ -256,8 +256,8 @@ flowchart LR | --- | --- | --- | | 进程内原生模块,经 peer link | Makepad 的 `OctosPeer::open` 暂存一对通道;模块宿主把它归给打开它的实例,Shell 用进程应用同一套 **peer link** 服务它(`peer_link::module_connected`、`octosense_ai_host::module_peer`),应用无需知道自己如何被承载 | 已在 main([#142](https://github.com/OctoSense-org/OctoSense/issues/142));Calculator、Clock、Notes、Reminders 和 Weather 在用,Terminal 在进程内运行时也在用 | | 进程内原生模块(Rinx) | **注入的服务**:`create` 之前调用 `ai_host::offer`,在其中调用 `octosense_app_peers::injection::claim`,得到受限的 `OctosAppService`(`Open`、`History`、`Turn`、`Interrupt`、`Approval`);模块永远看不到协议 | 已在 main | -| 脚本应用 | 向 `octos` 宿主服务(`crates/ai-host/src/contained.rs`)调用 `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt", …)`;受清单、`Policy::contained_gate`(默认 `ContainedGate::Consent`)和首次使用同意约束;其 peer 发起的工具审批交给 Shell 的审批路由。系统应用都不调用 `octos.*`,也都不绘制对话界面:用户在 Shell 的 “Ask ” 面板中与它们的 Agent 对话,用的是同一个 peer,Shell 也为系统 Agent 驱动它们 | 已在 main([#106](https://github.com/OctoSense-org/OctoSense/pull/106),同意机制来自 [#120](https://github.com/OctoSense-org/OctoSense/pull/120)) | -| 独立进程的原生应用 | **peer link**:应用 hub 连接上的独立通道(`PeerRequest`、`PeerReply`,以及由 Shell 盖上身份和调用方的 `PeerToolCall`),从不注册到 AI 总线;客户端 API 在 Makepad 的 `makepad-ai-services` 中 | Shell 一侧已在 main([#130](https://github.com/OctoSense-org/OctoSense/pull/130),`crates/shell/src/peer_link/`,帧由 `lib.rs` 从 hub 连接转发);Terminal 在用 | +| 脚本应用 | 向 `octos` 宿主服务(`crates/ai-host/src/contained.rs`)调用 `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt", …)`;受清单、`Policy::contained_gate`(默认 `ContainedGate::Consent`)和首次使用同意约束;其 peer 发起的工具审批交给 Shell 的审批路由。系统应用都不调用 `octos.*`,也都不绘制对话界面:用户在 Shell 的 “Ask <app>” 面板中与它们的 Agent 对话,用的是同一个 peer,Shell 也为系统 Agent 驱动它们 | 已在 main([#106](https://github.com/OctoSense-org/OctoSense/pull/106),同意机制来自 [#120](https://github.com/OctoSense-org/OctoSense/pull/120)) | +| 独立进程的原生应用 | **peer link**:应用 hub 连接上的独立通道(`octos_peer` 请求、回复,以及由 Shell 盖上身份和调用方的 `PeerToolCall`),从不注册到 AI 总线;客户端 API 在 Makepad 的 `makepad-ai-services` 中 | Shell 一侧已在 main([#130](https://github.com/OctoSense-org/OctoSense/pull/130),`crates/shell/src/peer_link/`,帧由 `lib.rs` 从 hub 连接转发);Terminal 在用 | ### Makepad 的 AI 服务总线与 OctoSense 的应用 Agent @@ -280,22 +280,20 @@ Agent 的工具来源: | 来源 | 示例 | 在哪里运行 | 状态 | | --- | --- | --- | --- | -| 应用自己的工具(`tools.json`:名称 `.`、schema、`risk`、`confirm: host` 或 `app`、`shareable`) | `news.list`、`terminal.read_screen`、`mail.notify`、`calendar.add_event` | 应用的宿主服务、模块或进程(或 Shell 的通知服务),由 Shell 调用 | 已在 main(`crates/shell/src/host_tools/`):原生应用从 `native-apps.json` 的 `agent.tools` 声明,脚本应用从准入后应用包的 `tools.json` 声明(`host_tools/script_apps.rs`,使用 App Hub 的加载器);按(所属应用,工具)和调用方授权,参数和结果按声明的 schema 检查,计入预算,盖上账号、上下文和客户端,并路由到进程应用的 peer link、进程内模块的执行器(`OctosAppService::set_tool_executor`)、脚本应用的宿主服务(`HostServiceExecutor`:工具命名空间对应的服务,应用的清单必须获授权该服务,系统应用自己的命名空间除外,例如 Calendar 的 `calendar`)或 Terminal 的 AI 总线服务。News 提供 `news.list`、`news.read` 和 `news.notify`;Mail 提供账户绑定读取、草稿/提议工具、`mail.notify` 和 `mail.publish_card`(完整列表见 `apps/mail/bundle/tools.json`);Calendar 提供 `calendar.events`、`calendar.add_event`、`calendar.remove_event`(破坏性,`confirm: host`)、`calendar.notify` 和 `calendar.agenda`;Photos、Maps、YouTube 和 Camera 提供 `.notify`,由 Shell 的通知服务执行;Terminal 提供 `terminal.run` 及其读取工具 | +| 应用自己的工具(`tools.json`:名称 `.`、schema、`risk`、`confirm: host` 或 `app`、`shareable`) | `news.list`、`terminal.read_screen`、`mail.notify`、`calendar.add_event` | 应用的宿主服务、模块或进程(或 Shell 的通知服务),由 Shell 调用 | 已在 main(`crates/shell/src/host_tools/`):原生应用从 `native-apps.json` 的 `agent.tools` 声明,脚本应用从准入后应用包的 `tools.json` 声明(`host_tools/script_apps.rs`,使用 App Hub 的加载器);按(所属应用,工具)和调用方授权,参数和结果按声明的 schema 检查,计入预算,盖上账号、上下文和客户端,并路由到应用的 peer link(进程应用,或在其上提供工具的模块)、进程内模块的执行器(`OctosAppService::set_tool_executor`)、脚本应用的宿主服务(`HostServiceExecutor`:工具命名空间对应的服务,应用的清单必须获授权该服务,系统应用自己的命名空间除外,例如 Calendar 的 `calendar`)或 Terminal 的 AI 总线服务。News 提供 `news.list`、`news.read` 和 `news.notify`;Mail 提供 `mail.list`、`mail.peek`、`mail.publish_card` 等读取和卡片工具;Calendar 提供 `calendar.events`、`calendar.add_event`、`calendar.remove_event`(破坏性,`confirm: host`)、`calendar.notify` 和 `calendar.agenda`;Photos、Maps、YouTube 和 Camera 提供 `.notify`,由 Shell 的通知服务执行;Terminal 提供 `terminal.run` 及其读取工具 | | 系统工具箱,按能力授予(`research`、`crawl`) | `toolbox.search`、`toolbox.web_read`、`toolbox.deep_crawl`、`workflow.run` | 宿主(`crates/toolbox`) | 已在 main,位于 `toolbox-peers` 特性之后([#151](https://github.com/OctoSense-org/OctoSense/pull/151);手机构建默认开启,桌面端默认关闭;`crates/ai-host/src/toolbox_peers.rs`):`research` 提供 `workflow.run`、`workflow.fork`、`toolbox.search` 和 `toolbox.web_read`,`crawl` 提供 `toolbox.deep_crawl`,只在用户同意后提供。在 App Hub 检查这些能力之前,脚本应用的声明只对系统应用有效,目前还没有应用声明其中任何一个 | | octos 的通用工具 | 限定在工作区内的文件读取、记忆、`web_search`、`deep_search` | octos | 已在 main 上按应用设置:每个 peer 注册时带着它被授予的精确 `generic_tools`(Rinx:工作区文件、`ask_user_question`、记忆、网页);没有授予的应用一个也没有;octos 的 `shell` 从不在其中(生成器、目录和脚本加载器都会去掉它),`_main` profile 的 `tool_policy` 也拒绝它 | | 其他应用可共享的工具,由 Shell 路由 | Calendar 的 Agent 调用 Mail 的 `mail.send`(规划中:Mail 还没有声明这个工具) | 所属应用,经 Shell | 已在 main(`relay::Catalog`):按(所属应用,工具)授予,原生应用来自 `native-apps.json` 的 `agent.grants`,脚本应用来自其 `agent.tools` 中带点的名称(安装时);注册时标明所属应用,每次调用都检查。News 的 `news.list` 和 `news.read` 是可共享的,但还没有应用申请其他应用的工具 | | 命令执行 | `terminal.run`(Terminal 的可共享工具:`confirm: host`、`auto_approvable: false`) | 宿主工具,在用户可见的终端中 | 系统 Agent 已在 main:Setup › Assistant › Command execution 开启、且 Terminal 作为自己的沙箱进程运行时(它最近一次启动在其操作系统沙箱中运行:macOS,以及有 Vulkan 和 Wayland 的 Linux;Windows 不行,它的沙箱尚未实现),注册在其会话上,每次调用经路由作为命令实时批准,输入到正在运行的 Terminal。进程内 Terminal 只提供读取工具,并按 ADR 0004 §10(2026-09-29 决定)保持只读。授予应用 Agent 属于步骤 11 | -**Agent 放到 Glance 屏幕上的内容。** `.notify` 填充宿主的固定通知模板(`crates/shell/resources/glance/notice.card`);Calendar 的 `notify` 与 `agenda` 填充其日程/议程模板,这些工具由模型提供文字。Mail 另有 `mail.publish_card`,接收模型编写的 L0 源码,并可附加宿主签发的草稿绑定。发布仍要求调用应用获得 `glance` 准入;重复卡片 ID 替换该应用的原发布。 - -手机通知在当前屏幕上方打开准确对应的卡片工作区;过期卡片回退到 Glance。紧凑摘要不执行生成界面,点击后驻留工作区展开到根安全视口,无须启动另一个应用。有 Agent 的发布者即使在 L0 或 Splash 源码中没有 `sys.chat`,也能使用原生 Card / Chat;宿主把有大小限制的发布信息和 L0 状态绑定到原账户。Mail 专用 Email / Chat 读取同一持久草稿,发送另经宿主审核。显式 `sys.chat` 保留声明的线程;显式 Mail 演示仍使用固定回复。桌面 toast、Glance 面板及移除/撤销保留原有入口。代码和实机证据见[卡片工作区导读](mail-composable-cards.zh-CN.md#所有发布者共用的卡片工作区)。 +**Agent 放到 glance 屏幕上的内容**([#267](https://github.com/OctoSense-org/OctoSense/pull/267)、[#273](https://github.com/OctoSense-org/OctoSense/pull/273)、[#274](https://github.com/OctoSense-org/OctoSense/pull/274))。**已在 main。** 每个 `.notify` 都填充 Shell 的同一张通知卡片(`crates/shell/resources/glance/notice.card`、`crates/shell/src/glance_notice.rs`):应用的图标和名称由 Shell 填入,标题和正文来自调用,同一个 `card_id` 会替换该应用之前的通知。Mail 和 News 的宿主服务把 `notify` 交给它;没有自己服务的应用由 Shell 的通知服务应答。`calendar.notify` 和 `calendar.agenda` 填充 Calendar 自己的 `event.card` 和 `agenda.card`(`apps/calendar/host-service/resources/`)。通知卡片中模型只写文字;Mail 的 `mail.publish_card` 接受模型编写、经 Shell 检查的 L0。每张卡片都经 glance 服务(`crates/shell/src/glance.rs`)以调用方应用的身份发布,前提是它的清单获准使用 `glance`(`glance::publish_for`),并发出一条通知。在桌面端,通知是一个 toast,点击它会在单独的卡片窗口中打开这张卡片(`glance_sheet.rs`);每来一张新卡片,glance 面板(`glance_panel.rs`,顶栏的铃铛、F9)也会打开,除非卡片窗口已经打开,而 toast 叠放在打开的面板左侧(`crates/shell/src/shell/notifications.rs`,`keep_clear_of`)。鼠标悬停的卡片会显示一个移除按钮(`glance::dismiss`),移除可以撤销([#290](https://github.com/OctoSense-org/OctoSense/pull/290))。在手机上,通知是通知栏中的一条通知,点击它会打开 glance 页面。这些卡片都没有声明卡内对话(`sys.chat`);唯一带对话的内置卡片是 `OCTOSENSE_GLANCE_DEMO=mail` 的演示卡片(`crates/shell/resources/glance/mail-request.card`),它的对话用固定的演示回复作答。 **系统 Agent 的工具集**(`crates/kernel/src/system_tools.rs`,[#117](https://github.com/OctoSense-org/OctoSense/pull/117))。**已在 main**,已生效: - 默认列表 `SYSTEM_AGENT_TOOLS`:监督(`peer_send_input`、`peer_gather`、`peer_list`、`peer_respond`;不含 `peer_handoff`,也不含 `peer_close`,`_main` 配置文件的 `tool_policy` 对所有智能体禁用它)、其工作区的文件工具、`ask_user_question` 和查看媒体、记忆、`web_search` / `web_fetch`、`tool_search`。授权在此基础上增加(`SystemAgentTools`:工具箱工具、其他应用的可共享工具、命令执行);目前只有命令执行有开关。 - **从不提供 octos 自己的 shell。** 每次启动内核前,`enforce` 写入 `_main` profile 的 `tool_policy`,拒绝 `group:runtime`(`shell`、`bash`、`exec_command`、`write_stdin`)。它只替换 OctoSense 自己写的策略,并拒绝用户自己的 octos 主目录。 - **恰好是它的列表。** 每次内核启动时,在宿主自己的连接上、在任何使用方的帧之前,用 octos 持久的宿主专用 `session/tool_list/set`(octos#2648)把系统会话的内核工具设为 `SystemAgentTools::kernel_tools`;授权变化时再设一次。该会话上的每个回合(无论由谁发起)都被收窄到它;宿主工具(`terminal.run`)在它之外注册,`spawn` 系列工具从不在其中。真实内核测试:`a_system_agent_turn_is_offered_exactly_the_system_agent_tools`。 -- **从 Settings 开启命令执行**(默认关闭,每条命令实时批准):Setup › Assistant › Command execution 设置授权;开启期间,系统对话在自己的连接上把 `terminal.run` 注册到系统会话(不带 `peer` 的 `peer/tools/register`),关闭时立即撤回。对话的连接就是宿主自己的连接,octos 对它不需要凭据(octos#2657,[#146](https://github.com/OctoSense-org/OctoSense/issues/146)):在任何应用的 Agent 启动之前就会立即提供该工具。只有在 Terminal 作为自己的进程运行、且它最近一次启动在其操作系统沙箱中运行时,这项授权才生效(ADR 0004 §10 和 §12,2026-09-29 决定;`system_chat::grants::terminal_target`):在没有 Vulkan 的 Linux、Windows(尚无沙箱)和手机上,`terminal.run` 不会注册,Setup 会说明它需要作为沙箱进程运行的 Terminal。 +- **从 Settings 开启命令执行**(默认关闭,每条命令实时批准):Setup › Assistant › Command execution 设置授权;开启期间,系统对话在自己的连接上把 `terminal.run` 注册到系统会话(不带 `peer` 的 `peer/tools/register`),关闭时立即撤回。对话的连接就是宿主自己的连接,octos 对它不需要凭据(octos#2657,[#146](https://github.com/OctoSense-org/OctoSense/issues/146)):在任何应用的 Agent 启动之前就会立即提供该工具。只有在 Terminal 作为自己的进程运行、且它最近一次启动在其操作系统沙箱中运行时,这项授权才生效(ADR 0004 §10 和 §12,2026-09-29 决定;`system_chat::grants::terminal_target`):在没有 Vulkan 的 Linux、Windows(尚无沙箱)、发布包和手机上,`terminal.run` 不会注册,Setup 会说明它需要作为沙箱进程运行的 Terminal。 **外部客户端**只有 octos 的固定允许列表([第 3 节](#内核与客户端之间的-oup));宿主路由的工具、开发者授权和 `dev.run` 永远到不了它们。 @@ -303,7 +301,7 @@ Agent 的工具来源: **授权不等于审批。** 授权表示 Agent 可以*拥有*某个工具;审批表示*这一次*调用、带着这些确切参数,可以执行。只读和应用内操作类工具授权后即可运行;对外或破坏性的调用(发送、发布、分享、购买、删除、运行命令)需要用户实时批准或由常设规则批准。只有用户能批准;系统 Agent 从不批准,Agent 自己输出的文字也从不作为审批界面(ADR 0004 §8)。 -**Agent 工具调用与用户操作。** 审批路由处理 `peer/tool/call`;普通应用 UI 操作按发布者策略经过 Card runner 服务关口及宿主服务检查。Mail 发送还必须经过宿主拥有的确切邮件审核及 Android 实体批准,包括旧版编辑器的发送入口;生成按钮、开发者模式及常设规则都不能授权 SMTP。仿冒卡片不能回答 `approval/respond` 或获得 Mail 审核凭证。生成卡片在发布者策略下的隔离环境中运行。L0 操作由 `glance_card::LiveCards` 分发;手机上只在展开工作区中执行,收起的摘要不执行这些操作。 +**Agent 的工具调用,而不是用户自己的操作。** 审批路由和 Shell 的面板管的是 Agent 的工具调用(`peer/tool/call`)。用户在应用自己的界面中所做的操作,包括它在 glance 屏幕上和通知背后的卡片,都是应用自己的操作:在应用的策略下,经 Card runner 的服务关口和各宿主服务自己的检查运行,Shell 不再另加审批(ADR 0004 §4 和 §8,2026-09-29 决定;卡片操作的安全加固推迟)。应用可以画出貌似审批卡片的界面,但只有 Shell 的宿主连接能回答 `approval/respond`。自 [#153](https://github.com/OctoSense-org/OctoSense/pull/153) 起**已在 main**:每个 glance 磁贴都在自己的隔离环境中、按发布应用解析后的策略运行,并接受输入(`crates/shell/src/glance_card.rs`),它的 `host.request` 调用以该应用的身份经过 Card runner 的关口。脚本卡片的处理函数在它被绘制的任何地方都会运行。L0 卡片的点按和字段编辑在桌面端的卡片窗口(`glance_sheet.rs`,从卡片的 toast 打开)、glance 面板和手机的 glance 页面中运行,三者走同一条路径(`glance_card.rs` 的 `LiveCards`)。 **审批路由**(`crates/shell/src/approvals/router.rs`,[#120](https://github.com/OctoSense-org/OctoSense/pull/120))是 Shell 中唯一回答审批请求的地方,依据的是确切参数。**已在 main。** 对每个请求依次: @@ -399,7 +397,7 @@ flowchart TB | --- | --- | --- | | 脚本应用 ↔ Shell | App Hub 的嵌套隔离环境:没有 `mod.res` / `mod.run`,有 jail 和配额,只能对获授权的服务族使用 `host.request`;密码输入框失效;机密在宿主面板上输入 | 已在 main | | 原生模块 ↔ Shell | 内存上没有边界:只接受经评审的第一方代码(`native-apps.json`);模块边界上的 **panic 隔离**(`catch_unwind`,[#114](https://github.com/OctoSense-org/OctoSense/pull/114));局限:展开过程中再次 panic、`panic = "abort"`、FFI | 已在 main | -| 进程应用 ↔ Shell | 独立的地址空间;依据清单的 `sandbox` 和 `storage` 生成的**操作系统沙箱**(macOS 沙箱配置、Linux Landlock 和 seccomp、Windows AppContainer) | macOS 已在 main(`crates/shell/src/sandbox/macos.rs`,Seatbelt 配置,已构建并测试;[#130](https://github.com/OctoSense-org/OctoSense/pull/130));Linux 的 Landlock 和 seccomp 已在 main(`crates/shell/src/sandbox/linux.rs`),2026-09-30 已在真实内核上验证([#138](https://github.com/OctoSense-org/OctoSense/issues/138),修复见 [#199](https://github.com/OctoSense-org/OctoSense/pull/199)、[#200](https://github.com/OctoSense-org/OctoSense/pull/200)、[#202](https://github.com/OctoSense-org/OctoSense/pull/202));它们的测试在本地 CI 的 Linux 主机作业 `linux-host / sandbox` 中运行([local-ci.md](local-ci.md),英文),不在 GitHub CI 中;Windows 规划中([#137](https://github.com/OctoSense-org/OctoSense/issues/137)):那里的进程应用以用户权限运行 | +| 进程应用 ↔ Shell | 独立的地址空间;对原生应用(目录中的上游 Makepad 应用,例如 Browser 和 Files,不在沙箱中运行),依据清单的 `sandbox` 和 `storage` 生成的**操作系统沙箱**(macOS 沙箱配置、Linux Landlock 和 seccomp、Windows AppContainer) | macOS 已在 main(`crates/shell/src/sandbox/macos.rs`,Seatbelt 配置,已构建并测试;[#130](https://github.com/OctoSense-org/OctoSense/pull/130));Linux 的 Landlock 和 seccomp 已在 main(`crates/shell/src/sandbox/linux.rs`),2026-09-30 已在真实内核上验证([#138](https://github.com/OctoSense-org/OctoSense/issues/138),修复见 [#199](https://github.com/OctoSense-org/OctoSense/pull/199)、[#200](https://github.com/OctoSense-org/OctoSense/pull/200)、[#202](https://github.com/OctoSense-org/OctoSense/pull/202));它们的测试在本地 CI 的 Linux 主机作业 `linux-host / sandbox` 中运行([local-ci.md](local-ci.md),英文),不在 GitHub CI 中;Windows 规划中([#137](https://github.com/OctoSense-org/OctoSense/issues/137)):那里的进程应用以用户权限运行 | | 应用 ↔ 内核 | 应用从不使用内核协议,**永远看不到宿主 token**:模块使用 peer link 或受限的 `OctosAppService`,脚本应用得到宿主服务,进程应用使用 peer link | 已在 main | | peer ↔ peer | octos:独立的工作区(拒绝重叠的工作区)、记忆命名空间、对话记录;请求上下文被限定在 `contexts//` 内 | 已在 main(octos UPCR-2026-034) | | Agent ↔ 机密 | 机密位于所有 jail 和工作区之外;启动检查 | 已在 main | @@ -411,7 +409,7 @@ flowchart TB ## 8. 完整示例:用邮件发送会议邀请 -这是 **ADR 0004 的历史草图,不是已实现的发信流程**。Calendar 提供 `calendar.add_event`;Mail 现有账户绑定读取、草稿/提议操作和卡片发布,但没有声明可共享的 Agent 工具 `mail.send`。ADR0007 已取代草图中假设的常设规则发信方式:当前 Mail 必须由用户实体批准确切的宿主审核。下图只说明当时提出的跨应用中继形态,不代表当前 Mail 的审批契约。实际回复流程见[组合 Mail 卡片](mail-composable-cards.zh-CN.md)。 +此 ADR 0004 示例组合了已实现的 Calendar 操作与**规划中的 Mail 发信工具**。Calendar(仅桌面端)已提供 `calendar.add_event`;Mail 当前给 Agent 读取和卡片工具,但没有 `mail.send`。完成流程还需要声明可共享的 `mail.send`、准入与调用方授权,以及执行器。下方的 broker、relay 与审批机制已经实现。 ```mermaid sequenceDiagram @@ -447,12 +445,12 @@ sequenceDiagram | 步骤 | 发生什么 | 状态 | | --- | --- | --- | | 1 | 用户在系统对话 `_main:api:octosense#system` 中向系统 Agent 提出请求。 | 已在 main:Shell 的系统对话([#132](https://github.com/OctoSense-org/OctoSense/pull/132)),或已配对的 Talk to Octos 客户端(系统助手面板与应用的 Makepad `aichat` 总线接口分别实现) | -| 2 | 系统 Agent 做计划。有歧义就提问,不去猜(“两个 Edward?”);有限的读取可以直接调用获授权的工具。需要 Calendar 自己判断的工作用 `peer_send_input` 交给 Calendar 的 Agent;如果用户还没有允许 Calendar 的 Agent,`agents.ask` 会向用户显示首次使用面板并等待回答。 | `peer_send_input`、`agents.list` 和 `agents.ask` 已在 main,relay 授权也已实现;系统 Agent 的宿主工具见第 4 节,此示例的 Mail 工具和授权尚未实现 | +| 2 | 系统 Agent 做计划。有歧义就提问,不去猜(“两个 Edward?”);有限的读取可以直接调用获授权的工具。需要 Calendar 自己判断的工作用 `peer_send_input` 交给 Calendar 的 Agent;如果用户还没有允许 Calendar 的 Agent,`agents.ask` 会向用户显示首次使用面板并等待回答。 | `peer_send_input`、`agents.list` 和 `agents.ask` 已在 main,relay 授权也已实现;系统 Agent 的宿主工具是 `agents.*`、原生应用的只读工具,以及开启后的 `terminal.run`,此示例的 Mail 工具和授权仍在规划中 | | 3 | octos 把输入以 `peer/input` 送到 Shell;Shell 在 Calendar 的 peer 上用 `turn/start` 启动回合,使其带有 Calendar 的工具、记忆和账号上下文。如果 Calendar 的 peer 没有宿主连接,系统 Agent 会被告知该应用未连接。 | 已在 main(锁定版本含 [octos#2567](https://github.com/octos-org/octos/pull/2567);由 broker 启动回合) | -| 4 | Calendar 的 Agent 调用 `calendar.add_event`(自己的工具,`act`:运行时不弹面板),并为每位受邀者调用一次 Mail 可共享的 `mail.send`。每次调用都以 `peer/tool/call` 到达 Shell;Shell 盖上调用方(Calendar 的 Agent)、账号和上下文。 | Calendar 操作(`calendar` 宿主服务把日程保存在 `/calendar/events.json`)与中继已实现;`mail.send` 规划中 | +| 4 | Calendar 的 Agent 调用 `calendar.add_event`(自己的工具,`act`:运行时不弹面板),并为每位受邀者调用一次 Mail 可共享的 `mail.send`。每次调用都以 `peer/tool/call` 到达 Shell;Shell 盖上调用方(Calendar 的 Agent)、账号和上下文。 | Calendar 操作(仅桌面端;`calendar` 宿主服务把日程保存在 `/calendar/events.json`)与中继已实现;`mail.send` 规划中 | | 5 | Shell 检查 Calendar 的清单是否获授权 `mail.send`(脚本应用在安装时授权);不需要第二道 Agent 级别的同意。 | Relay 与 App Hub 准入检查已实现;此特定授权/工具尚不存在 | | 6 | `mail.send` 是对外的 `confirm: host` 工具,由审批路由处理:开发者模式未开启;不是 `confirm: app`;不是 `auto_approvable: false`;如果有针对(Mail,`mail.send`)的常设规则(例如“发给我的联系人”),就由规则批准(通知并审计;只有用户打开了“在审批规则中使用我的联系人”,联系人条件才会成立),否则一个合并的 Shell 面板列出每封邀请:所属应用 Mail、工具 `mail.send`、调用方应用 Calendar 以及确切参数。 | 路由、规则、面板和审计已在 main([#120](https://github.com/OctoSense-org/OctoSense/pull/120));经中继由内核的 `host_tool` 审批提交请求,Calendar 的破坏性工具 `calendar.remove_event` 如今就走这条路径;`mail.send` 还在规划中 | -| 7 | 批准后,Shell 把每次调用交给 Mail 的宿主服务,它用用户在 Mail 宿主面板上登录的账号发送(密码永远不会到达 Agent),并把结果以 `peer/tool/result` 返回内核。 | Mail 宿主服务已实现(旧版 `mail.send` 入口也要求宿主审核);Agent 已有 `mail.notify`,`mail.send` 作为 Agent 工具仍在规划中 | +| 7 | 批准后,Shell 把每次调用交给 Mail 的宿主服务,它用用户在 Mail 宿主面板上登录的账号发送(密码永远不会到达 Agent),并把结果以 `peer/tool/result` 返回内核。 | Mail 宿主服务已实现(它的 `mail.send` 方法服务于 Mail 自己的界面);Agent 已有读取和卡片工具,`mail.send` 作为 Agent 工具仍在规划中 | | 8 | Calendar 的回合结束,octos 写入 `peers//result.md` 和 `turns.txt`;系统 Agent 用 `peer_gather` 读取。失败的邀请会被点名,结果未知的邀请未经用户同意绝不重试。 | 黑板已在 main(octos) | | 9 | 系统 Agent 宣布“已预订周二下午 3 点;已向 3 人发送邀请”。Calendar 和 Mail 自己的界面会显示变化,因为它们的数据变了。 | 规划中:Calendar 自己的窗口还不能列出日程(它需要 App Hub 的 `calendar` 能力);如今 `calendar.notify` 可以把日程放到 glance 屏幕上 | @@ -467,12 +465,12 @@ sequenceDiagram 5. **过时的背景描述。** 已修复:ADR 0004 的背景表格和各 README 的目录结构表不再列出 ADR 0004 步骤 1 删除的原生 News、Maps 和 Photos 模块(#113)。[ai-services.zh-CN.md](ai-services.zh-CN.md) 现已描述 2026-09-28 的 `main`(#106 的 Card runner `octos` 服务、#120 的同意机制;当前 `Policy::contained_gate` 默认是 `ContainedGate::Consent`,不是全局关闭)。 6. **“在 Settings 中开启”命令执行。** 已修复:Setup › Assistant › Command execution 设置它(#132),开启期间系统对话把 `terminal.run` 注册到系统会话。 7. **`host::processes_available()` 的测试。** 已修复:`the_desk_knows_where_it_runs` 除了 `wasm32` 也检查手机目标平台。 -8. **审批,ADR 0004 §8。** 每次应用工具调用都通过 `peer/tool/call` 到达 Shell,每个有门控的调用都到达路由(见上文)。还没有应用注册自己的 `confirm: app` 面板(Rinx 需要通过 `OctosAppService::set_confirm_sheet` 交出它的发送面板;进程应用的面板在其 peer link 打开时注册,但还没有进程应用使用 peer link),因此这类调用会等待后被拒绝。main 上唯一的 `confirm: app` 工具是 Shell 自己的 `agents.ask`,由系统对话自己持有。审计记录的是参数摘要而不是参数。发送队列和撤销窗口尚未实现。 +8. **审批,ADR 0004 §8。** 每次应用工具调用都通过 `peer/tool/call` 到达 Shell,每个有门控的调用都到达路由(见上文)。还没有应用注册自己的 `confirm: app` 面板(Rinx 需要通过 `OctosAppService::set_confirm_sheet` 交出它的发送面板;进程应用的面板在其 peer link 打开时注册,但还没有进程应用注册这样的面板),因此这类调用会等待后被拒绝。main 上唯一的 `confirm: app` 工具是 Shell 自己的 `agents.ask`,由系统对话自己持有。审计记录的是参数摘要而不是参数。发送队列和撤销窗口尚未实现。 9. **存储,ADR 0004 §11。** 机密只在 macOS 和 iOS 上使用系统钥匙串(其他平台为 0600 明文文件)。启动检查拒绝通过链接或包含关系通向机密的工作区,而不是查找 `secrets/` 路径,并且不会中止启动。(已修复:app storage 和同意面板中 `storage.accounts` 都默认为 `false`,同意面板现在读取 `StorageSpec`。) 10. **开发者模式,ADR 0004 §13。** 已修复:`dev.run` 已注册在被覆盖应用的 peer 上,由 shell 执行。已修复:Settings 可以选择覆盖哪些应用(桌面端在 Setup › Developer options,手机上在 About phone › Developer options),手机上连续点按 Build number 七次显示 Developer options,再在那里确认开启。未完成:进程内模块仍会显示自己的确认面板。 11. **提问,ADR 0004 §6(2026-09-29 决定)。** Agent 用 octos 的 `ask_user_question` 提问,Shell 把每个问题转到应用的对话或系统对话;`host.ask` 已放弃。已修复:broker 把应用 peer 的 `user_question/requested` 转给 Shell 的请求模型(`crates/shell/src/questions/`),请求模型按回合的发起者把它转到应用的对话或系统对话;只有用户能在 Shell 界面上回答(卡片从不回答)。只有当 Agent 的 `generic_tools` 列出 `ask_user_question` 时它才会提问。 12. **进程沙箱,ADR 0004 §3。** Linux 已修复:Landlock 和 seccomp 已于 2026-09-30 在真实内核上验证(#138,修复见 #199、#200 和 #202),它们的测试在本地 CI 的 `linux-host / sandbox` 作业中运行,不在 GitHub CI 中。未完成:Windows 尚未实现(#137)。 -13. **2026-09-29 应用 Agent 审查的后续,2026-10-01 复核。** “所有审批被丢弃”“全部固定为 `device`”“输入启动失败只记日志”等旧描述已不符合当前实现。`broker.rs::on_user_question` 和审批路由把问题与审批交给宿主;`contained.rs::account_of` 获取宿主账号;失败的系统输入使用 `peer/input/reject`;`driver_of`/`take_over` 协调同一 peer 的多个 broker,`broker` 测试覆盖了这些路径。受限 `host.request` 适配器仍返回汇总回合回复,没有脚本可订阅的流式接口;原生 context 和 Shell 聊天则有事件订阅。`implemented_by: "app"` 的脚本工具执行、`AGENT.md` 提示词加载和自动技能/触发器仍未实现。确切边界见[代码导读](architecture-walkthrough.zh-CN.md)。 +13. **2026-09-29 应用 Agent 审查的后续,2026-10-01 复核。** “所有审批被丢弃”“全部固定为 `device`”“输入启动失败只记日志”等旧描述已不符合当前实现。`broker.rs::on_user_question` 和审批路由把问题与审批交给宿主;`contained.rs::account_of` 获取宿主账号;失败的系统输入使用 `peer/input/reject`;`driver_of`/`take_over` 协调同一 peer 的多个 broker,`broker` 测试覆盖了这些路径。受限 `host.request` 适配器仍返回汇总回合回复,没有脚本可订阅的流式接口;原生 context 和 Shell 聊天则有事件订阅。`implemented_by: "app"` 的脚本工具执行仍未实现;应用包的 `AGENT.md` 和技能随每轮发送,只有 Mail 有触发器(新邮件)。确切边界见[代码导读](architecture-walkthrough.zh-CN.md)。 ## 源码位置 @@ -491,7 +489,7 @@ sequenceDiagram | 应用 peer:broker、托管启动、存储约定、注入 | [`crates/app-peers/src/`](../crates/app-peers/README.md)(`broker.rs`、`hosted.rs`、`storage.rs`、`injection.rs`、`host_approvals.rs`) | | 各 Shell 的脚本系统应用 | [`desktop/system-apps.json`](../desktop/system-apps.json)、[`phone/system-apps.json`](../phone/system-apps.json) | | 哪些应用有 Agent;系统 Agent 的 `agents.list` 和 `agents.ask` | [`crates/shell/src/apps.rs`](../crates/shell/src/apps.rs)(`agent_apps`)、[`crates/shell/src/agents.rs`](../crates/shell/src/agents.rs) | -| 系统对话和 “Ask ” 面板 | [`crates/shell/src/system_chat/`](../crates/shell/src/system_chat/mod.rs)、[`crates/shell/src/app_chat/`](../crates/shell/src/app_chat/mod.rs) | +| 系统对话和 “Ask <app>” 面板 | [`crates/shell/src/system_chat/`](../crates/shell/src/system_chat/mod.rs)、[`crates/shell/src/app_chat/`](../crates/shell/src/app_chat/mod.rs) | | 宿主工具中继:应用工具、脚本应用的执行器、宿主读取工具、工具箱 | [`crates/shell/src/host_tools/`](../crates/shell/src/host_tools/mod.rs)(`relay.rs`、`script_apps.rs`、`files.rs`、`toolbox.rs`)、[`crates/ai-host/src/toolbox_peers.rs`](../crates/ai-host/src/toolbox_peers.rs)、[`crates/toolbox`](../crates/toolbox/README.md) | | Agent 的提问 | [`crates/shell/src/questions/`](../crates/shell/src/questions/mod.rs) | | Glance 卡片:服务、通知卡片、磁贴、面板、卡片窗口、卡内对话、toast | [`crates/shell/src/glance.rs`](../crates/shell/src/glance.rs)、[`glance_notice.rs`](../crates/shell/src/glance_notice.rs)、[`glance_card.rs`](../crates/shell/src/glance_card.rs)、[`glance_panel.rs`](../crates/shell/src/glance_panel.rs)、[`glance_sheet.rs`](../crates/shell/src/glance_sheet.rs)、[`glance_chat.rs`](../crates/shell/src/glance_chat.rs)、[`shell/notifications.rs`](../crates/shell/src/shell/notifications.rs);[`crates/l0-chat`](../crates/l0-chat/README.md) | diff --git a/docs/images/ai-services-cross-app-call.png b/docs/images/ai-services-cross-app-call.png index 52aa4e67b104f86a83b016175ec88df9f14d3a09..ea881dc9cc36de2219129785b856bc7d72854579 100644 GIT binary patch delta 404208 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

*-j=Qj~R^1%>5R^RHRu-H4iB|LC) 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