diff --git a/AGENTS.md b/AGENTS.md index 68baf293..5d790b9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,13 +12,18 @@ | The desktop packaging: entry point, catalogs, WM sync | `desktop/` | `cargo check --locked -p octosense` (and `--features mobile-apps`), `bash tools/check-shell-graph.sh -p octosense`, `desktop.yml` | | Home's packaging: Settings, Android/OpenHarmony/iOS packaging, the phone side of the bridge | `phone/` (run cargo from `phone/`) | `cargo check --locked -p octosense-home --features mobile-apps`, `bash ../tools/check-shell-graph.sh -p octosense-home`, `phone.yml` | | The OnePlus 6 image: vendor, patches, flash/OTA, APK build scripts, web installer | `rom/` | from `rom/`: `python3 -m unittest discover -s tests`, `rom.yml`; image builds are not in CI | -| The shell's AI services (kernel start, `llm` and `model` services, QR import, app peers) | `crates/ai-host/` | `cargo test --locked -p octosense-ai-host --features octos-core,llm`, `apps.yml` | -| The octos kernel service | `crates/kernel/` | `cargo test --locked -p octosense-kernel`, `apps.yml` | +| The shell's AI services (kernel start, `llm` and `model` services, QR import, the `octos` host service that gives script apps their agents, native apps' assistant offer) | `crates/ai-host/` (`src/contained.rs`: script apps' peers `card.`; `src/lib.rs`: `Policy::shipped`, the consent gate) | `cargo test --locked -p octosense-ai-host --features octos-core,llm`, `apps.yml` | +| The octos kernel service, and the system agent's exact tool list | `crates/kernel/` (`src/system_tools.rs`: `SYSTEM_AGENT_TOOLS`, the `_main` profile's `tool_policy`) | `cargo test --locked -p octosense-kernel`, `apps.yml` | | The octos kernel an Android APK bundles | `tools/kernel-artifact.py` | `python3 -m unittest discover -s tools -p 'test_*.py'` | -| Apps' access to the assistant | `crates/app-peers/` | its README, `apps.yml` | -| The system toolbox's workflow templates (library, runner, forks, evaluation, `mod.research`) | `crates/toolbox/` (templates in `crates/toolbox/templates//`) | `cargo test --locked -p octosense-toolbox`, its README, `apps.yml` | +| The app-agent broker: one peer per (app, account), its two lanes, `peer/input`, tool registration, deadlines, purge | `crates/app-peers/` | `cargo test --locked -p octosense-app-peers --features octos-core,ws`, its README, `apps.yml` | +| The system agent's pane (the system chat) and the host tools on its session (`agents.list`, `agents.ask`, `terminal.run`) | `crates/shell/src/system_chat/`, `crates/shell/src/agents.rs` | from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell system_chat`; `phone.yml` | +| Which apps have an agent, their tools, and the relay that checks and routes every agent tool call | `crates/shell/src/apps.rs` (`agent_apps`), `crates/shell/src/host_tools/` (`script_apps.rs`, `relay.rs`, `files.rs`) | from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell host_tools`; `phone.yml` | +| Approvals, first-use consent, standing rules, the audit | `crates/shell/src/approvals/` | from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell approvals`; `phone.yml` | +| Glance cards (`glance.publish`), the desktop's glance panel and card window, a card's in-card chat | `crates/shell/src/glance*.rs`; the shared `sys.chat` host side in `crates/l0-chat/` | `cargo test --locked -p octosense-l0-chat`; from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell glance`; `apps.yml`, `phone.yml` | +| The system toolbox's workflow templates (library, runner, forks, evaluation, `mod.research`) | `crates/toolbox/` (templates in `crates/toolbox/templates//`); offered to app agents by `crates/ai-host/src/toolbox_peers.rs` (feature `toolbox-peers`) | `cargo test --locked -p octosense-toolbox`, its README, `apps.yml` | | A system app | `apps//bundle/` | App Hub's `card-host --bundle apps//bundle --system`; then in a shell | -| A host service (`mail`, `llm`, `model`) | `apps/mail/host-service/`, `apps/ai-providers/` | `cargo test --locked -p octosense-mail-service -p octosense-llm-service` | +| A system app's agent: its tools and the cards they publish | `apps//bundle/tools.json` and the manifest's `agent` block; the tools run on the app's host service | its host service's tests (below); then in a shell, with the system agent asking the app's agent | +| A host service (`mail`, `calendar`, `news`, `llm`, `model`) | `apps/mail/host-service/`, `apps/calendar/host-service/`, `apps/news/host-service/`, `apps/ai-providers/` (`llm` and `model.complete`) | `cargo test --locked -p octosense-mail-service -p octosense-calendar-service -p octosense-news-service -p octosense-llm-service` | | AppCard (opt-in) | `apps/appcard/` | [apps/appcard/AGENTS.md](apps/appcard/AGENTS.md), `apps.yml` | | A native app (App Hub, Rinx, Terminal, Sheets, Reference, AppCard): its crate, pin, features, hosting per target, sandbox, storage and agent grants | `native-apps.json` only; `python3 tools/native_apps.py` writes the marked blocks in the `Cargo.toml`s, `crates/shell/src/native_apps.rs` and `Cargo.lock` | `python3 tools/native_apps.py --check`, `python3 -m unittest discover -s tools -p 'test_*.py'` | | An external pin (Makepad, OctoScript, App Hub, octos, Rinx) | root `Cargo.toml` `[workspace.dependencies]` (a native app's in `native-apps.json`), `native-runtime.lock.json`, `runtime-patches.lock.json` | `python3 tools/setup.py --update`, then `--check --cargo` and `python3 tools/native_apps.py --check` | diff --git a/README.md b/README.md index 3e24d40a..9624d840 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,13 @@ flowchart LR person(["Person"]) ext["Talk to Octos client
web or terminal, opt-in"] subgraph shellp["OctoSense shell process"] - ui["Window manager, launcher,
system chat, sheets"] + ui["Window manager, launcher,
system chat, Ask app panel, sheets"] mods["Native modules
App Hub, Rinx"] runner["Card runner
script apps, glance cards"] aihost["ai-host + app-peers broker
host connection"] relay["Host-tool relay"] router["Approval router"] + svc["App host services
mail, calendar, news"] end term["Terminal
process app in an OS sandbox"] subgraph kern["octos kernel: child process, in process on OpenHarmony"] @@ -44,6 +45,7 @@ flowchart LR person --> ui ui --- mods ui --- runner + ui -->|"person's turns:
Ask app, in-card chat"| aihost mods -->|"OctosAppService"| aihost runner -->|"host.request octos.*"| aihost term <-->|"hub: frames, AI bus"| ui @@ -52,6 +54,8 @@ flowchart LR sys -->|"peer_send_input"| peers kern -->|"peer/tool/call, approvals"| relay relay --> router + relay -->|"app tools"| svc + svc -->|"glance.publish: cards"| runner ext -.->|"external token:
system conversation only"| sys ``` @@ -68,11 +72,63 @@ flowchart LR | --- | --- | --- | | In-process native module | the same peer link as a process app, through Makepad's `OctosPeer` client (the module host claims the link for the instance that opened it) | on `main`; no module uses it yet | | In-process native module (Rinx) | the injected `OctosAppService`: `open_conversation` (the app's conversation with its agent) and `open_context` (a per-client request context, such as a Rinx mini app) | on `main` | -| Script app, and its cards | `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt")` to the `octos` host service | on `main`, behind `Policy::contained_apps` (off in the shipped policy) and first-use consent | +| Script app, and its cards | `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt")` to the `octos` host service, for the names its manifest declares; a card's in-card chat (`sys.chat`) through the shell | on `main`, after first-use consent (the shipped gate; `OCTOSENSE_CONTAINED_APPS=1` asks nobody, `0` turns it off). Today's system apps with an agent declare no `octos.*` name: the shell drives their agents | | Process app | the peer link on its hub connection (`octos.session.open`, `octos.turn.start`, …), identity stamped by the shell | shell side on `main`; no process app granted an agent yet | | The system agent | the kernel's own session `_main:api:octosense#system`, reached from the shell's system chat | on `main` | | Talk to Octos client | the system conversation only, with the external token | on `main` | +### The system agent and the app agents + +**The system agent** is the kernel session `_main:api:octosense#system` on the `_main` profile (`crates/kernel/src/network.rs`). It owns every app agent and supervises them. The person talks to it in the shell's **assistant pane**, the system chat (`crates/shell/src/system_chat/`: the dock's Assistant icon, the phone home's Assistant tile or F8; a medium pane at the left on a desktop that moves and resizes and renders Markdown, full screen on a phone), or from a paired Talk to Octos client. Its kernel tools are exactly `SYSTEM_AGENT_TOOLS` (`crates/kernel/src/system_tools.rs`): `peer_send_input`, `peer_gather`, `peer_list` and `peer_respond` to supervise app agents, its workspace's file tools, memory, `ask_user_question`, media viewing, `web_search`, `web_fetch` and `tool_search`. Every kernel start sets that list with octos's `session/tool_list/set`, so octos's own shell, the spawn family and `peer_close` are never offered. The system chat also registers host tools on the session: `agents.list` and `agents.ask` (`crates/shell/src/agents.rs`: which apps have an agent, and the first-use sheet for one), and `terminal.run` while Setup › Assistant › Command execution is on. + +**An app agent** is one host-owned octos peer per (app, account) ([`crates/app-peers`](crates/app-peers/README.md)). These apps have one on `main` (`crates/shell/src/apps.rs`, `agent_apps`): + +| App | Declared by | Its tools (run by) | What it puts on the glance screen | +| --- | --- | --- | --- | +| Rinx (native) | `native-apps.json` `agent.octos` (the four `octos.*` services) and `agent.generic_tools` | octos's generic tools in its list; its own assistant UI | – | +| News (`os.news`) | `apps/news/bundle/tools.json`, the manifest's `agent` block | `news.list`, `news.read` (the `news` host service) | – | +| Mail (`os.mail`) | `apps/mail/bundle/tools.json`, the manifest's `agent` block and `glance` | `mail.notify` (the `mail` host service) | a notice card with a notification | +| Calendar (`os.calendar`, desktop only) | `apps/calendar/bundle/tools.json`, the manifest's `agent` block and `glance` | `calendar.events`, `calendar.add_event`, `calendar.remove_event` (destructive: the person approves it), `calendar.notify`, `calendar.agenda` (the `calendar` host service) | an event card or an agenda card, with a notification | + +A script app has an agent when its manifest declares `octos.*` names or an `agent` block (`"tools": ["ask_user_question"]` names the kernel tools it may use), or its bundle ships `tools.json` (each tool `.` with its schemas, `risk`, `confirm` and `shareable`). Its peer is `card.`. The person allows the agent once, on the first-use sheet (from the shell's "Ask " panel, the app's own `octos` call or the system agent's `agents.ask`). From then on the shell prepares the peer at startup, with the app's tools registered, so the system agent's `peer_list` shows it. Besides its own tools, an app agent gets the host read tools `files.list`, `files.read` and `files.search` over its account folder. On the phone, which builds `toolbox-peers` by default, it also gets the system toolbox's tools when its manifest asks for `research` or `crawl`, which no app does yet. `AGENT.md`, skills and triggers ([ADR 0002](docs/adr/0002-event-driven-app-agents.md)) are not built: an app agent runs only when the system agent, the person or a card asks it. + +**From the system agent to a card on the glance screen**: + +![From the system agent to a card on the glance screen](docs/images/agents-card-flow.png) + +
Text version (Mermaid) + +```mermaid +sequenceDiagram + autonumber + actor P as Person + participant S as System agent + participant B as Shell: app-peers broker + participant A as Mail's agent (peer card.os.mail) + participant R as Shell: tool relay + participant M as Mail's host service + participant G as Shell: glance service + P->>S: "Tell me on the glance screen when ..." + S->>B: peer_send_input (octos delivers peer/input) + B->>A: turn/start on the peer's session, with Mail's tools + A->>R: peer/tool/call mail.notify {title, body} + R->>R: grant, consent, schema, budget + R->>M: run on Mail's host service + M->>G: glance.publish as os.mail: notice.card, notify + G-->>P: notification (a desktop toast, the phone's shade) + M-->>R: {card_id} + R-->>A: peer/tool/result + A-->>S: the turn's result on the blackboard (peer_gather) + P->>G: opens the card, types in its chat + G->>A: a turn in the person's lane (sys.chat) + A-->>G: the reply, drawn as AI-written +``` + +
+ +- The model never writes card code: `mail.notify`, `calendar.notify` and `calendar.agenda` fill a fixed L0 card the app's host service ships (`apps/mail/host-service/resources/notice.card`, `apps/calendar/host-service/resources/*.card`) and publish it as the app. +- `mail.notify` and `calendar.add_event` are `act` tools and run without a sheet; octos asks for approval only for destructive and outward tools, which go to the shell's approval router ([below](#a-tool-call-with-an-approval)). + ### One app agent, two lanes An app agent is one host-owned octos **peer** per (app, account), owned by the system agent, with its own workspace, memory namespace, model and tool list. The system agent and the person each talk to it in their own lane: @@ -95,7 +151,7 @@ flowchart TB end sys -->|"peer_send_input"| input["octos: peer/input
to the shell"] input -->|"the shell starts the turn"| lane1 - person -->|"app UI or its cards"| lane2 + person -->|"the shell's Ask-app panel,
a card's chat or the app's own UI"| lane2 lane1 -.->|"recent messages, read-only"| lane2 lane2 -.->|"recent messages, read-only"| lane1 lane1 --- own @@ -106,10 +162,29 @@ flowchart TB - **The system agent's lane** is the peer's own session, `…#peer-`. The system agent sends `peer_send_input`; octos delivers it to the shell's host connection as `peer/input`, and the shell starts the turn itself, so it runs with the app's tools, memory and approvals (or refuses it with `peer/input/reject` for a signed-out account or an app the person has not allowed). The peer's results go to the peers' blackboard, which the system agent reads. -- **The person's lane** is a request context, `…#peerctx-.`, opened with `share_history` from the app's UI or its interactive cards (a native module's `open_conversation`, a script app's `octos.session.open`, a process app's peer link), a new one for every handle ([octos#2636](https://github.com/octos-org/octos/pull/2636), UPCR-2026-034). The two lanes run in parallel, one turn at a time per session: a person's message never waits for the system agent's turn. Each turn sees the other lane's recent messages as a read-only block that is never written into its own transcript, and every turn is labelled by its speaker (`[from the person: ]`, `[from the system agent]`). The app follows both lanes, each event tagged with its `lane` and speaker; `octos.session.history` merges both transcripts by time. The person's turns also leave rounds on the blackboard (`origin: person`), so the system agent sees them with `peer_gather`. +- **The person's lane** is a request context, `…#peerctx-.`, opened with `share_history` by the shell's "Ask " panel (`agents::conversation`, client instance `shell-ask`), by a card's in-card chat, or by the app's own UI (a native module's `open_conversation`, a script app's `octos.session.open`, a process app's peer link), a new one for every handle ([octos#2636](https://github.com/octos-org/octos/pull/2636), UPCR-2026-034). The two lanes run in parallel, one turn at a time per session: a person's message never waits for the system agent's turn. Each turn sees the other lane's recent messages as a read-only block that is never written into its own transcript, and every turn is labelled by its speaker (`[from the person: ]`, `[from the system agent]`). The app follows both lanes, each event tagged with its `lane` and speaker; `octos.session.history` merges both transcripts by time. The person's turns also leave rounds on the blackboard (`origin: person`), so the system agent sees them with `peer_gather`. - *Until 2026-09-29 both spoke in one shared conversation on the peer's session ([#166](https://github.com/OctoSense-org/OctoSense/pull/166), octos#2626): one queue per peer, one turn at a time.* - **Rinx mini apps** keep their own request contexts (`open_context`), each with its own transcript and folder, not shared with either lane. +### Talking to an app's agent yourself + +The person is not limited to the system agent: they can talk to any app's own agent directly. Every turn the person starts is a person turn in the person's lane of that app's peer, beside the system agent's lane. It runs with the app's tools, memory and approvals, as a system agent's turn does. + +| Surface | Where it is | How it opens | +| --- | --- | --- | +| **"Ask "** (`crates/shell/src/app_chat/`) | A shell panel for every app with an agent, whether or not the app draws a chat of its own: the system chat's pane drawn as the app's conversation (`app_panel: true`). On a desktop it stands right of the system chat, so the two lanes show side by side. | The bar's "Ask " button (shown while the focused window's app has an agent), Shift+F8, or the menu row "Ask this app's agent". With an app without an agent focused, the shell says "No app agent here". On the phone the pane is drawn as a full-screen sheet, but no touch control opens it on `main` yet. | +| **A card's in-card chat** (`sys.chat`, `crates/shell/src/glance_chat.rs`) | A glance card the app published | The person types in the card's chat. Only the publishing app's own agent answers, and its reply is marked AI-written ([below](#cards-and-questions)). | +| **The app's own UI** | A native module's `open_conversation`, a script app's `octos.session.open`, a process app's peer link | Inside the app. Rinx draws its own assistant UI; none of the system apps (News, Mail, Calendar) draws a chat, so for them the panel and their cards are the way in. | + +How the "Ask " panel behaves: + +- **Consent first.** An agent the person has not decided on shows the first-use sheet, and the panel waits ("'s assistant is not allowed yet: allow it on the sheet."). An agent turned off says so: "'s assistant is off. Turn it on in Setup › Assistant › Approvals." +- **Both lanes, labelled.** The panel follows both lanes and loads their merged history; every row shows its speaker (the person, the system agent, the app's agent). +- **Send** starts a person turn (`TurnTrigger::Person`). It never waits for the system agent's turn: Send stays available while only the system agent's lane runs. With a question from the app's agent open, the text answers it instead. +- **Stop** (in Send's place while the person's own turn runs) stops only that turn ("Stopped.", or "Nothing of yours was running."). A running system agent turn has its own row with **"Stop the system agent's task"**, which stops only that lane. The **"Stop 's agent"** button on the shell's approval and question sheets stops both lanes (`approvals::stop_agent`): the person owns the device. +- **Questions** from turns the person or the app started are shown and answered in the panel; the system agent's go to the system chat (F8). Approvals are the shell's sheets, as everywhere. +- **Close** hides the panel. Its context stays open with its follower, so a reopen shows the person's rows again. The context closes when the panel opens for another app, when the agent is turned off, or when the app's peer goes (a signed-out account, for Mail). + ### A tool call with an approval ![A tool call with an approval](docs/images/agents-tool-call.png) @@ -125,7 +200,7 @@ sequenceDiagram participant Ro as Approval router participant P as Person participant Ex as Owning app - Ag->>K: call mail.send + Ag->>K: call calendar.remove_event alt confirm host K->>Ro: approval/requested, host_tool Ro->>Ro: dev mode, then standing rules @@ -151,12 +226,14 @@ sequenceDiagram - **Tool calls**: octos sends `peer/tool/call` to the shell's relay (`crates/shell/src/host_tools/`), which checks the grant by (owning app, tool) and caller, the arguments against the tool's schema and the caller's budget, and routes the call to the owning app's executor: an in-process module's, a script app's host service, a process app's peer link, or the Terminal's `run` on the AI bus. - **Approvals** go to the approval router (`crates/shell/src/approvals/`): developer mode, then standing rules on (owning app, tool), then a shell-drawn sheet. A `confirm: app` tool is confirmed on the owning app's own sheet, which shows the caller. Only the person approves; the system agent never does. -- **Deadlines and Stop** ([#167](https://github.com/OctoSense-org/OctoSense/pull/167)): an approval or question the shell holds for an app peer expires after 10 minutes (`OCTOSENSE_PROMPT_DEADLINE_SECS`): the router denies it, a question is declined, both stay visible as "Expired: no answer in 10 min". If the turn is still running 30 s later, the broker interrupts it so the next turn can start. The person's Stop ends the running turns of both lanes, the person's and the system agent's. +- **Deadlines and Stop** ([#167](https://github.com/OctoSense-org/OctoSense/pull/167)): an approval or question the shell holds for an app peer expires after 10 minutes (`OCTOSENSE_PROMPT_DEADLINE_SECS`): the router denies it, a question is declined, both stay visible as "Expired: no answer in 10 min". If the turn is still running 30 s later, the broker interrupts it so the next turn can start. The sheets' "Stop 's agent" ends the running turns of both lanes, the person's and the system agent's; the "Ask " panel's Stop ends only the person's own turn ([above](#talking-to-an-apps-agent-yourself)). - **External clients' prompts** stay with the client: the shell does not answer or expire approvals of a Talk to Octos client's turns (octos#2624). ### Cards and questions -- **Interactive cards** ([#153](https://github.com/OctoSense-org/OctoSense/pull/153)): an app's glance cards run under the app's own policy, as the app's UI does in the Card runner; a card published with `notify` also posts a notification, which opens the live card on the glance page or panel. What the person does on a card is the app's own action, through the app's capability gate and host services, not an agent tool call, so it needs no extra shell approval. +- **Glance cards** (`crates/shell/src/glance.rs`): an app with the `glance` capability publishes with `glance.publish` (`glance.withdraw`, `glance.list`), as itself: the shell takes the publisher from the caller, never from the arguments. A card is an L0 `source` filled from `data` (presentation only, checked with Octoscript's L0 checker) or a Splash `script`. Limits: 6 publishes a minute and 4 cards per app, 32 kept, 6 shown by priority, then recency. A card published with `notify` also posts a notification: on a desktop the toast opens that card in its own card window (`glance_sheet.rs`), and the glance panel (`glance_panel.rs`; the bar's bell, F9) opens on every new card; on a phone the shade's notification opens the glance page. +- **Interactive cards** ([#153](https://github.com/OctoSense-org/OctoSense/pull/153)): an app's glance cards run under the app's own policy, as the app's UI does in the Card runner. What the person does on a card is the app's own action, through the app's capability gate and host services, not an agent tool call, so it needs no extra shell approval. +- **In-card chat** ([#263](https://github.com/OctoSense-org/OctoSense/pull/263)): an L0 card may declare `sys.chat(app, thread, fields)` and draw `ChatEntry` rows, and show text the model wrote (`class: model-copy`), marked AI-written and never acted on. The transcript is the host's ([`crates/l0-chat`](crates/l0-chat/README.md), `crates/shell/src/glance_chat.rs`): only the publishing app's own agent, only what the person typed is written as theirs, and the agent answers in the person's lane. Threads are kept in the app's account folder (`apps//accounts//chat/.json`). - **Questions** (octos's `ask_user_question`) are routed by the turn's trigger: a turn from the person's lane (or the app) asks in the app's conversation, a turn from the system agent's lane asks in the system chat. Only the person answers, on a shell surface. ## Layout @@ -167,10 +244,12 @@ sequenceDiagram | [`phone/`](phone/README.md) | The Home app, package `octosense-home` (APK id `dev.makepad.octosense`): the entry point that wraps the shell (`src/main.rs`), the built-in Settings app (`src/settings_*.rs`, `src/android_settings.rs`, `resources/settings/`), Android, OpenHarmony and iOS packaging, the phone side of the system bridge (`android/`), the phone's system-app selection. | | [`rom/`](rom/README.md) | The OnePlus 6 ROM image only: `vendor/` (product, privileged permissions, overlays, Settings backends, the privileged agent), `patches/`, image, flash and OTA scripts, the Home APK build scripts, `web-installer/`, product tests. | | `crates/shell/` | The one shell, package `octosense-shell`, linked by both packages: window manager (desk, styles, tiling, scene), hosting (processes, in-process modules, App Hub, the AI pane), the phone layer (home pages, shade, gestures, the Android launcher bridge), themes, wallpapers and icons (`resources/`). | -| [`crates/ai-host/`](crates/ai-host/README.md) | The shell's AI services behind one entry point, package `octosense-ai-host`: the octos kernel service, the `llm` host service with the platform's QR import, and apps' assistant access. | -| [`crates/kernel/`](crates/kernel/README.md) | The octos kernel service, package `octosense-kernel`: the [octos](https://github.com/octos-org/octos) agent kernel as a shell service, one per process, configured by AI providers and shared by its consumers. | -| [`crates/app-peers/`](crates/app-peers/README.md) | The app-agent broker: apps' access to the assistant ([Rinx ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md)). | -| [`apps/`](apps/README.md) | The system apps (News, Photos, Maps, Camera, Mail, AI providers, YouTube) as contained script apps, their host services (`mail`, `llm`), `apps/reference`, and the opt-in AppCard assistant (`apps/appcard`). | +| [`crates/ai-host/`](crates/ai-host/README.md) | The shell's AI services behind one entry point, package `octosense-ai-host`: the octos kernel service, the `llm` host service with the platform's QR import, the `model` service (`model.complete`), the `octos` host service that gives each script app its agent (`card.`), and native apps' assistant access. | +| [`crates/kernel/`](crates/kernel/README.md) | The octos kernel service, package `octosense-kernel`: the [octos](https://github.com/octos-org/octos) agent kernel as a shell service, one per process, configured by AI providers and shared by its consumers; the system agent's exact tool list. | +| [`crates/app-peers/`](crates/app-peers/README.md) | The app-agent broker, package `octosense-app-peers`: one host-owned octos peer per (app, account), its two lanes, its tools, `peer/input`, deadlines, purge ([Rinx ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md)). | +| [`crates/l0-chat/`](crates/l0-chat/README.md) | The host side of an L0 card's in-card chat (`sys.chat`), package `octosense-l0-chat`, shared by the shell's glance cards and AppCard. | +| [`crates/toolbox/`](crates/toolbox/README.md) | The system toolbox, package `octosense-toolbox`: workflow templates, their runner, forks and evaluation, and `mod.research`, offered to app agents as host tools behind the `toolbox-peers` feature. | +| [`apps/`](apps/README.md) | The system apps (News, Photos, Maps, Camera, Mail, Calendar, AI providers, YouTube) as contained script apps, their host services (`mail`, `calendar`, `news`, `llm`), `apps/reference`, and the opt-in AppCard assistant (`apps/appcard`). | | `tools/` | `setup.py` (the pinned framework sources), the reviewed Makepad runtime patch (`runtime-patches/`), `kernel-artifact.py` (the octos kernel an Android APK bundles as `liboctos.so`), `check-shell-graph.sh` (the dependency-graph guards every shell build passes). | | [`docs/adr/`](docs/adr/README.md) | Architecture decisions: this repository's, and the Home decisions 0001–0006 kept as history. | | `Cargo.toml`, `Cargo.lock` | One workspace. Every external dependency is pinned once in `[workspace.dependencies]`. | diff --git a/README.zh-CN.md b/README.zh-CN.md index 3af1c45e..8b735c1c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -29,12 +29,13 @@ flowchart LR person(["用户"]) ext["Talk to Octos 客户端
网页或终端,需手动开启"] subgraph shellp["OctoSense Shell 进程"] - ui["窗口管理器、启动器、
系统对话、面板"] + ui["窗口管理器、启动器、
系统对话、Ask app 面板、面板"] mods["原生模块
App Hub、Rinx"] runner["Card runner
脚本应用、glance 卡片"] aihost["ai-host + app-peers 代理
宿主连接"] relay["宿主工具中转"] router["审批路由"] + svc["应用宿主服务
mail、calendar、news"] end term["Terminal
在系统沙箱中的进程应用"] subgraph kern["octos 内核:子进程,OpenHarmony 上在进程内"] @@ -44,6 +45,7 @@ flowchart LR person --> ui ui --- mods ui --- runner + ui -->|"用户的回合:
Ask app、卡内对话"| aihost mods -->|"OctosAppService"| aihost runner -->|"host.request octos.*"| aihost term <-->|"hub:画面、AI bus"| ui @@ -52,6 +54,8 @@ flowchart LR sys -->|"peer_send_input"| peers kern -->|"peer/tool/call、审批"| relay relay --> router + relay -->|"应用的工具"| svc + svc -->|"glance.publish:卡片"| runner ext -.->|"外部 token:
只能用系统对话"| sys ``` @@ -68,11 +72,63 @@ flowchart LR | --- | --- | --- | | 进程内原生模块 | 与进程应用相同的 peer link,经 Makepad 的 `OctosPeer` 客户端(模块宿主把链接归给打开它的实例) | 已在 `main` 上;尚无模块使用 | | 进程内原生模块(Rinx) | 注入的 `OctosAppService`:`open_conversation`(应用与自己 Agent 的对话)和 `open_context`(每个客户端一个请求上下文,例如 Rinx 小程序) | 已在 `main` 上 | -| 脚本应用及其卡片 | 向 `octos` 宿主服务发送 `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt")` | 已在 `main` 上,受 `Policy::contained_apps`(发布策略中关闭)和首次使用同意约束 | +| 脚本应用及其卡片 | 向 `octos` 宿主服务发送 `host.request("octos.session.open" / "octos.session.history" / "octos.turn.start" / "octos.turn.interrupt")`,限于其 manifest 声明的名称;卡片的卡内对话(`sys.chat`)经过 Shell | 已在 `main` 上,需用户首次使用时同意(发布时的默认闸门;`OCTOSENSE_CONTAINED_APPS=1` 不询问任何人,`0` 关闭)。如今带 Agent 的系统应用都没有声明 `octos.*`:它们的 Agent 由 Shell 驱动 | | 进程应用 | 其 hub 连接上的 peer link(`octos.session.open`、`octos.turn.start` 等),身份由 Shell 标注 | Shell 一侧已在 `main` 上;尚无进程应用被授予 Agent | | 系统 Agent | 内核自己的会话 `_main:api:octosense#system`,从 Shell 的系统对话进入 | 已在 `main` 上 | | Talk to Octos 客户端 | 只能用系统对话,使用外部 token | 已在 `main` 上 | +### 系统 Agent 与应用 Agent + +**系统 Agent** 是 `_main` profile 上的内核会话 `_main:api:octosense#system`(`crates/kernel/src/network.rs`)。它拥有并监督所有应用 Agent。用户在 Shell 的**助手面板**(即系统对话,`crates/shell/src/system_chat/`:Dock 上的 Assistant 图标、手机主屏的 Assistant 磁贴或 F8;桌面端是左侧一个中等大小、可移动、可调整大小并渲染 Markdown 的面板,手机上是全屏)或已配对的 Talk to Octos 客户端中与它对话。它的内核工具恰好是 `SYSTEM_AGENT_TOOLS`(`crates/kernel/src/system_tools.rs`):用于监督应用 Agent 的 `peer_send_input`、`peer_gather`、`peer_list` 和 `peer_respond`,其工作区的文件工具,记忆,`ask_user_question`,媒体查看,`web_search`、`web_fetch` 和 `tool_search`。每次内核启动都用 octos 的 `session/tool_list/set` 设置这份列表,所以 octos 自带的 shell、spawn 一族和 `peer_close` 永远不会提供给它。系统对话还会在这个会话上注册宿主工具:`agents.list` 和 `agents.ask`(`crates/shell/src/agents.rs`:哪些应用有 Agent,以及某个 Agent 的首次使用面板),以及在 Setup › Assistant › Command execution 打开期间的 `terminal.run`。 + +**应用 Agent** 是每个(应用,账号)一个由宿主拥有的 octos peer([`crates/app-peers`](crates/app-peers/README.md))。`main` 上以下应用有 Agent(`crates/shell/src/apps.rs`,`agent_apps`): + +| 应用 | 声明方式 | 它的工具(由谁执行) | 它放到 glance 屏幕上的内容 | +| --- | --- | --- | --- | +| Rinx(原生) | `native-apps.json` 的 `agent.octos`(四个 `octos.*` 服务)和 `agent.generic_tools` | 其列表中的 octos 通用工具;它自己的助手界面 | – | +| 新闻(`os.news`) | `apps/news/bundle/tools.json`、manifest 中的 `agent` 块 | `news.list`、`news.read`(`news` 宿主服务) | – | +| 邮件(`os.mail`) | `apps/mail/bundle/tools.json`、manifest 中的 `agent` 块和 `glance` | `mail.notify`(`mail` 宿主服务) | 一张通知卡片,并发出一条通知 | +| 日历(`os.calendar`,仅桌面端) | `apps/calendar/bundle/tools.json`、manifest 中的 `agent` 块和 `glance` | `calendar.events`、`calendar.add_event`、`calendar.remove_event`(破坏性操作:由用户批准)、`calendar.notify`、`calendar.agenda`(`calendar` 宿主服务) | 一张日程卡片或议程卡片,并发出一条通知 | + +脚本应用在以下情况下拥有 Agent:manifest 声明了 `octos.*` 名称或 `agent` 块(`"tools": ["ask_user_question"]` 列出它可以使用的内核工具),或者应用包附带 `tools.json`(每个工具名为 `.`,带 schema、`risk`、`confirm` 和 `shareable`)。它的 peer 是 `card.<应用 id>`。用户只需在首次使用面板上允许一次(从 Shell 的 “Ask ” 面板、应用自己的 `octos` 调用,或系统 Agent 的 `agents.ask` 打开)。此后 Shell 在启动时就准备好这个 peer 并注册应用的工具,所以系统 Agent 的 `peer_list` 能看到它。除了自己的工具,应用 Agent 还能用宿主的只读工具 `files.list`、`files.read` 和 `files.search` 读取其账号文件夹。手机默认构建 `toolbox-peers`,因此在手机上,manifest 申请了 `research` 或 `crawl` 的应用 Agent 还会得到系统工具箱的工具;目前还没有应用申请。`AGENT.md`、技能和触发器([ADR 0002(英文)](docs/adr/0002-event-driven-app-agents.md))尚未实现:应用 Agent 只在系统 Agent、用户或卡片请求时运行。 + +**从系统 Agent 到 glance 屏幕上的一张卡片**: + +![From the system agent to a card on the glance screen](docs/images/agents-card-flow.png) + +
文字版(Mermaid) + +```mermaid +sequenceDiagram + autonumber + actor P as Person + participant S as System agent + participant B as Shell: app-peers broker + participant A as Mail's agent (peer card.os.mail) + participant R as Shell: tool relay + participant M as Mail's host service + participant G as Shell: glance service + P->>S: "Tell me on the glance screen when ..." + S->>B: peer_send_input (octos delivers peer/input) + B->>A: turn/start on the peer's session, with Mail's tools + A->>R: peer/tool/call mail.notify {title, body} + R->>R: grant, consent, schema, budget + R->>M: run on Mail's host service + M->>G: glance.publish as os.mail: notice.card, notify + G-->>P: notification (a desktop toast, the phone's shade) + M-->>R: {card_id} + R-->>A: peer/tool/result + A-->>S: the turn's result on the blackboard (peer_gather) + P->>G: opens the card, types in its chat + G->>A: a turn in the person's lane (sys.chat) + A-->>G: the reply, drawn as AI-written +``` + +
+ +- 模型从不编写卡片代码:`mail.notify`、`calendar.notify` 和 `calendar.agenda` 填充应用宿主服务自带的固定 L0 卡片(`apps/mail/host-service/resources/notice.card`、`apps/calendar/host-service/resources/*.card`),并以该应用的身份发布。 +- `mail.notify` 和 `calendar.add_event` 是 `act` 工具,运行时不弹面板;octos 只对破坏性和对外的工具请求审批,这些审批交给 Shell 的审批路由([见下文](#一次带审批的工具调用))。 + ### 一个应用 Agent,两条通道 一个应用 Agent 就是每个(应用,账号)一个由宿主拥有的 octos **peer**,归系统 Agent 所有,有自己的工作区、记忆命名空间、模型和工具列表。系统 Agent 和用户各自在自己的通道里与它对话: @@ -95,7 +151,7 @@ flowchart TB end sys -->|"peer_send_input"| input["octos:peer/input
发给 Shell"] input -->|"Shell 启动这一轮"| lane1 - person -->|"应用界面或其卡片"| lane2 + person -->|"Shell 的 Ask-app 面板、
卡片的对话或应用自己的界面"| lane2 lane1 -.->|"最近消息,只读"| lane2 lane2 -.->|"最近消息,只读"| lane1 lane1 --- own @@ -106,10 +162,29 @@ flowchart TB - **系统 Agent 的通道**是 peer 自己的会话 `…#peer-`。系统 Agent 发送 `peer_send_input`;octos 把它作为 `peer/input` 交给 Shell 的宿主连接,由 Shell 自己启动这一轮,所以这一轮带着应用的工具、记忆和审批运行(对已退出登录的账号或用户未允许的应用,Shell 以 `peer/input/reject` 拒绝)。peer 的结果写到 peer 黑板上,由系统 Agent 读取。 -- **用户的通道**是一个请求上下文 `…#peerctx-.`,由应用界面或其交互式卡片以 `share_history` 打开(原生模块的 `open_conversation`、脚本应用的 `octos.session.open`、进程应用的 peer link),每个句柄一个新的上下文([octos#2636](https://github.com/octos-org/octos/pull/2636),UPCR-2026-034)。两条通道并行运行,每个会话同一时间只有一轮:用户的消息不必等系统 Agent 的回合。每一轮都会以只读块的形式看到另一条通道的最近消息,这个块不会写入自己的对话记录;每一轮都标明说话者(`[from the person: ]`、`[from the system agent]`)。应用跟随两条通道,每个事件带有 `lane` 和说话者;`octos.session.history` 按时间合并两份对话记录。用户的回合也会在黑板上留下结果(`origin: person`),系统 Agent 用 `peer_gather` 就能看到。 +- **用户的通道**是一个请求上下文 `…#peerctx-.`,以 `share_history` 打开,打开方可以是 Shell 的 “Ask ” 面板(`agents::conversation`,客户端实例 `shell-ask`)、卡片的卡内对话,或应用自己的界面(原生模块的 `open_conversation`、脚本应用的 `octos.session.open`、进程应用的 peer link),每个句柄一个新的上下文([octos#2636](https://github.com/octos-org/octos/pull/2636),UPCR-2026-034)。两条通道并行运行,每个会话同一时间只有一轮:用户的消息不必等系统 Agent 的回合。每一轮都会以只读块的形式看到另一条通道的最近消息,这个块不会写入自己的对话记录;每一轮都标明说话者(`[from the person: ]`、`[from the system agent]`)。应用跟随两条通道,每个事件带有 `lane` 和说话者;`octos.session.history` 按时间合并两份对话记录。用户的回合也会在黑板上留下结果(`origin: person`),系统 Agent 用 `peer_gather` 就能看到。 - *2026-09-29 之前两者在 peer 会话上的同一个共享对话中说话([#166](https://github.com/OctoSense-org/OctoSense/pull/166),octos#2626):每个 peer 一个队列,一次一轮。* - **Rinx 小程序**保留各自的请求上下文(`open_context`),各有自己的对话记录和文件夹,不与任一通道共享。 +### 直接与应用的 Agent 对话 + +用户不只能和系统 Agent 对话:任何应用自己的 Agent,用户都可以直接与它对话。用户发起的每一轮都是该应用 peer 上用户通道里的一轮,与系统 Agent 的通道并列。它和系统 Agent 发起的一轮一样,带着应用的工具、记忆和审批运行。 + +| 入口 | 位置 | 如何打开 | +| --- | --- | --- | +| **“Ask ”**(`crates/shell/src/app_chat/`) | Shell 为每个拥有 Agent 的应用提供的面板,不论应用自己是否绘制对话界面:它就是系统对话的窗格,以应用对话的形式绘制(`app_panel: true`)。在桌面端,它位于系统对话的右侧,两条通道并排显示。 | 顶栏的 “Ask ” 按钮(当前聚焦窗口的应用拥有 Agent 时显示)、Shift+F8,或菜单项 “Ask this app's agent”。如果聚焦的应用没有 Agent,Shell 会提示 “No app agent here”。在手机上,这个窗格绘制为全屏面板,但 `main` 上还没有可以打开它的触控入口。 | +| **卡片的卡内对话**(`sys.chat`,`crates/shell/src/glance_chat.rs`) | 应用发布的一张 glance 卡片 | 用户在卡片的对话里输入。只有发布卡片的应用自己的 Agent 会回答,它的回复标为 AI 撰写([见下文](#卡片与提问))。 | +| **应用自己的界面** | 原生模块的 `open_conversation`、脚本应用的 `octos.session.open`、进程应用的 peer link | 在应用内。Rinx 绘制自己的助手界面;系统应用(新闻、邮件、日历)都不绘制对话界面,所以对它们来说,入口就是这个面板和它们的卡片。 | + +“Ask ” 面板的行为: + +- **先征得同意。** 用户还没有决定的 Agent 会先弹出首次使用面板,面板等待用户(“'s assistant is not allowed yet: allow it on the sheet.”)。已关闭的 Agent 会直接说明:“'s assistant is off. Turn it on in Setup › Assistant › Approvals.” +- **两条通道,标明说话者。** 面板跟随两条通道,并加载合并后的历史;每一行都显示说话者(用户、系统 Agent、应用的 Agent)。 +- **发送**会发起用户的一轮(`TurnTrigger::Person`)。它从不等待系统 Agent 的回合:只有系统 Agent 的通道在运行时,“发送”仍然可用。如果应用的 Agent 有一个提问正在等待,输入的文字就作为对它的回答。 +- **停止**(用户自己的回合运行时取代“发送”的位置)只停止这一轮(“Stopped.”,或 “Nothing of yours was running.”)。正在运行的系统 Agent 回合有自己的一行,带 **“Stop the system agent's task”**,只停止那条通道。Shell 的审批和提问面板上的 **“Stop 's agent”** 按钮会停止两条通道(`approvals::stop_agent`):设备归用户所有。 +- **提问**:用户或应用发起的回合中的提问在面板中显示和回答;系统 Agent 的提问出现在系统对话(F8)中。审批与其他地方一样,都是 Shell 的面板。 +- **关闭**只是隐藏面板。它的上下文和跟随者保持打开,所以再次打开时仍能看到用户自己的那些行。面板为另一个应用打开、Agent 被关闭,或应用的 peer 消失(对邮件来说,就是账号退出登录)时,这个上下文才会关闭。 + ### 一次带审批的工具调用 ![A tool call with an approval](docs/images/agents-tool-call.png) @@ -125,7 +200,7 @@ sequenceDiagram participant Ro as 审批路由 participant P as 用户 participant Ex as 拥有工具的应用 - Ag->>K: 调用 mail.send + Ag->>K: 调用 calendar.remove_event alt confirm host K->>Ro: approval/requested,host_tool Ro->>Ro: 先看开发者模式,再看常设规则 @@ -151,12 +226,14 @@ sequenceDiagram - **工具调用**:octos 把 `peer/tool/call` 发给 Shell 的中转(`crates/shell/src/host_tools/`)。中转按(拥有工具的应用,工具)和调用方检查授权,按工具的 schema 检查参数,检查调用方的预算,再把调用路由到拥有工具的应用的执行器:进程内模块的执行器、脚本应用的宿主服务、进程应用的 peer link,或 AI bus 上 Terminal 的 `run`。 - **审批**交给审批路由(`crates/shell/src/approvals/`):先看开发者模式,再看针对(拥有工具的应用,工具)的常设规则,否则弹出 Shell 绘制的面板。`confirm: app` 的工具在拥有它的应用自己的面板上确认,面板显示调用方。只有用户能批准;系统 Agent 永远不能。 -- **时限与停止**([#167](https://github.com/OctoSense-org/OctoSense/pull/167)):Shell 为应用 peer 持有的审批或提问在 10 分钟后过期(`OCTOSENSE_PROMPT_DEADLINE_SECS`):审批路由拒绝它,提问被婉拒,两者都保持显示为 "Expired: no answer in 10 min"。如果 30 秒后这一轮仍在运行,代理会中断它,好让下一轮开始。用户的“停止”会结束两条通道上正在运行的回合,包括用户的和系统 Agent 的。 +- **时限与停止**([#167](https://github.com/OctoSense-org/OctoSense/pull/167)):Shell 为应用 peer 持有的审批或提问在 10 分钟后过期(`OCTOSENSE_PROMPT_DEADLINE_SECS`):审批路由拒绝它,提问被婉拒,两者都保持显示为 "Expired: no answer in 10 min"。如果 30 秒后这一轮仍在运行,代理会中断它,好让下一轮开始。面板上的 “Stop 's agent” 会结束两条通道上正在运行的回合,包括用户的和系统 Agent 的;“Ask ” 面板的“停止”只结束用户自己的回合([见上文](#直接与应用的-agent-对话))。 - **外部客户端的提示**留在客户端:Shell 不回答、也不让 Talk to Octos 客户端各轮的审批过期(octos#2624)。 ### 卡片与提问 -- **交互式卡片**([#153](https://github.com/OctoSense-org/OctoSense/pull/153)):应用的 glance 卡片在应用自己的策略下运行,与应用界面在 Card runner 中一样;以 `notify` 发布的卡片还会发出一条通知,点开后在 glance 页面或面板上打开这张实时卡片。用户在卡片上的操作是应用自己的操作,经过应用的能力闸门和宿主服务,而不是 Agent 的工具调用,因此不需要额外的 Shell 审批。 +- **Glance 卡片**(`crates/shell/src/glance.rs`):拥有 `glance` 权限的应用用 `glance.publish`(以及 `glance.withdraw`、`glance.list`)以自己的身份发布卡片:Shell 从调用方取得发布者,从不读取参数里的发布者。卡片要么是由 `data` 填充的 L0 `source`(只有呈现,由 Octoscript 的 L0 检查器检查),要么是 Splash `script`。限制:每个应用每分钟发布 6 次、最多保留 4 张卡片,总共保留 32 张,按优先级再按时间显示 6 张。以 `notify` 发布的卡片还会发出一条通知:桌面端点击 toast 会在单独的卡片窗口中打开这张卡片(`glance_sheet.rs`),每来一张新卡片 glance 面板(`glance_panel.rs`;顶栏的铃铛、F9)都会打开;手机上点通知栏里的通知会打开 glance 页面。 +- **交互式卡片**([#153](https://github.com/OctoSense-org/OctoSense/pull/153)):应用的 glance 卡片在应用自己的策略下运行,与应用界面在 Card runner 中一样。用户在卡片上的操作是应用自己的操作,经过应用的能力闸门和宿主服务,而不是 Agent 的工具调用,因此不需要额外的 Shell 审批。 +- **卡内对话**([#263](https://github.com/OctoSense-org/OctoSense/pull/263)):L0 卡片可以声明 `sys.chat(app, thread, fields)` 并绘制 `ChatEntry` 行,也可以显示模型写的文字(`class: model-copy`),这些文字标为 AI 撰写,且从不被当作操作执行。对话记录归宿主所有([`crates/l0-chat`](crates/l0-chat/README.md)、`crates/shell/src/glance_chat.rs`):只能与发布卡片的应用自己的 Agent 对话,只有用户亲手输入的内容才记为用户的话,Agent 在用户的通道里回答。对话按线程保存在应用的账号文件夹中(`apps//accounts//chat/.json`)。 - **提问**(octos 的 `ask_user_question`)按这一轮的触发方路由:来自用户通道(或应用)的一轮在应用的对话中提问,来自系统 Agent 通道的一轮在系统对话中提问。只有用户能回答,而且只能在 Shell 的界面上回答。 ## 目录结构 @@ -167,10 +244,12 @@ sequenceDiagram | [`phone/`](phone/README.zh-CN.md) | Home 应用,package `octosense-home`(APK id `dev.makepad.octosense`):包装 Shell 的入口(`src/main.rs`)、内置设置应用(`src/settings_*.rs`、`src/android_settings.rs`、`resources/settings/`)、Android、OpenHarmony 和 iOS 打包、系统桥的手机端(`android/`),以及手机端的系统应用选择。 | | [`rom/`](rom/README.zh-CN.md) | 仅 OnePlus 6 ROM 镜像:`vendor/`(产品定义、特权权限、overlay、设置后端、特权 agent)、`patches/`、镜像/刷机/OTA 脚本、Home APK 构建脚本、`web-installer/`、产品测试。 | | `crates/shell/` | 唯一的一份 Shell,package `octosense-shell`,两个包都链接它:窗口管理器(desk、样式、平铺、场景)、托管(子进程、进程内模块、App Hub、AI 面板)、手机层(主屏页面、下拉面板、手势、Android 启动器桥),以及主题、壁纸和图标(`resources/`)。 | -| [`crates/ai-host/`](crates/ai-host/README.md) | Shell 的 AI 服务,统一入口,package `octosense-ai-host`:octos 内核服务、带平台二维码导入的 `llm` 宿主服务,以及应用访问助手的通道。 | -| [`crates/kernel/`](crates/kernel/README.md) | octos 内核服务,package `octosense-kernel`:把 [octos](https://github.com/octos-org/octos) Agent 内核作为 Shell 服务,每个进程一个,由 AI 服务商配置,供所有使用方共享。 | -| [`crates/app-peers/`](crates/app-peers/README.md) | 应用与 Agent 之间的代理:应用访问助手的通道([Rinx ADR 0007(英文)](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md))。 | -| [`apps/`](apps/README.zh-CN.md) | 系统应用(新闻、相册、地图、相机、邮件、AI 服务商、YouTube),均为受隔离约束的脚本应用;它们的宿主服务(`mail`、`llm`);`apps/reference`;以及需显式启用的 AppCard 助手(`apps/appcard`)。 | +| [`crates/ai-host/`](crates/ai-host/README.md) | Shell 的 AI 服务,统一入口,package `octosense-ai-host`:octos 内核服务、带平台二维码导入的 `llm` 宿主服务、`model` 服务(`model.complete`)、为每个脚本应用提供 Agent(`card.<应用 id>`)的 `octos` 宿主服务,以及原生应用访问助手的通道。 | +| [`crates/kernel/`](crates/kernel/README.md) | octos 内核服务,package `octosense-kernel`:把 [octos](https://github.com/octos-org/octos) Agent 内核作为 Shell 服务,每个进程一个,由 AI 服务商配置,供所有使用方共享;以及系统 Agent 的精确工具列表。 | +| [`crates/app-peers/`](crates/app-peers/README.md) | 应用 Agent 的代理,package `octosense-app-peers`:每个(应用,账号)一个由宿主拥有的 octos peer,它的两条通道、工具、`peer/input`、时限和清除([Rinx ADR 0007(英文)](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md))。 | +| [`crates/l0-chat/`](crates/l0-chat/README.md) | L0 卡片卡内对话(`sys.chat`)的宿主一侧,package `octosense-l0-chat`,由 Shell 的 glance 卡片和 AppCard 共用。 | +| [`crates/toolbox/`](crates/toolbox/README.md) | 系统工具箱,package `octosense-toolbox`:工作流模板、运行器、分支(fork)与评估,以及 `mod.research`;在 `toolbox-peers` 特性下作为宿主工具提供给应用 Agent。 | +| [`apps/`](apps/README.zh-CN.md) | 系统应用(新闻、相册、地图、相机、邮件、日历、AI 服务商、YouTube),均为受隔离约束的脚本应用;它们的宿主服务(`mail`、`calendar`、`news`、`llm`);`apps/reference`;以及需显式启用的 AppCard 助手(`apps/appcard`)。 | | `tools/` | `setup.py`(锁定版本的框架源码)、经审查的 Makepad 运行时补丁(`runtime-patches/`)、`kernel-artifact.py`(Android APK 以 `liboctos.so` 形式打包的 octos 内核)、`check-shell-graph.sh`(每个 Shell 构建都要通过的依赖图检查)。 | | [`docs/adr/`](docs/adr/README.zh-CN.md) | 架构决策记录:本仓库的决策,以及作为历史保留的 Home 决策 0001–0006。 | | `Cargo.toml`、`Cargo.lock` | 一个工作区。所有外部依赖都只在 `[workspace.dependencies]` 中锁定一次。 | diff --git a/apps/AGENTS.md b/apps/AGENTS.md index 2f5adb69..f4a78eda 100644 --- a/apps/AGENTS.md +++ b/apps/AGENTS.md @@ -2,7 +2,7 @@ > **Any coding agent, or none.** These instructions work the same for Codex, Claude Code, Cursor, Gemini CLI, GitHub Copilot or a person at a terminal: every step is a shell command or a file edit, and nothing here needs a particular agent, model or vendor. `AGENTS.md` is the one source of truth; `CLAUDE.md` and `GEMINI.md` only import it for agents that look for those names. -> octos appears below only as the runtime of the AppCard assistant, a product dependency. Changing or building the script apps and the Mail service does not need octos, and no step asks you to use octos as your coding agent. +> octos appears below only as a product dependency: the runtime of the apps' own agents and of the AppCard assistant. Changing or building the script apps and their host services does not need octos, and no step asks you to use octos as your coding agent. These are shipping apps. Keep changes small, test them in a shell, and keep the rules in README.md. The repository-wide rules in [../AGENTS.md](../AGENTS.md) @@ -33,7 +33,21 @@ app's own agent and `tools.json`, the system toolbox, `glance.publish` and - Validate on a phone through Home (`phone/`) built as a separate test package; never replace the device's installed Home. - Mail's service: change `apps/mail/host-service` and run - `cargo test --locked -p octosense-mail-service`. + `cargo test --locked -p octosense-mail-service`. Calendar's and News's: + `apps/calendar/host-service`, `apps/news/host-service`, and + `cargo test --locked -p octosense-calendar-service -p octosense-news-service`. +- An app's agent (News, Mail, Calendar) is declared by its manifest's + `agent` block and `bundle/tools.json`; each tool runs on the app's host + service (`implemented_by: "host-service"`), so a new tool is a new + host-service method plus its `tools.json` entry, with schemas that match + what the method answers (octos needs an object `output_schema`). Give a + tool the smallest `risk` that is true: `destructive` and outward tools get + the person's approval, `act` ones do not. A tool that shows something on + the glance screen fills a fixed L0 card the host service ships + (`resources/*.card`) and publishes it through the publisher the shell + installs (`on_publish_card`); never let the model write card code. The + app needs `glance` in its manifest. See the README's + [App agents](README.md#app-agents). - There are no pins to bump: both shells pack `apps/` from the same commit (`desktop/system-apps.json`, `phone/system-apps.json`), so one pull request carries a change to every shell. App Hub, octos and the runtime are pinned @@ -51,10 +65,13 @@ app's own agent and `tools.json`, the system toolbox, `glance.publish` and `OCTOS_CORE_TEST_KERNEL= cargo test -p octosense-kernel --test real_kernel` with a real kernel); AppCard and the `llm` service link it, so run their checks too. Consumers never spawn a kernel of their own. -- Native apps reach the assistant through `../crates/app-peers` - (`octosense-app-peers`): the shell gives each app whose declared `octos.*` - services host policy grants ONE peer owned by the system agent and injects - a scoped service at module creation; apps never get raw kernel protocol. +- Apps reach the assistant through `../crates/app-peers` + (`octosense-app-peers`): the shell gives each app with an agent ONE peer + per account, owned by the system agent: a native app whose declared + `octos.*` services host policy grants gets a scoped service injected at + module creation; a script app gets `card.` from the shell's + `octos` host service (`../crates/ai-host/src/contained.rs`). Apps never + get raw kernel protocol. Test with `cargo test --locked -p octosense-app-peers --features octos-core,ws` (and `OCTOS_APP_PEERS_TEST_KERNEL= cargo test -p octosense-app-peers --features octos-core --test real_kernel`); see its README. diff --git a/apps/README.md b/apps/README.md index 58752d3a..73dd4540 100644 --- a/apps/README.md +++ b/apps/README.md @@ -2,21 +2,29 @@ English | [简体中文](README.zh-CN.md) -> **Where this fits.** The system apps are script apps in App Hub's Card runner. They never talk to the octos kernel: an app reaches its own agent (one peer per app and account) only through the shell, with `host.request("octos.*")`, and the agent's calls to the app's tools come back through the shell's relay and approval router. An app's glance cards run under the app's own policy, and what the person does on them is the app's own action, not an agent tool call. Diagrams of the processes, an app agent's two lanes and a tool call with its approval: [How it fits together](../README.md#how-it-fits-together); the details: [docs/architecture.md](../docs/architecture.md) and [ADR 0004](../docs/adr/0004-native-apps-hosting-and-peers.md). +> **Where this fits.** The system apps are script apps in App Hub's Card runner. They never talk to the octos kernel. News, Mail and Calendar have an agent (one host-owned octos peer per app and account, `card.`), declared by their manifest's `agent` block and `tools.json`; the shell drives it for the system agent, the shell's "Ask " panel and a card's in-card chat, and an app could also reach it with `host.request("octos.*")` (none of them does). The agent's calls to the app's tools come back through the shell's relay and approval router and run on the app's host service; Mail's and Calendar's tools put cards on the glance screen ([App agents](#app-agents)). An app's glance cards run under the app's own policy, and what the person does on them is the app's own action, not an agent tool call. Diagrams of the processes, an app agent's two lanes and a tool call with its approval: [How it fits together](../README.md#how-it-fits-together); the details: [docs/architecture.md](../docs/architecture.md) and [ADR 0004](../docs/adr/0004-native-apps-hosting-and-peers.md). The first-party apps that ship with [OctoSense](https://github.com/OctoSense-org), the agent shell on top of your operating system, and the host services behind them. They live in `apps/` of the [OctoSense repository](../README.md); until 2026-09-27 they were the OctoSense-System-Apps repository (archived). -- **News, Photos, Maps, Camera, Mail, AI providers and YouTube** are *contained script apps*. Each is +- **News, Photos, Maps, Camera, Mail, Calendar, AI providers and YouTube** are *contained script apps*. Each is an OctoScript (Splash) program in a `bundle/`, run by App Hub's Card runner in its own isolate, under exactly the permissions its `manifest.json` asks for. That is the same containment a store app gets. They are also worked examples of the app shape any developer publishes through the App Hub. - **Mail's host service** (`mail/host-service`) is the Rust half of Mail: IMAP/POP3/SMTP, the account store and the sign-in sheet, run by the shell. - The app gets mail, never a password or a socket. + The app gets mail, never a password or a socket. It also runs Mail's + agent's tool `mail.notify`, which puts a notice card on the glance screen. +- **Calendar's host service** (`calendar/host-service`) keeps Calendar's + events in the host's directory and runs Calendar's agent's tools: + `calendar.events`, `add_event`, `remove_event`, and `notify` and `agenda`, + which put an event or agenda card on the glance screen. Calendar is a + desktop system app (`desktop/system-apps.json`). +- **News's host service** (`news/host-service`) collects News's stories on a + timer, with no model, and runs News's agent's `news.list` and `news.read`. - **The `llm` host service** (`ai-providers/host-service`) is the Rust half of AI providers: the assistant's LLM providers over octos's model catalog, keys in the platform secret store, Test connection, and moving providers @@ -61,9 +69,10 @@ OctoScript-App-Design-Flow: | [Photos](photos/bundle) | `os.photos` | A sample library: moments, albums, people, favorites, a grid with selection, a full-screen viewer | `storage` | none | none (full-size files come from a shell asset mount, see below) | | [Maps](maps/bundle) | `os.maps` | `MapView` map, place search, places, routes with a changeable start and up to two stops, and a drive mode with turn-by-turn and a 2D/3D view; starts at the device's GPS fix when there is one | `storage`, `net`, `location` | `photon.komoot.io`, `router.project-osrm.org`, `overpass-api.de`, `overpass.kumi.systems`, `maps.mail.ru`, `overpass.openstreetmap.fr` | none | | [Camera](camera/bundle) | `os.camera` | Photo and video over the runtime's `CameraPreview` widget, flash and zoom, a thumbnail of the last shot and a viewer | `storage`, `camera`, `microphone`, `library` | none | none | -| [Mail](mail/bundle) | `os.mail` | Accounts, folders, message list, reader (HTML rebuilt by the service) and composer | `storage`, `mail` | none (the service connects, not the app) | [`mail`](mail/host-service) | +| [Mail](mail/bundle) | `os.mail` | Accounts, folders, message list, reader (HTML rebuilt by the service) and composer; its agent puts notice cards on the glance screen (`mail.notify`) | `storage`, `mail`, `glance` | none (the service connects, not the app) | [`mail`](mail/host-service) | | [AI providers](ai-providers/bundle) | `os.ai-providers` | The assistant's LLM providers: a primary and fallbacks, each with a model pull-down from octos's catalog and Test connection; an add wizard (family, model, route, key, test); Show QR for phone and import by camera, image or paste | `storage`, `llm` | none (the service connects, not the app) | [`llm`](ai-providers/host-service) | | [YouTube](youtube/bundle) | `os.youtube` | YouTube search (the runtime's keyless `sys.video`, which reads YouTube's own results page), result rows with thumbnails and LIVE or length badges, topic chips, playback of YouTube's mobile watch page in `WebReader`, and a history of what was played on this device | `storage`, `net` | `www.youtube.com`, `m.youtube.com`, `i.ytimg.com` | none | +| [Calendar](calendar/bundle) | `os.calendar` (desktop) | Its agent keeps the person's events and puts event and agenda cards on the glance screen; its own window cannot list the events yet (it needs an App Hub `calendar` capability) | `storage`, `glance` | none | [`calendar`](calendar/host-service) (for Calendar's agent only) | | [AppCard](appcard) | native, opt-in | The AppCard assistant: a routing brain picks or composes an app agent, which generates a live Splash or webview card. Shells link it only with `app-appcard`; not shipped by default | n/a (not a bundle) | n/a | the shell's octos kernel | What each capability means is defined by App Hub's closed list @@ -107,6 +116,11 @@ host's mail service, `llm` reaches the host's LLM-provider service. `net` reache `mailbox-*.json` files. The script Mail app's mail now lives in the host service's own directory (`/mail/box-*.json`), so the skill probably no longer sees it; not verified. +- **Calendar** (2026-10-01, desktop, hidden `--remote` run with a live + model): the system agent asked Calendar's agent for a card; the first-use + sheet came up, the agent added an event and its card opened the glance + panel. Not packed on the phone. Its host service's tests are not in + `apps.yml` yet. - Only Camera ships its own launcher icon (`bundle/icon.png`); the shells draw the others. @@ -133,10 +147,12 @@ standalone launcher and ROM image). Each packaging: } ``` -2. Links the host services `octosense-mail-service` and +2. Links the host services `octosense-mail-service`, + `octosense-calendar-service`, `octosense-news-service` and `octosense-llm-service` (workspace path dependencies) through the shell, - [`crates/shell`](../crates/shell), and registers them at startup: Mail with `register()` for real accounts, or `register_demo()` - when the shell's app config has `mail_demo: true`; `llm` with the octos + [`crates/shell`](../crates/shell) (its `app-hub` feature), and registers them at startup (`crates/shell/src/apps.rs`, `register_host_services`): Mail with `register()` for real accounts, or `register_demo()` + when the shell's app config has `mail_demo: true`, and with the publisher + its `mail.notify` (like Calendar's cards) publishes through; `llm` with the octos kernel's core dir and the shell's QR scanner and image picker (see [the `llm` service](#the-llm-service)). App Hub is pinned once, in the root `Cargo.toml`, so there is one host-service registry. @@ -159,7 +175,11 @@ request. ``` /bundle/ a contained script app: manifest.json, main.splash, artwork photos/resources/ Photos' sample library, which Home mounts -mail/host-service/ octosense-mail-service, the `mail` host service (Rust) +mail/host-service/ octosense-mail-service, the `mail` host service (Rust); resources/notice.card +mail/docs/ Mail's plans (the email action card) +calendar/host-service/ octosense-calendar-service, the `calendar` host service; resources/event.card, agenda.card +news/host-service/ octosense-news-service, the `news` host service (News's data service) +/bundle/tools.json an app agent's own tools (News, Mail, Calendar) ai-providers/ the `llm` host service (host-service/) and octosense-llm-config (config/: octos's model catalog and provider registry, the profile merge, OCTOS1/OCTOS1E QR) reference/ the reference module @@ -176,7 +196,8 @@ appcard/ the native AppCard assistant ../crates/shell/ octosense-shell: the one shell both packagings link ../crates/ai-host/ octosense-ai-host: the shell's AI services (kernel, `llm`, app peers), one entry point ../crates/kernel/ octosense-kernel: the shell's octos kernel (one per process, shared) -../crates/app-peers/ octosense-app-peers: apps' scoped access to the assistant +../crates/app-peers/ octosense-app-peers: the app-agent broker, one peer per (app, account) +../crates/l0-chat/ octosense-l0-chat: the host side of a card's in-card chat (sys.chat) ../.github/workflows/apps.yml CI for the host services, AppCard and the shell services ``` @@ -281,17 +302,37 @@ isolate under no app's policy. Calls from the sheet arrive marked | File | Role | | --- | --- | -| `lib.rs` | the service: `mail.accounts`, `add_account` (raises the sign-in sheet), `remove_account`, `folders`, `sync`, `list`, `message`, `mark_read`, `send`; `register()`, `register_demo()`, `register_with*()`; the `Transport` trait | +| `lib.rs` | the service: `mail.accounts`, `add_account` (raises the sign-in sheet), `remove_account`, `folders`, `sync`, `list`, `message`, `mark_read`, `send`, `notify` (the agent's notice card, published through the shell's glance service as Mail); `register()`, `register_demo()`, `register_with*()`; the `Transport` trait; account events for the shell (`on_account_event`) | | `imap.rs` | IMAP client (folders, read flag back to the server) | | `network.rs` | POP3 and SMTP, MIME decoding; credentials never appear in errors | | `html.rs` | rebuilds a message as the few tags Mail's `Html` view draws, with nothing remote in it | -| `vault.rs` | where passwords go: macOS/iOS Keychain, Android (a file sealed with an Android Keystore key), owner-only file elsewhere; `OCTOSENSE_MAIL_VAULT=file` forces the file store for unsigned dev builds | +| `vault.rs` | where passwords go: macOS/iOS Keychain, Android (a file sealed with an Android Keystore key), owner-only file elsewhere, the files in the host's secrets folder `/secrets/os.mail/`; `OCTOSENSE_MAIL_VAULT=file` forces the file store for unsigned dev builds | +| `contacts.rs` | the addresses the person's accounts sent mail to, for the approval rule "recipients in my contacts" (off until the person turns it on in Settings) | +| `resources/notice.card` | the fixed L0 notice card `mail.notify` fills | Account metadata (no passwords) and fetched mail live under the host's own directory (`/mail`), outside every app's jail. Each account is granted only to the apps that added it. The service tests an account before keeping it. +### The `calendar` service + +`octosense-calendar-service` (`apps/calendar/host-service/src/lib.rs`) answers +only Calendar (`os.calendar`): App Hub's capability list has no `calendar`, +so no other app can be granted it, and the shell runs Calendar's agent's +`calendar.*` tools on it as the system app's own service. + +| Method | Args | Answer | +| --- | --- | --- | +| `calendar.events` | `{from?, to?, limit?}` | `{events: [{id, title, start, end, location, notes}]}`, soonest first | +| `calendar.add_event` | `{title, start, end?, location?, notes?}` | `{id, start}` | +| `calendar.remove_event` | `{id}` | `{removed}` | +| `calendar.notify` | `{event}` or `{title, when, location?, notes?}`, and `{card_id?, priority?}` | `{card_id, replaced, expires_at}` once an event card (`resources/event.card`) is on the glance screen, with a notification | +| `calendar.agenda` | `{days?}` | the same, for the agenda card (`resources/agenda.card`): the next three events within `days` (7) | + +Times are local (`2026-10-02T15:00`, or a date for the whole day). Events +live in `/calendar/events.json`, outside every app's jail. + ### The `llm` service `octosense-llm-service` (`apps/ai-providers/host-service`) is the Rust half @@ -310,6 +351,62 @@ method table and registration are in its **Talk to Octos** (off by default): **AI providers → Talk to Octos** turns on a loopback server so a web client or a terminal UI can talk to this device's assistant. While it is on, the kernel runs as `octos serve --host-managed` instead of `--stdio` and native apps keep working over its WebSocket; external clients get a separate token that opens the UI Protocol socket and nothing else. A web client pairs with a one-time code or the QR of its link; a terminal client of this user reads the private connection file. The server stays up when native apps close, until it is turned off or the shell exits. See [ADR 0003](../docs/adr/0003-shared-octos-client-access.md) and the [kernel guide](../crates/kernel/README.md). +## App agents + +An app agent is the app's own octos peer, owned by the system agent: its own +workspace (the app's account folder, `apps//accounts//`), memory, +model lane and tools. Which system apps have one, and how +([`../crates/shell/src/apps.rs`](../crates/shell/src/apps.rs) `agent_apps`, +[`../crates/shell/src/host_tools/script_apps.rs`](../crates/shell/src/host_tools/script_apps.rs)): + +| App | `manifest.json` | `tools.json` | Cards | +| --- | --- | --- | --- | +| News | `agent` block | `news.list`, `news.read` (read, shareable) | – | +| Mail | `agent` block, `glance`, `storage.accounts` (the agent acts for the signed-in account) | `mail.notify` (act, background) | `notice.card` | +| Calendar | `agent` block, `glance` | `calendar.events` (read), `calendar.add_event` (act), `calendar.remove_event` (destructive, `confirm: host`), `calendar.notify`, `calendar.agenda` (act) | `event.card`, `agenda.card` | + +- **Declaring one.** The manifest's `agent` block names the kernel tools the + agent may use (`"tools": ["ask_user_question"]`; a dotted name there asks + for another app's shareable tool), and `bundle/tools.json` declares the + app's own tools: `.`, `input_schema`, `output_schema`, `risk` + (`read`, `act`, `destructive`), `background`, `confirm` (`host` or + `app`), `shareable` and `implemented_by: "host-service"`. App Hub admits + and pins both. The `agent` block's `profile` and `model` are admitted but + not used by the shell yet. +- **Running one.** Nothing runs before the person allows the agent on the + first-use sheet (shown when the person opens the shell's "Ask " + panel, with the bar's "Ask ", Shift+F8 or the menu row "Ask this + app's agent", or when the system agent asks it with `agents.ask`). Then + the shell prepares the peer, so the system agent can reach it with + `peer_send_input`. A turn starts only when the system agent, the person + or a card's in-card chat asks: there are no triggers or schedules yet + (ADR 0002 M3, planned). +- **Talking to it yourself.** The person can chat with the app's agent + directly, not only through the system agent: in the "Ask " panel, + which the shell draws for every app with an agent (none of these apps + draws a chat of its own), or in a card's in-card chat. Those turns run + in the person's lane, beside the system agent's, with the app's tools. + The panel's Stop stops only the person's own turn. On the phone no touch + control opens the panel yet. See the root + [README](../README.md#talking-to-an-apps-agent-yourself). +- **Its tools run on the app's host service**, as the app, after the + shell's relay checked the grant, the schema and the budget. octos asks + for an approval only for destructive and outward tools (here + `calendar.remove_event`), which the person answers on a shell sheet. +- **Cards.** `mail.notify`, `calendar.notify` and `calendar.agenda` fill a + fixed L0 card the host service ships and publish it with `notify` through + the shell's `glance` service as the app (the app needs the `glance` + capability). The model only supplies the text; it never writes card code. + A card can hold an in-card chat with the app's own agent (`sys.chat`, + [`../crates/l0-chat`](../crates/l0-chat/README.md)). +- **Trying it** on the desktop: open the assistant (F8) and ask the system + agent to have Mail's or Calendar's agent put a card on the glance screen; + allow the agent on the sheet that comes up. Mail needs a signed-in + account (the demo mailbox, below, will do). Mail's richer action card + ([the plan](mail/docs/2026-10-01-email-action-card-plan.md)) is so far a + demo with fake data: `OCTOSENSE_GLANCE_DEMO=mail` publishes it at + startup. **Unverified** here (run in #267's checks, not for this page). + ## The octos kernel The octos agent kernel is a **shell service**, not part of any app. @@ -425,6 +522,8 @@ plus an entry in each shell's `system-apps.json`. | What | How | | --- | --- | | Mail service | `cargo test --locked -p octosense-mail-service` from the repository root. The keychain test is ignored by default: `cargo test -p octosense-mail-service -- --ignored keychain` | +| Calendar and News services | `cargo test --locked -p octosense-calendar-service -p octosense-news-service` (Calendar's are not in `apps.yml` yet) | +| A card's in-card chat | `cargo test --locked -p octosense-l0-chat` | | octos kernel service | `cargo test --locked -p octosense-kernel` (a stand-in kernel); `OCTOS_CORE_TEST_KERNEL= cargo test -p octosense-kernel --test real_kernel` (a real one) | | `llm` service and config | `cargo test --locked -p octosense-llm-service -p octosense-llm-config`; add `--features octosense-llm-service/octos-core` for the shells' build | | AppCard | the commands above | diff --git a/apps/README.zh-CN.md b/apps/README.zh-CN.md index 610723fa..1adca50d 100644 --- a/apps/README.zh-CN.md +++ b/apps/README.zh-CN.md @@ -2,20 +2,27 @@ [English](README.md) | 简体中文 -> **在整个系统中的位置。**系统应用是运行在 App Hub 的 Card runner 中的脚本应用。它们从不直接与 octos 内核通信:应用只能通过 Shell,用 `host.request("octos.*")` 使用自己的 Agent(每个应用、每个账号一个 peer),Agent 对应用工具的调用也经由 Shell 的中转和审批路由回到应用。应用的 glance 卡片在应用自己的策略下运行,用户在卡片上的操作是应用自己的操作,而不是 Agent 的工具调用。进程、应用 Agent 的两条通道以及一次带审批的工具调用的图示:[整体如何运作](../README.zh-CN.md#整体如何运作);详细说明:[docs/architecture.zh-CN.md](../docs/architecture.zh-CN.md) 和 [ADR 0004(英文)](../docs/adr/0004-native-apps-hosting-and-peers.md)。 +> **在整个系统中的位置。**系统应用是运行在 App Hub 的 Card runner 中的脚本应用。它们从不直接与 octos 内核通信。新闻、邮件和日历有 Agent(每个应用、每个账号一个由宿主拥有的 octos peer,`card.<应用 id>`),由 manifest 的 `agent` 块和 `tools.json` 声明;Shell 替系统 Agent、Shell 的 “Ask ” 面板和卡片的卡内对话驱动它,应用自己也可以用 `host.request("octos.*")` 使用它(目前没有应用这样做)。Agent 对应用工具的调用经由 Shell 的中转和审批路由回到应用,在应用的宿主服务上运行;邮件和日历的工具会把卡片放到 glance 屏幕上([应用 Agent](#应用-agent))。应用的 glance 卡片在应用自己的策略下运行,用户在卡片上的操作是应用自己的操作,而不是 Agent 的工具调用。进程、应用 Agent 的两条通道以及一次带审批的工具调用的图示:[整体如何运作](../README.zh-CN.md#整体如何运作);详细说明:[docs/architecture.zh-CN.md](../docs/architecture.zh-CN.md) 和 [ADR 0004(英文)](../docs/adr/0004-native-apps-hosting-and-peers.md)。 [OctoSense](https://github.com/OctoSense-org/.github/blob/main/profile/README.zh-CN.md)(运行在操作系统之上的 Agent 交互 Shell)自带的第一方应用,以及它们背后的宿主服务。 它们位于 [OctoSense 仓库](../README.zh-CN.md)的 `apps/`;2026-09-27 之前它们是 OctoSense-System-Apps 仓库(已归档)。 -- **新闻(News)、相册(Photos)、地图(Maps)、相机(Camera)、邮件(Mail)、AI providers 和 YouTube** +- **新闻(News)、相册(Photos)、地图(Maps)、相机(Camera)、邮件(Mail)、日历(Calendar)、AI providers 和 YouTube** 是*隔离运行的脚本应用*。每个应用都是 `bundle/` 里的一个 OctoScript(Splash) 程序,由 App Hub 的 Card runner 在独立的 isolate 中运行,权限严格等于其 `manifest.json` 所申请的内容,与商店应用受到的隔离完全相同。它们同时也是 任何开发者通过 App Hub 发布的应用形态的完整示例。 - **邮件的宿主服务**(`mail/host-service`)是 Mail 的 Rust 部分: IMAP/POP3/SMTP、账户存储和登录面板,由 Shell 运行。应用拿到的是邮件, - 永远拿不到密码或 socket。 + 永远拿不到密码或 socket。它还运行邮件 Agent 的工具 `mail.notify`,把一张通知卡片 + 放到 glance 屏幕上。 +- **日历的宿主服务**(`calendar/host-service`)把日历的日程保存在宿主目录中,并运行日历 + Agent 的工具:`calendar.events`、`add_event`、`remove_event`,以及把日程卡片或议程卡片 + 放到 glance 屏幕上的 `notify` 和 `agenda`。日历是桌面端的系统应用 + (`desktop/system-apps.json`)。 +- **新闻的宿主服务**(`news/host-service`)按定时器收集新闻条目,不使用模型,并运行新闻 + Agent 的 `news.list` 和 `news.read`。 - **`llm` 宿主服务**(`ai-providers/host-service`)是 AI providers 的 Rust 部分:基于 octos 模型目录的大模型服务商、存放在平台密钥库中的密钥、“测试连接”, 以及通过受 PIN 保护的 `OCTOS1E` 二维码在设备之间迁移服务商(相机、图片或粘贴)。 @@ -54,9 +61,10 @@ OctoScript-App-Design-Flow 的 `AGENTS.md`,再读 `docs/QUICKSTART.md`), | [Photos](photos/bundle) | `os.photos` | 示例相册:回忆、相簿、人物、收藏、可多选的网格、全屏查看器 | `storage` | 无 | 无(原图来自 Shell 的资源挂载,见下文) | | [Maps](maps/bundle) | `os.maps` | `MapView` 地图、地点搜索、地点详情、可更改起点并最多添加两个途经点的路线,以及带逐向导航和 2D/3D 视图的驾驶模式;有 GPS 定位时从当前位置开始 | `storage`、`net`、`location` | `photon.komoot.io`、`router.project-osrm.org`、`overpass-api.de`、`overpass.kumi.systems`、`maps.mail.ru`、`overpass.openstreetmap.fr` | 无 | | [Camera](camera/bundle) | `os.camera` | 基于运行时 `CameraPreview` 控件的拍照和录像,闪光灯和变焦,最近一张的缩略图和查看器 | `storage`、`camera`、`microphone`、`library` | 无 | 无 | -| [Mail](mail/bundle) | `os.mail` | 账户、文件夹、邮件列表、阅读(HTML 由服务重建)和写信 | `storage`、`mail` | 无(由服务联网,而不是应用) | [`mail`](mail/host-service) | +| [Mail](mail/bundle) | `os.mail` | 账户、文件夹、邮件列表、阅读(HTML 由服务重建)和写信;它的 Agent 把通知卡片放到 glance 屏幕上(`mail.notify`) | `storage`、`mail`、`glance` | 无(由服务联网,而不是应用) | [`mail`](mail/host-service) | | [AI providers](ai-providers/bundle) | `os.ai-providers` | 助手的大模型服务商:一个主用与若干备用,每项都有来自 octos 模型目录的型号下拉菜单和“测试连接”;添加向导(系列、型号、线路、密钥、测试);“为手机显示二维码”,以及通过相机、图片或粘贴导入 | `storage`、`llm` | 无(由服务联网,而不是应用) | [`llm`](ai-providers/host-service) | | [YouTube](youtube/bundle) | `os.youtube` | YouTube 搜索(运行时无需密钥的 `sys.video`,读取 YouTube 自己的搜索结果页),带缩略图和直播或时长角标的结果列表、话题标签,在 `WebReader` 中播放 YouTube 移动版观看页,以及本机播放记录 | `storage`、`net` | `www.youtube.com`、`m.youtube.com`、`i.ytimg.com` | 无 | +| [Calendar](calendar/bundle) | `os.calendar`(桌面端) | 它的 Agent 保存用户的日程,并把日程卡片和议程卡片放到 glance 屏幕上;它自己的窗口还不能列出日程(需要 App Hub 提供 `calendar` 权限) | `storage`、`glance` | 无 | [`calendar`](calendar/host-service)(只供日历的 Agent 使用) | | [AppCard](appcard) | 原生,需显式启用 | AppCard 助手:路由大脑选择或组合一个应用 Agent,由它生成实时的 Splash 或 webview 卡片。Shell 只在启用 `app-appcard` 时链接它;默认不发布 | 不适用(不是 bundle) | 不适用 | Shell 的 octos 内核 | 每项权限的含义由 App Hub 的封闭列表定义(`crates/app-policy/src/manifest.rs` @@ -89,6 +97,9 @@ OctoScript-App-Design-Flow 的 `AGENTS.md`,再读 `docs/QUICKSTART.md`), - **AppCard 的 `personal-data` 技能**读取旧原生 Mail 模块的 `mailbox-*.json` 文件。脚本版 Mail 的邮件现在存放在宿主服务自己的目录 (`/mail/box-*.json`),该技能大概率已读不到;未验证。 +- **日历**(2026-10-01,桌面端,隐藏窗口的 `--remote` 运行,接真实模型):系统 Agent + 请日历的 Agent 放一张卡片;首次使用面板弹出,Agent 添加了一个日程,它的卡片打开了 + glance 面板。手机上没有打包。它的宿主服务测试还不在 `apps.yml` 中。 - 只有 Camera 自带启动器图标(`bundle/icon.png`),其他应用的图标由 Shell 绘制。 ## Shell 如何打包它们 @@ -112,10 +123,12 @@ OctoScript-App-Design-Flow 的 `AGENTS.md`,再读 `docs/QUICKSTART.md`), } ``` -2. 通过 Shell([`crates/shell`](../crates/shell))链接宿主服务 - `octosense-mail-service` 和 `octosense-llm-service`(workspace 内的 path 依赖) - 并在启动时注册:Mail 服务在真实账户下用 `register()`,Shell 的应用配置中 - `mail_demo: true` 时用 `register_demo()`;`llm` 服务使用 octos 内核的 core 目录以及 +2. 通过 Shell([`crates/shell`](../crates/shell) 的 `app-hub` 特性)链接宿主服务 + `octosense-mail-service`、`octosense-calendar-service`、`octosense-news-service` 和 + `octosense-llm-service`(workspace 内的 path 依赖),并在启动时注册 + (`crates/shell/src/apps.rs` 的 `register_host_services`):Mail 服务在真实账户下用 + `register()`,Shell 的应用配置中 `mail_demo: true` 时用 `register_demo()`,并装上 + `mail.notify`(与日历的卡片一样)发布卡片所用的发布器;`llm` 服务使用 octos 内核的 core 目录以及 Shell 的二维码扫描器和图片选择器(见 [`llm` 服务](#llm-服务))。App Hub 只在根目录 `Cargo.toml` 中固定一次,因此只有一个宿主服务注册表。 3. 通过统一入口 [`crates/ai-host`](../crates/ai-host/README.md)(`octosense-ai-host`) @@ -133,7 +146,11 @@ OctoScript-App-Design-Flow 的 `AGENTS.md`,再读 `docs/QUICKSTART.md`), ``` /bundle/ 隔离运行的脚本应用:manifest.json、main.splash、图片资源 photos/resources/ Home 挂载的 Photos 示例图库 -mail/host-service/ octosense-mail-service,`mail` 宿主服务(Rust) +mail/host-service/ octosense-mail-service,`mail` 宿主服务(Rust);resources/notice.card +mail/docs/ 邮件的计划(邮件操作卡片) +calendar/host-service/ octosense-calendar-service,`calendar` 宿主服务;resources/event.card、agenda.card +news/host-service/ octosense-news-service,`news` 宿主服务(新闻的数据服务) +/bundle/tools.json 应用 Agent 自己的工具(新闻、邮件、日历) ai-providers/ `llm` 宿主服务(host-service/)和 octosense-llm-config(config/: octos 模型目录与服务商注册表、profile 合并、OCTOS1/OCTOS1E 二维码) reference/ reference 模块 @@ -150,7 +167,8 @@ appcard/ 原生 AppCard 助手 ../crates/shell/ octosense-shell:两种打包形态共同链接的唯一 Shell ../crates/ai-host/ octosense-ai-host:Shell 的 AI 服务(内核、`llm`、app peers),统一入口 ../crates/kernel/ octosense-kernel:Shell 的 octos 内核(每进程一个,共享) -../crates/app-peers/ octosense-app-peers:应用对助手的受限访问 +../crates/app-peers/ octosense-app-peers:应用 Agent 的代理,每个(应用,账号)一个 peer +../crates/l0-chat/ octosense-l0-chat:卡片卡内对话(sys.chat)的宿主一侧 ../.github/workflows/apps.yml 宿主服务、AppCard 和 Shell 服务的 CI ``` @@ -250,15 +268,34 @@ MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --release -p octosense-home -- | 文件 | 作用 | | --- | --- | -| `lib.rs` | 服务本体:`mail.accounts`、`add_account`(弹出登录面板)、`remove_account`、`folders`、`sync`、`list`、`message`、`mark_read`、`send`;`register()`、`register_demo()`、`register_with*()`;`Transport` trait | +| `lib.rs` | 服务本体:`mail.accounts`、`add_account`(弹出登录面板)、`remove_account`、`folders`、`sync`、`list`、`message`、`mark_read`、`send`、`notify`(Agent 的通知卡片,以 Mail 的身份通过 Shell 的 glance 服务发布);`register()`、`register_demo()`、`register_with*()`;`Transport` trait;给 Shell 的账户事件(`on_account_event`) | | `imap.rs` | IMAP 客户端(文件夹、已读标记回写服务器) | | `network.rs` | POP3 和 SMTP、MIME 解码;错误信息中从不包含凭据 | | `html.rs` | 把邮件重建为 Mail 的 `Html` 视图能绘制的少量标签,不含任何远程内容 | -| `vault.rs` | 密码的存放位置:macOS/iOS 钥匙串;Android 上用 Android Keystore 密钥加密的文件;其他平台为仅所有者可读的文件;`OCTOSENSE_MAIL_VAULT=file` 强制使用文件存储,便于未签名的开发构建 | +| `vault.rs` | 密码的存放位置:macOS/iOS 钥匙串;Android 上用 Android Keystore 密钥加密的文件;其他平台为仅所有者可读的文件,这些文件放在宿主的密钥文件夹 `/secrets/os.mail/` 中;`OCTOSENSE_MAIL_VAULT=file` 强制使用文件存储,便于未签名的开发构建 | +| `contacts.rs` | 用户的账户发过邮件的地址,供审批规则“收件人在我的联系人中”使用(用户在设置中打开之前不生效) | +| `resources/notice.card` | `mail.notify` 填充的固定 L0 通知卡片 | 账户元数据(不含密码)和已拉取的邮件存放在宿主自己的目录(`/mail`), 位于所有应用沙箱之外。每个账户只授权给添加它的应用。服务会先测试账户可用,再保存。 +### `calendar` 服务 + +`octosense-calendar-service`(`apps/calendar/host-service/src/lib.rs`)只为日历 +(`os.calendar`)服务:App Hub 的权限列表中没有 `calendar`,因此其他应用无法获得它; +Shell 把日历 Agent 的 `calendar.*` 工具当作这个系统应用自己的服务在这里运行。 + +| 方法 | 参数 | 返回 | +| --- | --- | --- | +| `calendar.events` | `{from?, to?, limit?}` | `{events: [{id, title, start, end, location, notes}]}`,最近的在前 | +| `calendar.add_event` | `{title, start, end?, location?, notes?}` | `{id, start}` | +| `calendar.remove_event` | `{id}` | `{removed}` | +| `calendar.notify` | `{event}` 或 `{title, when, location?, notes?}`,以及 `{card_id?, priority?}` | 日程卡片(`resources/event.card`)上了 glance 屏幕并发出通知后返回 `{card_id, replaced, expires_at}` | +| `calendar.agenda` | `{days?}` | 同上,用于议程卡片(`resources/agenda.card`):`days`(7)天内接下来的三个日程 | + +时间使用本地时间(`2026-10-02T15:00`,或只写日期表示全天)。日程保存在 +`/calendar/events.json`,位于所有应用沙箱之外。 + ### `llm` 服务 `octosense-llm-service`(`apps/ai-providers/host-service`)是 AI providers 的 Rust @@ -274,6 +311,47 @@ MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --release -p octosense-home -- **Talk to Octos**(默认关闭):在 **AI providers → Talk to Octos** 中打开后,本机会启动一个仅监听回环地址的服务,让 Web 客户端或终端界面与本设备的助手对话。开启期间内核以 `octos serve --host-managed` 代替 `--stdio` 运行,原生应用继续通过其 WebSocket 工作;外部客户端使用单独的令牌,只能打开 UI Protocol 套接字。Web 客户端通过一次性配对码或其链接的二维码配对;本用户的终端客户端读取私有连接文件。原生应用关闭后服务仍保持运行,直到关闭该功能或 Shell 退出。见 [ADR 0003(英文)](../docs/adr/0003-shared-octos-client-access.md) 和[内核指南](../crates/kernel/README.zh-CN.md)。 +## 应用 Agent + +应用 Agent 是应用自己的 octos peer,归系统 Agent 所有:有自己的工作区(应用的账号文件夹 +`apps//accounts//`)、记忆、模型通道和工具。哪些系统应用有 Agent,以及如何声明 +([`../crates/shell/src/apps.rs`](../crates/shell/src/apps.rs) 的 `agent_apps`、 +[`../crates/shell/src/host_tools/script_apps.rs`](../crates/shell/src/host_tools/script_apps.rs)): + +| 应用 | `manifest.json` | `tools.json` | 卡片 | +| --- | --- | --- | --- | +| 新闻 | `agent` 块 | `news.list`、`news.read`(read,可共享) | – | +| 邮件 | `agent` 块、`glance`、`storage.accounts`(Agent 代表已登录的账户工作) | `mail.notify`(act,后台) | `notice.card` | +| 日历 | `agent` 块、`glance` | `calendar.events`(read)、`calendar.add_event`(act)、`calendar.remove_event`(destructive,`confirm: host`)、`calendar.notify`、`calendar.agenda`(act) | `event.card`、`agenda.card` | + +- **声明。** manifest 的 `agent` 块列出 Agent 可以使用的内核工具(`"tools": ["ask_user_question"]`; + 其中带点的名称表示申请另一个应用的可共享工具),`bundle/tools.json` 声明应用自己的工具: + `.`、`input_schema`、`output_schema`、`risk`(`read`、`act`、`destructive`)、 + `background`、`confirm`(`host` 或 `app`)、`shareable` 和 `implemented_by: "host-service"`。 + App Hub 接纳并固定这两个文件。`agent` 块中的 `profile` 和 `model` 会被接纳,但 Shell 还没有使用。 +- **运行。** 在用户于首次使用面板上允许之前什么都不会运行(用户用顶栏的 “Ask ”、 + Shift+F8 或菜单项 “Ask this app's agent” 打开 Shell 的 “Ask ” 面板时,或系统 + Agent 用 `agents.ask` 询问时,弹出这个面板)。之后 Shell 准备好 peer,系统 Agent + 就能用 `peer_send_input` 找到它。只有系统 Agent、用户或卡片的卡内对话发起请求时才会 + 开始一轮:还没有触发器或定时任务(ADR 0002 M3,计划中)。 +- **直接与它对话。** 用户可以直接与应用的 Agent 对话,而不只是通过系统 Agent:在 Shell + 为每个拥有 Agent 的应用提供的 “Ask ” 面板里(这些应用都不绘制自己的对话界面), + 或在卡片的卡内对话里。这些回合在用户的通道里运行,与系统 Agent 的通道并列,带着应用的 + 工具。面板的“停止”只停止用户自己的回合。手机上还没有打开这个面板的触控入口。详见根目录的 + [README](../README.zh-CN.md#直接与应用的-agent-对话)。 +- **它的工具在应用的宿主服务上运行**,以应用的身份运行,在此之前 Shell 的中转已检查授权、 + schema 和预算。octos 只对破坏性和对外的工具请求审批(这里是 `calendar.remove_event`), + 由用户在 Shell 的面板上回答。 +- **卡片。** `mail.notify`、`calendar.notify` 和 `calendar.agenda` 填充宿主服务自带的固定 + L0 卡片,并以应用的身份、带 `notify` 通过 Shell 的 `glance` 服务发布(应用需要 `glance` + 权限)。模型只提供文字,从不编写卡片代码。卡片里可以有与应用自己的 Agent 的卡内对话 + (`sys.chat`,[`../crates/l0-chat`](../crates/l0-chat/README.md))。 +- **试一试**(桌面端):打开助手(F8),请系统 Agent 让邮件或日历的 Agent 在 glance 屏幕上 + 放一张卡片;在弹出的面板上允许该 Agent。邮件需要一个已登录的账户(下文的演示邮箱即可)。 + 邮件更完整的操作卡片([计划(英文)](mail/docs/2026-10-01-email-action-card-plan.md))目前只是 + 使用假数据的演示:`OCTOSENSE_GLANCE_DEMO=mail` 会在启动时发布它。**未验证**(在 #267 的检查中 + 运行过,没有为本页重新运行)。 + ## octos 内核 octos Agent 内核是 **Shell 服务**,不属于任何应用。 @@ -372,6 +450,8 @@ Shell 的 `system-apps.json` 中加入它。 | 对象 | 方法 | | --- | --- | | Mail 服务 | 在仓库根目录:`cargo test --locked -p octosense-mail-service`。钥匙串测试默认忽略:`cargo test -p octosense-mail-service -- --ignored keychain` | +| 日历和新闻服务 | `cargo test --locked -p octosense-calendar-service -p octosense-news-service`(日历的测试还不在 `apps.yml` 中) | +| 卡片的卡内对话 | `cargo test --locked -p octosense-l0-chat` | | octos 内核服务 | `cargo test --locked -p octosense-kernel`(替身内核);`OCTOS_CORE_TEST_KERNEL= cargo test -p octosense-kernel --test real_kernel`(真实内核) | | `llm` 服务与配置 | `cargo test --locked -p octosense-llm-service -p octosense-llm-config`;加 `--features octosense-llm-service/octos-core` 即 Shell 的构建方式 | | AppCard | 上文的命令 | diff --git a/apps/mail/docs/2026-10-01-email-action-card-plan.md b/apps/mail/docs/2026-10-01-email-action-card-plan.md index e0f4ac36..9c526ee1 100644 --- a/apps/mail/docs/2026-10-01-email-action-card-plan.md +++ b/apps/mail/docs/2026-10-01-email-action-card-plan.md @@ -1,7 +1,7 @@ # Plan: Mail's email action card (desktop validation) - **Date:** 2026-10-01 -- **Status:** Plan, not started. Two decisions are open (end of this page). +- **Status:** Plan, in progress. Two decisions are open (end of this page). On `main` (2026-10-01): the card MVP on the desktop with fake data (#261, `OCTOSENSE_GLANCE_DEMO=mail`), model-copy and the in-card chat `sys.chat` (#263, `crates/l0-chat`), and Mail's agent with one tool, `mail.notify`, which puts a notice card on the glance screen (#267). Not yet: background sync and the new-mail trigger, the `triage` and `email-card` skills and `AGENTS.md`, Mail's read tools as agent tools, `mail.draft_reply`. - **Relates to:** [ADR 0002](../../../docs/adr/0002-event-driven-app-agents.md) (app agents, triggers, cards; Mail's row), [ADR 0004](../../../docs/adr/0004-native-apps-hosting-and-peers.md) (one agent per app and account, host tools, approvals, interactive cards), [ADR 0005](../../../docs/adr/0005-app-contract.md) (the app contract), the L0 card language (Octoscript `docs/ui-profile-l0.md`) and the a2app L0 cards (`apps/appcard/a2app-l0/`). ## What we want to show diff --git a/crates/ai-host/README.md b/crates/ai-host/README.md index aeb102ee..40570a6f 100644 --- a/crates/ai-host/README.md +++ b/crates/ai-host/README.md @@ -5,13 +5,22 @@ One entry point for what every OctoSense shell (desktop/, phone/) hosts: - **the octos kernel** (`crates/kernel`) as a shell service: configured once, - started when a consumer (AppCard, Rinx) first connects, restarted by the - `llm` service after a provider change, stopped at shutdown; + started when a consumer (the system chat, an app's agent, Rinx, AppCard) + first connects, restarted by the `llm` service after a provider change, + stopped at shutdown; - **the `llm` host service** (`apps/ai-providers/host-service`) the AI providers system app calls, with the platform's QR import (Android camera and image picker, desktop open panel and drops, elsewhere a pasted code); -- **apps' assistant access** (Rinx ADR 0007): a scoped `crates/app-peers` - service offered to each granted native module instance at creation. +- **the `model` host service** (`model.complete`, implemented in + `apps/ai-providers/host-service/src/complete/`): one-shot model calls over + the same providers for apps granted the `model` capability, with per-app + budgets; +- **script apps' agents**: the `octos` host service (`src/contained.rs`, + below), one host-owned peer `card.` per app; +- **native apps' assistant access** (Rinx ADR 0007): a scoped + `crates/app-peers` service offered to each granted native module instance + at creation (`offer`), and a module's own peer link + (`module_peer::ModulePeerLink`, which no module uses yet). ```rust use octosense_ai_host as ai_host; @@ -47,12 +56,41 @@ mobile targets always have it — `cfg(kernel)`, set by build.rs), `llm` `toolbox-peers` (below; off by default, turned on by the shell's feature of the same name). +## Script apps' agents: the `octos` host service (`src/contained.rs`) + +`start` registers `ContainedOctos` (family `octos`) in App Hub's host-service +registry where the shell hosts a kernel. It is how every script app, system +or store, has an agent: + +- **The peer.** One host-owned octos peer per app, `card.` + (`PEER_PREFIX`, `peer_id`), launched through + `octosense_app_peers::hosted::launch` and owned by the system agent. It acts + for `device` (`ACCOUNT`), or, for an app whose manifest sets + `storage.accounts`, for the account the shell reports (`set_account_of`; + Mail's signed-in account); without one it answers `SIGN_IN`. +- **The gate.** `Policy::shipped()` reads `OCTOSENSE_CONTAINED_APPS` + (`contained_gate_from`): unset is `ContainedGate::Consent` (each app once + the person allowed its agent on the first-use sheet), `1` is `Everyone` + (asks nobody, for development), `0` is `Off` (`TURNED_OFF` for every app). + The service is registered even when off, so an app hears why. +- **The app's own calls.** `host.request("octos.session.open" | + "octos.session.history" | "octos.turn.start" | "octos.turn.interrupt")`, + only the names its manifest declares (`set_declared`, else + `NOT_DECLARED`); text at most 32 KiB, replies at most 2 MiB. None of + today's system apps with an agent (News, Mail, Calendar) declares one: + their agents are driven by the shell. +- **The shell's calls.** `prepare` (the shell prepares every allowed app's + peer at startup and when it is allowed, so the system agent's `peer_list` + shows it), `conversation` (the person's lane, for the "Ask " panel and + a card's `sys.chat`), `revoke` (the agent turned off) and + `account_changed`. + ## The system toolbox for app agents (`toolbox-peers`) ADR 0002 section 6 and ADR 0004 section 12. The toolbox is one more owner of host-routed tools in the shell's host-tool relay (octos#2567's shell side, -`crates/shell/src/host_tools/`): main's broker registers them after every -`peer/prepare` and reconnect (`generic_tools` omitted), and the relay +`crates/shell/src/host_tools/`): the broker registers them after every +`peer/prepare` and reconnect, with the app's other tools, and the relay authorizes each call and routes it to the toolbox's executor. This crate adds only the toolbox's part (`src/toolbox_peers.rs`, over `crates/toolbox`'s `peer` module): @@ -71,8 +109,9 @@ only the toolbox's part (`src/toolbox_peers.rs`, over `crates/toolbox`'s app's manifest (`for_manifest`: `research`/`crawl` in `capabilities`, the scope in octos's `Scope` shape under the top-level `research` object, App Hub #26's shape) is, **temporarily**, granted only to system apps (`os.*`) - until the shells' App Hub pin includes #26 and the host reads its verified - grant. + until the host reads App Hub's verified grant. The shells' App Hub pin + (`58c3c8ae`) already includes #26; the code still keeps the `os.*` gate + (`system_app_only`). No system app declares `research` or `crawl` yet. - `ToolboxExecutor`: the relay's executor for the `toolbox` owner. It checks the calling app's grant again (a forged `toolbox.deep_crawl` is `not_granted`), runs the call with the app's `AppContext` (id, grants, @@ -85,7 +124,14 @@ only the toolbox's part (`src/toolbox_peers.rs`, over `crates/toolbox`'s Nothing else is held back: octos's own generic tools (`deep_research` among them) are the kernel's, and which of them a peer gets is its `generic_tools` -list, which the broker does not set. +list: exactly the kernel tools its manifest names and the person granted, +which the broker sets with every registration. + +Which shells build it: the phone's default features include `toolbox-peers` +(`phone/Cargo.toml`, generated from `native-apps.json`); the desktop's do +not (the desktop package has the feature, off by default). On the phone, +`src/webview_render.rs` lets the octos reader render pages it cannot read +over plain HTTP in hidden system WebViews. Results are written to the host-owned `/.host/toolbox/` (`toolbox_folder`, always compiled; run results under diff --git a/crates/app-peers/README.md b/crates/app-peers/README.md index d3b6528e..3b5086fb 100644 --- a/crates/app-peers/README.md +++ b/crates/app-peers/README.md @@ -1,13 +1,17 @@ # octosense-app-peers: host-owned octos app peers -> **Where this fits.** The broker is the host connection for app agents: one host-owned peer per (app, account), owned by the system agent. It starts the system agent's `peer/input` turns on the peer's session (`#peer-`), runs the person's turns (a separate `share_history` request context `#peerctx-.` is in progress), registers the app's tools and hands every `peer/tool/call`, approval and question to the shell, and applies the 10-minute prompt deadline and the person's Stop. Diagrams of the processes, an app agent's two lanes and a tool call with its approval: [How it fits together](../../README.md#how-it-fits-together); the details: [docs/architecture.md](../../docs/architecture.md) and [ADR 0004](../../docs/adr/0004-native-apps-hosting-and-peers.md). +> **Where this fits.** The broker is the host connection for app agents: one host-owned peer per (app, account), owned by the system agent. It starts the system agent's `peer/input` turns on the peer's session (`…#peer-`), runs the person's turns in a separate request context opened with `share_history` (`…#peerctx-`; octos#2636), registers the app's tools and hands every `peer/tool/call`, approval and question to the shell, and applies the 10-minute prompt deadline and the person's Stop. Diagrams of the processes, an app agent's two lanes and a tool call with its approval: [How it fits together](../../README.md#how-it-fits-together); the details: [docs/architecture.md](../../docs/architecture.md) and [ADR 0004](../../docs/adr/0004-native-apps-hosting-and-peers.md). Rinx [ADR 0007](https://github.com/hagency-org/Rinx/blob/main/docs/adr/0007-host-owned-octos-app-peers.md): an OctoSense shell runs ONE octos kernel and ONE provider profile -([`crates/kernel`](../kernel)). A native app that declares assistant -services (the exact `octos.*` names App Hub publishes) and that host policy -grants gets ONE octos peer owned by the shell's system agent, and a scoped -service handle injected at module creation. The app talks with its agent in +([`crates/kernel`](../kernel)). An app with an agent gets ONE octos peer per +account, owned by the shell's system agent. A native app that declares +assistant services (the exact `octos.*` names App Hub publishes) and that +host policy grants gets its peer and a scoped service handle injected at +module creation. A script app with an agent (News, Mail, Calendar today) +gets its peer `card.` from the shell's `octos` host service +(`crates/ai-host/src/contained.rs`), which launches it through +`hosted::launch` as well. The app talks with its agent in its conversation (`open_conversation`): the person's lane, a request context opened with `share_history` that runs in parallel with the peer's own session (the system agent's lane, `peer/input`); each lane's model sees the other's @@ -60,7 +64,11 @@ data dir. A new peer's workspace is the account's folder the host names names the recorded one, made again first if the account's folder was removed. A peer recorded without a workspace (older `.token` files) resumes with the account folder, else the kernel's, and the one the kernel takes is recorded. -Their memory namespace is `app//acct-`. +Their memory namespace is `app//acct-` (`broker::app_namespace`; +`` is the id the broker was launched with: `rinx` for Rinx, +`card.os.mail` for Mail's script-app peer; `` is 16 hex digits of an +FNV-1a hash of the normalized account, not the account folder's SHA-256 +hash, #139). ## An app @@ -76,8 +84,11 @@ service.release(); // app closed ## Policy on the ADR's open questions -- **Approvals** reach the person through the app's native approval UI - (`ContextOp::Approval`); the system agent never approves for an app. +- **Approvals**: a gated host tool's approval (`confirm: host`) goes to the + shell's approval router through the `ToolHost`, and a `confirm: app` + call to the owning app's own sheet; other octos approvals raised in an + app's context reach the app (`ContextOp::Approval`). The system agent + never approves for an app. - **Background work after close**: `release()` closes every context and interrupts the peer's running turn. The peer and its memory stay for the next launch. The app's last instance then releases the peer's route diff --git a/crates/kernel/README.md b/crates/kernel/README.md index 5e7a7494..27188fc0 100644 --- a/crates/kernel/README.md +++ b/crates/kernel/README.md @@ -7,9 +7,11 @@ English | [简体中文](README.zh-CN.md) The [octos](https://github.com/octos-org/octos) agent kernel is a **shell service**. The shell (Home in `phone/`, the desktop in `desktop/`) owns it; the **AI providers** system app configures it through the `llm` host service; -**AppCard**, and next Rinx's native mini-app host, connect to it. This crate -is that service: one kernel per process, started on demand, shared, -restarted when the providers change. +its consumers connect to it: the shell's system chat (the system agent's +pane, `crates/shell/src/system_chat/link.rs`), every app agent's broker +(`crates/app-peers`, `CoreConnector`: Rinx and the script apps with an +agent) and the opt-in AppCard. This crate is that service: one kernel per +process, started on demand, shared, restarted when the providers change. It lives in `crates/` rather than `apps/` because it is not an app: it is the shared runtime piece the shells, AppCard (`apps/appcard/app`) and the @@ -85,11 +87,12 @@ AppCard's transport (`apps/appcard/app/crates/octos-app-transport`, still waiting, reconnects and opens its sessions again from their replay cursors, so the app carries on. -**Rinx and other consumers.** A native mini-app host takes its own -connection (`connect()`), opens sessions with ids of its own (Rinx uses -`:api:rinx-mini-…`) and gets only its sessions' traffic, with no -coupling to AppCard's connection or UI queue. It must handle -`CloseReason::Restarted` by reconnecting. +**Other consumers.** Each takes its own connection (`connect()`) and gets +only its sessions' traffic: the system chat opens `_main:api:octosense#system`; +an app-peers broker drives its app's peer and request contexts (Rinx's mini +apps are request contexts of Rinx's peer, through `OctosAppService`, not +sessions of their own). Each must handle `CloseReason::Restarted` by +reconnecting. Other functions: `core_dir()`, `home()`, `profile()`, `launch()` / `is_available()` (whether and how a kernel would start), `status()`, and the @@ -166,9 +169,14 @@ system agent's tool set is its grants. Its default octos tools are profile's `tool_policy` denies to every agent, since octos cannot resume a closed peer), its workspace's file tools (octos fences them to the session's working directory), memory, -`ask_user_question`, media viewing, octos's `web_search` / `web_fetch` (until -toolbox grants replace them, #108) and `tool_search`. Granted toolbox and -cross-app tools join it as host tools through `SystemAgentTools`. Command +`ask_user_question`, media viewing (`view_image`, `view_video`), octos's +`web_search` / `web_fetch` and `tool_search`. Toolbox and cross-app tools +are meant to join it as host tools through `SystemAgentTools` +(`grant_toolbox`, `grant_cross_app`), but nothing outside the tests grants +them yet, so the system agent has none. The system chat always registers two +host tools of its own on the session, `agents.list` and `agents.ask` +(`crates/shell/src/agents.rs`: which apps have an agent, and the first-use +sheet that allows one; only the person answers it). Command execution is **done** as a grant: the person's switch in Setup → Assistant → Command execution (off by default; turning it on needs the confirmation the person types, which says what it risks; `crates/shell/src/system_chat/grants.rs`) @@ -181,8 +189,10 @@ approval router as `auto_approvable: false` with a live sheet showing the exact command (developer mode still answers it). While the switch is on, the shell's system chat registers the host tool on the system session over its own connection (octos#2567's host session target, `peer/tools/register` without -`peer`) and withdraws it when the switch goes off; the shell types each approved -call into the Terminal the person sees. +`peer` and, since octos#2657, without any app peer's host token, so it is +offered before any app's agent has started) and withdraws it when the switch +goes off; the shell types each approved call into the Terminal the person +sees. **What the kernel enforces.** Every start writes the `_main` profile's `tool_policy` (`system_tools::tool_policy`): everything a grant can give, diff --git a/crates/kernel/README.zh-CN.md b/crates/kernel/README.zh-CN.md index 0b506e3e..cea963c0 100644 --- a/crates/kernel/README.zh-CN.md +++ b/crates/kernel/README.zh-CN.md @@ -6,7 +6,9 @@ [octos](https://github.com/octos-org/octos) Agent 内核是一项 **Shell 服务**。 Shell(`phone/` 中的 Home、`desktop/` 中的桌面)拥有它;**AI providers** 系统应用 -通过 `llm` 宿主服务配置它;**AppCard** 以及之后 Rinx 的原生小程序宿主连接它。 +通过 `llm` 宿主服务配置它;它的使用方连接它:Shell 的系统对话(系统 Agent 的面板, +`crates/shell/src/system_chat/link.rs`)、每个应用 Agent 的代理(`crates/app-peers`, +`CoreConnector`:Rinx 以及带 Agent 的脚本应用)和需显式启用的 AppCard。 本 crate 就是这项服务:每个进程一个内核,按需启动、共享,服务商变化时重启。 它放在 `crates/` 而不是 `apps/`,因为它不是应用,而是 Shell、AppCard @@ -77,9 +79,10 @@ AppCard 的传输层(`apps/appcard/app/crates/octos-app-transport`,`kernel.r 使用方:收到 `Restarted` 时,它让仍在等待的请求失败,重新连接,并从各会话的回放游标重新 打开会话,应用得以继续。 -**Rinx 与其他使用方。** 原生小程序宿主使用自己的连接(`connect()`),用自己的 id 打开 -会话(Rinx 使用 `:api:rinx-mini-…`),只收到自己会话的流量,不与 AppCard 的 -连接或 UI 队列耦合。它必须通过重新连接来处理 `CloseReason::Restarted`。 +**其他使用方。** 每个使用方都用自己的连接(`connect()`),只收到自己会话的流量:系统对话 +打开 `_main:api:octosense#system`;app-peers 代理驱动其应用的 peer 和请求上下文(Rinx 的 +小程序是 Rinx 的 peer 的请求上下文,经 `OctosAppService` 使用,没有自己的会话)。每个使用方 +都必须通过重新连接来处理 `CloseReason::Restarted`。 其他函数:`core_dir()`、`home()`、`profile()`、`launch()` / `is_available()`(是否以及如何 启动内核)、`status()`,以及下面的 Talk to Octos 控制函数。 @@ -135,12 +138,12 @@ adb -s SERIAL forward tcp:PORT tcp:PORT [ADR 0004](../../docs/adr/0004-native-apps-hosting-and-peers.md) §12:系统智能体的工具集就是它获得的授权。它默认的 octos 工具是 `system_tools::SYSTEM_AGENT_TOOLS`:监督(`peer_send_input`、`peer_gather`、`peer_list`、`peer_respond`; -不含 `peer_close`:octos 无法恢复已关闭的 peer,配置文件的 `tool_policy` 对所有智能体都禁用它)、其工作区内的文件工具(octos 将其限制在会话工作目录内)、记忆、`ask_user_question`、查看媒体、octos 的 -`web_search` / `web_fetch`(在工具箱授权取代它们之前,#108)以及 `tool_search`。授予的工具箱工具和跨应用工具通过 `SystemAgentTools` 作为宿主工具加入。命令执行作为授权**已完成**:用户在“设置 → 助手 → 命令执行”中的开关(默认关闭;开启需要用户输入确认语,确认语说明其风险; +不含 `peer_close`:octos 无法恢复已关闭的 peer,配置文件的 `tool_policy` 对所有智能体都禁用它)、其工作区内的文件工具(octos 将其限制在会话工作目录内)、记忆、`ask_user_question`、查看媒体(`view_image`、`view_video`)、octos 的 +`web_search` / `web_fetch` 以及 `tool_search`。工具箱工具和跨应用工具按设计通过 `SystemAgentTools`(`grant_toolbox`、`grant_cross_app`)作为宿主工具加入,但目前除测试外没有任何地方授予它们,所以系统智能体还没有这些工具。系统对话总会在该会话上注册自己的两个宿主工具 `agents.list` 和 `agents.ask`(`crates/shell/src/agents.rs`:哪些应用有 Agent,以及允许某个 Agent 的首次使用面板;只有用户能回答)。命令执行作为授权**已完成**:用户在“设置 → 助手 → 命令执行”中的开关(默认关闭;开启需要用户输入确认语,确认语说明其风险; `crates/shell/src/system_chat/grants.rs`)通过 `SystemAgentTools::grant_command_execution` 为系统代理授予宿主工具 `terminal.run`。 Shell 把授权交给本 crate(`system_tools::set_grants`);每次内核启动时采用(`grants_at_start`、`system_agent_tools_in_effect()`), 因此更改在重启后生效,设置中提供重启按钮。每条命令都经过 Shell 的批准路由器,按 `auto_approvable: false` 处理,并在实时批准表单上显示完整命令(开发者模式仍可直接批准)。 -开关开启期间,Shell 的系统对话在自己的连接上把该宿主工具注册到系统会话(octos#2567 的宿主会话目标,不带 `peer` 的 `peer/tools/register`),开关关闭时撤回;每个获批的调用由 Shell 输入到用户可见的 Terminal。 +开关开启期间,Shell 的系统对话在自己的连接上把该宿主工具注册到系统会话(octos#2567 的宿主会话目标,不带 `peer` 的 `peer/tools/register`;自 octos#2657 起也不需要任何应用 peer 的宿主 token,所以在任何应用的 Agent 启动之前就能提供),开关关闭时撤回;每个获批的调用由 Shell 输入到用户可见的 Terminal。 **内核执行的内容。** 每次启动都写入 `_main` profile 的 `tool_policy`(`system_tools::tool_policy`):任何授权可给予的一切, 唯独去掉 octos 自己的 shell(`group:runtime`:`shell`、`bash`、`exec_command`、`write_stdin`)——这是 OctoSense 唯一从不提供的工具, diff --git a/crates/l0-chat/README.md b/crates/l0-chat/README.md new file mode 100644 index 00000000..40e5d1e1 --- /dev/null +++ b/crates/l0-chat/README.md @@ -0,0 +1,57 @@ +# octosense-l0-chat: the host side of an L0 card's in-card chat + +> **Where this fits.** An app's agent can put an L0 card on the glance screen, and the person can chat with that agent inside the card. The card only declares the chat; this crate is everything the host decides about it: who may chat, who wrote what, the limits, and where the transcript is kept. The shell's glance cards and card window (`crates/shell/src/glance_chat.rs`) and AppCard's L0 cards both use it. The agent behind the chat is the app's own (one host-owned octos peer per app and account, [`crates/app-peers`](../app-peers/README.md)); the overview is in the root README, [The system agent and the app agents](../../README.md#the-system-agent-and-the-app-agents). + +A card declares a conversation with its app's agent (Octoscript profile §5.15, +`sys.chat` and `ChatEntry`, OctoScript#53): + +```text +source convo sys.chat(app: "os.mail", thread: "ana-contract", fields: [entries, id, role, text]) +event send { convo: append($value), draft: clear } +for m in convo.entries key m.id { ChatEntry(text: m.text, role: m.role) } +``` + +Octoscript's L0 checker admits the card. The rest is the host's, and is here +(`src/lib.rs`): + +| Rule | How | +| --- | --- | +| **Ownership** | A card chats only with the app that published it. `check_publisher` refuses, at publish, a `sys.chat` whose `app` is not the publisher; `seed` and `perform` check again on every read and write, and a card naming another app reads an `unavailable` transcript and writes nothing. | +| **The transcript is the host's** | `seed` puts the host's own transcript under every `sys.chat` source's name, replacing whatever the publisher put in its `data`: no forged `model` entry, no other app's thread. | +| **Roles** | Entries are `{id, role, text, at}`, `role` one of `user`, `model`, `host`. A card's one write is `append`, recorded as a `user` entry only when its payload is what the person typed (`ValueOrigin::UserInput`). Only the host's `ChatStore::append_reply` writes `model` (the agent's answer, drawn AI-written) or `host` (a notice). A payload that looks like `{"role":"model",…}` is just the text of a `user` entry. | +| **Limits** | `TEXT_MAX_BYTES` 4 KiB per message after trimming, control characters dropped; one message per thread every `MIN_INTERVAL_MS` (2 s) and none while the agent is still answering; the last `RETENTION` (200) entries kept; a reply cut at `REPLY_MAX_BYTES` (16 KiB); app and thread ids at most `ID_MAX` (64), threads `[A-Za-z0-9_-]`. | +| **Storage** | `ChatStore::with_folder` keeps one JSON file per (app, thread) in the folder the host names; `ChatStore::in_memory` for tests. The shell names the app's account folder (ADR 0004 §11): `apps//accounts//chat/.json`, owner-only, for the account the app's agent acts for (`device` without one), so switching accounts switches transcripts. AppCard keeps its chats in `apps/appcard/accounts/device/chat/`. | +| **Stale** | Every change bumps `ChatStore::generation` and calls the host's change hook (`set_on_change`), so a surface re-seeds and re-lowers the card when the reply arrives. | + +## Who answers: `Responder` + +The host passes each accepted message to a `Responder` and appends what it +answers: + +| Responder | Where | Answer | +| --- | --- | --- | +| `AgentResponder` | the shell (`crates/shell/src/glance_chat.rs`) | a turn in the app's conversation, the person's lane on the app's peer (`crate::agents::conversation`, `TurnTrigger::AppSaysPerson`), once the person allowed the app's agent; otherwise a `host` notice that says why | +| `Canned` | the shell under `OCTOSENSE_GLANCE_DEMO=mail` (Mail's demo cards, fake data) | a fixed `model` reply; no model is called | +| `NoAgent` | AppCard | a `host` notice: its agent does not answer inside a card yet | + +The agent gets the person's message (`Request::text`); the shell's responder +does not pass the thread's earlier entries (`Request::history`) to the agent. + +## Model-written text in a card + +The same Octoscript change lets an L0 card show text the model wrote, in +`copy` blocks of `class: model-copy`: it is drawn marked as AI-written, as +plain text, and never triggers, retargets or changes an action. Actions stay +the card's declared ones, which the host checks. That rule is the runtime's +(Octoscript's L0 profile); this crate adds only the chat. + +## Testing + +From the repository root: + +```sh +cargo test --locked -p octosense-l0-chat +cargo clippy --locked -p octosense-l0-chat --all-targets --no-deps -- -D warnings +``` + +CI: the `services` job of `.github/workflows/apps.yml` runs both. diff --git a/crates/toolbox/README.md b/crates/toolbox/README.md index 0b9da06a..15d565c6 100644 --- a/crates/toolbox/README.md +++ b/crates/toolbox/README.md @@ -4,7 +4,7 @@ The system toolbox's library of **OctoScript workflow templates** ([ADR 0002](../../docs/adr/), section 6, "Workflow templates", proposed in OctoSense PR #77). A template is a fixed, bounded procedure (a news digest, a multi-language topic brief, a plan from the weather) written in OctoScript with a manifest that says what it may call. An app's agent picks one and fills its parameters: one model call to choose, instead of a multi-call tool loop. The host then runs the independent steps concurrently. -This crate holds the library, the runner, forks, evaluation, the `mod.research` v1 host module, and the `workflow.*` tool surface as a Rust API. **The shells do not link it yet.** See [What remains](#what-remains). +This crate holds the library, the runner, forks, evaluation, the `mod.research` v1 host module, and the `workflow.*` tool surface as a Rust API. The shells link it through `crates/ai-host`'s `toolbox-peers` feature: on by default on the phone (`phone/Cargo.toml`), off by default on the desktop. Its tools reach app agents only (the system agent gets none yet), and only an app whose manifest declares `research` or `crawl`, which no app does yet. See [What remains](#what-remains). The first templates are ported from the AppCard research experiment ([`apps/appcard/tools/splash-research`](../../apps/appcard/tools/splash-research)). Its composition harness (Python) was not used; what was needed is in Rust here. @@ -18,10 +18,11 @@ The first templates are ported from the AppCard research experiment ([`apps/appc | `market-brief` (1.1.0) | One news search per ticker, started together; per-symbol reads run concurrently; one brief (a comparison for several symbols). Research, not advice: v1 has no quote method, so prices appear only as the sources state them | 13 | 1 | | `briefing` (1.1.0) | Up to four topics searched together; the top articles of each are read concurrently; one briefing | 17 | 1 | | `compare` (1.1.0) | The same aspect of two subjects searched together; reads run concurrently; one comparison. It needs sources for both sides | 9 | 1 | +| `dossier` (1.1.0) | Background research, not news: for each of a few questions, the caller's sub-queries in each language and metasearch category (general, it, news, social, science) over a long window (up to 1500 days), all started together; readable results pooled per question in rank order; up to `read_top` reads per question within one run-wide read budget, a failed or off-topic read replaced by the next candidate and an article another question read reused; one digest per question, citing only articles read in this run | 64 | 6 | `weather-plan` and `market-brief` adapt the experiment's `weather` and `stock` workflows. Those called `forecast`, `air_quality`, `quote` and `baseline` methods, which `mod.research` v1 does not have. Here they read published forecasts and news. Structured weather and market data will come with the research engine's providers. `briefing` and `compare` come from the experiment's composition set. Its `travel`, `outdoor` and `market` families need places and quotes, which the research module does not provide, so they are deferred. -Every template ships recorded fixtures in `templates//fixtures/`, with the expected `status`, `reasons` and `data`. The 20 cases include ready, partial and failed runs, off-topic pages dropped by the host and by the model (`news-digest/off-topic-dropped`), read budget moving to the language with readable results (`topic-brief/budget-moves-to-readable`), and Traditional-script pages matching a Simplified search (`topic-brief/traditional-script`). +Every template ships recorded fixtures in `templates//fixtures/`, with the expected `status`, `reasons` and `data`. The 23 cases include ready, partial and failed runs, off-topic pages dropped by the host and by the model (`news-digest/off-topic-dropped`), read budget moving to the language with readable results (`topic-brief/budget-moves-to-readable`), and Traditional-script pages matching a Simplified search (`topic-brief/traditional-script`). ## Template format @@ -234,21 +235,21 @@ The tests show this rule deciding real cases: `peer` is what an app's peer is offered (octos#2567, ADR 0004 §12). `peer::catalog(library)` gives every toolbox tool's `tools.json` entry, each marked with its owning app (`app: "toolbox"`), `shareable`, `background`, not `outward`, `confirm: host`: the shell's host-tool relay declares them and grants each app exactly what it declares and the person granted. `peer::tool_decls(app, library)` is that catalog narrowed to the app's grants (`research`: `workflow.run`, `workflow.fork`, `toolbox.search`, `toolbox.web_read`; `crawl` with crawl limits in the scope: `toolbox.deep_crawl`). Nothing else is held back: octos's own generic tools, `deep_research` among them, are the kernel's. `peer::PeerToolbox::call(app, name, args)` checks the name against the grants again (`not_granted` otherwise) and runs it: the two workflow tools through `handle_json` (the agent gets the data, sources and the result's path; the trace stays in the file), the single tools on the same `ResearchBackend` as `mod.research`, narrowed by the scope (`scope::narrow_search`, `check_domain`, `scope::narrow_crawl`), with their items saved under `research/` in the app's folder. `toolbox.deep_crawl` reads pages with `ResearchBackend::read_links` (the octos engine keeps each page's HTML for its links) and stays on the start's site, under `path_prefix`, inside the domains and within `max_depth` and `max_pages`. -## How an app agent will use it +## How an app agent uses it -This flow needs the wiring in [What remains](#what-remains): +The wiring is in place behind `toolbox-peers` (`crates/ai-host/src/toolbox_peers.rs`, `crates/shell/src/host_tools/toolbox.rs`); no app declares `research` yet, so no shipped app agent has gone through it: -1. The app's manifest asks for `research` with a scope. App Hub pins the request and the person grants it. -2. The kernel offers the app's peer `workflow.list` and `workflow.run` (and `workflow.fork` / `workflow.evaluate` if granted). The host fills `AppContext` from the peer's identity and the app's grants, never from the model. +1. The app's manifest asks for `research` with a scope. App Hub pins the request and the person grants it (for now the host grants it only to system apps, `os.*`). +2. The shell registers the app's peer `workflow.run`, `workflow.fork`, `toolbox.search` and `toolbox.web_read` (and `toolbox.deep_crawl` with `crawl`), owned by `toolbox`; `workflow.list` and `workflow.evaluate` stay Rust API. The host fills `AppContext` from the peer's identity and the app's grants, never from the model. 3. The agent picks a template, fills its parameters in one model call, and calls `workflow.run`. The host runs it within the app's scope and budget and writes `toolbox/runs//.json`. The agent gets back the structured result, including provenance. 4. To improve a procedure, the agent forks it, edits the fork, and evaluates it against the parent on recorded cases. It adopts the fork only if the verdict is `better`. ## Commands -All of these were run on 28 Sep 2026 with octos's merge of #2585 (7bec0918) in place of the pin, through an uncommitted override (the pin's octos has no `octos-research` toolbox, so every build needs it until the pin moves, and `--locked` needs the lock file updated locally for it): `--config 'patch."https://github.com/octos-org/octos.git".octos-research.path="/crates/octos-research"'` (**unverified** against the pin until it moves). +The pinned octos (`ae230ce0`) includes `octos-research`, so no override is needed any more. On 1 Oct 2026 `cargo test --locked -p octosense-toolbox` passed at the pin (83 tests). The others below were run on 28 Sep 2026 against octos's merge of #2585 (7bec0918) through a local override, before the pin moved; at the pin `apps.yml` runs the plain, `live` and `octos-engine` tests and both clippy lines, and the live and real-model runs are **unverified** at the pin. ```sh -cargo test --locked -p octosense-toolbox # 67 tests, fixtures only (the thin scope parser) +cargo test --locked -p octosense-toolbox # 83 tests, fixtures only (the thin scope parser) cargo test --locked -p octosense-toolbox --features live # 81 tests (4 ignored): + adapter tests on a local server # (robots.txt never requested by default; honoured when on; SSRF; backoff; # Google News editions; the GDELT breaker; provider deadlines; the feed filter) @@ -372,7 +373,7 @@ After the [status rules](#status) and the [summary check](#modresearch-v1): four ## What remains -- **Peer tool wiring** is in place behind the shell's `toolbox-peers` feature (`crates/ai-host`'s `toolbox_peers`, the shell's `host_tools::toolbox`, and the `peer` module here: the tools per grant, `PeerToolbox::call`, and `toolbox.deep_crawl` over `ResearchBackend::read_links`). Script apps' grants wait for the shells' App Hub pin to include App Hub #26 (`research`/`crawl`). +- **Peer tool wiring** is in place behind the shell's `toolbox-peers` feature (`crates/ai-host`'s `toolbox_peers`, the shell's `host_tools::toolbox`, and the `peer` module here: the tools per grant, `PeerToolbox::call`, and `toolbox.deep_crawl` over `ResearchBackend::read_links`). The shells' App Hub pin now includes App Hub #26 (`research`/`crawl`), but the host still grants a script app's declaration only to system apps (`os.*`) until it reads App Hub's verified grant. The system agent's toolbox grant (`SystemAgentTools::grant_toolbox` in `crates/kernel`) is not wired. - **Engine**: the octos research engine is behind `ResearchBackend` (`octos-engine`). Still to do: - drop the interim adapter once the shells use it; - add metasearch (octos#2582) and publisher feeds (octos#2585); @@ -380,5 +381,5 @@ After the [status rules](#status) and the [summary check](#modresearch-v1): four - report why a read had no main text (final URL, consent page, bot challenge); - stop a provider that the breaker skipped from making every run `partial`. - **Durable execution** through `octoscript-workflow` (checkpointed, resumable runs; queueing and batching by the system agent). -- **App Hub**: the `research` and `crawl` capabilities with a scope in the manifest; pinning forks shipped in a bundle. +- **App Hub**: pinning forks shipped in a bundle. (The `research` and `crawl` capabilities with a scope in the manifest are App Hub #26, in the shells' pin.) - **Back upstream**: offering a winning fork to the library, with the person's consent. diff --git a/docs/images/agents-card-flow.png b/docs/images/agents-card-flow.png new file mode 100644 index 00000000..83b96f0c Binary files /dev/null and b/docs/images/agents-card-flow.png differ diff --git a/docs/images/agents-processes.png b/docs/images/agents-processes.png index 03009cf5..a9193f00 100644 Binary files a/docs/images/agents-processes.png and b/docs/images/agents-processes.png differ diff --git a/docs/images/agents-tool-call.png b/docs/images/agents-tool-call.png index 83696921..ac61c1f3 100644 Binary files a/docs/images/agents-tool-call.png and b/docs/images/agents-tool-call.png differ diff --git a/docs/images/agents-two-lanes.png b/docs/images/agents-two-lanes.png index 92d63afb..1e51a209 100644 Binary files a/docs/images/agents-two-lanes.png and b/docs/images/agents-two-lanes.png differ