Files
openchamber/review-flow-implementation-plan.md
T
Bohdan Triapitsyn 1f9769a932 feat: add session review handoff flow
Introduce a desktop/web-only /handoff-review flow that generates a handoff from the active implementation session, creates or reuses a separate review session in the same directory, and links the pair through hidden OpenChamber session metadata.

Add review flow orchestration, metadata helpers, magic prompts, localized command/action labels, session metadata create/update support, and assistant message transfer actions for sending reviewer feedback back to the implementer or implementation responses back to the reviewer.

Review sessions are ordinary sessions, not child sessions. The flow avoids exposing session IDs or routing metadata to agents, hides review controls on mobile and VS Code, hides unrelated assistant actions inside review sessions, cleans up stale metadata where possible, and uses the optimistic send path so cross-session sends scroll like normal composer messages.
2026-06-07 01:22:40 +03:00

26 KiB

Review Flow Implementation Plan

Goal

Build an end-to-end OpenChamber review handoff flow that lets one session implement changes and another normal session review them, with OpenChamber metadata connecting the two sessions invisibly.

The agents must not see session IDs, metadata, linked-session wording, or routing details. They should only receive natural prompts:

  • Review session initial prompt: a handoff plus an instruction to review it.
  • Review-to-implementer prompt: another agent reviewed the changes and left feedback; resolve relevant issues.
  • Implementer-to-review prompt: the agent implementing changes responded to previous feedback; review latest state again.

The review session is not a child/subsession. It is a normal session in the same directory as the original session.

Proposed Command Name

Do not use /review, because that overlaps with OpenCode's default review command semantics.

Use /handoff-review unless we choose a shorter name before implementation.

Other acceptable names if /handoff-review feels too long:

  • /review-handoff
  • /ask-review
  • /start-review

This plan assumes /handoff-review.

Existing Code Paths To Reuse

Slash Command Routing

Relevant files:

  • packages/ui/src/sync/session-ui-store.ts
  • packages/ui/src/lib/opencode/client.ts
  • packages/ui/src/lib/magicPrompts.ts

Current behavior:

  • routeMessage(...) detects messages starting with /.
  • It checks command metadata from getDirectoryState(requestDirectory)?.command and useCommandsStore.getState().commands.
  • If the command exists, it uses optimisticSend(...) and calls opencodeClient.sendCommand(...).
  • opencodeClient.sendCommand(...) calls SDK client.session.command(...) with sessionID, command, arguments, selected model, selected agent, variant, files, and client-generated messageID.

Reuse this for invoking the handoff-generation command. The review flow should not invent a separate command transport.

Magic Prompt Registry

Relevant file:

  • packages/ui/src/lib/magicPrompts.ts

Current examples:

  • session.summary.visible
  • session.summary.instructions
  • session.review.visible
  • session.review.instructions
  • git.commit.generate.visible
  • git.commit.generate.instructions

Add new prompt entries for this flow instead of hardcoding long prompts in components/actions.

Needed prompt entries:

  • session.reviewHandoff.visible
  • session.reviewHandoff.instructions
  • session.reviewSession.visible
  • session.reviewSession.instructions if we need hidden instructions for the review session starter prompt
  • session.reviewFeedbackToImplementer.visible
  • session.implementationResponseToReviewer.visible

The two cross-session prompts must match the agreed wording closely.

Review feedback sent back to the original session:

Another agent reviewed your changes and left the feedback below.

Please review the feedback, resolve the relevant issues, and explain what you changed.

<review feedback>

Implementation response sent back to the review session:

The agent implementing the changes has responded to the previous review feedback.

Please review the latest state again and report any remaining issues.

<implementation response / latest assistant message>

Initial review session prompt should be similar to:

Please review the changes described in this handoff.

Focus on correctness, regressions, missing implementation, missing tests, and whether the implementation satisfies the stated intent. Provide concise, actionable feedback for the agent implementing the changes.

<handoff>

The handoff-generation prompt should be based on the summary command, but explicitly include the user's intent and enough implementation context for another agent to review.

Handoff Generation Concept

Relevant existing prompt:

  • session.summary.instructions in packages/ui/src/lib/magicPrompts.ts

Current summary instructions already include:

  • completed work
  • in-progress work
  • modified files and why
  • open questions and next steps
  • user requests, constraints, preferences
  • technical decisions and rationale

The new handoff prompt should keep those ideas and make intent explicit:

  • What the user wanted and why
  • What was implemented
  • What files changed and why
  • Important design choices
  • Known limitations or uncertainty
  • Validation/test status if known from the session
  • Anything the reviewer should pay special attention to

This is an implementation detail, not a risk. The prompt should be specific enough that the review agent can judge intent and implementation without needing private OpenChamber routing context.

Active Session Generation Concept

Relevant files:

  • packages/ui/src/lib/gitApi.ts
  • packages/ui/src/components/views/GitView.tsx

Current commit-message generation uses:

  • resolveSessionGenerationContext() to find current session, model, agent, and variant.
  • runStructuredGenerationInActiveSession(...) to send a visible prompt plus hidden synthetic instructions to the active session.
  • extractAssistantText(...) and JSON parsing to get output from the assistant response.

Important difference for review flow:

  • Commit generation uses client.session.prompt(...) and receives the response directly.
  • Slash commands use client.session.command(...), are effectively fire-and-forget from the UI path, and rely on SSE to populate messages/status.

For /handoff-review, prefer the visible slash command path so the original session contains the generated handoff. Then wait for the resulting assistant output through sync state. Reuse the commit-generation concepts for:

  • selected model/agent/variant resolution
  • extracting text from assistant message parts
  • forcing chat scroll if useful
  • timeout/error handling style

Create a small reusable helper for waiting for the next completed assistant text after a known user command message ID.

Session Create/Update/Delete

Relevant files:

  • packages/ui/src/lib/opencode/client.ts
  • packages/ui/src/sync/session-actions.ts
  • packages/ui/src/sync/event-reducer.ts
  • packages/ui/src/stores/useGlobalSessionsStore.ts

Current behavior:

  • opencodeClient.createSession(...) calls client.session.create(...) using the legacy OpenCode session API.
  • OpenCode supports metadata on that API, but OpenChamber currently only forwards parentID and title.
  • opencodeClient.updateSession(...) currently only forwards title and time.archived.
  • OpenCode metadata update replaces the whole metadata object. It does not deep-merge.
  • deleteSession(...) and deleteSessionInDirectory(...) optimistically remove the session, then call opencodeClient.deleteSession(...), and restore snapshots on failure.
  • event-reducer.ts replaces session objects from session.created and session.updated events.

Add metadata support here first. The review flow depends on it.

Context Panel Session Tabs

Relevant files:

  • packages/ui/src/stores/useUIStore.ts
  • packages/ui/src/components/session/sidebar/SessionNodeItem.tsx
  • packages/ui/src/components/layout/ContextPanel.tsx
  • packages/ui/src/components/chat/message/MessageBody.tsx
  • packages/ui/src/components/chat/message/parts/ToolPart.tsx

Current behavior:

  • useUIStore.openContextPanelTab(directory, tab) opens or upserts context panel tabs.
  • Chat tabs use mode: 'chat'.
  • Existing dedupe key convention for session chat tabs is session:<sessionID>.
  • Sidebar already opens a session in the side panel with:
openContextPanelTab(sessionDirectory, {
  mode: 'chat',
  dedupeKey: `session:${session.id}`,
  label: sessionTitle,
})

Reuse the same convention for opening the review session in the context panel.

Assistant Message Action Buttons

Relevant file:

  • packages/ui/src/components/chat/message/MessageBody.tsx

Current behavior:

  • AssistantMessageActionButtons renders icon-only buttons for copy, save image, and TTS.
  • The buttons use shared Button, Tooltip, and Icon components.
  • The shared icon sprite already contains arrow-left-right.

Extend this action area with an optional review-transfer action. Do not import icons directly from Remixicon.

Metadata Contract

Use a namespaced metadata object so we do not collide with user or upstream metadata.

Original session metadata:

{
  openchamber: {
    reviewSessionID: string
  }
}

Review session metadata:

{
  openchamber: {
    kind: 'review'
    originalSessionID: string
  }
}

Rules:

  • Only one review session per original session.
  • If original metadata already has openchamber.reviewSessionID, reuse that session instead of creating a new review session.
  • The review session must not have parentID set to the original session.
  • Both sessions must stay in the same directory.
  • Metadata is internal routing state only. Never include it in prompts.
  • Metadata updates must preserve unrelated metadata keys.

Recommended helpers:

type OpenChamberSessionMetadata = {
  openchamber?: {
    kind?: 'review'
    originalSessionID?: string
    reviewSessionID?: string
  }
  [key: string]: unknown
}

Helper functions should live in a focused module, for example:

  • packages/ui/src/lib/sessionReviewMetadata.ts

Functions:

  • getOpenChamberMetadata(session)
  • isReviewSession(session)
  • getOriginalSessionID(session)
  • getReviewSessionID(session)
  • withReviewSessionLink(metadata, reviewSessionID)
  • withReviewSessionMarker(metadata, originalSessionID)
  • withoutReviewSessionLink(metadata, reviewSessionID)

The helpers should clone only the metadata branch they change and preserve all unrelated metadata.

Implementation Steps

1. Load Required Skills Before Editing

When implementing this plan, load these skills before changing code:

  • ui-api-decoupling because the work changes SDK data access and session API wrapper behavior.
  • theme-system because the work adds a UI button/icon.
  • locale-ui-patterns because the work adds tooltips, aria labels, toasts, and command text.

If the final implementation touches Settings magic prompt UI, also load:

  • settings-ui-patterns

2. Add Metadata Support To OpenCode Client Wrapper

File:

  • packages/ui/src/lib/opencode/client.ts

Change createSession signature from:

async createSession(params?: { parentID?: string; title?: string }, directory?: string | null): Promise<Session>

to:

async createSession(
  params?: {
    parentID?: string
    title?: string
    metadata?: Record<string, unknown>
  },
  directory?: string | null,
): Promise<Session>

Forward metadata: params?.metadata only when it is defined.

Change updateSession patch type from:

patch: { title?: string; time?: { archived?: number | null } }

to:

patch: {
  title?: string
  metadata?: Record<string, unknown>
  time?: { archived?: number | null }
}

Forward metadata when defined.

Important: this method should still replace metadata because the upstream API replaces metadata. Do not hide this with an implicit merge here. Add merge behavior in a separate helper so call sites are explicit.

3. Make Session Types Metadata-Aware In OpenChamber

OpenCode SDK response types should include metadata in the current v2 SDK legacy Session, but verify local imports and generated types used by OpenChamber.

Files to inspect/update:

  • packages/ui/src/stores/types/sessionTypes.ts
  • Any local Session wrapper/normalizer if present
  • packages/ui/src/sync/sanitize.ts
  • packages/ui/src/sync/event-reducer.ts
  • packages/ui/src/stores/useGlobalSessionsStore.ts

Goal:

  • session.metadata should survive list, get, create, update, SSE event replacement, global sessions, and reconnect recovery.
  • Do not strip metadata in sanitation helpers.
  • Do not create new broad store subscriptions. Use leaf selectors where UI only needs review metadata for one session.

4. Add Explicit Metadata Merge Helpers

Add a helper, likely in packages/ui/src/sync/session-actions.ts or a small module imported by it:

async function patchSessionMetadata(
  sessionId: string,
  directory: string | null | undefined,
  updater: (metadata: Record<string, unknown>) => Record<string, unknown>,
): Promise<Session>

Behavior:

  1. Read the current session with opencodeClient.getSession(sessionId) using the correct directory.
  2. Read current.metadata ?? {}.
  3. Apply updater.
  4. Call opencodeClient.updateSession(sessionId, { metadata: nextMetadata }, directory).
  5. Upsert the returned session into useGlobalSessionsStore and the relevant child store if needed.

Do not swallow fetch/update errors. Callers need to know if metadata linkage failed.

5. Add Review Flow Magic Prompts

File:

  • packages/ui/src/lib/magicPrompts.ts

Add these prompt records:

  1. session.reviewHandoff.visible

Suggested template:

Prepare a handoff for another agent to review this work.
  1. session.reviewHandoff.instructions

Suggested template:

Produce a review handoff for another agent. Do not compact or mutate session history. Your output is an assistant message that OpenChamber will send to a separate reviewer agent.

Include:
- The user's original intent and any later clarifications that changed the intent
- What was implemented and why
- Files changed, with brief purpose per file
- Important design decisions and tradeoffs
- Validation/tests run, if known
- Known gaps, uncertainty, or areas the reviewer should inspect closely

Formatting:
- Concise markdown with clear sections
- No preamble like "Here is a handoff"
- Do not mention OpenChamber metadata, linked sessions, session IDs, or routing
- Respond in the same language the user used most in the conversation
  1. session.reviewSession.visible

Suggested template with {{handoff}} placeholder:

Please review the changes described in this handoff.

Focus on correctness, regressions, missing implementation, missing tests, and whether the implementation satisfies the stated intent. Provide concise, actionable feedback for the agent implementing the changes.

{{handoff}}
  1. session.reviewFeedbackToImplementer.visible

Suggested template with {{review_feedback}} placeholder:

Another agent reviewed your changes and left the feedback below.

Please review the feedback, resolve the relevant issues, and explain what you changed.

{{review_feedback}}
  1. session.implementationResponseToReviewer.visible

Suggested template with {{implementation_response}} placeholder:

The agent implementing the changes has responded to the previous review feedback.

Please review the latest state again and report any remaining issues.

{{implementation_response}}

6. Add Localized UI Strings

Files:

  • packages/ui/src/lib/i18n/messages/en.ts
  • Other locale files as required by the project pattern

Add strings for:

  • Command autocomplete description for /handoff-review.
  • Review flow button aria label on review session: “Send review feedback to implementing agent”.
  • Review flow button aria label on original session: “Send implementation response to reviewing agent”.
  • Tooltip text for both directions.
  • Toasts for starting handoff generation, review session creation/reuse, transfer success, transfer failure, missing linked session, missing assistant text.

Follow locale-ui-patterns. Do not hardcode user-facing text inside components.

7. Register The New OpenChamber Slash Command

There are two possible implementation paths. Pick the one matching how OpenChamber-owned commands are currently registered.

Likely locations:

  • packages/ui/src/lib/magicPrompts.ts
  • command autocomplete/store code around useCommandsStore
  • command rendering in ChatInput / command autocomplete components

The command should appear as /handoff-review in OpenChamber command autocomplete.

It should be treated as an OpenChamber flow command, not only a raw OpenCode command, because after the handoff assistant output completes OpenChamber must create/reuse/open/send to the review session.

Implementation options:

  1. Intercept /handoff-review in routeMessage(...) before normal OpenCode command lookup.
  2. Add it to the command store as an OpenChamber-owned command with a handler.

Prefer the smallest approach consistent with existing command architecture.

8. Implement Handoff Generation And Wait Helper

Add a helper that starts the handoff command in the original session and resolves with the assistant handoff text.

Possible module:

  • packages/ui/src/lib/reviewFlow.ts

Inputs:

{
  originalSessionID: string
  directory: string
  providerID: string
  modelID: string
  agent?: string
  variant?: string
}

Flow:

  1. Render session.reviewHandoff.visible and session.reviewHandoff.instructions.
  2. Send a user message to the original session using opencodeClient.sendMessage(...) or the existing command route, depending on final command integration.
  3. Include the visible handoff request as the visible user text.
  4. Include hidden instructions as synthetic additional part if using sendMessage(...).
  5. Capture the generated user message ID.
  6. Wait until a later assistant message for the same session is complete and has text.
  7. Extract text with the same idea as flattenAssistantTextParts(...) / extractAssistantText(...).
  8. Timeout with a clear failure if no handoff arrives.

Waiting rules:

  • Prefer sync store state over polling the server repeatedly.
  • Use existing getSyncMessages(sessionID) and getSyncParts(sessionID) from sync-refs if they expose enough data.
  • If a subscription-based wait is not easy, use a bounded interval that reads sync refs and stops on timeout or completion.
  • Ensure it waits for assistant completion, not just first streaming text.
  • Avoid broad store subscriptions in React components.

9. Create Or Reuse The Review Session

After handoff text is available:

  1. Read original session with opencodeClient.getSession(originalSessionID).
  2. Read original.metadata.openchamber.reviewSessionID.
  3. If it exists:
    • Try to get that review session in the same directory.
    • If it exists and has metadata.openchamber.kind === 'review', reuse it.
    • If it is missing/deleted, clear the stale link and create a new review session.
  4. If it does not exist, create a new normal session in the same directory with metadata:
{
  openchamber: {
    kind: 'review',
    originalSessionID,
  }
}
  1. Patch original session metadata with:
{
  openchamber: {
    reviewSessionID: reviewSession.id,
  }
}

Preserve unrelated metadata on both sessions.

If metadata patching original fails after creating review session, report failure clearly. Do not silently proceed with an unlinked session.

10. Send Initial Prompt To Review Session

After create/reuse:

  1. Render session.reviewSession.visible with handoff.
  2. Send it to the review session as a normal user message.
  3. Use the same provider/model/agent/variant policy as the current session unless product decision says otherwise.
  4. Do not mention session IDs or linked sessions.

Important:

  • If reusing an existing review session, still send the new handoff prompt into it.
  • Reuse does not mean “do nothing”; it means continue the same review conversation.

11. Open Review Session In Context Panel

Use:

useUIStore.getState().openContextPanelTab(directory, {
  mode: 'chat',
  dedupeKey: `session:${reviewSession.id}`,
  label: reviewSession.title,
})

This should happen after the review session exists and the initial prompt has been sent, or immediately after creation if sending happens asynchronously but errors are still surfaced.

12. Add Cross-Session Transfer Button On Assistant Messages

File:

  • packages/ui/src/components/chat/message/MessageBody.tsx

Add optional props to AssistantMessageActionButtons:

reviewTransferAction?: {
  ariaLabel: string
  tooltip: string
  disabled?: boolean
  onClick: () => Promise<void> | void
}

Render an icon-only button with:

<Icon name="arrow-left-right" ... />

Visibility rules:

  • Only assistant messages.
  • Only messages with copyable text.
  • In a review session: show button to send review feedback to the original session.
  • In an original session with metadata.openchamber.reviewSessionID: show button to send implementation response to the review session.
  • Do not show in mini-chat if that surface should avoid extra controls; follow current action-button surface rules.

To avoid button spam:

  • Preferred first implementation: show on assistant messages where normal assistant action buttons already show.
  • Do not add the button to user messages.
  • If this feels too noisy in testing, narrow to latest completed assistant message per session as a follow-up, but not required for initial end-to-end implementation.

13. Implement Review Feedback Transfer

When clicking the button in a review session:

  1. Get current review session metadata.
  2. Resolve originalSessionID.
  3. Extract the clicked assistant message text.
  4. Render session.reviewFeedbackToImplementer.visible with review_feedback.
  5. Send it as a normal user message into the original session.
  6. Use original session directory.
  7. Optionally open/focus the original session or leave context panel as-is. The agreed behavior only requires sending.
  8. Show success/failure toast.

Message sent to the agent must be exactly natural-language feedback, not routing data.

14. Implement Implementation Response Transfer

When clicking the button in the original session:

  1. Get original session metadata.
  2. Resolve reviewSessionID.
  3. Extract the clicked assistant message text.
  4. Render session.implementationResponseToReviewer.visible with implementation_response.
  5. Send it as a normal user message into the review session.
  6. Use same directory.
  7. Open/focus the review session context panel tab, because review continuation happens there.
  8. Show success/failure toast.

15. Cleanup Metadata When Deleting Review Session

Files:

  • packages/ui/src/sync/session-actions.ts
  • packages/ui/src/lib/opencode/client.ts

Before deleting a session:

  1. Read the session being deleted.
  2. If it is a review session and has originalSessionID, read the original session.
  3. If original metadata has reviewSessionID equal to the deleted review session ID, patch original metadata to remove it.
  4. Then delete the review session.

Failure behavior:

  • If metadata cleanup fails, do not delete silently. Return failure and show the existing delete failure path/toast.
  • If original session no longer exists, continue deleting the review session; there is nothing to clean.
  • If delete fails after metadata cleanup succeeded, restore the original metadata link as part of rollback if possible. At minimum, log and surface the delete failure.

Also apply this to deleteSessionInDirectory(...).

If original metadata points to a review session that no longer exists:

  1. Patch original metadata to remove stale reviewSessionID.
  2. Create a fresh review session.
  3. Patch original metadata with the fresh review session ID.

This is not a background reconciler. It only happens when the user starts the review flow.

17. Tests

Add focused tests for helpers and flow boundaries.

Likely files:

  • New packages/ui/src/lib/sessionReviewMetadata.test.ts
  • Existing packages/ui/src/sync/session-actions.test.ts
  • Component test around MessageBody only if nearby test patterns exist

Test cases:

  1. Metadata helper marks review session without removing unrelated metadata.
  2. Metadata helper links original session without removing unrelated metadata.
  3. Metadata helper removes review link only when it matches the deleted review session ID.
  4. createSession forwards metadata to SDK client.
  5. updateSession forwards metadata to SDK client.
  6. Review flow reuses existing review session ID instead of creating another.
  7. Review flow clears stale review session ID when referenced review session is missing.
  8. Delete review session cleans original metadata before deleting.
  9. Transfer prompt for review-to-implementer contains no session ID / metadata / linked-session wording.
  10. Transfer prompt for implementer-to-reviewer contains no session ID / metadata / linked-session wording.

18. Validation

Run:

bun run type-check
bun run lint

Manual validation checklist:

  1. Start /handoff-review in a normal session.
  2. Confirm a handoff assistant message appears in original session.
  3. Confirm a normal review session is created in the same directory, not as a child.
  4. Confirm original metadata has openchamber.reviewSessionID.
  5. Confirm review metadata has openchamber.kind === 'review' and openchamber.originalSessionID.
  6. Confirm review session opens in context panel.
  7. Confirm review session receives the initial handoff review prompt.
  8. Confirm arrow-left-right appears on review assistant message actions.
  9. Click it and confirm original session receives the agreed review feedback prompt.
  10. Confirm arrow-left-right appears on original assistant message actions when original has review metadata.
  11. Click it and confirm review session receives the agreed implementation response prompt.
  12. Run /handoff-review again on the same original session and confirm it reuses the existing review session.
  13. Delete the review session and confirm original metadata link is removed.
  14. Delete original session and confirm no review cleanup crash occurs.

Non-Goals

  • Do not add a review status state machine.
  • Do not allow multiple review sessions for one original session in this first implementation.
  • Do not expose metadata, linked sessions, or session IDs to agents.
  • Do not use parent/child session relationships for this feature.
  • Do not change OpenCode core or SDK unless OpenChamber cannot access metadata from the existing SDK types.
  • Do not make a background metadata reconciler.

Main Implementation Risks To Watch While Coding

These are coding concerns, not product blockers:

  • Metadata replacement must not drop unrelated metadata.
  • Event/store sanitation must not strip metadata from session records.
  • The handoff wait helper must wait for completed assistant output, not first streaming text.
  • Cross-session sends must use the correct directory dynamically, not cached closure values.
  • Message action buttons must not subscribe broad chat rows to global session collections.
  • Delete cleanup should not delete the review session if cleanup fails in a way that would leave confusing metadata behind.