Files
iris_x_hermes/docs/06-channels-cron-search.md
T
ARIA 59acf66c89 M0: toolchain, monorepo scaffold, gateway plugin skeleton, CMP app
- gateway-plugin/: android platform plugin (plugin.yaml + adapter.py
  register(ctx) + no-op AndroidAdapter) + stub modules for M1-M5
- app/: Compose Multiplatform project (shared KMP + androidApp +
  desktopApp) with Gradle wrapper; builds :androidApp:assembleDebug
  and :desktopApp:compileKotlin
- scripts/guard_hermes_agent.sh + pre-commit hook: fail if hermes-agent/
  is staged (read-only reference, never committed)
- .gitignore excludes hermes-agent/; docs/ reference library
2026-08-19 11:27:02 +02:00

5.5 KiB

06 — Channels, Threads, Cron Delivery, Search

6.1 Concept model

The hermes gateway already models conversations as SessionSource with chat_id + thread_id + chat_topic (gateway/platforms/base.py:7047 build_source). We map app concepts onto these existing primitives — no new gateway identity concepts.

App concept hermes primitive Example
Default chat home channel chat_id android:default
A thread (inside default chat) thread_id under the default chat_id chat_id=android:default, thread_id=t_12
A user-created channel a new chat_id android:chan_7
A thread inside a channel thread_id under that chat_id chat_id=android:chan_7, thread_id=t_31
  • chat_id = the conversation lane (a channel or the default chat).
  • thread_id = an optional sub-lane within a chat_id (topic-like).
  • The channel directory (plugin SQLite, channels.db) stores {chat_id, name, kind: default|channel|thread, parent_chat_id, is_default, created} and is the source of truth for the app's channel list.

6.2 Default chat

  • On first connect, the plugin ensures a default channel exists: chat_id = ANDROID_HOME_CHANNEL (default android:default), kind=default, is_default=true, name "Default".
  • It is also the cron home channel (cron_deliver_env_var= ANDROID_HOME_CHANNEL), so deliver=android (bare) routes here.
  • The app opens the default chat on launch.

6.3 Threads (toggle for overview)

  • Requirement: a default chat where the user can activate threads for a better overview, or not.
  • Thread toggle (per default chat, in the chat header menu):
    • Threads OFF — flat conversation; all messages use thread_id=null.
    • Threads ON — the app groups the conversation into topic-like lanes. Each new "topic" mints a thread_id (via channel.create {kind:thread, parent_chat_id:android:default} or an implicit thread). The UI shows a topic switcher (like Telegram topics) above the message list.
  • Threads are app-organized but gateway-real: each thread_id is a distinct hermes session lane, so context is isolated per thread and cron can target a specific thread.
  • The gateway's create_handoff_thread is used where hermes wants to open a named thread (e.g. continuable cron).

6.4 User-created channels (for cron delegation)

  • Requirement: the user creates new channels so cron job outputs can be delegated to them instead of the default chat.
  • channel.create {name, kind:"channel"} → plugin mints chat_id = android:chan_<n>, stores in directory, broadcasts channel.created to all devices. The new channel appears in the channel list.
  • channel.rename / channel.set_default / channel.delete manage the directory (rename broadcasts channel.renamed; delete is soft — marks archived, keeps history for search).
  • Cron targeting (the key payoff): because the plugin registers parse_target_ref_fn and cron_deliver_env_var, cron jobs and the send_message tool can target any channel/thread:
    • deliver="android" → home (default) channel.
    • deliver="android:android:chan_7" → that channel.
    • deliver="android:android:chan_7:t_31" → that channel's thread.
    • In-chat: the agent's cronjob tool can be told "deliver to the Cron Reports channel"; the gateway resolves the name via the channel directory.
  • In-app affordance: each channel's menu has "Set as cron target" / shows a badge when a cron job points at it, and the channel name is offered in the /cron creation flow.

6.5 Cron delivery mechanics (how it works under the hood)

  • Cron resolves delivery targets in cron/scheduler.py:2148 (_resolve_single_delivery_target). For platform:chat_id[:thread_id] it calls tools.send_message_tool.resolve_send_target, which uses our parse_target_ref_fn to parse android:<chat>[:<thread>].
  • Delivery then calls the live adapter's send(chat_id, text, …) (gateway running) → our WS message frame (or outbox+push if the app is offline).
  • Cron deliveries are framed with a [Cron delivery: <name>] header by hermes; the app can style cron messages distinctly (e.g. a small "⏰ " chip) using the role:"cron" / header.
  • Mirror option: hermes cron.mirror_delivery (default off) can also mirror a cron delivery into the origin session; we leave it off to keep channels clean.
  • Requirement: search with settings "search everywhere" / "search in this chat/channel".
  • Backend: hermes's session store is SQLite + FTS5 (hermes_state.py, hermes_state_search.py). The plugin's search.py opens the session DB read-only and runs FTS5 queries.
  • search frame → search.results:
    • scope:"all" — search everywhere (all channels/threads/sessions).
    • scope:"chat" — restrict to the given chat_id (and optional thread_id).
  • Result hit: {message_id, chat_id, thread_id, role, snippet, ts}. The app renders a results list; tapping a hit navigates to that channel/thread and scrolls to + highlights the message.
  • Query syntax: plain text (FTS5). Optional role: / channel: qualifiers are a nice-to-have; v1 is plain-text + scope.
  • Privacy: search is local to the user's own hermes home; no data leaves the machine.

6.7 Channel list frame

hello.ack and channel.* frames carry the directory. App keeps a local copy (Room) and reconciles on channel.* events (merge, don't clobber — see 10-android-app.md state rules).