Skip to content

refactor: delegate workspace export - #522

Merged
delete-cloud merged 1 commit into
mainfrom
runtime-workspace-export-service
Jun 3, 2026
Merged

delete-cloud merged 1 commit into
mainfrom
runtime-workspace-export-service

Conversation

@delete-cloud

@delete-cloud delete-cloud commented Jun 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add RuntimeWorkspaceExportService for workspace archive export orchestration
  • delegate SessionManager.export_workspace_archive() to the service
  • preserve active-turn rejection, cloud-session validation, export-in-progress bookkeeping, thread offload, and post-export owner revalidation
  • update ADR-0058 follow-up status

Tests

  • uv run pytest tests/coding_agent/test_runtime_workspace_export_service.py -v
  • uv run pytest tests/ui/test_session_manager_public_api.py -k "export_workspace_archive" -v
  • uv run pytest tests/ui/test_session_manager_runtime.py -k "workspace_export" -v
  • uv run pytest tests/ui/test_http_server_failover.py -k "events or event_queue or owner" -v
  • uv run pytest tests/ui/test_session_manager_public_api.py -k "event_queue or close_session or clear_sessions" -v
  • uv run ruff check src/coding_agent/runs/workspace_export.py src/coding_agent/runs/init.py src/coding_agent/server/session_manager.py tests/coding_agent/test_runtime_workspace_export_service.py
  • uv run ruff format --check src/coding_agent/runs/workspace_export.py src/coding_agent/runs/init.py src/coding_agent/server/session_manager.py tests/coding_agent/test_runtime_workspace_export_service.py
  • uv run python -c "from coding_agent.runs import RuntimeWorkspaceExportService; print(RuntimeWorkspaceExportService.name)"
  • git diff --check

Notes

  • Does not change HTTP workspace transfer endpoints, archive generation, workspace cleanup, checkpoint behavior, runtime execution, daemon CLI, executor selection, or event streams.
  • Keeps concurrent idle exports allowed while still blocking turns through the existing export-in-progress guard.

Summary by CodeRabbit

  • Refactor

    • Workspace export functionality reorganized into a dedicated service with improved thread-safety and turn-based management.
    • Enhanced validation to prevent concurrent exports and ensure proper cloud-binding compatibility.
    • Improved owner revalidation after exports complete.
  • Tests

    • Added comprehensive test coverage for export scenarios, including success paths and various failure conditions.

Review Change Stack

@coderabbitai

coderabbitai Bot commented Jun 3, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Extracts workspace archive export orchestration from inline SessionManager implementation into a new RuntimeWorkspaceExportService that enforces turn-based locking, owner assertion, cloud-session validation, and pre/post-export callback bookkeeping. SessionManager delegates to this service via wiring during initialization.

Changes

Runtime Workspace Export Service Refactor

Layer / File(s) Summary
Export Service Protocol & Implementation
src/coding_agent/runs/workspace_export.py
New module defines turn-lock, owner-assertion, session-loading, and export-callback protocols, and implements RuntimeWorkspaceExportService.export_archive() that enforces sequential validation (lock → owner → load → state check → cloud binding), runs exporter in background thread, re-asserts ownership post-export, and brackets all execution with begin_export/end_export callbacks via try/finally.
Public API Exports
src/coding_agent/runs/__init__.py
Imports and re-exports RuntimeWorkspaceExportService and all related protocol types (RuntimeWorkspaceExportLock, RuntimeWorkspaceExportTask, RuntimeWorkspaceExportSession, callback aliases) to the runs package public surface.
SessionManager Wiring & Delegation
src/coding_agent/server/session_manager.py
SessionManager imports the service, wires it during __init__ with turn-lock, owner-assertion, session-loading, and begin/end-export callbacks, and refactors export_workspace_archive() to immediately delegate to RuntimeWorkspaceExportService.export_archive() instead of performing inline validation and orchestration.
Test Suite & Validation
tests/coding_agent/test_runtime_workspace_export_service.py
Async test module with lightweight fakes and a configurable service factory that records callback invocation order, validating export_archive across success, lock-rejection, session-state-rejection, cloud-only-enforcement, and cleanup-on-exception paths.
Planning & Architecture Status
.opencode/prompts/tasks/runtime-workspace-export-service-task-packet.md, docs/adr/0058-local-daemon-control-plane-executor-architecture.md
Task-packet documents the refactor implementation plan with scope and validation steps; ADR 0058 updated to record that export admission logic (cloud validation, turn guards, post-export revalidation) is delegated to the new service.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • delete-cloud/Coding-Agent-Loop#159: Both PRs refactor export turn/execution gating and bookkeeping (lock/counter, owner/cloud checks, cleanup) to coordinate workspace exports with active turns.
  • delete-cloud/Coding-Agent-Loop#118: Directly overlaps with this PR's refactor—both add owner/active-turn guards to SessionManager.export_workspace_archive(), with PR #522 extracting logic into the new service.

Poem

🐰 A service hops out of the manager's coat,
Turn-locks and revalidation now take note,
Cloud clouds are checked, threads offload with grace,
Export flows cleanly from its own safe place. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: delegating workspace export functionality from SessionManager to a new RuntimeWorkspaceExportService.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 runtime-workspace-export-service

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@delete-cloud

Copy link
Copy Markdown
Owner Author

Local fallback review because CodeRabbit has remained pending with only the auto "review in progress" comment and no completed review.

Manual P1/P2 review result: no blocking findings.

Checked:

  • RuntimeWorkspaceExportService.export_archive() preserves prior order: active turn-lock rejection, owner assertion, session load, active-session rejection, cloud binding validation, begin export, threaded exporter call, post-export owner revalidation, finally end export.
  • Export bookkeeping is still paired with finally, including exporter failure paths.
  • Concurrent idle exports remain allowed; active turns remain blocked through the existing export-in-progress guard used by RuntimeTurnAdmissionService.
  • SessionManager.export_workspace_archive() is now only a delegation wrapper.
  • No changes to HTTP workspace transfer endpoints, archive generation, workspace cleanup, checkpoint behavior, runtime execution, daemon CLI, executor selection, or event streams.

Verification run:

  • uv run pytest tests/coding_agent/test_runtime_workspace_export_service.py -v
  • uv run pytest tests/ui/test_session_manager_public_api.py -k "export_workspace_archive" -v
  • uv run pytest tests/ui/test_session_manager_runtime.py -k "workspace_export" -v
  • uv run pytest tests/ui/test_http_server_failover.py -k "events or event_queue or owner" -v
  • uv run pytest tests/ui/test_session_manager_public_api.py -k "event_queue or close_session or clear_sessions" -v
  • uv run ruff check src/coding_agent/runs/workspace_export.py src/coding_agent/runs/__init__.py src/coding_agent/server/session_manager.py tests/coding_agent/test_runtime_workspace_export_service.py
  • uv run ruff format --check src/coding_agent/runs/workspace_export.py src/coding_agent/runs/__init__.py src/coding_agent/server/session_manager.py tests/coding_agent/test_runtime_workspace_export_service.py
  • uv run python -c "from coding_agent.runs import RuntimeWorkspaceExportService; print(RuntimeWorkspaceExportService.__name__)"
  • git diff --check

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
coding-agent/.opencode/prompts/tasks/runtime-workspace-export-service-task-packet.md (1)

1-5: ⚡ Quick win

Align preserved behavior terminology with ADR completion item.

The goal lists preserved behaviors as "active-turn rejection, cloud-binding validation, concurrent idle exports, export-in-progress guards, and post-export owner revalidation." However, the corresponding ADR 0058 completion item (lines 362-365 in the other file) uses slightly different terminology: "cloud-session validation, export-in-progress bookkeeping, thread offload, and post-export owner revalidation."

Consider harmonizing these descriptions across both documents:

  • "cloud-binding validation" vs "cloud-session validation"
  • "active-turn rejection" is not mentioned in the ADR item
  • "concurrent idle exports" is not mentioned in the ADR item
  • "export-in-progress guards" vs "export-in-progress bookkeeping"
  • "thread offload" in ADR is not explicitly in the task packet goal
📝 Suggested alignment

Either update this goal to match the ADR wording, or ensure both documents use identical terminology for the same concepts. For example:

 Goal:
 Move workspace archive export orchestration out of `SessionManager` into a
-runtime/workspace service boundary while preserving active-turn rejection,
-cloud-binding validation, concurrent idle exports, export-in-progress guards,
-and post-export owner revalidation.
+runtime/workspace service boundary while preserving cloud-session validation,
+export-in-progress bookkeeping, thread offload, and post-export owner
+revalidation.

Or update the ADR item to explicitly mention all preserved behaviors from this task packet.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@coding-agent/.opencode/prompts/tasks/runtime-workspace-export-service-task-packet.md`
around lines 1 - 5, The goal description in
runtime-workspace-export-service-task-packet.md uses terminology that is
inconsistent with ADR 0058 (e.g., "cloud-binding validation" vs "cloud-session
validation", "active-turn rejection" and "concurrent idle exports" missing from
ADR, and "export-in-progress guards" vs "export-in-progress bookkeeping" while
ADR mentions "thread offload"); update the task packet so the preserved-behavior
list either matches ADR 0058 verbatim or expand ADR 0058 to include the missing
concepts—specifically reconcile "cloud-binding validation" to "cloud-session
validation", decide whether to include "active-turn rejection" and "concurrent
idle exports" in ADR or remove from the task packet, and standardize
"export-in-progress guards" to "export-in-progress bookkeeping" (and add
explicit mention of "thread offload" where intended) so both documents use
identical terminology.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@coding-agent/src/coding_agent/runs/workspace_export.py`:
- Around line 61-75: The turn state checks (turn_lock_for, turn_lock.locked(),
assert_owner, load_session, session.turn_in_progress) race with begin_export
because you never hold the session turn lock while validating and starting
export; fix by acquiring the per-session turn lock (use
turn_lock_for(session_id) as an async context or await turn_lock.acquire())
before performing the checks and calling begin_export, perform all checks
(including session.task and execution_binding) while the lock is held, then call
begin_export and only release the lock after export has been safely initiated
(ensure finally block releases the lock if you manually acquire it); update
references in this block (turn_lock_for, assert_owner, load_session,
begin_export, CloudWorkspaceBinding) accordingly.

---

Nitpick comments:
In
`@coding-agent/.opencode/prompts/tasks/runtime-workspace-export-service-task-packet.md`:
- Around line 1-5: The goal description in
runtime-workspace-export-service-task-packet.md uses terminology that is
inconsistent with ADR 0058 (e.g., "cloud-binding validation" vs "cloud-session
validation", "active-turn rejection" and "concurrent idle exports" missing from
ADR, and "export-in-progress guards" vs "export-in-progress bookkeeping" while
ADR mentions "thread offload"); update the task packet so the preserved-behavior
list either matches ADR 0058 verbatim or expand ADR 0058 to include the missing
concepts—specifically reconcile "cloud-binding validation" to "cloud-session
validation", decide whether to include "active-turn rejection" and "concurrent
idle exports" in ADR or remove from the task packet, and standardize
"export-in-progress guards" to "export-in-progress bookkeeping" (and add
explicit mention of "thread offload" where intended) so both documents use
identical terminology.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c38c7641-8a8b-4b9a-8194-85911ea75129

📥 Commits

Reviewing files that changed from the base of the PR and between 779a950 and 31437f3.

📒 Files selected for processing (6)
  • coding-agent/.opencode/prompts/tasks/runtime-workspace-export-service-task-packet.md
  • coding-agent/docs/adr/0058-local-daemon-control-plane-executor-architecture.md
  • coding-agent/src/coding_agent/runs/__init__.py
  • coding-agent/src/coding_agent/runs/workspace_export.py
  • coding-agent/src/coding_agent/server/session_manager.py
  • coding-agent/tests/coding_agent/test_runtime_workspace_export_service.py

Comment on lines +61 to +75
turn_lock = self.turn_lock_for(session_id)
if turn_lock.locked():
raise RuntimeError("turn already in progress")

await self.assert_owner(session_id)
session = await self.load_session(session_id)
if session.turn_in_progress or (
session.task is not None and not session.task.done()
):
raise RuntimeError("turn already in progress")
if not isinstance(session.execution_binding, CloudWorkspaceBinding):
raise ValueError("Workspace export requires cloud session")

self.begin_export(session_id)
try:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Close the admission race between turn checks and begin_export.

Lines 61-74 validate turn state without actually holding the session turn lock. A turn can start in that window, so export can proceed concurrently with an admitted turn.

Proposed fix
     async def export_archive(
         self,
         session_id: str,
         export_archive: RuntimeWorkspaceArchiveExporter[T],
     ) -> T:
         turn_lock = self.turn_lock_for(session_id)
         if turn_lock.locked():
             raise RuntimeError("turn already in progress")
 
-        await self.assert_owner(session_id)
-        session = await self.load_session(session_id)
-        if session.turn_in_progress or (
-            session.task is not None and not session.task.done()
-        ):
-            raise RuntimeError("turn already in progress")
-        if not isinstance(session.execution_binding, CloudWorkspaceBinding):
-            raise ValueError("Workspace export requires cloud session")
-
-        self.begin_export(session_id)
+        async with turn_lock:
+            await self.assert_owner(session_id)
+            session = await self.load_session(session_id)
+            if session.turn_in_progress or (
+                session.task is not None and not session.task.done()
+            ):
+                raise RuntimeError("turn already in progress")
+            if not isinstance(session.execution_binding, CloudWorkspaceBinding):
+                raise ValueError("Workspace export requires cloud session")
+            self.begin_export(session_id)
         try:
             result = await asyncio.to_thread(
                 export_archive,
                 session.execution_binding,
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@coding-agent/src/coding_agent/runs/workspace_export.py` around lines 61 - 75,
The turn state checks (turn_lock_for, turn_lock.locked(), assert_owner,
load_session, session.turn_in_progress) race with begin_export because you never
hold the session turn lock while validating and starting export; fix by
acquiring the per-session turn lock (use turn_lock_for(session_id) as an async
context or await turn_lock.acquire()) before performing the checks and calling
begin_export, perform all checks (including session.task and execution_binding)
while the lock is held, then call begin_export and only release the lock after
export has been safely initiated (ensure finally block releases the lock if you
manually acquire it); update references in this block (turn_lock_for,
assert_owner, load_session, begin_export, CloudWorkspaceBinding) accordingly.

@delete-cloud
delete-cloud merged commit 5896dfb into main Jun 3, 2026
1 check passed
@delete-cloud
delete-cloud deleted the runtime-workspace-export-service branch June 3, 2026 12:03
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