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
This commit is contained in:
commit
59acf66c89
49 files changed
+3950
No files matched your search
@@ -0,0 +1,107 @@
|
||||
# 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 "⏰ <job name>"
|
||||
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.
|
||||
|
||||
## 6.6 Search
|
||||
|
||||
- **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).
|
||||
Reference in new issue
Block a user