Skip to content

toolbox-peers: offer the system toolbox to app agents (ADR 0002 §6, behind a feature) - #108

Closed
ymote wants to merge 3 commits into
mainfrom
feat/toolbox-peers
Closed

ymote wants to merge 3 commits into
mainfrom
feat/toolbox-peers

Conversation

@ymote

@ymote ymote commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

ADR 0002 §6 (the system toolbox) as app peers' host-registered tools, for octos#2567 (UPCR-2026-035). Issue #62 records the registration rules. Everything is behind features that are off by default, so the shells, Rinx and CI's existing lines behave exactly as before.

Features

  • octosense-app-peers/peer-tools: registration and routing in the broker.
  • octosense-ai-host/toolbox-peers: turns that on and adds the toolbox (octos engine, model service, grants, host folder).

Both compile on main's octos pin (e6223efc after merging main, which brought #98 and serve --host-managed). The wire is JSON, so nothing needs #2567's types. CI now also tests and lints both features against the scripted kernel.

Registration (issue #62 rules)

  • After every successful peer/prepare, the broker calls peer/tools/register. It does so on the same link that later opens contexts and starts turns. It sends the empty set when the app has no tools (Rinx).
  • A lost link unbinds the peer, so the next request prepares and registers again before any turn.
  • If registration fails, the peer stays unbound: no context opens, no turn starts, and availability() reports Failed with the reason.
Grant Registered (risk)
none empty set
research workflow.run (read), workflow.fork (act), toolbox.search (read), toolbox.web_read (read)
crawl, with max_depth/max_pages > 0 toolbox.deep_crawl (read)
  • octos deep_research, workflow.list and workflow.evaluate are not offered.
  • Every tool is background: true, outward: false, confirm: host, so the kernel gates none of them.
  • call_timeout_ms is 300 000 and max_result_bytes is 1 MiB.
  • Names are toolbox.* because octos requires <app>.<tool>.

Calls

  • Deduplication: a call runs once per (session, turn, tool_call_id, args_digest). A repeat gets the first result.
  • Unregistered tools are refused as not_registered. The toolbox also checks the grant itself (not_granted).
  • Cancel: peer/tool/cancel, the call's deadline, release or link loss fire the call's Cancel, then drop its future. A cancelled call is never answered.
  • Routing:
    • workflow.* goes to Toolbox::handle_json.
    • search and web_read go to the same ResearchBackend (OctosResearch), narrowed by the octos Scope.
    • deep_crawl is a bounded same-site crawl over the new ResearchBackend::read_links. The octos reader keeps HTML for this.
  • Worker thread: the toolbox's futures are not Send, so each app gets its own worker thread.
  • Model calls: ModelHostClient runs the toolbox's ModelClient through ModelHost::complete. It uses the person's providers and the app's daily budget, in the same ledger at <apps root>/.host/model/ledger.json. Host callers may now send up to 256 KiB of input, because digests carry their articles' evidence. model.complete from an app stays at 32 KiB.
  • Results: written to <apps root>/.host/toolbox/<app>/…, outside the app's jail. octosense_ai_host::{TOOLBOX_HOST_DIR, toolbox_root, toolbox_folder} is always compiled, so glance: resolve sys.digest sources from host-held digests (ADR 0002 M4) #87's digest_root can adopt it.
  • Grants: read from the manifest's capabilities (research/crawl), with the scope in the top-level research object. This is App Hub mobile: native-feeling frame pacing on the phone #26's shape (merged). Temporary: only os.* apps get what they declare until the shells' App Hub pin includes mobile: native-feeling frame pacing on the phone #26 and the host reads its verified grant.

Tests

  • Scripted kernel:
    • app-peers tests/peer_tools.rs (6): register right after prepare and before any turn, and again after a reconnect; a failed registration starts no turns; routing and deduplication; an unregistered tool is refused; cancel and timeout; release.
    • ai-host tests/toolbox_peers.rs (7): registration per grant; the os.*-only rule; workflow.run/fork/search/web_read with FakeModel and fixture research, with results in .host; scope refusals; a forged deep_crawl without crawl (not registered and refused); cancel; the budget through ModelHost.
  • Toolbox: tests/peer.rs covers the crawl bounds. There are unit tests for the declarations and narrow_crawl. A parity test now covers octos's crawl_args too.
  • Real octos kernel, built from #2567 at e270d039 and again at 31c51064:
    • app-peers real_kernel without the feature: 6/6 pass.
    • With peer-tools: 4 pass. They include a new real-turn host-tool call and "empty set means no tools", both over stdio and over the host-managed server (host-token WebSocket, after merging main). 4 are ignored, see below.
    • ai-host toolbox_real_kernel: the real kernel accepts the toolbox's declarations, a real turn calls toolbox_search and workflow_run (the result lands in .host), and deep_crawl is not offered without crawl.
  • Checks run: cargo check --locked -p octosense with and without --features mobile-apps, check-shell-graph.sh -p octosense, and from phone/: cargo check --locked -p octosense-home --features mobile-apps. Tests pass for app-peers (plain, octos-core,ws, +peer-tools), toolbox (plain, live, octos-engine), ai-host (plain, octos-core,llm, toolbox-peers), the llm-service and the mail service. Clippy -D warnings is clean on the changed crates. Toolbox fmt passes.

Finding: a registered peer loses the system agent → peer flows

Under #2567, once a peer registers, kernel-internal continuations get no tools. With the empty set, even the peer's own turns get none: no ask_user_question, no shell.

So with the feature on, four ADR 0007 real-kernel flows fail (question/answer, second input, interrupt on close, approval refusal). The peer just echoes. These tests are #[ignore]d under peer-tools with that reason.

This is #2567's stated design, but it affects Rinx's assistant as soon as the feature is enabled. It needs a decision before the pin moves. Options include generic tools for Rinx's set, or the host driving those runs itself.

Unverified

To turn it on

  1. Merge octos#2567 and move the octos pin to include it (one pin move; Android liboctos.so from the same rev).
  2. Resolve the finding above for Rinx, then run News M3: triggers (data events, digest times) and an unattended run #62's manual Rinx check.
  3. Move the shells' App Hub pin to include App Hub mobile: native-feeling frame pacing on the phone #26 (research/crawl). Then read its verified AppPolicy::research and drop system_app_only.
  4. glance: resolve sys.digest sources from host-held digests (ADR 0002 M4) #87 adopts octosense_ai_host::toolbox_root for digest_root.
  5. Enable octosense-ai-host/toolbox-peers in the shells. ai-host: the octos host service for contained apps (Card runner) #106 can pass toolbox_peers::tools_for_manifest to hosted::launch_with_tools for contained apps.

No pin moved. Rinx is untouched. No local override was needed: #2567's kernel was built in a separate octos worktree only for the real-kernel tests.

🤖 Generated with Claude Code

ymote and others added 3 commits September 28, 2026 10:20
…3/M5)

Behind features, off by default until octos#2567 is on the pin:

- app-peers `peer-tools`: every broker registers its peer's tool set
  (possibly empty) with `peer/tools/register` right after each successful
  `peer/prepare` and after every reconnect, on the link that drives its
  turns; a failed registration leaves the peer unbound, so no turn starts
  (issue #62). `peer/tool/call` runs `BrokerConfig::host_tools` once per
  occurrence, refuses unregistered tools, honours `peer/tool/cancel`, the
  call's deadline, release and link loss, and answers `peer/tool/result`.
- toolbox `peer` module: the tools.json entries per grant (`research`:
  workflow.run, workflow.fork, toolbox.search, toolbox.web_read; `crawl`
  with crawl limits: toolbox.deep_crawl; never deep_research), and
  `PeerToolbox::call`, which checks the grant again and runs with the
  app's AppContext; deep_crawl over `ResearchBackend::read_links`;
  `scope::narrow_crawl` (octos `crawl_args`, with a compat mirror).
- ai-host `toolbox-peers`: ToolboxTools (a worker thread per app for the
  non-Send toolbox), the OctosResearch backend, a ModelClient over
  `ModelHost::complete` (same ledger and daily budget as model.complete),
  grants from the manifest (TEMPORARY: os.* only until App Hub verifies
  research/crawl), results in `<apps root>/.host/toolbox/<app>`
  (`toolbox_folder`, always compiled, for #87).
- llm-service: host callers (system set) may send up to 256 KiB of input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… real-kernel check

The manifest's top-level research object is App Hub #26's shape; the os.*
fallback stays until the shells' App Hub pin has #26. The host-tool
round trip also runs on the host-managed server (host-token WebSocket).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote added a commit that referenced this pull request Sep 28, 2026
… §12)

The system agent's turns got octos's full default tool set, shell
included. `octosense_kernel::system_tools` now defines its set in one
place: supervision (peer_send_input, peer_gather, peer_list,
peer_respond, peer_close), its workspace's fenced file tools, memory,
ask_user_question, media viewing, octos's web_search/web_fetch (until
toolbox grants replace them, #108) and tool_search. No command
execution, sub-agents, peer_handoff, administration, browser, plugin or
MCP tools. Granted toolbox and cross-app tools join through
`SystemAgentTools` (none yet).

octos has no per-session tool roster, so every kernel start writes the
set into the `_main` profile as its `tool_policy` (allowlist, with
group:runtime, group:sessions and group:admin denied), replacing any
other policy. octos applies it to every turn of the profile, wake
continuations included; it is also the ceiling for app peers, AppCard
and Rinx. Talk to Octos external turns keep octos's allowlist, which
the set contains.

Tests: a real-kernel test checks a system-agent turn is offered exactly
the set over stdio and over Talk to Octos, and that an external turn
keeps its allowlist; the app-peers question/answer test checks every
request, the wake included, stays inside the set. The ADR 0007 approval
test becomes: the system agent cannot get a command run through an app
peer (no shell, nothing to approve).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote added a commit that referenced this pull request Sep 28, 2026
… §12)

The system agent's turns got octos's full default tool set, shell
included. `octosense_kernel::system_tools` now defines its set in one
place: supervision (peer_send_input, peer_gather, peer_list,
peer_respond, peer_close), its workspace's fenced file tools, memory,
ask_user_question, media viewing, octos's web_search/web_fetch (until
toolbox grants replace them, #108) and tool_search. No command
execution, sub-agents, peer_handoff, administration, browser, plugin or
MCP tools. Granted toolbox and cross-app tools join through
`SystemAgentTools` (none yet).

octos has no per-session tool roster, so every kernel start writes the
set into the `_main` profile as its `tool_policy` (allowlist, with
group:runtime, group:sessions and group:admin denied), replacing any
other policy. octos applies it to every turn of the profile, wake
continuations included; it is also the ceiling for app peers, AppCard
and Rinx. Talk to Octos external turns keep octos's allowlist, which
the set contains.

Tests: a real-kernel test checks a system-agent turn is offered exactly
the set over stdio and over Talk to Octos, and that an external turn
keeps its allowlist; the app-peers question/answer test checks every
request, the wake included, stays inside the set. The ADR 0007 approval
test becomes: the system agent cannot get a command run through an app
peer (no shell, nothing to approve).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ymote

ymote commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #151: #108's toolbox parts are ported onto main's octos#2567 shell side (#145's broker registration and host-tool relay), with the deep_research exclusion dropped and the toolbox gated on the #120 first-use consent. Leaving this open for reference; not closing it here.

🤖 Generated with Claude Code

ymote added a commit that referenced this pull request Sep 29, 2026
toolbox-peers: the system toolbox as host tools for app agents, on #145's registration and relay (supersedes #108). Merged on local checks per the ADR 0004 rule, after merging main (G2, G4): shell 523/523, host_tools 15/15 with toolbox-peers; ai-host 28+8; toolbox_real_kernel 1/1 and app-peers real_kernel 8/8 against octos acffad3b; app-peers lib 18, broker 16; desktop/phone check with and without the feature; clippy -D warnings on ai-host and toolbox; native_apps --check.
@ymote

ymote commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #151 (merged as 8855326), which rebuilt this on main's #145 registration and relay.

@ymote ymote closed this Sep 29, 2026
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
…DR 0004 §12)

Re-port of OctoSense-org#108 onto main's octos#2567 shell side (OctoSense-org#145), behind the
`toolbox-peers` feature (off by default):

- the toolbox is one more owning app (`toolbox`) in the shell's host-tool
  relay: its tools are declared once in the catalog (`app: "toolbox"`,
  shareable), each app is granted exactly what it declares AND the person
  granted, and `ToolboxExecutor` (crates/ai-host) runs the calls the relay
  routes to it; main's broker registers them after every prepare and
  reconnect. OctoSense-org#108's own registration and routing are not ported;
- no hard-coded exclusions: OctoSense-org#108's "never offer octos deep_research"
  (its empty generic_tools) is gone;
- the OctoSense-org#120 first-use consent gates the toolbox: nothing offered
  (Catalog::offered) and nothing run (the relay's consent_pending) before
  the person allowed the app's agent;
- ported from OctoSense-org#108: crates/toolbox `peer` (plus a shared `catalog`),
  read_links/narrow_crawl, ModelHostClient, toolbox_folder, the 256 KiB host
  model input, the mock model's CALL_TOOL rule; tests against a scripted
  kernel through main's broker, the relay, and a real octos kernel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
…ir granted kernel tools [skip ci]

ADR 0004 §4, §7, §12 (G3).

- The seams (crates/shell/src/host_tools, for OctoSense-org#108 too):
  `declare(app, tools)` → `grant(caller, owning_app, tool)` (grants are
  keyed by owner and tool) → an executor per owning app
  (`set_executor`); `set_generic(app, tools)` sets exactly the octos
  kernel tools an app's agent keeps (never octos's shell).
- Native apps: the shipped catalog is native-apps.json's agent blocks
  (the Terminal's `terminal.run`, `terminal.read_screen`,
  `terminal.read_scrollback`; each app's grants and kernel tools). The
  Terminal's tools run on its AI bus service; declarations always name
  their owning app.
- Script apps (host_tools/script_apps.rs): a system or installed app's
  `tools.json` and manifest `agent.tools` are loaded with App Hub's own
  loader (`AgentBundle::load`: digest and gate rules) when its peer first
  registers and again at install; plain names become its kernel tools,
  dotted ones cross-app grants marked with their owner. Its host-service
  tools run on `HostServiceExecutor`: App Hub's dispatch with the app's
  identity, never from a sheet, only for a granted family; answers come
  back through `host_tools::pump`.
- News offers real tools: apps/news/bundle/tools.json (`news.list`,
  `news.read`, read-only, shareable) on its host service; its manifest
  now has the `news` capability.
- app-peers: `ToolHost::generic_tools`; the broker registers each peer
  with the host's exact `generic_tools` (the shell always sets one: an
  app granted none keeps none).
- The real-kernel test (host_tools/real_kernel_tests.rs): octos at the
  pinned rev → the broker → `ShellToolHost` and the relay → News's host
  service → the result in the model's turn; the peer is offered News's
  tools, no kernel tools and never octos's shell. phone.yml builds the
  pinned octos (cached) and runs it (`OCTOS_SHELL_TEST_KERNEL`); without
  a binary it says so and passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant