Description
Topic.from_checkpoint and NamedBarrierValue.from_checkpoint assign the checkpoint's container directly to the new channel instead of copying it:
# topic.py
empty.values = checkpoint
# named_barrier_value.py
empty.seen = checkpoint
BaseChannel.from_checkpoint documents that "If the checkpoint contains complex data structures, they should be copied", and the channels' own copy() methods do copy (self.values.copy(), self.seen.copy()), but from_checkpoint does not. So two channels restored from the same checkpoint object share the same list/set, and an in-place update to one mutates the checkpoint and the sibling.
Reproduction
from langgraph.channels.topic import Topic
source = Topic(str, accumulate=True)
source.update(["a", "b"])
checkpoint = source.checkpoint()
one = Topic(str, accumulate=True).from_checkpoint(checkpoint)
two = Topic(str, accumulate=True).from_checkpoint(checkpoint)
one.update(["c"])
print(two.get()) # ['a', 'b', 'c'] -> expected ['a', 'b']
print(checkpoint) # ['a', 'b', 'c'] -> checkpoint was mutated
NamedBarrierValue (and NamedBarrierValueAfterFinish) behave the same way with their seen set. This matters when a single loaded checkpoint is reused across restores (replay / forking a thread): an accumulate=True topic or a barrier value can retroactively corrupt the persisted checkpoint.
Expected
from_checkpoint should copy the container, like copy() already does, so restored channels are independent of the checkpoint and of each other.
System info
langgraph main; the affected code is in langgraph/channels/topic.py and langgraph/channels/named_barrier_value.py.
Description
Topic.from_checkpointandNamedBarrierValue.from_checkpointassign the checkpoint's container directly to the new channel instead of copying it:BaseChannel.from_checkpointdocuments that "If the checkpoint contains complex data structures, they should be copied", and the channels' owncopy()methods do copy (self.values.copy(),self.seen.copy()), butfrom_checkpointdoes not. So two channels restored from the same checkpoint object share the same list/set, and an in-place update to one mutates the checkpoint and the sibling.Reproduction
NamedBarrierValue(andNamedBarrierValueAfterFinish) behave the same way with theirseenset. This matters when a single loaded checkpoint is reused across restores (replay / forking a thread): anaccumulate=Truetopic or a barrier value can retroactively corrupt the persisted checkpoint.Expected
from_checkpointshould copy the container, likecopy()already does, so restored channels are independent of the checkpoint and of each other.System info
langgraph main; the affected code is in
langgraph/channels/topic.pyandlanggraph/channels/named_barrier_value.py.