refactor: sync session runtime lane#140
Conversation
|
Note Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported. |
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 26 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (12)
📝 WalkthroughWalkthroughAdded tail-preserving compaction that splits history into head+tail using token budgets and preserves a tail boundary ( Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Session
participant MessageStore as Message\nHistory
participant Estimator as Token\nEstimator
participant LLM as LLM\nSummarizer
Client->>Session: step() with long history
Session->>MessageStore: filterCompacted()
MessageStore-->>Session: active messages
Session->>Estimator: estimate(recent turns)
Estimator-->>Session: token counts
Session->>Session: select(head, tail_start_id) based on budget
alt tail preserved
Session->>LLM: summarize(head only)
LLM-->>Session: summary
Session->>MessageStore: store CompactionPart (summary, tail_start_id)
else fallback (no tail fit)
Session->>LLM: summarize full history
LLM-->>Session: summary
Session->>MessageStore: store CompactionPart (no tail_start_id)
end
Session-->>Client: next state includes summary + retained tail
sequenceDiagram
participant Client
participant EventBus
participant Session
participant AssistantState
Client->>EventBus: emit SessionEvent.Retried(attempt?, error)
EventBus->>Session: deliver retried event
Session->>Session: normalize error (string → RetryError if needed)
Session->>AssistantState: append AssistantRetry (attempt, error)
AssistantState-->>Client: updated assistant entry with retries
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
add7de7 to
c1c0dcc
Compare
Review SummaryThis PR introduces significant changes to the compaction system with retained recent turns and interleaved tool routing. Overall the implementation appears sound, but I have several nitpick-level concerns: Key Issues
Missing DocumentationSeveral complex logic blocks lack comments explaining their rationale:
Test Concerns
The inline comments above detail each concern. |
f36ca2d to
f88df74
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/opencode/src/session/compaction.ts`:
- Around line 369-375: The guard currently prevents clearing a persisted tail
because it requires selected.tail_start_id to be truthy; remove that truthiness
check so we compare and persist changes even when selected.tail_start_id is
undefined. Update the condition around compactionPart and the call to
session.updatePart (referencing compactionPart, selected.tail_start_id, and
session.updatePart) to simply check compactionPart &&
compactionPart.tail_start_id !== selected.tail_start_id and then call
session.updatePart with tail_start_id: selected.tail_start_id (allowing
undefined to clear the stored boundary).
In `@packages/opencode/src/session/message-v2.ts`:
- Around line 934-951: The loop currently only assigns tailStartID from
compaction parts on messages that are marked completed, which lets an older
completed compaction override a newer pending one; change the logic in the
newest→oldest scan (the block that inspects compactionPart and completed) so
that the first compactionPart you encounter with a defined tail_start_id
immediately sets tailStartID (regardless of msg.info.complete status) and stops
the scan (break/continue as appropriate), so the newest seen tail_start_id wins;
keep the existing fallback that scans result.flatMap(...).find(...) but ensure
it only runs if tailStartID is still unset.
In `@packages/opencode/test/session/compaction.test.ts`:
- Around line 695-702: The new tests create ManagedRuntime instances via
runtime("continue", Plugin.defaultLayer, ProviderTest.fake(), cfg({ prune: true
})) but never call .dispose(), leaking scoped resources; wrap each runtime usage
in a try/finally and call rt.dispose() in the finally (or use a helper that
auto-disposes) for the occurrences around the SessionCompaction.Service.use
calls (also fix the other two spans noted near the 825–840 and 882–897 regions)
to ensure each runtime is disposed after the test completes.
In `@packages/opencode/test/session/messages-pagination.test.ts`:
- Around line 714-742: Replace the raw Jest test declaration and manual Effect
runtime plumbing with the Effect test harness: change the top-level test("keeps
retained tail messages after a compaction part with tail_start_id", ...) to use
testEffect(...) (or it.live(...) as used in this file) from test/lib/effect.ts,
and lift the async body into the Effect test body so you no longer call
Instance.provide manually; keep the same interactions with svc.create,
svc.updatePart, MessageV2.stream/filterCompacted and the same assertions
(expect(ids).toContain(retained) / not.toContain(old)), and remove the explicit
Instance.provide/Instance cleanup plumbing so the harness manages the Effect
runtime and lifecycle.
- Around line 714-742: The test currently only asserts the retained user message
ID is preserved, but must also assert that its associated assistant/tool reply
is retained to ensure full-turn preservation; change the addAssistant call that
follows the retained user to capture its returned ID (e.g., const
retainedAssistant = await addAssistant(session.id, retained)) and add an
assertion expect(ids).toContain(retainedAssistant). Apply the same change for
the other similar test block (the one also noted at 744-780) so both user and
their assistant/tool message IDs are asserted present when using
MessageV2.filterCompacted(MessageV2.stream(session.id)).
In `@packages/sdk/openapi.json`:
- Around line 8918-8921: Add a clear description for the public OpenAPI schema
field "tail_start_id" so generated SDK docs are self-explanatory; update the
schema for "tail_start_id" (the property with "type": "string" and "pattern":
"^msg.*") to include a concise description like "Optional message ID (must start
with 'msg') to begin tailing events from the specified message onward" and
mention its behavior (e.g., inclusive/exclusive) if applicable.
- Around line 11545-11546: Update the OpenAPI description for the
compaction.prune property to explicitly state the behavior when set to false:
clarify that true enables pruning of old tool outputs, false disables pruning
(no automatic pruning), and omission defaults to no automatic pruning; modify
the description string for the "compaction.prune" schema entry accordingly so
generated docs and UIs unambiguously show true = prune, false = do not prune,
omitted = no automatic pruning.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: a9f64f91-a276-4a71-b8f8-1b2fe2021d3b
⛔ Files ignored due to path filters (1)
packages/sdk/js/src/v2/gen/types.gen.tsis excluded by!**/gen/**
📒 Files selected for processing (11)
packages/opencode/src/config/config.tspackages/opencode/src/session/compaction.tspackages/opencode/src/session/message-v2.tspackages/opencode/src/session/session.tspackages/opencode/src/v2/session-entry.tspackages/opencode/src/v2/session-event.tspackages/opencode/test/session/compaction.test.tspackages/opencode/test/session/messages-pagination.test.tspackages/opencode/test/session/session-entry.test.tspackages/opencode/test/session/session.test.tspackages/sdk/openapi.json
f88df74 to
c021977
Compare
Code Review Comments1.
|
Astro-Han
left a comment
There was a problem hiding this comment.
Code Review Summary
This PR introduces significant changes to session compaction behavior with a new tail retention mechanism and retry event tracking. Overall the implementation is solid, but there are several issues requiring attention.
P1 Issues (Suggest Fixing)
-
Prune semantics change (compaction.ts:133) - The shift from
prune === falsetoprune !== truesilently changes default behavior for existing users without migration guidance. -
Silent tail_turns edge case (compaction.ts:158) - Negative/zero values silently disable tail retention without logging.
-
Compaction part position assumption (compaction.ts:267) - Logic assumes compaction part is always at array end, may fail under race conditions.
-
filterCompacted iteration complexity (message-v2.ts:936) - The interaction between tail markers and compaction logic is subtle; needs explicit edge case tests.
-
z.lazy missing explanation (session.ts:261) - Circular reference fix lacks comment explaining necessity.
-
callID assumption unverified (session-entry.ts:169) - Tool routing assumes callID always exists; needs schema verification.
-
Config description lacks migration note (config.ts:258) - Users won't know behavior changed from default-enabled to opt-in.
P2 Issues (Record Only)
- Magic constants need rationale documentation
- Budget overflow may leave insufficient tail
- Re-compaction boundary update potential race
- Schema description style inconsistency
- RetryError normalization assumption questionable
- Test helper verbose type checks
- Test .live annotation possibly unnecessary
- Test title style inconsistency
P3 Issues (Optional)
- Fallback logic duplication
- RetryError optional fields lack documentation
Recommendation
Address P1 issues before merge, particularly:
- Add migration note/changelog for prune behavior change
- Add logging for edge case configurations
- Verify callID schema guarantees or add fallback handling
- Add comment explaining z.lazy necessity
6481ffe to
f486dd1
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Nitpick review. Logic works, but a few places look worth tightening. P2 items could bite on upgrade; P3 items are clarity/dead-code.
60eb047 to
d982d71
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/opencode/src/session/overflow.ts`:
- Around line 18-19: The current overflow check computes usable =
(input.model.limit.input ?? context) - reserved which can be negative when
input.model.limit.input is 0, causing count >= usable to always be true; update
the calculation in overflow.ts to clamp usable to a minimum of 0 (e.g., use
Math.max(0, ...)) so usable = Math.max(0, (input.model.limit.input ?? context) -
reserved) and then perform the existing comparison with count, referencing the
variables usable, input.model.limit.input, context, reserved and the return that
checks count >= usable.
In `@packages/opencode/src/v2/session-event.ts`:
- Around line 399-406: Change Retried.create to accept an optional attempt so
legacy callers can omit it: update the input type from BaseInput & { attempt:
number; error: RetryError | string } to BaseInput & { attempt?: number; error:
RetryError | string }, and in the factory set attempt to a safe default (e.g.
attempt: input.attempt ?? 0) when constructing the new Retried instance; update
references in the Retried.create implementation accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2a42a16d-4be5-4ddd-883e-14c6bc35f486
⛔ Files ignored due to path filters (1)
packages/sdk/js/src/v2/gen/types.gen.tsis excluded by!**/gen/**
📒 Files selected for processing (12)
packages/opencode/src/config/config.tspackages/opencode/src/session/compaction.tspackages/opencode/src/session/message-v2.tspackages/opencode/src/session/overflow.tspackages/opencode/src/session/session.tspackages/opencode/src/v2/session-entry.tspackages/opencode/src/v2/session-event.tspackages/opencode/test/session/compaction.test.tspackages/opencode/test/session/messages-pagination.test.tspackages/opencode/test/session/session-entry.test.tspackages/opencode/test/session/session.test.tspackages/sdk/openapi.json
d982d71 to
4089f23
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/opencode/src/session/compaction.ts (1)
269-380:⚠️ Potential issue | 🟠 MajorPersist
tail_start_idbefore the summary starts.
filterCompacted()now honorstail_start_idfor pending compactions, but this code only writes the marker afterprocessor.process()finishes. While the summary is still streaming or retrying, the compaction part in storage has no boundary yet, so readers will keep showing the old head instead of the retained tail. Please persist the selected boundary before starting the processor, then clear or revert it if compaction ultimately fails.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/opencode/src/session/compaction.ts` around lines 269 - 380, Move the logic that persists the tail_start_id boundary to occur before processor.process() is called instead of after. Specifically, before invoking processor.process(), check if compactionPart exists and if compactionPart.tail_start_id differs from selected.tail_start_id, then call session.updatePart() to persist the boundary. After this update, add revert logic in the error handling section where result === "compact" to undo this change by either clearing or reverting the tail_start_id on the compactionPart, ensuring the storage boundary stays in sync with actual compaction progress.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/opencode/test/session/compaction.test.ts`:
- Around line 685-727: Replace the ad-hoc test harness in the "compacts old
completed tool output when prune is explicitly enabled" test (which uses
Instance.provide, runtime(...), rt.runPromise and manual disposal) with the
existing testEffect/it.live harness: convert the test(...) into it.live(...)
from the file's testEffect(env) setup, use provideTmpdirInstance(...) instead of
Instance.provide(...) to supply the tmpdir and Instance, and run
SessionCompaction.Service.use((compact) => compact.prune({ sessionID: session.id
})) within the effect runtime provided by it.live (removing manual runtime,
cfg/prune wiring and explicit rt.dispose). Keep assertions the same and ensure
you still create the Part via svc.updatePart and then fetch svc.messages to
assert updated.state.time.compacted is set.
In `@packages/opencode/test/session/messages-pagination.test.ts`:
- Around line 718-842: The three new it.live tests use Effect.promise(async ()
=> ...) and call svc directly, which spawns separate runtimes; change each to
use Effect.gen(function* () { ... }) and yield the shared service from
SessionNs.Service (e.g., const svc = yield* SessionNs.Service) so the test
harness manages lifecycle; replace the async svc calls with yielded usage inside
the generator and remove the Promise wrapper, keeping the same test logic and
references to svc, MessageV2.filterCompacted,
addUser/addAssistant/addAssistant(summary), svc.updatePart and svc.remove.
---
Outside diff comments:
In `@packages/opencode/src/session/compaction.ts`:
- Around line 269-380: Move the logic that persists the tail_start_id boundary
to occur before processor.process() is called instead of after. Specifically,
before invoking processor.process(), check if compactionPart exists and if
compactionPart.tail_start_id differs from selected.tail_start_id, then call
session.updatePart() to persist the boundary. After this update, add revert
logic in the error handling section where result === "compact" to undo this
change by either clearing or reverting the tail_start_id on the compactionPart,
ensuring the storage boundary stays in sync with actual compaction progress.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f035580d-5a96-4f8c-847d-d50da3d6baf6
⛔ Files ignored due to path filters (1)
packages/sdk/js/src/v2/gen/types.gen.tsis excluded by!**/gen/**
📒 Files selected for processing (12)
packages/opencode/src/config/config.tspackages/opencode/src/session/compaction.tspackages/opencode/src/session/message-v2.tspackages/opencode/src/session/overflow.tspackages/opencode/src/session/session.tspackages/opencode/src/v2/session-entry.tspackages/opencode/src/v2/session-event.tspackages/opencode/test/session/compaction.test.tspackages/opencode/test/session/messages-pagination.test.tspackages/opencode/test/session/session-entry.test.tspackages/opencode/test/session/session.test.tspackages/sdk/openapi.json
4089f23 to
21beb25
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Summary
tail_start_idon compaction parts and compacted message filtering for successful summaries and pending compactions.compaction.prune, and update config, SDK, and OpenAPI metadata.callID, retry attempts are recorded with structured error context, and session error schema access is deferred withz.lazy.Why
Part of #92. This lands the session runtime slice after the Base PR #102, while keeping Runtime Resource and Desktop work out of scope.
Compatibility Notes
compaction.prune: true.retriedevents now store structured retry errors, and legacy string retry errors are still accepted and normalized while reducing session entries.Related Issue
Part of #92.
How To Verify
Latest local results after autosquash:
packages/opencodetypecheck: passpackages/sdk/jstypecheck: passgit diff --check origin/dev...HEAD: passNote: full
bun turbo typecheckcurrently fails in@opencode-ai/desktop-electronon@opencode-ai/util/file-extensionsmodule resolution. This is outside this PR diff, which does not touchpackages/desktop-electronorpackages/util.Screenshots or Recordings
Not applicable. No visible UI changes.
Checklist
dev, and my PR title and commit messages use Conventional Commits in EnglishSummary by CodeRabbit
New Features
Bug Fixes
Configuration
Tests