The activity log across the person's monorepos (JSON pages, or the person's WebSocket)
The web home's log: what happened in every monorepo the person belongs
to, newest first, each line with its monorepo. People, machines and
worktrees only: no agent field.
Lines are the activity kinds: push, branch.opened,
branch.deleted, merged, review, status (only when a check's
state changes), worktree.reported (a new worktree only),
worktree.removed, meta.saved (not files a merge brought in). The
cache-sync kinds stay on the per-monorepo stream.
Order and cursor: every line has the display key (at, monorepo, seq).
next is an opaque cursor, "older than this line"; pass it back as
cursor. It is stable while lines arrive: new lines are newer, and a
late or repeated delivery never moves a line already paged past.
Where lines come from: each person has a feed (a Durable Object). A
monorepo of 100 members or fewer copies its lines into its members'
feeds; a larger one is read from its own log on every page. A feed
keeps 7 days (at most 1,000,000 lines); older pages, and a monorepo
the feed joined with a partial history, are read from the monorepos'
own logs, which keep everything. At most 20 logs are read for one
page, most recently active first; the rest are named in skipped
(narrow with in to read one of them).
With Upgrade: websocket: one socket per person, for every small
monorepo. Send {"type": "hello", "since": <landed>} (since
optional): the feed replays the lines that landed after it (at most
500, else one resync frame: refetch the first page), then
{"type": "caught_up", "landed"}. landed is the feed's own arrival
number, not a time: keep the highest seen and send it on reconnect.
Live frames:
{"type": "event", "landed", "event": FeedEvent}: place it by its display key. The same line can arrive again with a fullersummary(a push's commit count); replace by(monorepo_id, seq).{"type": "summary", "monorepo", "monorepo_id", "summary": FeedSummary}.{"type": "needs", "monorepo", "monorepo_id", "changed": [{repo, branch, state}], "more"}: branches that entered or left "needs you" (statenull: left);more: over 50 changed, refetch listNeeds. A large monorepo sendssummaryandneedsonly when it crosses 100 members (either way); its line is read again with the list.{"type": "joined", "monorepo", "monorepo_id", "large"},{"type": "left", "monorepo", "monorepo_id"}: refetch the first page and the list.{"type": "contributed", "monorepo", "monorepo_id", "hour", "kind", "n"}: your own work was counted (kindcommit, branch or review;hourthe UTC hour,2026-10-03T17), for work of the last two days; refetch getContributions. Large monorepos send it here too.{"type": "error", "message"}: the frame sent was not understood (anything buthelloor the textping). Nothing changed; the socket stays open. Every frame'smonorepois theowner/namelabel (as the index last heard it; a rename shows within a minute) andmonorepo_idthe id. The textpinggetspong. Lines of large monorepos do not come here: open their own event socket withtopics=feed(the home opens at most 5; it refreshes the others every 60 s).
Cookie or Bearer. With the cookie, the upgrade must carry Pando's own
Origin (403 bad_origin).
https://app.gitpando.com/v1/eventsAuthorisation
Query parameters
Returns
101
The person's WebSocket.
200
A page, newest first