Skip to content

An agent for every app that declares one; shell-drawn "Ask <app>" panel (ADR 0004 §4) - #184

Merged
ymote merged 1 commit into
mainfrom
feat/agents-for-apps
Sep 30, 2026
Merged

ymote merged 1 commit into
mainfrom
feat/agents-for-apps

Conversation

@ymote

@ymote ymote commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

ADR 0004 §4 promises an agent for every app. A live run on 625d6cc found:

  • News ships tools.json (G3) but declares no octos.* and never calls octos, so it had no peer;
  • the system agent's peer_list was empty and News's tools never registered;
  • Settings › Assistant listed only Rinx;
  • the person had nowhere to talk to an app's agent.

Builds on #183 (merged).

Which apps have an agent

This comes from manifests and grants, never names (apps::agent_apps, crates/shell/src/agents.rs):

  • a native app granted agent.octos;
  • a script app whose manifest declares octos.* or an agent block;
  • a script app whose admitted bundle ships tools.json (App Hub's AgentBundle::load).

Settings lists every one of them.

Peers once allowed

  • Consent works as before (ADR 0004 §8/§4: approval router, standing rules, shell sheets, first-use consent #120 first use, shell, ai-host: consent turns script-app agents on and turning an agent off revokes it live (G10, G5) [skip ci] #159 revocation). The first use can come from the "Ask " panel, from the app's own octos call, or from the system agent's agents.ask, which shows the first-use sheet over the system chat.
  • When the person allows an agent, and at startup for agents already allowed, the shell prepares its peer: contained::prepare → OctosAppService::prepare (new; Broker::bind), i.e. peer/prepare with the tools and generic_tools registered. The system agent's peer_list then shows it, and peer_send_input reaches it.
  • Each app has one peer (card.<app id>). The app, its cards, the panel and the system agent all share it.
  • The peer is launched with every octos.* service for the shell's panel. The app's own context is still opened with only what its manifest declares, and ContainedOctos still checks declared first.
  • The system agent hears about agents it cannot list:
    • a note in front of the system chat's turn whenever the apps' agents changed (the pane and history hide it);
    • host tools agents.list and agents.ask on the system session, answered by the shell.
  • Revoking releases the peer and closes the panel's context. The shell refuses peer/input to it, as before.

"Ask "

This is the system chat's pane in app mode (app_chat/, system_chat/view.rs app_panel: true):

You can open it from:

  • the bar's "Ask " beside the focused window's title;
  • Shift+F8;
  • Setup › Assistant › Ask this app's agent.

On desktop it sits beside the system chat, so both lanes are visible; on phone it is a full-screen sheet.

Tests

  • news_is_listed_as_having_an_agent, allowing_an_agent_queues_its_preparation.
  • both_lanes_show_with_their_speakers_and_run_in_parallel, the_merged_history_names_its_speakers.
  • the_panel_opens_a_sharing_context_and_sends_person_turns_there: consent first, then open_conversation (never a plain context), history, a TurnFrom { Person }, preparation, and revocation. Runs on fake peers.
  • the_shell_prepares_a_consented_apps_peer_and_its_panel_shares_it (ai-host).
  • real_kernel_an_allowed_apps_agent_is_prepared_listed_and_shares_its_conversation (pinned kernel, scripted model):
    • nothing is prepared before consent, then the peer is prepared once allowed;
    • the system agent's peer_list shows os-news-…;
    • the panel's person turn runs in the person's lane;
    • the system agent's peer/input turn sees it through the shared history;
    • the peer's turn is offered news_list/news_read;
    • revocation releases the peer.
  • the_system_agent_is_told_about_agents_it_cannot_list.

Local checks: shell lib tests (desktop 568, phone 588, with the real kernel), app-peers (incl. real kernel), ai-host (with and without octos-core,llm), kernel, clippy for app-peers and ai-host, cargo check for desktop and phone, native_apps.py --check, setup.py --check --cargo. I also ran a hidden-window desktop pass against a scripted model: the bar showed "Ask News", the first-use sheet appeared, the panel reached "ready", it answered with speakers, and it sat side by side with the system chat.

Known limits (also in ADR 0004's follow-ups)

  • A native app's peer exists only while the app is open (Rinx needs its signed-in account).
  • A script app's person turns carry its app id as the kernel's speaker label (the peer name is kept for resume). The panel shows "You".
  • Prepared peers hold a kernel connection, so the kernel's idle stop waits for them.
  • A script app's agent cannot ask the person questions yet. App Hub offers a contained app's agent no octos kernel tool (HostLimits::offered_tools), so an admitted manifest cannot list ask_user_question in agent.tools (found by test: the two-lane scenario against the real kernel (and two fixes it found) #182). The panel shows and answers the app's questions once it can. The fix belongs in App Hub (offer ask_user_question to contained agents), not in a shell-side exception.

🤖 Generated with Claude Code

@ymote
ymote force-pushed the feat/agents-for-apps branch from f39f6f0 to a189b7c Compare September 30, 2026 02:20
ADR 0004 section 4. A live run found News (tools.json, no octos.*) with
no peer: the system agent's peer_list was empty, News's tools never
registered, Settings listed only Rinx, and the person had nowhere to
talk to an app's agent.

- Which apps: a native app granted agent.octos; a script app whose
  manifest declares octos.* or an agent block, or whose admitted bundle
  ships tools.json (App Hub's AgentBundle::load). Settings lists all.
- Peers once allowed: when the person allows an agent (first-use sheet,
  Settings) and at startup, the shell prepares its peer
  (contained::prepare -> OctosAppService::prepare -> peer/prepare, tools
  registered), so peer_list shows it. One peer per app, shared by the
  app, its cards, the panel and the system agent; the app's own context
  still holds only the octos.* services its manifest declares.
- The system agent is told about agents it cannot list: a note with the
  system chat's turn when the apps' agents changed, and host tools
  agents.list / agents.ask (the latter shows the first-use sheet).
- "Ask <app>": the system chat's pane in app mode (app_chat/): a sharing
  context via open_conversation, both lanes merged with speakers, a
  composer, Stop (both lanes), the app's questions; from the bar's
  "Ask <app>", Shift+F8 or Setup > Assistant; beside the system chat on
  a desktop, full screen on a phone.
- Revocation releases the peer and closes the panel's context.

Tests: News is listed; the panel opens a sharing context and sends
person turns there; allowed agents are prepared (fake peer); and on a
real kernel: prepared with News's tools, listed by the system agent's
peer_list, the panel's person turn seen by the system agent's peer/input
turn, revoked. Docs: ADR 0004 section 4 and a follow-up; architecture.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ymote
ymote force-pushed the feat/agents-for-apps branch from a189b7c to 9a59d7d Compare September 30, 2026 03:07
@ymote
ymote merged commit 3493290 into main Sep 30, 2026
5 checks passed
guofoo added a commit that referenced this pull request Oct 2, 2026
docs/ai-services.md and its Chinese twin still described the contained
apps' octos service as it was before #184:

- the gate is off by default and needs OCTOSENSE_CONTAINED_APPS=1; the
  code's default is first-use consent (contained_gate_from), with 1 the
  developer override that asks nobody and 0 off;
- a contained peer's tool approvals are declined; the broker hands them
  to the shell's approval router, and only a host without one declines;
- octos.turn.start takes only text; it also takes trigger and from;
- nothing in the shells runs an app's agent; since #184 an app that
  declares one, declares octos.*, or ships tools.json gets a prepared
  peer with its tools and the "Ask <app>" panel;
- the News bundle does not declare `news` yet; it does, and reads from
  it when host.has("news").
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Oct 8, 2026
…y test

Four follow-ups from the live run of "Ask <app>" (OctoSense-org#184).

1. Order across lanes. Each lane counts its own cursor.seq (the peer's
   session is long, the person's context new), so sorting both by seq
   drew the system agent's request below the person's later question.
   The kernel also sends a turn's user message only when the turn ENDS,
   and never for a stopped turn, so the request line sat after its answer
   and a stopped request never showed. Now: within a lane, ledger order;
   across lanes, one clock (the order events reached the panel: stream
   events carry no time; history comes merged by persisted_at). A turn's
   request line stands where the turn started: the broker records the
   words it sent (the person's message, the system agent's peer/input)
   and hands them over with turn/started (`request`), and the
   conversation's history adds a stopped turn's request the kernel never
   recorded, at the time it started.
2. Stop said "turn interrupted by client" twice: once from the turn's
   end, once from the send's error. The send's error is shown only when
   the turn never started.
3. The system agent tried the app id (os.news) with peer_send_input. The
   note and agents.list now give the peer slug (os-news-22a12f90;
   OctosAppService::peer_slug) and say to use it, or to find it with
   peer_list, never the app id.
4. the_person_and_the_system_agent_talk_in_parallel_lanes_that_share_history
   failed under load with turn_in_progress: the test drives the system
   session itself and started its second turn while the first was still
   finishing (after peer_send_input the system agent still writes its own
   answer). Not a broker race: the test now waits for its session, as the
   system chat does. Reproduced by slowing that answer in the scripted
   model; 20 runs at --test-threads=4 pass.

Tests: two lanes replayed with interleaved arrival (out-of-order
envelopes, user messages at the end, a stopped turn); the panel's Stop
shows one notice; the broker hands a turn's words over at its start and
keeps a stopped turn's request in history; the note and agents.list carry
the slug.

Co-Authored-By: Claude Opus 5.5 (1M context) <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