Skip to content

Let the owner find a session, and say so when one cannot be read - #83

Merged
heznpc merged 2 commits into
mainfrom
feat/session-inspection
Aug 21, 2026
Merged

Let the owner find a session, and say so when one cannot be read#83
heznpc merged 2 commits into
mainfrom
feat/session-inspection

Conversation

@heznpc

@heznpc heznpc commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Follow-up to 52e42d8, closing six review findings plus two bugs found while fixing them.

The browser moved to where the question is asked

inspect's only consumer was the repo-retirement screen — the one place someone is not browsing. The AI 세션 screen, where the original complaint came from, still had no way to find a conversation. It now has one: search across workspace, tool and path, newest first, click to read.

Backed by a new scree.py sessionsmetadata-only on purpose. The listing runs over every store on the machine (3,200 entries here), and describing that many transcripts by opening them would be a content read of the whole disk on every refresh. Bodies are still read one at a time, by name, through inspect.

Editor workspace state is listed beside agent transcripts — Modore's boundary is durable local state, not conversations specifically — but only entries whose store actually keeps a transcript offer to open one. That also retires the 대화 보기 VS Code bindings were getting on the retirement screen, which opened to an empty panel.

Four failures were all rendering as "there was nothing here"

The worst possible sentence to show someone about to delete something.

Finding Fix
missing / unreadable / undecodable all collapsed to [] each carries its own status and its own sentence
failed fetch spun on 대화를 읽는 중… forever ConversationLoadState with a failure message and a retry
Turn.id was role + text.hashValue ordinal identity — role+text collides on exactly the case dedupe preserves
conversation cache keyed by path keyed by the transcript's bytes, reusing PresentationCacheKey

Two bugs found along the way

  • NameError in _file_access_binding — a stray "status": status had been pasted into a function with no such variable in scope. It would have raised the first time a file-access binding was built.
  • PresentationCacheKey was reading through the caller's URL, and URL memoises resource values per instance. A screen holds its bindings for its whole lifetime, so the key whose entire purpose is noticing changed bytes answered with the size and mtime the file had when it was first looked at. The title cache had been quietly inheriting this. Verified directly: same instance reports 8 bytes after the file grew to 16; a fresh instance reports 16.

Docs corrected too — README and the module docstring both said three commands read inside a conversation while listing four.

Verification

  • Python 624 · Modore 238 · MothballCore 120, all green (swift test -Xswiftc -warnings-as-errors -Xswiftc -strict-concurrency=complete).
  • Swift tests added for every finding above — 52e42d8 shipped Python-only tests, which is why the ordinal and load-state bugs passed CI untouched.
  • release_smoke.py clean; no local user paths in packaged sources.
  • Exercised end to end against this machine's real stores through the app's own -c wrapper invocation path: index → pick a Codex session → inspect returns status: ok, ordinals [0,1,2], masked: true; a missing path returns status: missing.
  • App builds, signs, and launches. The browser UI itself was not driven interactively — screen-control access was declined this session, so the UI is verified by unit tests and by the CLI path behind it, not by clicking it.

heznpc added 2 commits August 22, 2026 01:06
Two complaints about the same screen, and they turned out to be one
thing: the page was a scree GUI, not Modore's judgment.

It opened with "도구별 세션 저장소 — Codex 2,886개". That number is
coverage, not a finding; nobody arrives at this screen asking how many
sessions a store holds, and the answer prompts "so which of the 2,886 is
the problem?". The order is inverted now — protected worktrees, then
what is about to expire, then lineage — and the store table is a footer
disclosure reading "Codex · Claude · Gemini · VS Code · Kiro — 5개
저장소 · 3302개 기록 확인". Verdict, then evidence, then inventory.

And Modore could not show a conversation it already had on disk. Three
commands read transcript bodies -- `bind --deep` to prove a session
touched a workspace, `title` to name it, `preserve` to export it -- and
none let the owner simply look. `scree.py inspect` is the fourth: one
named session, masked, per-turn capped, recent window plus the opening
request, subagents not opened. It appears in the app as 대화 보기 on a
session row, beside 보존 for the full masked export.

The rule that made the earlier restraint sound was never
"metadata-only product" -- it was **metadata-only judgment**, and that
is unchanged: no judgment path calls inspect, pinned by a test asserting
the report and bind outputs carry none of its keys.

Reading real sessions through it found a decoder bug immediately. Codex
records one reply twice, as `response_item/message` and again as
`event_msg/agent_message`, so every agent turn rendered doubled -- 16 of
41 turns on a live rollout were a repeat of the line before. Consecutive
identical turns collapse now, which cannot merge a genuine repeat
because those are separated by a reply. `developer` and `system` turns
are dropped for a different reason: they are the harness talking to the
agent, they are the longest thing in a Codex rollout, and they pushed
the actual exchange off the screen.

Verified in the running app: the reordered page, the footer coverage
line, and a real conversation rendering inline with speakers separated
and text selectable. The dedupe is verified against live rollouts
(41 turns to 21) and pinned by regression tests; it was not re-checked
on screen after the fix.
The viewer landed on the retirement screen, which is the one place
someone is not browsing -- they are deciding about a repo. The screen
the complaint came from still had no way to find a conversation, so
this puts the browser where the question is asked: search across
workspace, tool and path, most recent first, click to read.

The listing is metadata-only on purpose. It runs over every store on
the machine, and describing thousands of transcripts by opening them
would be a content read of the whole disk on every refresh. Bodies are
still read one at a time, by name, through `inspect`. Editor workspace
state is listed beside agent transcripts -- Modore's boundary is
durable local state, not conversations specifically -- but only entries
whose store actually keeps a transcript offer to open one, which also
retires the "대화 보기" that VS Code bindings were getting on the
retirement screen and that opened to an empty panel.

Four failures were all rendering as "there was nothing here", which is
the worst possible sentence to put in front of someone about to delete
something:

- a transcript that vanished, one that cannot be read, and one this
  build cannot decode now each carry their own status and their own
  sentence, instead of collapsing into an empty conversation;
- a fetch that fails leaves a failed state and a retry, instead of
  spinning on "대화를 읽는 중…" forever;
- turns are identified by ordinal, not by role plus text, which
  collided on exactly the case the dedupe rule preserves -- the same
  person saying the same thing twice with a reply between;
- the conversation cache is keyed by the transcript's bytes, so a
  session an agent is still appending to is a miss rather than a
  silently stale panel.

That last one exposed the same bug one level down: PresentationCacheKey
read resource values through the caller's URL, and URL memoises them per
instance. A screen holds its bindings for its whole lifetime, so the key
whose entire purpose is noticing changed bytes was answering with the
size and mtime the file had when it was first looked at. The title cache
had been quietly inheriting that too.

Also removes a stray `status` reference that would have raised
NameError the first time a file-access binding was built, and corrects
README and the module docstring, which both said three commands read
inside a conversation while listing four.

Python 624 · Modore 238 · MothballCore 120. Verified end to end against
this machine's real stores through the app's own invocation path.
@heznpc
heznpc merged commit f795e99 into main Aug 21, 2026
8 checks passed
@heznpc
heznpc deleted the feat/session-inspection branch August 21, 2026 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant