replay a Streamplace chat #
Streamplace chat messages are ordinary ATProto records in each chatter's repo.
Every place.stream.chat.message record names the streamer's DID in its
streamer field, so a Jetstream consumer can reconstruct a broadcast's chat.
choose a broadcast #
Pass a handle to replay the account's latest broadcast and then follow it live:
zig build example-streamplace-chat -- iame.li
Pass the exact livestream AT-URI to select a historical broadcast:
zig build example-streamplace-chat -- \
'at://did:plc:2zmxikig2sj7gqaezl5gntae/place.stream.livestream/3mst2znymsb2e'
The second form starts at that exact place.stream.livestream record on the
streamer's PDS. Streamplace can roll a single viewing session into another
livestream record, so the example also walks newer records and joins an
immediately contiguous segment for the same media URL (a rolled-over session
prints each joined record). The first record's TID supplies the lower replay
boundary and the final segment's endedAt supplies the upper chat boundary,
with five minutes of room for delayed ingestion.
logical Streamplace session:
- Loop engineering with @brittanyellich.com!
2026-08-11T16:48:57Z -> 2026-08-11T16:58:53.645Z
at://did:plc:.../place.stream.livestream/3mst2znymsb2e
replaying from the archive (https://stream.waow.tech)...
[2026-08-11T16:49:21.783Z] did:plc:...: ooo yeha i setup ssh between macs ...
[2026-08-11T16:49:39.355Z] did:plc:...: i know how to setup some secure ...
...
archive replay: 17 messages from 633 blocks
finishing the session tail over the live socket...
replay complete: 17 observed messages across 1 segment
The output uses author DIDs because Jetstream carries repo writes, not hydrated
profiles. The implementation is in examples/streamplace_chat.zig and is
compile-checked by zig build. Build it with -Doptimize=ReleaseSafe — the
archive pass decompresses hundreds of zstd blocks, which is slow in Debug.
Historical chat comes from zat.ArchiveBackfill against
stream.waow.tech's full-network archive, so replay
does not depend on Jetstream cursor retention. The session's time window maps
to plan seq bounds (fetchSeqBounds), so a recent broadcast plans only the
handful of blocks it touches. Sessions inside the archive's merged bootstrap
region (before 2026-08-04) still replay, but their rows are scattered per-DID,
so the plan spans more blocks — expect a bulk download, and watch the
archive backfill: N blocks decoded progress lines. The newest events sit in
the archive's unsealed tail, so the example always finishes over the live
socket from the covered boundary; the two passes overlap at the seam and the
printer dedups by rkey.
Replay reflects the archive's contents: records whose authors later deleted
them are folded out by the archive's delete-compaction (the .delete events
remain), and an instance's own documented coverage bounds apply.
For current-state discovery without an archive, use the same shape as the
collection_creators flow: enumerate every repo containing
place.stream.chat.message, resolve each repo's PDS, fetch its current
records, and filter them by streamer and time. That cannot recover deleted
records. A durable chat archive should still consume continuously and persist
both matching records and its cursor.
Streamplace's /api/websocket/<streamer-did> endpoint is useful when hydrated
author profiles, moderation events, and viewer counts matter more than complete
history. Its initial chat burst contains only the most recent 100 messages.
The record shape is documented in Streamplace's
place.stream.chat.message lexicon.
The com.atproto.repo.getRecord
query retrieves the selected livestream from its PDS, and the replay cursor is
part of the Jetstream subscription API.
wiring zat into your own build.zig
const jetstream = b.dependency("jetstream", .{}).module("jetstream");
exe.root_module.addImport("jetstream", jetstream);
after zig fetch --save https://tangled.org/zat.dev/jetstream/archive/main.