fix(inbound): route fabric DM channels as peer.kind=direct / ChatType=direct #6

Merged
hzhang merged 1 commits from fix/fabric-dm-routing-by-xtype into main 2026-05-25 14:03:20 +00:00
Contributor

Inbound was hardcoding peer: { kind: 'group' } and ChatType: 'group' for every fabric channel regardless of xType. Consequences:

  • sessionKey for a DM came out as agent:<id>:fabric:group:<chan> instead of agent:<id>:fabric:direct:<chan>
  • ctx.ChatType='group' caused the user-prompt metadata to render is_group_chat: true on a DM
  • openclaw's isDirectMessage() check (ChatType==='direct') returned false, so DM-specific prompt and turn behavior never engaged

This was caught by the recruiter sim test in session 40c51de2-14f5-47fc-ad22-da9ebee5c4a8: the model's thinking trace acknowledged "fabric DM channel" (from the ClawPrompts chat-injector hook) but the surrounding user-prompt metadata contradicted it with is_group_chat: true, and the model reasoned its way out of running workflow_start.

Approach

Small helper in channel.ts:

export type FabricPeerRouting = { peerKind: 'direct' | 'group'; chatType: 'direct' | 'group' };

export function fabricPeerRoutingForXType(xType: string | null | undefined): FabricPeerRouting {
  if (xType === 'dm') return { peerKind: 'direct', chatType: 'direct' };
  return { peerKind: 'group', chatType: 'group' };
}

export function fabricPeerRoutingForChannel(channelId: string): FabricPeerRouting {
  return fabricPeerRoutingForXType(getChannelType(channelId));
}
  • Inbound uses m.xType directly (live, authoritative).
  • Outbound (resolveFabricOutboundSessionRoute) has no xType in its call signature, so it consults the channel-meta cache populated by inbound — same getChannelType already exposed via __fabric. Cache miss falls back to 'group', i.e. the pre-fix default — no regression on cold cache.

Edge case left as-is: proactive DM to a channel with no prior inbound. That one outbound still routes as 'group'; the next inbound + outbound pair agrees on 'direct'. Fixable later by warming the cache via fabric channel-list discovery if it becomes a real problem.

Companion PR on the prompt side: hzhang/ClawPrompts #3 hardens the chat-injector wording so the model doesn't bypass workflow_start when the signals actually do align.

Will sim-test both together before merging either.

Inbound was hardcoding `peer: { kind: 'group' }` and `ChatType: 'group'` for every fabric channel regardless of xType. Consequences: - sessionKey for a DM came out as `agent:<id>:fabric:group:<chan>` instead of `agent:<id>:fabric:direct:<chan>` - `ctx.ChatType='group'` caused the user-prompt metadata to render `is_group_chat: true` on a DM - openclaw's `isDirectMessage()` check (`ChatType==='direct'`) returned false, so DM-specific prompt and turn behavior never engaged This was caught by the recruiter sim test in session `40c51de2-14f5-47fc-ad22-da9ebee5c4a8`: the model's thinking trace acknowledged "fabric DM channel" (from the ClawPrompts chat-injector hook) but the surrounding user-prompt metadata contradicted it with `is_group_chat: true`, and the model reasoned its way out of running `workflow_start`. **Approach** Small helper in `channel.ts`: ```ts export type FabricPeerRouting = { peerKind: 'direct' | 'group'; chatType: 'direct' | 'group' }; export function fabricPeerRoutingForXType(xType: string | null | undefined): FabricPeerRouting { if (xType === 'dm') return { peerKind: 'direct', chatType: 'direct' }; return { peerKind: 'group', chatType: 'group' }; } export function fabricPeerRoutingForChannel(channelId: string): FabricPeerRouting { return fabricPeerRoutingForXType(getChannelType(channelId)); } ``` - **Inbound** uses `m.xType` directly (live, authoritative). - **Outbound** (`resolveFabricOutboundSessionRoute`) has no xType in its call signature, so it consults the channel-meta cache populated by inbound — same `getChannelType` already exposed via `__fabric`. Cache miss falls back to `'group'`, i.e. the pre-fix default — **no regression on cold cache**. **Edge case left as-is**: proactive DM to a channel with no prior inbound. That one outbound still routes as `'group'`; the next inbound + outbound pair agrees on `'direct'`. Fixable later by warming the cache via fabric channel-list discovery if it becomes a real problem. Companion PR on the prompt side: `hzhang/ClawPrompts` #3 hardens the chat-injector wording so the model doesn't bypass `workflow_start` when the signals actually do align. Will sim-test both together before merging either.
hzhang added 1 commit 2026-05-25 13:27:03 +00:00
Inbound was hardcoding `peer: { kind: 'group' }` and `ChatType: 'group'`
for every fabric channel regardless of xType. As a result:

- sessionKey for a DM was `agent:<id>:fabric:group:<chan>` instead of
  `agent:<id>:fabric:direct:<chan>`
- ctx.ChatType='group' caused user-prompt metadata to render
  `is_group_chat: true` on a DM
- openclaw's `isDirectMessage()` check (ChatType==='direct') returned
  false, so DM-specific prompt and turn behavior never engaged

Caught by recruiter test in session 40c51de2: the model's thinking trace
acknowledged "fabric DM channel" (from the ClawPrompts chat-injector
hook) but the surrounding user-prompt metadata contradicted it with
`is_group_chat: true`, and the model reasoned its way out of running
`workflow_start`.

Fix factors a small helper `fabricPeerRoutingForXType` (and a cache-
backed `fabricPeerRoutingForChannel` for outbound) in channel.ts that
maps:
  - 'dm'  → { peerKind: 'direct', chatType: 'direct' }
  - rest  → { peerKind: 'group',  chatType: 'group' }   (no change)

Inbound uses m.xType directly (live, authoritative). Outbound has no
xType in its call signature, so it consults the channel-meta cache
populated by inbound (same `getChannelType` already exposed via
__fabric). Cache miss falls back to 'group' — the pre-fix default, no
regression. The proactive-DM-without-prior-inbound edge case still
routes that one outbound as 'group'; the next round agrees on 'direct'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hzhang merged commit dabaa6e1f2 into main 2026-05-25 14:03:20 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: nav/Fabric.OpenclawPlugin#6