Skip to content

L0 in-card chat (sys.chat) host side; AI-written text in the mail card - #263

Merged
ymote merged 4 commits into
mainfrom
feat/l0-chat-host
Oct 2, 2026
Merged

ymote merged 4 commits into
mainfrom
feat/l0-chat-host

Conversation

@ymote

@ymote ymote commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Why

OctoScript#53 lets L0 cards show text the model wrote (marked AI-written) and adds an in-card chat with the app's agent: source convo sys.chat(app, thread, fields) plus ChatEntry. The checker admits the card, but the host decides who may chat, keeps the transcript, writes each role and runs the agent. This PR is that host side. It also moves the email action card MVP off its workarounds.

Depends on OctoScript#53 and Octoscript-Makepad#68, and is stacked on #261 (feat/mail-card-mvp; retarget to main once it merges) (l0_ai_text and the AI-written mark). At the current pins this branch does not build, because l0_eval.rs reads the kit's new ai attribute. Pins are not moved here; see "Pin moves" below.

What

crates/l0-chat (octosense-l0-chat) is the code both host paths use: the shell's glance cards and card window, and AppCard's L0 cards.

  • Ownership.
    • app must be the publishing app. A card naming another app is refused at publish (check_publisher).
    • Such a card reads an unavailable transcript (seed) and writes nothing (perform).
  • The transcript is the host's.
    • seed replaces whatever the publisher put under a sys.chat source's name in data. A forged model entry is never drawn.
    • Entries are {id, role, text, at}. Ids are never reused.
  • Roles.
    • The card's one write, append, is accepted only when its payload is what the person typed (ValueOrigin::UserInput). It is recorded as a user entry.
    • Only ChatStore::append_reply writes a model entry (the agent's answer) or a host entry (a notice). A payload shaped like {"role":"model",…} is just the text of a user entry.
    • Any other op, helper or origin is refused.
  • Limits.
    • At most 4 KB per message after trimming. Control characters are dropped.
    • At most one message per 2 s per thread, and none while the agent is still answering.
    • The last 200 entries are kept.
    • A reply is cut at 16 KB.
  • Storage (ADR 0004 §11). One file per thread, owner-only (0600 files in 0700 directories), in the app's account folder: apps/<app>/accounts/<account>/chat/<thread>.json.
    • The account is the one the app's agent acts for; it falls back to device.
    • Threads are keyed by file, so switching accounts switches transcripts.
    • Without host storage (unit tests), threads are kept in memory.
  • Stale. Each change bumps a generation and wakes the UI. The card window lowers the card again when the reply arrives (L0Session::chat_moved).
  • Responder trait.
    • Shell AgentResponder: a turn in the app's conversation on the host-owned app peer (agents::conversation, octos.turn.start in the person's lane, TurnTrigger::AppSaysPerson).
    • If the app has no agent, or the person has not allowed it, the reply is a host notice saying why.
    • Mail has no agent wiring yet (no octos services, no tools.json). Under OCTOSENSE_GLANCE_DEMO=mail it answers with the existing canned demo reply (Canned).
    • AppCard uses NoAgent, a host notice. Its agent is not wired to answer inside a card yet.

Shell (glance).

  • glance.rs checks the publisher and seeds the host's transcript before lowering.
  • glance_card::L0Session knows its publisher and dispatches with event_payload_origin / dispatch_reporting_with_origin. It performs sys.chat writes through glance_chat.rs.
  • The demo seeds the earlier Q&A as host entries.

AppCard.

  • with_durable no longer treats a writable sys.chat as a user-store collection. It seeds the chat instead.
  • tap_inner routes chat writes.
  • The module claims the storage the shell offers and keeps chats in apps/appcard/accounts/device/chat/.
  • l0_eval.rs reads ai, and l0_widgets::to_dsl runs octoscript_node::ai::expand_ai_marks, so the mark is drawn on the device.

Mail card. mail-request.card now uses:

  • model-copy for the summary, the suggestion and the reply draft. Reply writes the draft into a text state with set(copy.draft).
  • sys.chat + ChatEntry for Ask.

The sys.dataset fields standing in for summary/draft, the sys.news_digest transcript and the sys.topics stand-in are gone. The hand-made "AI" eyebrows are dropped, because the kit's mark replaces them.

Docs.

  • framework/catalog.md is regenerated with gen-catalog.py. It adds ChatEntry and sys.chat, plus the missing sys.digest, sys.dataset, sys.news_digest and sys.news_status.
  • framework/l0.md §4: model-copy is shown and marked, never acted on; new sys.chat section.
  • docs/LEDGER-ARCHITECTURE.md §5.3: the new rule and the host's chat rules.

CI: apps.yml tests and lints octosense-l0-chat.

Tests

All run against OctoScript#53 (3ca709c) and Octoscript-Makepad#68 (51c4c51) checked out in .sources.

  • octosense-l0-chat, 8 tests:
    • ownership
    • role forgery refused (published transcript, role-shaped payload, non-append op, authored/model/no origin, other helper)
    • length, rate, retention and cut limits
    • append → model reply, synchronous and later from another thread
    • no agent → host notice
    • thread from state
    • files kept per app with 0600
  • Shell (--features mobile-apps):
    • the_ai_written_mark_reaches_the_card_window: summary and suggestion are marked, the draft is marked until the person edits it, and in the chat only the agent's entry is marked.
    • the_mail_card_asks_mails_agent_in_the_card: the field commit records the user entry plus the canned model reply, and a second message within 2 s is refused.
    • a_published_transcript_is_not_the_hosts
    • a_mail_toast_opens_its_own_card: also checks that another app cannot publish the card.
    • an_app_without_an_agent_gets_a_host_notice
    • the existing glance tests
  • AppCard: appcards_chat_is_the_hosts_and_its_own, plus the existing 200 octos-app tests.
  • desktop/scripts/mail_card_remote.sh passes in hidden windows, with a scratch home. Ask → ➤ stores apps/os.mail/accounts/device/chat/ana-contract.json.

Pin moves (after OctoScript#53 and Octoscript-Makepad#68 merge)

🤖 Generated with Claude Code

ymote and others added 3 commits October 1, 2026 12:29
…d an in-card chat

OctoScript#53 lets an L0 card show model-written text (marked AI-written)
and chat with its app's agent (`sys.chat` + `ChatEntry`). The host side:

- crates/l0-chat (octosense-l0-chat), shared by the glance cards and
  AppCard: answers sys.chat for the publishing app only; replaces any
  transcript the publisher put in `data`; a card's one write, `append`,
  is accepted only from what the person typed and recorded as a `user`
  entry; only the host appends `model` (the agent's reply) or `host`
  (a notice). Limits: 4 KB per message, one per 2 s per thread and none
  while answering, the last 200 entries, replies cut at 16 KB. Threads
  are files in the app's account folder (ADR 0004 §11:
  apps/<app>/accounts/<account>/chat/<thread>.json, 0600).
- A Responder trait: the shell's AgentResponder runs a turn in the app's
  conversation on its host-owned peer (agents::conversation,
  octos.turn.start); no agent or not allowed is a host notice. Mail has
  no agent wiring yet, so OCTOSENSE_GLANCE_DEMO=mail keeps the canned
  demo answer.
- glance.rs refuses a card whose sys.chat names another app and lowers
  with the host's transcript; L0Session dispatches with the payload's
  origin, performs chat writes, and the card window re-lowers when a
  reply arrives.
- mail-request.card: summary, suggestion and draft are model-copy (Reply
  sets the draft into a text state); Ask is sys.chat + ChatEntry. The
  sys.dataset/sys.news_digest/sys.topics stand-ins are gone.
- The hidden e2e follows the new layout and checks the chat write.

Needs OctoScript#53 and Octoscript-Makepad#68 (pins not moved here).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A writable sys.chat is no longer a user-store collection: with_durable
  seeds AppCard's own transcript (octosense-l0-chat, publisher
  `appcard`) and tap_inner routes chat writes to it (host notice: its
  agent is not wired to answer in a card yet).
- The module claims the storage the shell offers; chats go in
  apps/appcard/accounts/device/chat/.
- l0_eval reads the kit's `ai` stamp and l0_widgets::to_dsl runs
  octoscript_node::ai::expand_ai_marks, so the mark is drawn on device.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…egenerate the catalog

catalog.md regenerated with gen-catalog.py from OctoScript#53's TOML:
ChatEntry and sys.chat, and the missing sys.digest, sys.dataset,
sys.news_digest and sys.news_status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0c (L0 AI text and chat merged)

The two changes this pull request builds against are on their mains:
OctoSense-org/OctoScript#53 (5991dfae, model-written text in text slots,
strict actions, in-card chat) and OctoSense-org/OctoScript-Makepad#68
(8f103d0c, the l0_ai_text kit stamp and the AI-written mark; its
runtime.json pins Octoscript 5991dfae and Makepad e29a0eaa, unchanged).
native-runtime.lock.json (here and AppCard's) and the root manifest's
Octoscript and Octoscript-Makepad revs move to them; the crates resolve
from .sources through [patch], so Cargo.lock does not change.

Checked: python3 tools/setup.py --update, then --check --cargo; python3
tools/native_apps.py --check; cargo check -p octosense; cargo test -p
octosense-shell --features app-hub --lib (715 passed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymote
ymote changed the base branch from feat/mail-card-mvp to main October 2, 2026 01:08
@ymote
ymote marked this pull request as ready for review October 2, 2026 01:08
@ymote
ymote merged commit a0237ce into main Oct 2, 2026
7 checks passed
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