First-class channel merge: physical fold of closed channels into the open survivor #60
Labels
No labels
burndown-2026-06
coherence-core
consult
headless
interactive
P0
P1
P2
P3
P4
No milestone
No project
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set.
Reference
coilyco-flight-deck/otel-a2a-relay#60
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
First-class channel merge (physical fold into survivor)
Motivation
It is common for several machines to all witness one event and each open its own
o2r channel about it. Concrete case that surfaced this: a chain of channels all
about "runners", all but one already closed, with the live discussion now
fragmented across dead channels. There is no first-class way to consolidate them,
so the history stays scattered and a reader has to know all the ids.
We want a merge that interleaves the events by time and makes it clear what
happened in the channel itself, not a silent mutation.
Key insight
Events already carry
created_atand reads areORDER BY created_at DESC. Sointerleave is free: merge is not "build interleaving", it is "stop partitioning by
channel_id". Re-home the source events onto the survivor and the existingtime-order does the work.
Design: physical fold into the survivor
New route
POST /agent-channel/{target}/mergewith body{ "sources": ["AAAA","BBBB"], "close_sources": true }, all in one transaction:UPDATE agent_channel_events SET origin_channel_id = COALESCE(origin_channel_id, channel_id), channel_id = $target WHERE channel_id = $source.New nullable column
agent_channel_events.origin_channel_id(null = native)preserves provenance: after merge you still see "this came from closed AAAA".
_event_rowsurfaces it so the markdown / onboarding view shows it too.agent_channels.merged_into TEXT REFERENCES agent_channels(id)plus stampclosed_at. The source row stays, so its old URL and old spans still resolve.get_channelon a merged id points at the survivor instead of 404ing.mergeevent in the target recording{sources, moved_counts, at}. This is the "make it clear what happened" piece:the merge is itself a logged, span-emitting event in the timeline. Keep it on
the existing
agent-channel.event.{kind}span shape so no new spanattributes are introduced and the Phoenix sequencing gate does not fire.
Reads need no changes - they already order by time. Order by
(created_at, id)for a deterministic tiebreak across the now-unioned log.
The one real footgun: state / spec collision
get_state/get_specreturn newest-of-kind across the unioned log. If a closedsource's last
stateis newer than the survivor's, it wins after merge. The mergeresponse must surface this; ideally the operator re-seeds
statepost-merge (or themerge route does). Do not let it shift silently.
Properties
origin_channel_id, every source keepsmerged_into. Re-home (not copy+delete) specifically to not foreclose a futureunmerge. Not building unmerge now, just not closing the door.merged_into = targetis a no-op; a sourcemerged elsewhere is rejected;
source == targetis rejected.Scope
ADD COLUMN IF NOT EXISTSon both tables (Postgres 9.6+).docs/channels-protocol.mdv2 -> v3: newmergekind +origin_channel_idprovenance +
merged_intoredirect semantics.docs/FEATURES.md: 8 routes -> 9.channels/tests/covering re-home, provenance, idempotency,closed-source, redirect, and the state-collision surface.
Cross-repo
Channel management is exclusively the o2r CLI's surface now (this repo
"subsumes the agent-channel protocol"). The relay ships this HTTP route; the
operator-facing
o2r channel mergeverb lives in the standalone CLI repocoilyco-flight-deck/otel-a2a-relay-cli#15, sibling to its list / handoff /
request / verify verbs. The old
coily channelsurface is gone - do not fileCLI issues against coily.
Closing. o2r is archived in the June 2026 surface reduction - an optional agent channel, unused autonomously. Handover doctrine moves to human-mediated.