Skip to content

IM Provider integration and qualification

Verified Lark and Slack contracts and repeatable provider qualification

This guide describes the implemented BotHarness integration boundary and the evidence required to extend it. Product meaning belongs to Product Context, Messaging architecture, and specs #629 / #693. The source contracts are packages/core/src/messaging/provider.ts and dsh-im.ts; the public RPC Reference is generated from code.

For user-facing setup walkthroughs, see the verified Lark / Feishu connection guide and Slack connection guide.

Keep the two layers distinct

DSH-native Plugin/Fiber lifecycle owns the connection and Service registration. The Service Definition → Provider → Consumer seam supplies checked external operations; the API Gateway owns Host/Client transport. PersonaBot identities, Grants, Source Events, Channel placements, Inbox Admissions and Outbox intents are application-defined, durable BotHarness records.

flowchart LR
IM[Native platform] --> P[Checked Provider / exclusive Consumer]
P --> M[Messaging validates identity and Grant]
M --> S[(Canonical Source Event)]
S --> C[Channel placements or Inbox-only]
C --> A[Independent member Admission / Attention]
A --> O[Existing Orchestrator]
O --> R[Explicit external reply / own identity]
R --> P

The Provider does not start a parallel dsh-im Session when BotHarness owns the account receiver. Commit the canonical source, placement and applicable admissions before acknowledging intake. Process-local notifications follow the commit. Receiver registration is exclusive per account; BotHarness fanout shares that receiver across authorized conversations and routes. Retry/disposal must retain this ownership boundary.

Identity is separate from intake

  • A PersonaBot binds at most one identity per supported platform; it may bind several platforms. Credentials stay in the DSH credentials service, never Client DTOs, Git, prompts or public evidence.
  • Authorization pins the inspected account fingerprint and target digest, not only a display name or token. Replacing an App/account/target requires re-inspection and explicit rebind; a token refresh preserving the same identity is distinct.
  • A Channel connector selects what enters where. Group and DM Channels can have multiple routes; Inbox-only reception does not mirror the PersonaBot DM history. One external source can be placed in several Channels without becoming several source authorities.
  • A shared Channel member can read its shared source. External history/file reads and replies additionally require that member’s own enabled, authorized identity for the original conversation. Reading is not permission to borrow the receiver’s account.
  • Ordinary local Channel replies remain local. Only explicit external reply/file actions cross the bridge. Receipt state does not imply that the native client displays the Bot in an “already read” list.

Platform mapping is explicit

Field Lark / Feishu Qualified Slack adapter
Account Application/Bot identity Verified workspace, App, Bot and Bot-user identity; Socket hello App must match
Conversation Native chat ID Native channel ID, checked against workspace and membership
Message Native message ID Message ts string; never round or parse into a floating-point ID
Delivery evidence Native event ID Native event_id; repeated deliveries may differ while referring to one message
Sender Native sender ID; best-effort name Native user ID; bounded users.info name projection
Topic reply Preserve native thread/root/parent IDs when provided thread_ts if supplied; otherwise message ts becomes the derived reply root
Parent Native parent message ID when supplied No Lark-style parent ID; do not fabricate one

Thread, root and parent are routing metadata, not a new local Channel or independent Session store. A child message remains in its external conversation and carries the exact reply route. A reply does not automatically opt into topic following. No-thread platforms retain conversation-level policy instead of invented topic UI.

Explicit Slack follow uses the existing source-anchored thread policy. A root message can anchor its future topic, but only an unmentioned child with ts != thread_ts proves ordinary reply delivery on the current receiver lease. Slack requires matching root/thread timestamps and no parent field; Lark keeps its native parent requirement. Human follow/exclude overrides take precedence over Bot changes; restoring inheritance lets the Bot choose again. The Profile thread table remains available after a Grant migrates to Channel connector routes. Follow reuses count/time harvest; exit restores the group collection rule for future ordinary replies. Restart retains the policy but resets process-local delivery proof.

Name resolution is presentation only: retain sender ID, external message ID and canonical Source Event ID for exact lookup. The Channel bubble shows original content; its author label shows the platform and source name. Clicking that source opens a details Modal. Keep raw IDs in details rather than using them as the usual source name.

Collection, wake and participation are separate decisions

The built-in default is mention-only collection. Full ordinary-text collection requires both native permissions/subscriptions and a fresh, verified ordinary delivery on the current receiver lease. An inherited “all” setting alone proves neither permission nor delivery.

A connector filters and places messages. Each Group member’s existing Attention policy determines digest by count/time, next safe turn, mention-context or silent reading. Inbox-only reception uses its external group policy. Steer/turn boundaries and oldest-first bounded harvest remain owned by the existing runtime. Changing defaults affects inheriting configuration and future events; explicit overrides and committed admission snapshots remain intact.

Disable preserves configuration/history and stops new placements. Resume establishes a new intake boundary; delayed events from the paused interval are not backfilled. Deleting a route removes the configuration, not its retained history. Identity, Grant and individual route switches have different scopes. Revocation or Channel departure invalidates future authority, including a send waiting at the dispatch fence.

Reads, files and sends must stay checked

  • Group/nearby/topic history is bounded and provider-visible Human-text coverage, not a full workspace archive. Preserve omitted, hasMore, coverage and an opaque cursor. Bind cursors to account, conversation, route and query; reject reuse across scopes or identities. History reads do not silently create live Inbox admissions.
  • Nearby uses the qualified time window plus requested before/after message minima within the hard page bound. A busy window can still require pagination; do not promise unlimited “all nearby” content. Bot selects bounded counts where exposed. Lark and Slack pagination mechanisms differ.
  • For file reads, revalidate the original source, account, attachment ownership and resource key; bound bytes and validate download destinations. For file sends, run the authority fence before irreversible upload/share steps. Slack hosted files and Lark parent-file references are different mappings.
  • Before replying, inspect the same account/target and exact source route. Run the latest BotHarness authority fence immediately before dispatch. Persist a checked native receipt. An ambiguous network/result outcome is unknown, never automatically resent or redirected to the main conversation.
  • Suppress or correlate own echoes to avoid recursive intake. Expose stable bounded lifecycle logs, failure codes and duration without tokens or message dumps.

Implementation-to-documentation delivery trail

Start the documentation evidence trail with the first integration ticket, not after the final feature merge. This is the delivery workflow for each new IM; it does not qualify an untested platform.

  1. Qualification and setup. Record the selected product/channel type, official contracts, supported native fields and an immutable Provider baseline. Create one first-tracer ticket under the IM coordination parent; expand capabilities only after its Human feedback. Capture the original entry screen, app/bot creation or QR entry, permissions and installation steps as they actually occur. Keep account setup, external identity binding and Channel connector routing distinct.
  2. Continuous capture. Maintain an issue-local evidence manifest under .humanlayer/tasks/<issue>-<provider>/. Each item names its purpose, capture date, exact BotHarness/Provider/runtime revisions, locale, theme, viewport and verification state; associate synthetic input, canonical Source Event/Admission and native receipt assertions where applicable. Keep private correlation values and raw logs outside the public manifest. Capture connected state, binding, explicit source authorization, Inbox/Channel source details and the independently observed native reply. Label empty forms, candidate builds and successful live results separately.
  3. Each tracer PR. Include GitHub-renderable real screenshots or a recording, a runnable Human path and focused regression results. Use matched before/after evidence for changed UI; preserve immutable image URLs so later work does not rewrite old proof. Review every pixel and caption before publication: no tokens, active QR pairing codes, private login URLs, credentials, unrelated conversations or private account details. A disposable pairing screen may be represented by its sanitized entry/expired state, with the omission explained. Pause for Human QA before broadening scope; keep merge and deployment authorization separate.
  4. Connection guide handoff. After the relevant runtime and installable-product paths have actual acceptance, write docs/<provider>-connection.md and its maintained .zh.md counterpart. Explain prerequisites, platform-specific setup, local connection, PersonaBot identity binding, explicit source authorization/routing, a short real message/reply check, troubleshooting and unqualified capabilities. Select reviewed screenshots into apps/docs/public/guides/<provider>/, with step captions, useful alt text and clear evidence provenance. A successful source build is not proof of a published npm package.
  5. Documentation and live checks. Register both variants in scripts/sync-docs.mjs; never edit generated content. Cross-link the maintained IM qualification guide, keep bilingual release ledgers aligned, rebuild the Chinese OG font when the page’s Chinese copy changes and run the relevant docs checks/build. Verify desktop/mobile layout, light/dark presentation, language links, clean .md siblings and every screenshot in the rendered guide. After an independently authorized docs deployment, verify the live pages/assets against the deployed source and record the result in the issue. Publishing a guide must not upgrade any unqualified capability row.

The completed Lark guide and Slack guide are examples. Their historical screenshots remain historical evidence; when a changed UI would confuse a new user, recapture that step on the latest main with qualified data and retain the old provenance. The provider program’s documentation handoff is complete only when a user can follow the maintained guide and the screenshots render on the official site.

Current qualification ledger

These are development source qualifications, not a claim that every published installable artifact includes the same capability. Record the immutable Provider SHA and runtime artifact hash with each E2E. Product IM installation pins its separately verified artifact; do not substitute a fork tip or assume that an upstream merge updates an installed Profile.

Capability Lark / Feishu Slack Discord
Checked mention intake / own-topic reply Verified #12 Verified #802 Not qualified
Bounded history / nearby / topic reads Verified #612, #793 Verified #819 Not qualified
Hosted attachment workflow Verified #657 Verified single mentioned file #831 Not qualified
Ordinary public/group text and harvest Verified #613 Verified public-channel text #837 Not qualified
Platform defaults / Profile overrides Verified #701 Verified #843 Not qualified
Shared Channel placement Verified #634, #635, #638 Verified #845 Not qualified
Autonomous follow/exit native topic Verified #614 Verified #854 Not qualified
Proactive message with canonical receipt Verified #639 Verified root report and Human thread follow-up #863 Not qualified

Slack private channels/DMs, edits/deletions, ordinary file shares, workspace-wide search and gap backfill are not covered by the public-channel tracers. Remote withdrawal synchronization is deliberately outside the confirmed scope; a later read can report a missing source. Generic platform support in dsh-im is not BotHarness qualification.

Repeatable acceptance for the next provider

  1. Pin official API/source references and an immutable Provider build. Check real App/account, conversation membership, transport identity and capability versions. Discover the exact permissions and native event subscriptions; request Human approval at any required security-sensitive final step.
  2. Start an isolated verified DSH Profile with exactly one receiver; prove a real model DM works before diagnosing credentials. Keep existing receivers and credentials untouched.
  3. Deliver one native mention through Provider → canonical Messaging → Inbox/Channel → model → own-identity native reply. Independently read the reply: author, conversation, topic, body and receipt must match.
  4. Exercise redelivery, wrong account/conversation, stale Grant/identity, member departure, disabled route, pause/resume and restart. Assert one source/placement and independent member admissions; assert no borrowed identity, no unintended DM mirror and no automatic send retry.
  5. Add ordinary harvest, bounded reads, files and native topic follow as separate runnable tracers. Qualify actual capabilities instead of exposing every method the transport SDK happens to offer.
  6. Capture latest-main UI in the supported locale/themes; publish only synthetic test messages, sanitized assertions and exact revisions. Keep private logs/credentials local. Attach screenshots or recording to the PR and pause for Human QA before broadening scope.

Discord is next after Slack. Its Gateway event/intents, guild/channel/thread permissions, identity/name mapping, message content availability, history limits and attachment handling must each be verified against official documentation and a real authorized QA App. A Slack thread_ts or Lark parent ID must not become an assumed Discord contract. Reusable checked Provider contracts should be contributed upstream when appropriate; a qualified pinned fork can continue independently of upstream merge timing.

Discord mention/reply checkpoint — 2026-10-06

The #855 implementation is merged, but Discord remains unqualified. BotHarness #870 added checked mention intake and exact-location replies; #876 fixed adjacent identity/Grant/reception status refresh. Provider #6 merged at 1a605b11fa8d321110540de42d58a77bdcd60f13, with the same tree as tested candidate 8cf705ea474cdef8e658f7756ee48d5c936bd404. The dedicated QA Host uses 4138eeebffbf7abda53d9cd1ba1ccc98f95e16b1, DSH 0.2.0-rc.1, and 394 Provider runtime files with SHA-256 69c513ff44fb802377ec7648e9c9075d2fc2c63b6f1c3205ee1d6012f956e58e. These are QA revisions, not a product dependency promotion.

Real channel and existing-public-thread mentions reached one canonical Source Event/Inbox Admission, the existing Orchestrator model and one independently read own-Bot reply in the original native location. Subsequent bounded checks covered identity pause/recovery, revoked Grant, actual Provider Service loss/recovery, exclusive-consumer conflict, source editing and in-flight identity/Grant/Provider loss. Fresh channel and existing-thread model replies passed after recovery; interrupted requests were not automatically resent. Provider loss during sending retains unknown-outcome, even when native read-back finds no reply.

The Human subsequently instructed the agent to complete E2E acceptance directly and removed the personal-Human QA waiting condition. The agent used the existing Profile UI to unbind and rebind the same authenticated account, authorize the same target and enable mention-only reception. Fresh real browser-composed channel and existing-thread mentions each produced one canonical admission, one model bridge_reply intent and one independently read own-Bot reply at the original location. This is agent-operated UI acceptance, not a claim that the Human personally performed setup.

A subsequent real Gateway resume redelivered the same native message ID before RESUMED, with one admission, one model intent and one native reply. A real mention during reconnect was not backfilled; after all temporary observers were removed and the original Profile restored, a fresh model reply passed. Native HTTP 401/404 and registered-Service wrong-fingerprint refusals were also checked, with controlled inputs explicitly distinguished from full model/binding paths. Deleted-source lifecycle and wrong Application/user/guild binding still lack complete native/model evidence. Incorrect native routes and actual permission loss were checked through Provider preflight against Discord’s API; this is narrower than a model reply attempt being refused.

Read the current verification scope and historical first checkpoint and matched UI evidence from #876. ADR-0128 records the parent-conversation/native-child mapping. The qualified product Provider and every other Discord capability row remain unchanged.

The earlier read-only inspection used Provider abaee436, whose generic token controller lacked checked identity/consumer/reply operations and could create a standalone thread. That historical input is not the merged checked Provider. The current mention-only QA configuration uses GUILDS and GUILD_MESSAGES, without privileged Message Content intent. Gateway documentation exempts messages mentioning the App from the content restriction; ordinary collection and history remain separate qualification. Threads are native child channels: parent_id is not a parent message ID, and sending requires SEND_MESSAGES_IN_THREADS. Preserve Snowflake strings and recheck guild, ancestry, permission, source and receipt, with no fallback to the parent or a standalone Session.

Native reference and permission checks

Recheck these official contracts when adding a capability: Slack message.channels, Slack history and threads, Discord Gateway and Discord threads. They describe native behavior; the narrower checked BotHarness Provider contract still controls what is exposed.

The Slack public-channel QA App has Bot scopes app_mentions:read, chat:write, channels:read, channels:history, users:read, files:read and files:write; Socket Mode uses a separate App-level connections:write token. app_mention and message.channels subscriptions are distinct from scopes, and installing added scopes is distinct from saving event subscriptions. Do not request file permissions for a text-only tracer or use Human credentials to bypass a Bot capability refusal. Lark group-history permission im:message.group_msg must be granted to the application identity and published; Human OAuth for the same scope does not grant the Bot access. Verify native membership and the actual API result after configuration, rather than inferring capability from a green UI switch.

External-only reports and native follow-up

A Bot explicitly calls bridge_post with its own authorized Grant and a stable request ID. The existing Outbox retains the report text and native receipt; the report creates no Channel placement or self Inbox admission. bridge_outbox reads its result after restart. Reusing the request ID does not resend an accepted or ambiguous intent; an unknown outcome requires inspection, not a new ID retry.

The qualified Slack slice posts plain text as a root message in a joined public channel using existing chat:write. The Provider rechecks authenticated Bot identity, exclusive Consumer lease, public membership and cancellation before one non-retrying request. The receipt uses Slack channel and ts; a later admitted Human reply’s thread_ts/root ID associates it with that Outbox report under the same own identity, fingerprint and conversation. No Lark parentId is fabricated. Follow-up is still filtered by the configured connector and wake policy; report publication does not automatically follow the topic. Bot’s explicit bridge_reply stays in that native thread.

This qualification does not add a morning-report scheduler, DM/private-channel sending, rich blocks or own-message echo enrichment. Own Socket messages remain excluded from intake. The fixed fork provides this optional contract independently of upstream merge; its generic checked-receipt changes are candidates for upstream contribution after the narrow integration is reviewed.

The prior accepted product Provider 4.32.0-botharness.3 selected the qualified Slack input a0300e97; #868 verified that locally packed installation. Developer source qualification, packed product qualification and registry publication remain separate evidence. A developer pin change does not change the product qualification record.

Personal WeChat owner text DMs

The first WeChat tracer (#878) uses QR-paired owner text DMs, not group/mention/thread emulation. Explicit identity binding and DM Grant precede intake. Original source continuations stay private in the Provider; accepted sends carry a client acknowledgement rather than a native message ID. The developer candidate is 589e5507. Source-mode Inbox/model/reply E2E passed with an independently observed native reply. The locally installed product candidate 0.0.0-test.878 / Provider 4.32.0-botharness.4 restored the same authorized connection and passed a fresh text/model/reply exchange, with one handled Admission, one accepted reply and no local DM mirror. The Human confirmed the native reply. Human QA approved this first slice and supplied its native screenshot; public release and deployment remain separate actions. Context, files, other contacts and group capabilities remain unavailable.

See the personal WeChat connection guide for setup and real screenshots.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close