Pins are server-owned, so they survive paging and follow you between apps - #74234
Merged
Conversation
`list_sessions_rich` returns one recency-ordered window, so a pinned conversation that hadn't been touched in a while simply wasn't in the payload. The desktop's Pinned section resolves pins against the loaded rows, so the pin rendered as nothing until something dragged the row back onto the page. A pin is a "this must always be reachable" statement, which makes falling off the page a bug rather than a paging outcome. `include_pinned` adds one bounded query for the rows carrying `pinned = 1` that the window missed, reusing the page's own WHERE clause — an archived or filtered-out conversation stays out, and a pin is never a filter bypass. It runs before compression projection, so a back-filled root surfaces under its live tip exactly like a row that made the page on its own. Co-authored-by: hrnbld <260600092+hrnbld@users.noreply.github.com> Co-authored-by: liuhao1024 <11816344+liuhao1024@users.noreply.github.com> Co-authored-by: Tamaz-sujashvili <56168197+Tamaz-sujashvili@users.noreply.github.com> Co-authored-by: ferminquant <14808645+ferminquant@users.noreply.github.com>
The three list endpoints (`/api/sessions`, `/api/profiles/sessions`, and the batched sidebar route) now request the pinned back-fill and expose `pinned` as a real JSON boolean alongside `archived`. Both merge paths re-window rows after sorting, which would have thrown away exactly what the back-fill fetched, so pinned rows survive the cap. The sidebar's "load more" signal discounts them — they arrive past the LIMIT by design and would otherwise fake a full page on a short list. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com> Co-authored-by: konsisumer <11262660+konsisumer@users.noreply.github.com> Co-authored-by: eason2026 <209090628+eason2026@users.noreply.github.com>
The pin bridge only ever pushed: localStorage to the backend, never back. Two apps on the same gateway each kept their own localStorage, so a pin made on the Mac never appeared on the Windows app. Now that a pinned row is guaranteed to be in the page, its absence says nothing about its pin state — which makes the server row authoritative. `pullRemotePins` adopts pins this app hasn't seen and drops local pins the server says are gone, keying on the durable lineage root so a pin survives compression tip rotation. Adopted pins are recorded as already mirrored rather than echoed back as a redundant write. A list request in flight when we PATCH still carries the old value, so honouring it would silently undo the pin the user just made. Writes are guarded for the lifetime of their own request — no timers, no wall-clock windows. A runtime predating the flag sends no `pinned` at all; that's treated as no opinion and leaves the local set alone. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com> Co-authored-by: aman-merchant <274313970+aman-merchant@users.noreply.github.com> Co-authored-by: rerdi92 <76791321+rerdi92@users.noreply.github.com>
This was referenced Jul 29, 2026
OutThisLife
enabled auto-merge
July 29, 2026 17:06
Contributor
૮ >ﻌ< ა ci reviewran on 8ce8b70 ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job1 visual diff. inline evidence is publishing... |
|
nice one ty! |
This was referenced Jul 29, 2026
Open
19 tasks
Open
1 task
5 tasks
This was referenced Aug 3, 2026
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…ns-server-owned Pins are server-owned, so they survive paging and follow you between apps
33hodl
pushed a commit
to 33hodl/hermes-agent
that referenced
this pull request
Aug 12, 2026
…ns-server-owned Pins are server-owned, so they survive paging and follow you between apps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A pin means "keep this reachable." Pins were stored as localStorage strings, which made that promise unenforceable in two directions: the sidebar could only resolve a pin against the rows it happened to have loaded, and a second app on the same gateway had its own separate copy. Ten open PRs are circling one or the other half of that.
The
sessions.pinnedcolumn already exists and the desktop already mirrors pins into it. What was missing is that nothing ever read it back. This makes the server row authoritative: the list endpoints back-fill pinned conversations past their LIMIT, so a pinned row is always in the page, and the desktop reconciles against it in both directions.Once the back-fill is in place the read-back needs no new endpoint — the rows the sidebar already fetches carry the flag.
What changes
include_pinnedonlist_sessions_rich— one bounded extra query for pinned rows the window missed, reusing the page's own WHERE clause. An archived or filtered-out conversation stays out; a pin is not a filter bypass. It runs before compression projection, so a back-filled root surfaces under its live tip like any other row.The three list endpoints request the back-fill and expose
pinnedas a JSON boolean. Both merge paths re-window after sorting, so pinned rows are explicitly kept through the cap rather than re-dropped on recency, and the sidebar's "load more" signal discounts them so they can't fake a full page.pullRemotePinsadopts pins made elsewhere and drops local pins the server has released, keyed on the durable lineage root. A write in flight is guarded for the lifetime of its own request, so a list response that predates the PATCH can't silently undo the pin the user just made. A runtime without the flag sends nopinned, which is treated as no opinion.Verification
Reproduced the fail state first — pin the oldest of 30 sessions, request a 10-row page, confirm it is absent — then confirmed the flag brings it back without disturbing the page. Covered: filters still apply to a back-filled pin, no duplicate when the pin is already on the page, compression roots project to their tip, cross-app adopt/drop, and the stale-page race.
tests/test_hermes_state.py483 passed. Desktop store + sidebar/session suites 1040 passed.tsc --noEmitclean.Supersedes
Same root cause, one fix:
pinned-idsendpoint isn't needed once pinned rows are guaranteed in the page.include_ids. Same insight; the server already knows which rows are pinned, so the client needn't send the list.state.dbalready has the column.Closes #72948
Co-authored-by: Kyzcreig 9063726+Kyzcreig@users.noreply.github.com
Co-authored-by: webtecnica 75556242+webtecnica@users.noreply.github.com
Co-authored-by: hrnbld 260600092+hrnbld@users.noreply.github.com
Co-authored-by: aman-merchant 274313970+aman-merchant@users.noreply.github.com
Co-authored-by: eason2026 209090628+eason2026@users.noreply.github.com
Co-authored-by: konsisumer 11262660+konsisumer@users.noreply.github.com
Co-authored-by: rerdi92 76791321+rerdi92@users.noreply.github.com
Co-authored-by: liuhao1024 11816344+liuhao1024@users.noreply.github.com
Co-authored-by: Tamaz-sujashvili 56168197+Tamaz-sujashvili@users.noreply.github.com
Co-authored-by: ferminquant 14808645+ferminquant@users.noreply.github.com