Repository navigation
ADR 0004 §8/§4: approval router, standing rules, shell sheets, first-use consent - #120
Merged
Merged
Conversation
…nt [skip ci] ADR 0004 §8/§4 (plan step 7), in new modules under crates/shell/src/approvals/: - router: developer mode first (adapter, stubbed until #118), then the confirm: app hand-off to the owning app's sheet (wait with a timeout or refuse visibly), auto_approvable:false / outcome_unknown / external calls always to the person, standing rules (incoming content excluded by default), else a sheet; every decision audited, every automatic one notified - rules keyed on (owning app, tool) with contacts / thread / attachments / person-triggered / amount / count conditions, a daily cap, a time-boxed "everything" (max 60 min) with an indicator, one-tap all off; created only with a PersonGesture from Settings or a sheet; persisted per home - sheet model and shell-drawn sheets (one per request, batched in the system chat), args pretty-printed with secrets redacted, approve once / deny / always for ... - Settings > Assistant > Approvals page; first-use consent with consent::granted(app) - relay seam: ApprovalIntake::approval_requested in, ApprovalRelay:: approval_decided out, with a recording test double - append-only owner-only audit (args digest only) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…onsent [skip ci] - dev_hooks: ShellDevMode forwards to dev_mode (answers_approval as HostConfirm, overrides_app_confirm, approves_command, grants_all) and writes developer mode's audit for each automatic approval it gives - ai_bus: confirm:host tools (the Terminal's run) are no longer parked on the pane's card; each call is held (Route::Approval) until the router answers, then released to the app or refused to the pane. The card's argument-length refusal is gone: the shell's sheet shows the arguments in full - consent at first use gates the assistant offer in module_host and the contained apps' octos service (ai_host::contained::set_consent) - Setup > Assistant > Approvals opens the Approvals page - the gesture is ApprovalGesture, so dev_mode's setter scan stays exact Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ymote
force-pushed
the
adr0004/approvals
branch
from
September 28, 2026 23:05
2028151 to
226f3a7
Compare
ymote
added a commit
that referenced
this pull request
Sep 29, 2026
… [skip ci]
- approvals/contacts.rs: ContactsSource (is_known for email addresses and
Matrix IDs), ContactList/NoContacts test doubles, MailContacts reading
Mail's host service data, and ContactsGate: off by default, on only by
the person's Settings tap ("Use my contacts in approval rules"), kept
owner-only in approvals/contacts.json. The router evaluates "recipients
in contacts" through the gate.
- mail host service: contacts::known_addresses (own accounts, recorded
send recipients, To/Cc of sent folders; never the secret store); the
decoder keeps To/Cc; a removed account forgets its recipients.
- docs/ai-services(.zh-CN).md refreshed against main (#106, #113, #117,
#118, #120, #124, #95, #86), linking architecture.md; architecture's
contacts lines updated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ymote
added a commit
that referenced
this pull request
Sep 29, 2026
ADR 0004 §5. peer_link/ gives a process app its own channel to its octos
app agent on its hub socket ("octos_peer" frames, never the AI bus):
requests on its one peer through the app-peers service in-process
modules get (an adapter until octos#2567's relay), and the tool-call
half behind seams for the relay (set_tool_relay, tool_call, tool_cancel,
context_owner, has_link, close_account).
Identity is the socket's slot; a call's account, context and client come
from the shell's own context records. Links open only for apps whose
native-apps.json entry grants agent.octos, and requests need consent.
The shell keeps the host obligations (once per call, nothing after
cancel, acknowledge before any sheet; confirmations through the #120
router, whose decisions get their own peerlink: route), and a dead
process fails its calls (outcome_unknown unless read-only), closes its
contexts and keeps the peer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ymote
added a commit
that referenced
this pull request
Sep 29, 2026
ADR 0004 §5. peer_link/ gives a process app its own channel to its octos
app agent on its hub socket ("octos_peer" frames, never the AI bus):
requests on its one peer through the app-peers service in-process
modules get (an adapter until octos#2567's relay), and the tool-call
half behind seams for the relay (set_tool_relay, tool_call, tool_cancel,
context_owner, has_link, close_account).
Identity is the socket's slot; a call's account, context and client come
from the shell's own context records. Links open only for apps whose
native-apps.json entry grants agent.octos, and requests need consent.
The shell keeps the host obligations (once per call, nothing after
cancel, acknowledge before any sheet; confirmations through the #120
router, whose decisions get their own peerlink: route), and a dead
process fails its calls (outcome_unknown unless read-only), closes its
contexts and keeps the peer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced 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>
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 plan step 7 (§8 approvals, §4 consent): the shell's approval surface. The new code lives in
crates/shell/src/approvals/. It is wired into #118's developer mode, the AI bus'sconfirm: hostpath, the module host's offer and the contained apps'octosservice.What's built
The router (
router.rs) takes each request on its exact arguments and checks, in order:dev_hooks.rs:answers_approval,overrides_app_confirm,approves_command,grants_all). It approvesauto_approvable: falsetoo, but never for an external client;confirm: apptools: handed to the owning app's registered sheet (AppConfirm), with the caller and context. If the app isn't running, the request waitsapp_wait_s(120 s), then is refused visibly;auto_approvable: false,outcome_unknownand external calls always go to the person;Batch. An unanswered sheet line expires and is declined.Every decision goes to the relay once and to the audit. Every automatic one is also a notification.
Relay seam (
relay.rs):ApprovalIntake::approval_requested(app, tool: ToolSpec, args, caller, context);ApprovalRelay::approval_decided(id, Decision::{ApproveOnce | Deny | ApproveByRule(rule_id)}, reason);approvals::set_relayinstalls the relay and hands over decisions made before it;RecordingRelayis the test double.Standing rules (
rules.rs):PersonGesture, which only the sheet and the Settings page construct (a test scans the sources);<home>/approvals/rules.json, 0600.Audit (
audit.rs):<home>/logs/approvals-audit.jsonl, append-only and 0600. Each entry has the rule, app, tool, a SHA-256 digest of the arguments (never the arguments), caller, trigger, who answered, time and result.Sheets (
sheet.rsmodel,view.rsview): drawn in the shell's chrome kit, like the menu, notifications and dev banner. Each line shows the owning app and tool, "Asked by …", the pretty-printed arguments with secrets redacted (by schemasecret_fieldsand by name), and why it needs the person. Answers per line: Approve once, Deny, or up to two "Always …" choices (contacts, this thread, when I start it, 1 hour, everything for 60 min). Choosing one creates the rule and answers the other open lines that now match. An agent's text is never an approval surface.Settings → Assistant → Approvals (
settings_page.rs):First-use consent (
consent.rs): the first time an app asks, a sheet shows what the agent may read and use (from the manifest and grants) and where the model runs. Allow or Don't allow is remembered in<home>/approvals/consent.json.approvals::consent::granted(app)is there for #106's contained apps and the Rinx/native offer path. Developer mode skips the prompt and stores nothing.Tests
cargo test -p octosense-shell --lib: 432 pass (443 withmobile-apps), 35 of them inapprovals, plus the reworkedai_busTerminal test (held, released, refused, dropped on death) and a menu test.octosense-ai-hosttests also pass. Theapprovalstests cover:auto_approvable: falseandconfirm: app; never for an external client;confirm: apphand-off, wait, timeout and unregister;MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE, throwawayOCTOSENSE_HOME, ended with/gq):--test-action approval-sheet: the sheet drew, withsmtp_passwordredacted. Approve once closed it and wrote aperson/approvedaudit line (0600);r1;r1. The Settings page listed the rule ("2 of 20 today"), News's agent (Allowed) and the log line;cargo checkdesktop and phone (mobile-apps),tools/check-shell-graph.shfor both, andtools/setup.py --check --cargoall pass.Wired in (after #118)
dev_hooks::ShellDevModeforwards to:dev_mode::answers_approval(asHostConfirm),overrides_app_confirm,approves_commandandgrants_all;dev_mode::audit_auto_approval, which writes each approval it gives to developer mode's own audit.confirm: hostpath (host_confirmed/apply_rules, e.g. the Terminal'srun): those calls now go through the router, with nothing duplicated.Route::Approval) until the router answers, thenAiBus::releasesends it on or refuses it to the pane.runis a command andauto_approvable: false, so developer mode approves it throughapproves_commandand every other run asks the person on the sheet.module_host: the first time, the sheet asks and that instance goes without; once allowed, the next instance gets its agent;octosservice (ai_host::contained::set_consent, installed byapprovals::init);Remaining
approval_requested/set_relay. The app-peers broker's octos approvals get to the router once that relay exists.AppConfirmyet (the module contract can't carry a host confirmation).pane_links.rs) applies no host rules, before or after this PR.NoContacts). Rule fact fields use conventional argument names untiltools.jsonfields are agreed.🤖 Generated with Claude Code