mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
52 lines
2.9 KiB
Markdown
52 lines
2.9 KiB
Markdown
# Timeline sync
|
|
|
|
Agent chat delivery has two paths:
|
|
|
|
1. **Live stream** — `agent_stream` WebSocket messages for immediacy. These may be delta-shaped lifecycle updates.
|
|
2. **Authoritative history** — `fetch_agent_timeline_request` for correctness. This always returns full projected timeline items, never lifecycle deltas.
|
|
|
|
The invariant is:
|
|
|
|
> If the daemon has committed timeline rows for an agent, any connected client that opens or resumes that agent eventually displays every row through the daemon's current tail.
|
|
|
|
Tool output is bounded before it enters either delivery path. Canonical shell tool output and failed
|
|
shell error text are capped at 64 KiB of UTF-8 data, and the same bounded item is used for durable
|
|
timeline rows and live stream events. Provider history hydration applies the same rule so reopening
|
|
an agent cannot restore an oversized tool payload.
|
|
|
|
## Presence is not delivery
|
|
|
|
Client heartbeat reports presence:
|
|
|
|
- device type
|
|
- app visibility
|
|
- focused agent
|
|
- last activity time
|
|
|
|
Heartbeat is used for notification routing. It must not be used as a correctness gate for `agent_stream` delivery. A stale mobile focus heartbeat may affect whether the user gets notified; it must not make timeline rows disappear from the live stream.
|
|
|
|
## Catch-up is paged but complete
|
|
|
|
Large unbounded timeline responses can exceed relay frame limits, so catch-up uses bounded pages. Bounded does not mean partial.
|
|
|
|
Page limits are projected-item targets. A tool call lifecycle is one projected item even if it spans many source sequence numbers, and assistant/reasoning chunks are merged before counting. The response carries `seqStart`, `seqEnd`, `sourceSeqRanges`, and `collapsed` so clients can advance sequence cursors without rendering delta rows.
|
|
|
|
When the app fetches `direction: "after"` and the daemon responds with `hasNewer: true`, the app must immediately fetch the next page from `endCursor`. The catch-up is complete only when `hasNewer: false`.
|
|
|
|
Initialization timeouts guard lack of catch-up progress, not the full multi-page sync. A successful page that queues the next `after` page refreshes the watchdog.
|
|
|
|
The first load of an agent without a local cursor is different: it fetches a bounded latest tail page. Older history remains user-driven by scrolling upward.
|
|
|
|
## Resume behavior
|
|
|
|
When a client resumes with a known cursor, it catches up after that cursor to completion. It does not replace the view with a latest tail page, because tail pagination can skip the middle of a long background run.
|
|
|
|
When a client resumes without a cursor, it fetches the latest tail page.
|
|
|
|
## Relevant code
|
|
|
|
- Server live stream forwarding: `packages/server/src/server/session.ts`
|
|
- App sync planning: `packages/app/src/timeline/timeline-sync-plan.ts`
|
|
- App stream/timeline reducer: `packages/app/src/timeline/session-stream-reducers.ts`
|
|
- Session wiring: `packages/app/src/contexts/session-context.tsx`
|