惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Recent Announcements
Recent Announcements
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
A
About on SuperTechFans
量子位
Engineering at Meta
Engineering at Meta
B
Blog
The Cloudflare Blog
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
Y
Y Combinator Blog
J
Java Code Geeks
D
DataBreaches.Net
aimingoo的专栏
aimingoo的专栏
T
Tailwind CSS Blog
H
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
Stack Overflow Blog
Stack Overflow Blog
C
Check Point Blog
酷 壳 – CoolShell
酷 壳 – CoolShell

Hacker News - Newest: "AI"

AI can't read an investor deck AI as an attorney? Student uses ChatGPT, Gemini to sue UW over alleged racial discrimination Hacking MCP Servers in AI Systems – The Rug Pull: Tool Changes After Approval GitHub - MeepCastana/KubeezCut: Free Web based video editor Can AI judge journalism? A Thiel-backed startup says yes, even if it risks chilling whistleblowers Coming soon: 10 Things That Matter in AI Right Now DARPA built an AI to fact-check enemy weapons claims What explains heterogeneity in AI adoption? When AI Meets Muscle: Context-Aware Electrical Stimulation Promises a New Way to Guide Human Movements - Department of Computer Science AI Changed How We Build. It Did Not Change What Matters. Linux rules on using AI-generated code - Copilot is OK, but humans must take 'full responsibility for the… Meta spins up AI version of Mark Zuckerberg to engage with employees Code Mode: Let Your AI Write Programs, Not Just Call Tools | TanStack Blog GitHub - Delavalom/graft: Go framework for building AI agents. Type-safe tools, multi-provider (OpenAI, Anthropic, Gemini, Bedrock), zero vendor SDKs. India's TCS tops estimates, says new AI models did not dent services demand Gen Z's fading AI hype Strong feeling: we are in a folded AI reality GitHub - machinarii/total-recall-catalog: A reference catalog of latest knowledge retrieval, memory & RAG systems GitHub - mensfeld/code-on-incus: Give each AI agent its own isolated machine with root, Docker, and systemd. Active defense detects and stops threats automatically.. Quantization, LoRA, and the 8% Problem: Benchmarking Local LLMs for Production AI Iran war: We spoke to the man making Lego-style AI videos that experts say are powerful propaganda Powell, Bessent discussed Anthropic's Mythos AI cyber threat with major U.S. banks GitHub - immartian/bellamem: Persistent belief-graph memory for AI agents. Retrieves decisive context by importance — not recency, not RAG, not /compact. recursive-mode: The Repo-Native Operating System for AI Engineering After the attack on Sam Altman's home, will AI CEO's go on the offensive? The biggest advance in AI since the LLM Opus 4.6 vs GPT 5.4 One Prompt Unity World Generation Test “AI polls” are fake polls Client Challenge Can AI be a 'child of God'? Inside Anthropic's meeting with Christian leaders
Conversation tree branching in `@ably/ai-transport`
zknill · 2026-05-08 · via Hacker News - Newest: "AI"

7 min readLast updatedUpdated May 8, 2026

Conversation tree branching in `@ably/ai-transport`

Real conversations aren't linear

Picture a developer pair-programming with an AI assistant. The model returns a function that almost works. The developer asks it to try again. The second attempt is worse. They want the first one back.

In a linear chat, that history is gone, or it's a third bubble in the thread that pollutes context for every future turn. The same problem shows up when a user edits a message and wants to compare both phrasings, or when an agent picks a tool that fails and you'd rather not feed the failure back into its next reasoning step.

`@ably/ai-transport` exposes a tree instead. Branches are first-class: they are explicit in the wire format, exposed through the React hooks, and persisted on the channel alongside the linear messages.

The tree model

Every message has three headers: a `msgId`, a `parentId`, and an optional `forkOf`. Together those three fields describe a directed acyclic graph rooted at the start of the conversation. A normal turn appends a child. A regeneration adds a sibling that shares a `parentId` with an existing assistant message and points to it via `forkOf`. An edit does the same one level higher, on a user message.

The SDK then projects that tree into a linear list of messages for rendering. You navigate between siblings at any fork point, and the projection updates. The underlying tree never loses anything.

The contract is on the `View` interface:

export interface View<TEvent, TMessage> {
  flattenNodes(): MessageNode<TMessage>[];

  send(messages: TMessage | TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>>;
  regenerate(messageId: string, options?: SendOptions): Promise<ActiveTurn<TEvent>>;
  edit(messageId: string, newMessages: TMessage | TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>>;

  select(msgId: string, index: number): void;
  getSiblings(msgId: string): TMessage[];
  hasSiblings(msgId: string): boolean;
  // ...
}

A `View` is a windowed, branch-aware projection over the underlying `Tree`. The tree is the source of truth across all clients on the channel. Each view holds its own branch selection, so two users in the same conversation can be looking at different branches at the same time.

How it works in practice

The use-client-transport demo wires the tree primitives into `useView` and `useCreateView`. Three operations carry most of the weight.

Regenerate

When the user wants another shot at the last assistant reply, hand its `msgId` to `regenerate`:

<MessageList
  view={view}
  onRegenerate={(id) => view.regenerate(id)}
  onEdit={(id, text) => view.edit(id, [userMessage(text)])}
  // ...
/>

The hook computes `forkOf`, `parent`, and the truncated history from the currently selected branch. The server publishes the new turn under the same parent as the old one, and both versions live side by side in the tree.

Edit

Editing forks at a user message instead of an assistant message. You pass the target `msgId` and the replacement content:

onEdit={(id, text) => view.edit(id, [userMessage(text)])}

Everything downstream of the edited message stays in the tree, untouched, on a different branch. A user can flip back to the original phrasing whenever they want.

Sibling navigation

The `MessageList` component asks the view, per node, whether siblings exist and which one is selected:

{nodes.map((node) => (
  <MessageBubble
    key={node.message.id}
    message={node.message}
    hasSiblings={view.hasSiblings(node.msgId)}
    siblings={view.getSiblings(node.msgId)}
    selectedIndex={view.getSelectedIndex(node.msgId)}
    onSelectSibling={(index) => view.select(node.msgId, index)}
    // ...
  />
))}

The bubble renders a small `< 1/3 >` navigator when `hasSiblings` is true. Clicking calls `view.select`, which updates the projection and re-emits the visible node list. The swap is purely local. The data is already in the tree, so there is no round trip to the server.

use-client-transport demo branching
use-client-transport demo branching 2

Real-world use cases

Branching can look like a niche UI flourish at first. The three scenarios below are where it earns its weight.

Customer support agent assist

A human agent is on a live customer chat. The AI drafts a reply. The supervisor reviews it, isn't happy, and regenerates. In a linear thread, you've now either overwritten the original draft or left both visible to the customer. Neither is ideal.

With branching, the regeneration is a sibling. The agent sees both drafts side by side, picks one, and only the chosen branch becomes the visible reply. The discarded draft stays in the tree for audit. Because the transport runs on Ably channels, the supervisor and the agent are subscribed to the same conversation in realtime and they're operating on the same tree.

AI agent tool retry

An agent calls a search tool and gets a noisy result. The next reasoning step is going to anchor on that result. You'd like to retry with a different query, or a different tool, without leaving the failed call in the context window the model sees.

A regenerate forks the assistant turn. The original tool call still lives in the tree (you might want it for evals), but the active branch for the next turn is the cleaner attempt. The history sent to the model on the next request is computed from the selected branch, so the failed path doesn't leak into the prompt.

This is harder than it sounds in a linear architecture because tool calls and their outputs are usually pinned together. The tree treats them as part of a single forkable unit.

Content and copywriting tools

An editorial team uses an AI tool to draft intros for an article. Someone types "give me three options." The model returns one. They regenerate twice more.

With a single linear thread, you're scrolling up and down comparing three blocks of text. With a split view, you put two on screen at once via `useCreateView` and flip the third in via `select`. The tree records all three forever, so going back next week to retrieve the rejected drafts is a `getSiblings` call rather than a database archaeology project.

Play The Ably Affair

The Ably Affair is a detective mystery where the investigation itself is a tree. Press a witness one way and you can regenerate to press them another, with the original line of questioning preserved as a sibling you can come back to. Share the room URL and other tabs join the same case, each detective free to chase their own theory down a different fork while the tree stays in sync underneath.

Play The Ably Affair

Tradeoffs

Branching is not free. Storing the full tree means you're keeping messages a linear chat would discard, and your UI needs to handle the case where a user has no idea which branch they're on. Sibling navigators help, but they're another affordance to teach.

The storage cost compounds on long, heavily-edited threads, since every regenerate is a permanent sibling. Analytics get an awkward question. "What did the user actually see?" is the active branch. "What did the conversation cost?" sums every branch. Export and replay hit the same fork in the road.

The SDK gives you a tree-shaped data model and a wire format that preserves it. It does not decide which branch to send to the model on the next turn, when to surface alternates in the UI, or what counts as "the conversation" for analytics and export. Those remain product decisions.

Try it yourself

To see code examples and how a working demo is constructed, clone the Ably AI Transport repository and run the use-client-transport demo. In this demo you can create a branched conversation by editing messages in the chat UI.