Skip to content

Durable stream persists a full message snapshot per token delta — stored size is O(n²), measured 99.56% redundant in production #1441

Description

@mechiland

What problem are you trying to solve?

Every message.appended event carries the full accumulated message in messageSoFar alongside the delta, and every event is persisted as a durable workflow stream chunk — so a message's stored footprint is O(n²) in its number of deltas. reasoningSoFar doubles this on reasoning models.

  • packages/eve/src/harness/emission.ts:554messageSoFar: currentMessage (as of eve@0.29.2)
  • packages/eve/src/harness/emission.ts:529reasoningSoFar: currentReasoning
  • MessageAppendedStreamEvent has carried messageSoFar since the first commit, so this is protocol design rather than a regression.

This is the storage-side counterpart of #1051, which reports the read-side symptom (rebuilding history re-downloads every partial copy) and proposes a history endpoint. A history endpoint helps readers, but the quadratic bytes still land on disk.

Measured impact (production deployment, postgres world)

We run eve agents on @workflow/world-postgres, one database per project:

  • One moderately busy agent (~300–450 runs/day) grew its workflow_stream_chunks table to 13 GB in two weeks (~2.5 GB/day) and took the host disk to 85%.
  • One representative run: 8,110 chunks, 153 MB written for a final assistant message of 21 KB — ~7,000× amplification. Per-chunk size grows linearly to the end of the run (2.2 KB in the first decile → 37 KB in the last).
  • Across the whole database: 18 GB of chunk bytes, of which the sum of each stream's largest chunk — the only bytes carrying new information — is 81 MB. 99.56% of persisted bytes are redundant copies.

The snapshots are not needed for correctness: packages/eve/src/client/message-reducer.ts already accumulates deltas client-side, and since durable sessions moved program memory into workflow step results (packages/eve/src/execution/durable-session-store.ts), the chunk stream is delivery-only.

Proposed solution

Stop persisting the accumulated snapshots on per-delta events; emit them only on terminal events:

  1. message.appended / reasoning.appended carry the delta only (drop or empty messageSoFar / reasoningSoFar).
  2. message.completed / reasoning.completed keep the full final text (they already do).

Live consumers reduce deltas as they already do; a resuming consumer replays from its cursor or rebuilds from the *.completed events. This turns storage O(n²) → O(n) at the source — on our data it would shrink the chunk table by ~99% — and it composes with #1051's history endpoint rather than replacing it.

If dropping the fields outright is too breaking for one release, an opt-in (e.g. an emission/agent-level streamSnapshots: false, or making the fields optional in the protocol and omitting them by default in a future major) would let deployments that pay for storage opt out immediately.

Happy to contribute a PR if the direction is agreeable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcorep1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions