Skip to content

chat: per-session message history is never trimmed — every message re-joins the full history into the 5-line viewport, so a long-lived session grows unbounded and rendering is quadratic #3268

Open

Description

@dezren39

Problem

In pkgs/chat (main.go, main 4b0f9ceb), every client's model keeps its entire chat history forever and rebuilds the whole viewport content on every message:

case chatMsg:
    m.messages = append(m.messages, m.senderStyle.Render(msg.id)+": "+msg.text)
    m.viewport.SetContent(strings.Join(m.messages, "\n"))
    m.viewport.GotoBottom()

(main.go:177-180; the field is declared at :113 and initialised at :144.)

Those are the only three references to messages in the file: nothing ever caps, trims, or windows the slice. The viewport is 5 lines tall (viewport.New(30, 5), :141-143), so nearly everything retained is invisible — yet a long-lived session's model grows without bound, and each new message costs a strings.Join over the full history: total rendering work is quadratic in the number of messages the session has seen, and the joined string is re-allocated whole every time.

This is the per-session sibling of #3201 (the server-side progs registry in the same file, which is never pruned either): #3201 covers the server retaining dead programs; this issue covers a single live session's own history and render cost, which grows even while the session is healthy and connected.

Evidence (verified this run)

  • Code: grep -n messages pkgs/chat/main.go returns exactly three sites — field :113, init :144, append+Join :178-179. No cap, trim, or ring buffer anywhere in the package (the package is the single main.go, 197 lines).
  • Laptop probe (~/git/fleet/probe-2026-10-31-laneB-chathistory/, plain Go replicating the append + full strings.Join performed per chatMsg; pkgs/chat/main.go sha256 70e8215c5d13db1fb… identical on the sandbox clone at 4b0f9ceb and the laptop clone ~/git/code):
n= 1000  total append+Join time=8ms     retained messages=1000
n= 5000  total append+Join time=135ms   retained messages=5000
n=10000  total append+Join time=480ms   retained messages=10000
n=20000  total append+Join time=1.898s  retained messages=20000

Doubling the history roughly quadruples the total work — the per-message Join dominates, exactly as the code shape predicts.

Dedup: open + closed search for chat message history / viewport returns only #3201 (server progs registry: fan-out to dead programs + data race). No existing issue covers the per-model messages slice.

Why it matters

The chat server is a long-running SSH service: a busy room or a client left connected for days steadily eats more memory per session and gets visibly laggier with every message, because each keystroke-era message re-renders history the 5-line viewport will never show. It degrades exactly in the long-lived-session case the service exists for, and there is no operator-visible bound to tune.

Acceptance criteria

  • A session's retained history is bounded by a named cap (sized relative to what the viewport can show / scroll back, and documented in the code), or the viewport content is built incrementally without re-joining the full history per message.
  • After the cap is reached, older messages are dropped from the model (the visible behaviour — latest messages shown, GotoBottom — is unchanged).
  • Per-message render work no longer grows with total history length once the cap is reached.
  • A test or benchmark pins the behaviour: feeding more messages than the cap leaves the retained count at the cap and the newest message visible.

Activity

  1. added
    kind/performancePerformance problem or optimization
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/chatPackage/app: chat (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 1, 2026
  2. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator-code-b
    • Session / ID: new-issue-creator-code-b cron run 2026-10-31 19:20 CDT
    • Found via: filed this run — read pkgs/chat/main.go on main 4b0f9ce while sweeping packages Lane B had not covered this cycle; grep shows messages has exactly three references (field :113, init :144, append+Join :178-179) and no cap anywhere, then measured the cost with a laptop Go probe.
    • Task: Filed this issue: a chat session's model retains every message forever and re-joins the full history into its 5-line viewport per message — unbounded per-session growth, quadratic total render work. Laptop probe (~/git/fleet/probe-2026-10-31-laneB-chathistory/, main.go sha256 70e8215c5d13db1fb… matched on both clones): append+Join totals 8ms @1k, 480ms @10k, 1.898s @20k messages.
    • Expectation: effort s; done when retained history is bounded by a named cap (or rendering is incremental), visible behaviour is unchanged, and a test/benchmark pins the cap — see the issue's acceptance criteria.
    • Links: server-side sibling chat: session program registry never pruned — unbounded growth, per-message fan-out to dead programs, and a data race on the shared slice #3201
  3. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 1, 2026
  5. 155 remaining items

  6. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked task 5697 for 30 minutes. Same-fix PR #3284 is still OPEN and draft, authored and assigned to @dezren39; no competing branch or PR was created. Resume after #3284 merges/closes or Drew explicitly hands off the work.\n\n

  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 4, 2026
  8. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  9. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  10. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: handed_off
    task: 5697
    lane: code-j
    worker: repo-worker-code
    execution_lane: opencode
    issue: #3268
    kind: small_fix
    state_checked: 2026-11-04T12:00Z — issue #3268 OPEN, no held/user-owned/do-not-merge/do-not-auto-edit labels; labels synced to queue/claimed + status/in-progress + work/assigned.
    next: opencode claimant launches the bound session for task 5697.

  11. added
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 5, 2026
  12. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor
    • Session / ID: task 5697 (repo-fleet-monitor run 2026-11-05T15:32:18Z)
    • Found via: monitor reconciliation (Duty 1 claim/label divergence repair)
    • Task: Claim evaporated: task 5697 is queued and unclaimed in repo-health-code-pilot, but this issue still carried claimed labels. Labels reset to the truth (queue/queued + status/needs-triage). Checkpoint stage at evaporation: handed_off.
    • Expectation: The next lane claim picks task 5697 up from the queued state and posts a fresh pickup note.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/chatPackage/app: chat (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/performancePerformance problem or optimizationpriority/lowNice to have; do when convenientqueue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobstatus/needs-triageNot yet triaged; needs a first looktask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions