mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 21:44:38 +00:00
216 lines
13 KiB
Markdown
216 lines
13 KiB
Markdown
# TinyChannels Integration Plan
|
||
|
||
Plan for adopting the `tinychannels` crate
|
||
(`~/work/tinyhumansai/tinychannels`) as the typed channel layer, deleting the
|
||
duplicated copies in this repo, and fixing the channel bugs surfaced by the
|
||
2026-07-04 cross-repo audit.
|
||
|
||
Companion docs in the tinychannels repo:
|
||
|
||
- `docs/spec/openclaw-hermes-channel-porting.md` — the upstream research spec.
|
||
- `docs/spec/tinychannels-execution-plan.md` — the crate-side phased plan.
|
||
Steps below reference its phases.
|
||
|
||
## Current State
|
||
|
||
- This repo now depends on `tinychannels` through the TinyChannels
|
||
`feat/phase-0-hygiene` branch while the companion PR lands. The first
|
||
duplicate-removal pass has landed:
|
||
|
||
| openhuman-4 file | tinychannels file |
|
||
| --- | --- |
|
||
| `src/openhuman/channels/traits.rs` | re-exports `src/traits.rs` |
|
||
| `src/openhuman/channels/controllers/definitions.rs` | re-exports `src/controllers/definitions.rs` |
|
||
| `src/openhuman/channels/controllers/schemas.rs` schema declarations | converts from `src/controllers/schemas.rs` |
|
||
| `src/openhuman/channels/controllers/ops/connect.rs` allowlist/key helpers | calls `src/controllers/credentials.rs` |
|
||
| `src/openhuman/channels/controllers/ops/types.rs` | re-exports `src/controllers/types.rs` |
|
||
| `src/openhuman/config/schema/channels.rs` | re-exports provider config from `src/config.rs`, while keeping OpenHuman-owned security/sandbox config local |
|
||
| `src/openhuman/channels/context.rs` key/constants helpers | calls/re-exports `src/context.rs` where type-compatible |
|
||
| `src/openhuman/channels/runtime/supervision.rs` in-flight sizing | re-exports `src/runtime.rs::compute_max_in_flight_messages` |
|
||
| Telegram/Discord text splitting | calls `src/text.rs` chunker with UTF-16 measurement |
|
||
|
||
- `OpenHumanChannelBackend` now lives in
|
||
`src/openhuman/channels/controllers/backend.rs` and implements
|
||
`tinychannels::ChannelBackend` by delegating to the existing
|
||
`channels/controllers/ops/{connect,messaging,discord,telegram}.rs` flows.
|
||
- The crate-side Phase 5 relay contract now includes typed gateway/connector
|
||
relay frames, the request/response frame transport loop, a feature-gated
|
||
WebSocket dialer with reconnect supervision, and a portable relay runtime
|
||
config shape under `channels_config.relay`. OpenHuman now enables the
|
||
crate's relay WebSocket feature and starts the relay runtime for complete
|
||
relay config.
|
||
- The metadata controllers (`channels.list` / `channels.describe`) now use
|
||
`ChannelManager` for definition lookup. The `channels.status` and
|
||
`channels.test` controllers now route through
|
||
`ChannelManager<OpenHumanChannelBackend>`, preserving the existing no-log
|
||
JSON response shapes while using the crate manager for definition lookup and
|
||
credential validation where applicable. `channels.get_default` also routes
|
||
through the manager while preserving the public `{ active_channel }` response,
|
||
`channels.connect` and `channels.set_default` preserve their public log/value
|
||
envelopes after manager dispatch, and the managed Telegram/Discord link
|
||
controllers route through the manager while preserving their no-log typed
|
||
responses. The no-log send/reaction/thread controllers now dispatch through
|
||
the manager and unwrap the backend `raw` payload to preserve legacy top-level
|
||
JSON shapes; `channels.send_message`, direct channel-bus backend sends, and
|
||
legacy runtime/proactive/provider-helper `Channel::send` callers now cross
|
||
the crate intent bridge as `ChannelOutboundIntent`s and get deterministic
|
||
idempotency keys before legacy backend/provider calls.
|
||
`channels.disconnect` also routes through the manager while preserving the old
|
||
log string and extra fields such as
|
||
`memory_chunks_deleted`. Discord guild/channel/permission discovery dispatches
|
||
through the manager while restoring the old log strings and top-level provider
|
||
JSON. Outbound sends now use the live relay transport for configured relay
|
||
identities. The remaining OpenHuman work is still full session identity
|
||
switchover, provider extraction, and the planned test split below; the first
|
||
envelope slice is landed because the runtime now publishes a TinyChannels
|
||
`ChannelInboundEnvelope` on `ChannelMessageReceived` events, memory
|
||
conversation persistence records the TinyChannels session key as migration
|
||
metadata, and startup now has a relay inbound handler that preserves the
|
||
original authenticated relay envelope, including `scope_id`, from the live
|
||
relay socket through dispatch.
|
||
|
||
## Step 1 — Add the dependency, delete duplicates
|
||
|
||
- **Landed:** Add the `tinychannels` git dependency to `Cargo.toml` (single
|
||
crate, not a workspace; edition 2021 depending on an edition-2024 crate is
|
||
fine).
|
||
- **Landed for the first slice:** Delete the duplicated
|
||
definitions/types/traits/config-schema/helper code and
|
||
re-export from the old paths so the 100+ call sites keep compiling:
|
||
- `src/openhuman/channels/mod.rs`: `pub use tinychannels::{Channel,
|
||
ChannelMessage, SendMessage, ...};`
|
||
- `src/openhuman/channels/controllers/...`: re-export `ChannelDefinition`,
|
||
`ChannelAuthMode`, response types.
|
||
- `src/openhuman/config/schema/channels.rs`: re-export `ChannelsConfig` and
|
||
provider config structs. Note tinychannels inlined `EmailConfig` and
|
||
`YuanbaoConfig`, which this repo currently sources from
|
||
`channels::email_channel` / `providers::yuanbao` — repoint those two to
|
||
the crate and delete the local definitions.
|
||
- Still pending: delete migrated in-module tests once each suite is fully
|
||
represented in tinychannels. The duplicate definitions, config-schema, and
|
||
in-flight sizing helper suites have been deleted; keep everything provider-
|
||
or app-specific (see Step 4).
|
||
- Do not remove sandbox/security config from this repo based on the crate's
|
||
copy: the crate is dropping its unwired `SecurityConfig`/`SandboxConfig`
|
||
cluster as scope creep; this repo's originals (if used) stay where they are.
|
||
|
||
## Step 2 — Implement `ChannelBackend`
|
||
|
||
- **Landed:** New `OpenHumanChannelBackend` in
|
||
`src/openhuman/channels/controllers/backend.rs`, delegating each trait
|
||
method to the existing ops functions:
|
||
- `send_message` → `messaging.rs::channel_send_message` (already composes
|
||
`effective_backend_api_url` + `jwt::get_session_token` +
|
||
`BackendOAuthClient`), with `send_message_value` preserving OpenHuman's
|
||
arbitrary rich-message JSON for the public RPC controller.
|
||
- `connect_channel` / `disconnect_channel` → `connect.rs` flows plus
|
||
`credentials::ops::{store,remove}_provider_credentials` (keyed
|
||
`"channel:<slug>"`), with disconnect returning the raw legacy payload for
|
||
fields such as `memory_chunks_deleted`.
|
||
- `channel_status` / `test_channel` → existing status/test ops plus
|
||
`health::snapshot`.
|
||
- Telegram login and Discord link/guild/permission methods → the existing
|
||
`telegram.rs` / `discord.rs` ops.
|
||
- **Landed:** The current `channels.*` controller entry points dispatch through
|
||
`ChannelManager<OpenHumanChannelBackend>` where they cross this crate seam,
|
||
while preserving the legacy public JSON/log envelopes.
|
||
- The event bus, health bus, and dispatch engine stay app-side and *drive*
|
||
tinychannels. Never add a tinychannels → openhuman dependency; the
|
||
`runtime/` dispatch engine and the `web` provider are consumers of the
|
||
crate, not porting candidates (they import ~45 openhuman modules).
|
||
|
||
## Step 3 — Bug fixes coordinated with the crate
|
||
|
||
Fix these here in lockstep with tinychannels Phase 1/2 (same semantics, one
|
||
implementation — prefer calling the crate's new helpers over patching local
|
||
copies):
|
||
|
||
1. **Landed: UTF-16 chunking.** Telegram and Discord splitting now call the
|
||
crate's Phase 2 chunker with UTF-16 code-unit measurement and markdown fence
|
||
preservation.
|
||
2. **Landed for existing helper semantics: Telegram history keys.**
|
||
`channels/context.rs` now delegates to the crate helper, preserving
|
||
OpenHuman's current Telegram topic behavior. Runtime received-message events
|
||
now also carry the crate's normalized inbound envelope, with Telegram
|
||
`thread_ts` projected as `topic_id`; conversation persistence records the
|
||
TinyChannels session key when an event carries an envelope. Relay inbound now
|
||
preserves the relay-supplied envelope and `scope_id` through dispatch. Full
|
||
non-relay session identity switchover still needs provider-sourced
|
||
`scope_id`.
|
||
3. **Partially landed for relay inbound; non-relay providers still deferred:
|
||
workspace/tenant discriminator.** Session keys
|
||
(`channels/bus.rs:1005-1042`) omit guild/team/tenant; Slack channel ids are
|
||
only workspace-unique. Relay inbound carries the relay envelope's `scope_id`
|
||
through received-message events; provider-originated legacy messages still
|
||
need native envelope construction with provider-sourced scope. Deferral
|
||
reason: adding scope to the current legacy string keys would change
|
||
conversation identity without the envelope migration that can preserve
|
||
compatibility aliases.
|
||
4. **Landed for portable send boundaries; provider wire enforcement remains
|
||
platform-specific.**
|
||
No idempotency key exists on legacy sends; a retry after a transport error
|
||
double-posts. `channels.send_message` now routes through a crate
|
||
`ChannelOutboundIntent` at the `ChannelBackend` seam while preserving the
|
||
legacy public RPC response and backend JSON shape. The direct channel bus
|
||
backend sends and runtime/proactive/provider-helper `Channel::send` paths use
|
||
the same crate bridge, carrying deterministic idempotency keys through
|
||
`SendMessage`. Provider-specific use of those keys on external wire APIs
|
||
remains Phase 6/platform work.
|
||
5. **Deferred pending product decision: `conversation_memory_key` intent
|
||
check.** It keys on `msg.id`
|
||
(per-message, not per-conversation) and this repo's tests assert that
|
||
behavior (`tests/memory.rs`). Confirm whether per-turn keying is intended;
|
||
the crate will rename or fix accordingly — this repo should follow.
|
||
|
||
## Step 4 — Test split
|
||
|
||
- **Partly landed:** the portable `controllers/schemas_tests.rs` schema-catalog
|
||
assertions, pure allowlist/key helper tests, catalog/request-shape halves of
|
||
`controllers/ops_tests.rs`, backend-agnostic default-channel validation, and
|
||
config-backed channel status detection now live in tinychannels. The
|
||
duplicate definitions suite, config-schema assertions, in-flight sizing
|
||
helper assertions, and portable schema-catalog assertions were deleted here.
|
||
This repo keeps handler parity, adapter conversion, params, legacy envelope
|
||
helper tests, local security/sandbox config defaults, supervisor
|
||
classification tests, and app-side persistence/REST wiring tests.
|
||
- **Still migrate to tinychannels:** any remaining backend-agnostic assertions
|
||
from `controllers/ops_tests.rs`, rewritten against a mock `ChannelBackend`,
|
||
plus the already-mirrored suites listed in Step 1.
|
||
- **Stay here:** all `providers/*_tests.rs` (telegram 148, web 93, imessage
|
||
58, discord 55+23, email 50, lark 42, whatsapp 41+29, irc 37, mattermost
|
||
33, signal 32, presentation 29, linq 23, qq 11), `bus_tests.rs`,
|
||
`routes_tests.rs`, `runtime/dispatch_tests.rs`, `runtime/startup_tests.rs`,
|
||
and the harness-driven `tests/` integration files (prompt, telegram/discord
|
||
integration, runtime tool calls, health, identity).
|
||
- The REST-wiring halves of `ops_tests.rs` become the test bed for
|
||
`OpenHumanChannelBackend` (Step 2).
|
||
|
||
## Step 5 — Provider extraction ladder (later, tracks crate Phase 6)
|
||
|
||
Move provider wire code into tinychannels only when its dependencies reduce to
|
||
crate traits:
|
||
|
||
1. **Now portable (no cross-module imports):** email, irc, yuanbao, cli,
|
||
imessage, mattermost, qq, dingtalk, presentation.
|
||
2. **After a configured-HTTP-client trait** (replaces
|
||
`config::build_runtime_proxy_client` / `apply_runtime_proxy_to_builder`):
|
||
discord, slack, whatsapp, lark, signal.
|
||
3. **After approval, voice/STT, pairing, and conversation-memory traits:**
|
||
telegram (imports `approval`, `voice::create_stt_provider`,
|
||
`memory_conversations`, `security::pairing`).
|
||
4. **Never:** `web` provider and `runtime/` dispatch — they are the harness
|
||
front-end and orchestrator that consume the crate.
|
||
|
||
## Sequencing and risk notes
|
||
|
||
- Land Step 1 and Step 2 together in one PR if possible: adding the dep while
|
||
keeping duplicates invites split-brain imports (some call sites on the
|
||
crate types, some on the local copies — Rust will treat them as different
|
||
types).
|
||
- Session-key changes (Step 3, items 2–3) change conversation identity. Ship
|
||
them behind the crate's legacy-key canonicalization with a migration test
|
||
that maps a sample of existing keys to new keys losslessly.
|
||
- Positive invariants to preserve (verified in the audit): no secrets are
|
||
logged in the channels tree, and no blocking calls exist in async provider
|
||
paths. Any moved code must keep both properties.
|