Repository navigation
ADR 0004 G3: apps offer tools end to end (+G8, G11, G12, G13, peer/input/reject) - #160
Merged
Merged
Conversation
…right conversation [skip ci] ADR 0004 §6 (G11, shell part): octos keeps `ask_user_question` on host-driven turns, so an app agent's question needs a conversation. - app-peers: the broker takes `user_question/requested` on the peer's session and its request contexts, stamps the turn's origin (a context's, the peer's own, or a `peer/input` turn's) and the context's client, and hands it to the host (`ToolHost::user_question`) with a `QuestionAnswer` that sends `user_question/respond` once, on the link it came on. The app's context hears only `user_question/handled_by_host`; the turn's end closes the question (`ToolHost::user_question_closed`); an app's `ContextOp::Approval` for an id the host holds (a question or a `host_tool` approval) is refused. - shell: `crates/shell/src/questions/` is the request model, a routing layer: one `Request` per question (owning app, peer, account, context, client, turn, questions and options, who started the turn, the conversation, state), consumers subscribe to it. A context's or the peer's own turn asks in the app's conversation; a `peer/input` turn (the system agent's) in the system chat. The system chat subscribes and shows its questions in the conversation; the approvals overlay shows the app's. The only answer path is `questions::answer` with a `PersonAnswer`, made only by the shell's surfaces (a test scans the sources); the peer link and the `octos` host service have no method to answer. No `host.ask`. - docs: architecture.md (+zh) item 11 and the questions bullet; the ADR follow-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rom the manifest [skip ci]
native-apps.json's `agent` block now carries what an app's agent gets,
reviewed with the entry (ADR 0004 §1, §7, §12):
- `tools`: the app's own tools, as `tools.json` entries (the Terminal's
`terminal.run`, `terminal.read_screen`, `terminal.read_scrollback`);
- `generic_tools`: exactly the octos kernel tools its agent gets (Rinx:
its workspace files, `ask_user_question`, memory, the web); never
octos's shell (the generator refuses `shell`, `bash`, `exec_command`,
`write_stdin`, `group:runtime`);
- `grants`: other apps' shareable tools, `{app, tool}` (a native owner's
tool must be declared and shareable);
- `budget`: tool calls per turn and per day.
tools/native_apps.py validates it, generates it into
crates/shell/src/native_apps.rs, and generates
crates/ai-host/src/native_agents.rs: the `octos.*` grants
`Policy::shipped()` now hands out instead of a hard-coded Rinx line
(same behaviour: Rinx gets the four `octos.*` services). --check covers
both files; generator and ai-host tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ir granted kernel tools [skip ci] ADR 0004 §4, §7, §12 (G3). - The seams (crates/shell/src/host_tools, for #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>
…p ci]
ADR 0004 §12 (G13): `launch::prepare` ignored a refused `tool_policy`
write (a foreign policy in the profile, the person's own octos home via
OCTOS_APP_CORE_DIR, an unwritable profile) and started the kernel anyway,
so the system agent and every app peer had octos's shell.
- `system_tools::enforce` reads the profile back after writing and
refuses when the policy is not there.
- `launch::prepare` returns the refusal; the generation then does not
start the kernel and closes every consumer's connection with
`CloseReason::Failed` saying why ("The assistant was not started:
OctoSense could not enforce its tool policy …").
- App peers no longer rely on the profile policy alone: they are
registered with exactly their granted `generic_tools` (previous
commit), never octos's shell.
- Test: a foreign policy starts no kernel program, leaves the person's
policy alone, and a fixed profile starts it with the policy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
G11 follow-up (the person's decision): an app agent has ONE shared conversation on its peer session, driven by the person (the app's UI, its cards), the app and the system agent; a question follows the TURN's origin, not its session. - app-peers: `TurnOrigin` (person, app, system_agent) on each `AgentQuestion`: the kernel's `origin` on the event when octos carries it (`origin_reported`), until then the broker's derivation: the turns it started for `peer/input` are the system agent's, a context's the person's, any other the app's. - shell: the request model keeps the `Origin` (and whether it was reported); person and app turns ask in the app's conversation, system agent turns in the system chat. The later octos field replaces the derivation without touching the consumers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d budget each agent [skip ci] ADR 0004 §3 (G8): the relay authorized by (owning app, tool) and caller only. - Arguments: checked against the tool's declared `input_schema` (the `tools.json` subset: type, properties, required, additionalProperties, items, enum, const, bounds, lengths, item counts; host_tools/schema.rs) and capped at 64 KiB, before anything runs; refused as `invalid_args` with the path of the first violation. - Results: every answer goes back through a checked reply: over 256 KiB is `result_too_large`, outside the declared `output_schema` is `invalid_result`; errors pass through; a cancel closes both replies. - Budgets: each calling agent (and the system agent) has calls per turn and per day, from its manifest (`native-apps.json` `agent.budget`) or the defaults (32 per turn, 1000 per day); spent is `budget_exceeded`; a new turn and a new day start again. - Tests for each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cess on this device [skip ci] ADR 0004 §10, §12 (G12, decided 2026-09-29): the in-process Terminal offers its read tools only, so `terminal.run` has a target only where the Terminal runs as its own process. - `apps::terminal_runs_as_process`: processes on this device, the Terminal's hosting resolved to a process (`process`, or `process-if-vulkan` on a Vulkan build in a Wayland session, unless the person switched it to a module) and a process form to start. - The system chat registers `terminal.run` on the system session (and the relay lets the system agent call it) only when command execution is granted AND that holds; granted without it, nothing is registered. - Setup › Assistant › Command execution says "needs Terminal as a process on this device" there, on or off. - Tests: the hosting resolution, the registration gate, the Setup rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reason [skip ci]
octos#2621 (merged as octos main c608384d) adds `peer/input/reject`: the
host can refuse the system agent's `peer/input`, and the system agent
learns why instead of the refusal being only logged (octos#2618, the
ADR 0004 follow-up "a refused peer/input is only logged").
- Pin octos c608384d: octos-core, octos-cli and octos-research move
together; tools/test_kernel_artifact.py and the docs that name the
current pin follow.
- `ToolHost::admit_input` refuses with an `InputRefusal` (`signed_out`,
`no_consent`, `busy`, `other` + a one-line message); the shell's host
says `signed_out` for a suspended account and `no_consent` without the
person's consent.
- The broker sends `peer/input/reject {session_id, peer, host_token,
profile_id, input_id, reason, message?}` on the connection the input
came on, before any `turn/start` with its turn id: a signed-out or
suspended account, the host's refusal, or a busy peer past
`MAX_QUEUED_INPUTS` (8). Queued inputs still start later with their own
turn ids.
- Tests: each reason, the message only with `other`, the connection, no
turn for a refused input, the queue limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…checks are on main [skip ci] architecture.md (+zh): an app peer's tool list is exactly its granted `generic_tools` plus its declared and granted tools (the Terminal's, News's `news.list`/`news.read`); Rinx's grant comes from native-apps.json; the relay checks arguments and results against the declared schemas and budgets each agent; disagreement 4's fail-open note is fixed (G13). ADR 0004 follow-ups: what G3/G8/G13 and `peer/input/reject` closed, and what stays open. ai-services.md: the shell side of #2567. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ADR 0004: apps offer tools end to end (G3), fail closed (G13), relay checks (G8), terminal.run only with a process Terminal (G12), agent questions (G11), Rinx's grant from the manifest,
peer/input/rejectCloses the ADR 0004 gap-review items G3, G13, G8, G12, the shell part of G11, and "the agent grants are generated" (§1). Rebased on
mainafter #151 (toolbox peers), #152/#153/#155 (G1/G2/G4), #154 (makepad 953d9fa3), #156/#157 (G6/G9), #158 (G7) and #159 (G5/G10).Commits
user_question/requestedon an app peer's session and contexts to the host with aQuestionAnswer(sendsuser_question/respondonce, on the link it came on); the app's context only hearsuser_question/handled_by_host, and an app'sContextOp::Approvalfor an id the host holds is refused.crates/shell/src/questions/is a routing layer (request model plus subscribing consumers): the system chat shows the system agent's questions, and the approvals overlay shows the app's. The only answer path isquestions::answerwith aPersonAnswer, which only the shell's surfaces make (a source-scan test checks this). The peer link and theoctoshost service have no answer method. There is nohost.ask.native-apps.json'sagentblock is validated and generated. It holdstools(the app's owntools.jsonentries),generic_tools(the exact kernel tools; octos's shell is refused),grants({app, tool}) andbudget.crates/ai-host/src/native_agents.rsis generated, andPolicy::shipped()reads Rinx's grant from it. The behaviour is unchanged.declare(app, tools)→grant(caller, owning_app, tool)(with toolbox-peers: offer the system toolbox to app agents (ADR 0002 §6, ADR 0004 §12, behind a feature) #151'sset_grantsandofferedadopted) →set_executor(app, …), plusset_generic(app, tools).terminal.run,terminal.read_screenandterminal.read_scrollback, run on its AI bus service.tools.jsonandagent.toolsare loaded with App Hub'sAgentBundle::loadwhen the peer first registers and again at install. Plain names become kernel tools and dotted names become grants marked with their owner; the toolbox grant goes throughtoolbox::grant_manifest.HostServiceExecutorruns a tool as the app's ownhost.request. News offersnews.listandnews.read(apps/news/bundle/tools.json, capabilitynews).generic_toolsand never gets octos's shell.ShellToolHostand relay → News's host service → the model's turn.phone.ymlbuilds the pinned octos (cached) and runs the test.input_schema(capped at 64 KiB) before anything runs, and results againstoutput_schema(capped at 256 KiB). Each calling agent has a budget per turn and per day, set byagent.budgetor the defaults (32 and 1000).terminal.runis registered, and callable by the system agent, only when command execution is granted and the Terminal runs as a process on this device (apps::terminal_runs_as_process, whereprocess-if-vulkanresolves). Setup › Assistant › Command execution otherwise says "needs Terminal as a process on this device".c608384d(octos#2621). Apeer/inputthe host refuses (signed_out,no_consent,busypast 8 queued,otherplus a message) gets apeer/input/rejecton its connection before anyturn/start.Tests
Policy::shippedfrom the manifest, the shipped catalog and grants keyed by owner, the script-app loader and executor, generic tools never including the shell, schemas and budgets, G13 intests/core.rs, the G12 gate and the Setup row, questions routing and "no app-facing path answers", and eachpeer/input/rejectreason.cargo test -p octosense-shell --features app-terminal548 passed; from phone/ with--features mobile-apps, shell 565 and home 183; app-peers (octos-core,ws) lib 21, broker 19, account lifecycle 1; ai-host 29; kernel lib 27 and core 12;python3 -m unittest discover -s tools55.c608384dand were not skipped:crates/kernel6 (+1 ignored),crates/app-peers8, and the new shell testhost_tools::real_kernel_tests(together with the system chat's).Not in this PR
tools.jsonand executor (Rinx#40).files.*.🤖 Generated with Claude Code