Skip to content

fix(session): unify cache-only readers behind openSessionContent#562

Merged
rsnodgrass merged 1 commit intomainfrom
ryan/cache-only-readers
Apr 25, 2026
Merged

fix(session): unify cache-only readers behind openSessionContent#562
rsnodgrass merged 1 commit intomainfrom
ryan/cache-only-readers

Conversation

@rsnodgrass
Copy link
Copy Markdown
Contributor

@rsnodgrass rsnodgrass commented Apr 25, 2026

Summary

Single canonical entrypoint for reading hydrated session content from the ledger, plus a recording-time invariant comment. Closes the loop on the cache-only design started in #561.

Why

After #561, only `ox session regenerate --summary` used the cache-only resolver. Other readers (`lint`, `token-optimize`, default `regenerate`) still read raw.jsonl in-place and would fail on LFS pointer stubs (or worse, in the case of `regenerate --redact`, hydrate in-place and risk re-introducing the linkage breakage that broke 2 sessions on 2026-04-25).

This PR consolidates all ledger-side readers behind one helper carrying the CACHE-ONLY DESIGN invariant comment in full, so the next person editing this code can't miss the contract.

What changed

`cmd/ox/session_content.go:openSessionContent` — canonical resolver. Cache → in-place real content → hydrate to cache via Batch API. Returns a path that is guaranteed to hold real bytes (never a pointer).

Converted readers:

  • `session regenerate` default mode (`readSessionViaCache` wraps the resolver)
  • `session regenerate --summary` (was already using the local `resolveHydratedRawPath`; replaced with the canonical helper, removing the local copy)
  • `session lint` (single-session)
  • `session token-optimize`

`session view` already auto-hydrates correctly; daemon `session-finalize` already skips on pointer; both unchanged.

`agent_session.go` recording stop path gets an explicit invariant comment so future refactors don't start writing recording content directly to the ledger working tree.

Tests

  • `TestOpenSessionContent_PrefersCacheWhenInPlaceIsPointer` — load-bearing case: cache wins, in-place stays a pointer
  • `TestOpenSessionContent_ReturnsErrorWhenFullyDehydrated` — caller-must-hydrate fallback

Existing tests from #561 (`TestResolveContentPath_CacheOnlyDesign` × 4 subtests) still pass and continue to assert the contract at the lower-level resolver.

Test plan

  • go test ./internal/lfs/ ./internal/session/ ./internal/daemon/agentwork/ ./cmd/ox/ — all pass
  • make lint — 0 issues
  • CI green
  • After merge: run `ox session regenerate --all --force` against the team ledger to backfill the 31 sessions whose summary.md is still an LFS pointer to a failure-marker stub. No LLM cost — the default regenerate path just re-derives summary.md from the (already-restored) summary.json.

Summary by CodeRabbit

  • Chores

    • Closed four issue records in the project ledger.
  • Refactor

    • Improved session content resolution and caching mechanisms to enhance reliability and consistency across session operations.

All ledger-side session content readers now go through one canonical
entrypoint that hydrates on demand into the gitignored cache and never
writes to the in-place git-tracked path:

  cmd/ox/session_content.go:openSessionContent

The function carries the load-bearing CACHE-ONLY DESIGN comment in full,
including both failure modes from the 2026-04-25 post-mortem (LFS-linkage
break + daemon anti-entropy clobber) so the next person to touch this
code can't miss the contract.

Converted readers:
- ox session regenerate (default mode + --summary)
- ox session lint (single-session)
- ox session token-optimize
- (ox session view already auto-hydrates correctly)

Recording-time invariant comment added to agent_session.go:
state.SessionPath is the local recording cache, never the ledger path.
The recording lifecycle commits an LFS POINTER to the ledger after
uploading bytes via the Batch API. This was implicit before and is now
explicit so future refactors don't accidentally start writing recording
content directly to the ledger working tree.

Tests:
- TestOpenSessionContent_PrefersCacheWhenInPlaceIsPointer asserts the
  cache path wins when in-place is a stub, and the in-place file remains
  a pointer after resolution (the load-bearing case).
- TestOpenSessionContent_ReturnsErrorWhenFullyDehydrated covers the
  caller-must-hydrate path.

Co-Authored-By: SageOx <ox@sageox.ai>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53898aa7-9cd3-416c-9018-f90ea82af10c

📥 Commits

Reviewing files that changed from the base of the PR and between 0c364eb and dd34c55.

📒 Files selected for processing (7)
  • .beads/issues.jsonl
  • cmd/ox/agent_session.go
  • cmd/ox/session_content.go
  • cmd/ox/session_content_test.go
  • cmd/ox/session_lint.go
  • cmd/ox/session_regenerate.go
  • cmd/ox/session_token_optimize.go

📝 Walkthrough

Walkthrough

The PR introduces a centralized cache-only session content accessor (openSessionContent) that enforces LFS pointer semantics and refactors multiple session commands (lint, regenerate, token_optimize) to use it, ensuring pointers remain in the ledger while actual content is hydrated from the local cache.

Changes

Cohort / File(s) Summary
Session Content Resolution
cmd/ox/session_content.go
New function openSessionContent implements cache-only hydration semantics: resolves session content from cache via LFS, falling back to ledger hydration if unresolved, ensuring full content never resides in-place as LFS pointers.
Session Content Tests
cmd/ox/session_content_test.go
Two test cases verify resolver behavior: one confirms cache preference when both in-place and cache contain the file, another confirms resolver returns empty when content is fully dehydrated.
Session Commands Refactored
cmd/ox/session_lint.go, cmd/ox/session_regenerate.go, cmd/ox/session_token_optimize.go
Commands updated to delegate content resolution to openSessionContent instead of direct file checks or manual pointer detection, consolidating cache-only invariant enforcement.
Agent Session Documentation
cmd/ox/agent_session.go
Expanded comment clarifies invariant: SessionPath is local cache; only LFS pointers committed to ledger; full content hydrated via openSessionContent.
Issue Tracking
.beads/issues.jsonl
Four issues (ox-91sl, ox-4ncz, ox-o45g, ox-5cc9) marked closed with closure metadata.

Sequence Diagram(s)

sequenceDiagram
    participant Cmd as Session Command
    participant OSC as openSessionContent
    participant Cache as Local Cache
    participant LFS as LFS Resolver
    participant Ledger as Ledger Sessions

    Cmd->>OSC: openSessionContent(projectRoot, ledgerPath, sessionName, filename)
    OSC->>Cache: Build cache directory path
    OSC->>LFS: ResolveContentPath(cache path)
    LFS-->>OSC: real content path OR empty
    
    alt Content Found in Cache
        OSC-->>Cmd: return cache path
    else Content Not in Cache
        OSC->>Ledger: hydrateFromLedger(ledger sessions dir, quiet mode)
        OSC->>LFS: Re-attempt ResolveContentPath(cache path)
        LFS-->>OSC: real content path OR empty
        
        alt Hydration Successful
            OSC-->>Cmd: return hydrated cache path
        else Hydration Failed/Missing
            OSC-->>Cmd: error (hydration complete but content unresolved)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • galexy

Poem

🐰 Hops through the ledger with pointers so neat,
Cache hydrates content—no LFS deceit!
Three commands now unified, semantics so clean,
The finest resolver you've ever seen. 🌱

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ryan/cache-only-readers

Comment @coderabbitai help to get the list of available commands and usage tips.

@rsnodgrass rsnodgrass marked this pull request as ready for review April 25, 2026 18:49
@rsnodgrass rsnodgrass merged commit 070960d into main Apr 25, 2026
2 of 3 checks passed
@rsnodgrass rsnodgrass deleted the ryan/cache-only-readers branch April 25, 2026 18:50
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