Skip to content

Chat: per-conversation model binding for multi-model Multi-Chat - #1174

Draft
worsnoptr wants to merge 2 commits into
enricoros:mainfrom
worsnoptr:conversation-model-binding
Draft

Chat: per-conversation model binding for multi-model Multi-Chat#1174
worsnoptr wants to merge 2 commits into
enricoros:mainfrom
worsnoptr:conversation-model-binding

Conversation

@worsnoptr

Copy link
Copy Markdown
Contributor

Implements the design proposed in #1173 — see that issue for the full rationale, UX mock, and discussion of the pane-binding alternative. Opening as a draft so the design conversation in the issue can proceed independently of code review; happy to adjust (field name, UX details, or switch to pane-binding) based on how that discussion goes.

Summary

  • DConversation.userLlmId?: DLLMId — optional pinned model, resolved at execution time as pin ?? primaryChat domain default.
  • getConversationChatLLMId(conversationId): validated pin or default; broken pins (model removed, or chat imported from an install with different services) silently degrade to the default, mirroring llmsResolveDomainModel's broken-assignment semantics.
  • _handleExecute reads it instead of getChatLLMId() — one swap covers send, regenerate, retry, ReAct, and Multi-Chat broadcast (which already dispatches per conversation, so per-pane models fall out with no changes to the broadcast code itself). Beam's seed (beamOpen/beamImportRays) resolves per-conversation too.
  • Utility flows (autotitle, fastUtil, codeApply, imageCaption) are untouched — they use the separate domain accessors.
  • UX: model selector gains a "Pin model to this chat" / "Unpin · follow app default" entry; selecting a model edits the pin when pinned, the app default otherwise (zero behavior change until you pin). Each pane's title overlay now shows its bound model — outlined chip + 📌 when pinned, dimmed italic when following the default — so non-focused panes' models stay visible for side-by-side comparison.
  • Persistence: optional field, no store version bump; duplicateDConversation copies the pin so branches keep their model; export carries it and import round-trips it (chats.converters).

Tested

  • Single-pane, no pin: byte-identical behavior to before (verified via the send/regenerate/ReAct paths reading the same resolved model as getChatLLMId() would return).
  • Pin / unpin / re-pin via the selector menu; broken-pin degradation (delete the pinned service, confirm fallback to default).
  • Duplicate/branch carries the pin; export → import round-trip.
  • 4-pane broadcast with four different models across all Bedrock dispatch paths (Converse, Invoke-Anthropic, Mantle Chat Completions, Mantle Responses) — verified each pane continues multi-turn on its own model and the per-model tool state (e.g. a Responses-only model's Web Search toggle) stays scoped to its own pane.
  • tsc --noEmit, eslint, and next build all clean.

Multi-Chat split view + multicast can send one message to several
conversations, but all panes run on the single app-level 'primaryChat'
model assignment - side-by-side multi-turn comparison of DIFFERENT
models isn't possible. Beam covers single-turn scatter/gather; this
enables persistent multi-turn conversations each bound to its own
model. The two compose rather than compete.

Design: optional conversation.userLlmId ("pinned" model), resolved at
execution time as pin ?? 'primaryChat' domain default:

- getConversationChatLLMId(conversationId): validated pin or default;
  broken pins (model removed, or chat imported from an install with
  different services) silently degrade to the default, mirroring the
  broken-domain-assignment semantics of llmsResolveDomainModel.
- _handleExecute reads it instead of getChatLLMId() - one swap covers
  send, regenerate, retry, ReAct, and multicast (which already fans
  out per conversation, so per-pane models fall out for free).
- Beam seeds (beamOpen/beamImportRays) resolve per-conversation too.
- Utility flows (autotitle, fastUtil, codeApply, imageCaption) are
  untouched: they use the separate domain accessors.

UX (conservative: zero behavior change until a chat is pinned):
- Model selector: 'Pin model to this chat' / 'Unpin - follow app
  default' menu entry; when pinned, the selector shows the pin and
  edits the pin instead of the global default.
- Split view: each pane's title overlay shows the conversation's
  bound model - outlined chip + pin for pinned, dimmed italic for
  following-default - so non-focused panes' models stay visible.

Persistence: optional field, no store version bump (absent = follow
default, all existing chats unaffected); branching copies the pin;
export carries it and import round-trips it (chats.converters).
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

@worsnoptr is attempting to deploy a commit to the Token Fabrics Pro Team on Vercel.

A member of the Team first needs to authorize it.

@enricoros

Copy link
Copy Markdown
Owner

Thanks for opening this as a draft - DConversation.userLlmId is the right first move. To make it land cleanly: (1) keep the field; (2) route all chat model resolution through one function (chat pin > persona default > global fallback) instead of reading the pin at call sites - that mirrors how our hosted branch already works, and it is the difference between merging cleanly and fighting every rebase; (3) degrade silently when a pinned model disappears (fall back, never error); (4) leave the utility model domains (title/fast) alone. Reshape to that and review follows quickly.

… directly

Three sites still read the plain 'primaryChat' global instead of the
conversation-aware resolver, contradicting the single-seam requirement:
- AppChat: Ctrl+Shift+O (Model Options) opened the global default's
  options instead of the focused pane's bound model.
- store-chats: editMessage's and updateMessagesTokenCounts' token
  counting (import/historyReplace/historyTruncateToIncluded/append)
  used the global default instead of the owning conversation's model.

All now resolve through getConversationChatLLMId(conversationId), the
single function per the reviewed seam (chat pin > primaryChat domain
default). Adds store-chats.test.ts covering the resolution order and
silent broken-pin degradation against the real stores.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants