fix: preserve recovered workspace path (#3167)
This commit is contained in:
@@ -2175,27 +2175,17 @@ func (e *Executor) injectHandoverIfNeeded(ctx context.Context, taskID, currentSe
|
||||
// to the task root via filepath.Dir would diverge hot vs cold cwd and break
|
||||
// resume with -32002 Resource not found.
|
||||
//
|
||||
// resp.WorktreePath here already mirrors what executor_standalone.go writes
|
||||
// into metadata["worktree_path"] (= req.WorkspacePath from the env preparer),
|
||||
// which is also what becomes cmd.Dir of the agent process. So persisting it
|
||||
// as-is keeps a single source of truth.
|
||||
// resp.WorkspacePath is the lifecycle execution's actual working directory and
|
||||
// is therefore authoritative. WorktreePath remains a compatibility fallback
|
||||
// for lifecycle responses that only expose the preparer's legacy metadata.
|
||||
func computeWorkspacePath(req *LaunchAgentRequest, resp *LaunchAgentResponse) string {
|
||||
// SSH materializes repositories on the remote host. RepositoryPath still
|
||||
// identifies the source checkout on this host, so persisting it would make a
|
||||
// later sibling attach to a different remote directory. The lifecycle
|
||||
// response is the canonical remote task-directory handle.
|
||||
if req.ExecutorType == string(models.ExecutorTypeSSH) && resp.WorkspacePath != "" {
|
||||
if resp.WorkspacePath != "" {
|
||||
return resp.WorkspacePath
|
||||
}
|
||||
if resp.WorktreePath != "" {
|
||||
return resp.WorktreePath
|
||||
}
|
||||
if req.RepositoryPath != "" {
|
||||
return req.RepositoryPath
|
||||
}
|
||||
// Quick-chat sessions have no worktree/repo but the lifecycle manager
|
||||
// creates a workspace directory — use it as fallback.
|
||||
return resp.WorkspacePath
|
||||
return req.RepositoryPath
|
||||
}
|
||||
|
||||
// persistTaskEnvironment creates or updates the task environment record after a successful launch.
|
||||
|
||||
@@ -52,6 +52,7 @@ func TestResolveTaskEnvWorkspacePath(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// @covers AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.3
|
||||
t.Run("no worktree falls back to repository path", func(t *testing.T) {
|
||||
req := &LaunchAgentRequest{RepositoryPath: "/repos/myrepo"}
|
||||
resp := &LaunchAgentResponse{} // no WorktreePath
|
||||
@@ -60,6 +61,15 @@ func TestResolveTaskEnvWorkspacePath(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// @covers AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.1
|
||||
t.Run("recovered execution keeps lifecycle workspace", func(t *testing.T) {
|
||||
req := &LaunchAgentRequest{RepositoryPath: "/source/kandev"}
|
||||
resp := &LaunchAgentResponse{WorkspacePath: "/tasks/task-1/kandev"}
|
||||
if got := computeWorkspacePath(req, resp); got != "/tasks/task-1/kandev" {
|
||||
t.Fatalf("recovered workspace: want lifecycle workspace, got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SSH keeps the remote task directory over the host repository path", func(t *testing.T) {
|
||||
req := &LaunchAgentRequest{
|
||||
ExecutorType: string(models.ExecutorTypeSSH),
|
||||
|
||||
@@ -239,7 +239,10 @@ export function FileBrowserToolbar({
|
||||
{displayPath ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="min-w-0 truncate text-xs font-medium text-muted-foreground">
|
||||
<span
|
||||
className="min-w-0 truncate text-xs font-medium text-muted-foreground"
|
||||
data-testid="file-browser-workspace-path"
|
||||
>
|
||||
{displayPath}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -148,6 +148,71 @@ test.describe("Session resume (ACP mode)", () => {
|
||||
// 10. The agent should respond to the new prompt
|
||||
await session.expectChatResponseVisible("simple mock response", 1, { timeout: 30_000 });
|
||||
});
|
||||
|
||||
// @covers AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.2
|
||||
test("preserves the worktree Files path after backend restart", async ({
|
||||
testPage,
|
||||
apiClient,
|
||||
seedData,
|
||||
backend,
|
||||
}) => {
|
||||
test.setTimeout(120_000);
|
||||
|
||||
const task = await apiClient.createTaskWithAgent(
|
||||
seedData.workspaceId,
|
||||
"Worktree Path Resume Task",
|
||||
seedData.agentProfileId,
|
||||
{
|
||||
description: "/e2e:simple-message",
|
||||
workflow_id: seedData.workflowId,
|
||||
workflow_step_id: seedData.startStepId,
|
||||
repository_ids: [seedData.repositoryId],
|
||||
executor_profile_id: seedData.worktreeExecutorProfileId,
|
||||
},
|
||||
);
|
||||
const sessionId = task.session_id;
|
||||
if (!sessionId) throw new Error("createTaskWithAgent did not return a session_id");
|
||||
|
||||
const session = await openTaskSession(testPage, "Worktree Path Resume Task");
|
||||
await session.waitForChatIdle({ timeout: 30_000 });
|
||||
await waitForSessionState(apiClient, {
|
||||
taskId: task.id,
|
||||
sessionId,
|
||||
expectedState: "WAITING_FOR_INPUT",
|
||||
message: "Initial worktree session did not settle before restart",
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
const { sessions } = await apiClient.listTaskSessions(task.id);
|
||||
const initialSession = sessions.find((candidate) => candidate.id === sessionId);
|
||||
const expectedWorkspacePath = initialSession?.workspace_path ?? initialSession?.worktree_path;
|
||||
if (!expectedWorkspacePath) throw new Error("Worktree session did not expose a workspace path");
|
||||
const expectedDisplayPath = expectedWorkspacePath.replace(/^\/(?:Users|home)\/[^/]+\//, "~/");
|
||||
|
||||
await session.clickTab("Files");
|
||||
const visibleWorkspacePath = session.files.getByTestId("file-browser-workspace-path");
|
||||
await expect(visibleWorkspacePath).toHaveText(expectedDisplayPath, { timeout: 30_000 });
|
||||
|
||||
// Keeping Files active makes reload reconstruct the workspace-only execution
|
||||
// before automatic session resume promotes and persists that execution.
|
||||
await backend.restart();
|
||||
await testPage.reload();
|
||||
await expect(session.files).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await session.clickSessionChatTab();
|
||||
await session.waitForLoad();
|
||||
await session.waitForChatIdle({ timeout: 60_000 });
|
||||
await expect(session.chat.getByText("Resumed agent Mock", { exact: false })).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
|
||||
// Reload once promotion has persisted the task environment. The boot payload
|
||||
// must project the promoted execution's canonical workspace, not stale UI state.
|
||||
await testPage.reload();
|
||||
await session.waitForLoad();
|
||||
await session.clickTab("Files");
|
||||
await expect(visibleWorkspacePath).toHaveText(expectedDisplayPath, { timeout: 30_000 });
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
created: 2026-08-30
|
||||
status: done
|
||||
requirements:
|
||||
- REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-001
|
||||
- REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002
|
||||
system_design:
|
||||
- ../../specs/tasks/system-design/additional-session-workspace-reuse.md
|
||||
legacy_specs: []
|
||||
---
|
||||
|
||||
# Implementation Plan: Recovered Workspace Path Persistence
|
||||
|
||||
## Overview
|
||||
|
||||
Preserve a Worktree task's actual execution workspace when backend restart
|
||||
recovery creates a workspace-only execution and later promotes it during agent
|
||||
resume. The work starts with a focused failing precedence regression, applies
|
||||
the smallest orchestrator correction, and then proves the user-visible Files
|
||||
path through the existing restart E2E flow.
|
||||
|
||||
The confirmed failure is:
|
||||
|
||||
1. Restart recovery reconstructs an execution at the correct worktree path.
|
||||
2. Workspace-only promotion returns that path as `WorkspacePath`, but the
|
||||
recovered metadata has no `WorktreePath`.
|
||||
3. `computeWorkspacePath` prefers the request's source `RepositoryPath` over
|
||||
the response's actual `WorkspacePath`.
|
||||
4. `persistTaskEnvironment` overwrites the correct environment path with the
|
||||
source checkout.
|
||||
5. Session reads prefer the environment path, so Files displays the source
|
||||
checkout even though agentctl still runs in the worktree.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
|
||||
- Treat the lifecycle response's execution workspace as authoritative over
|
||||
source repository input.
|
||||
- Preserve legacy fallbacks for responses without an execution workspace.
|
||||
- Add a focused Go regression that fails under the current precedence.
|
||||
- Extend the existing session-restart E2E coverage to assert that the Files
|
||||
path remains the original Worktree task path after automatic resume.
|
||||
- Add only the stable selector needed for that UI assertion.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Database schema changes or startup migrations.
|
||||
- Files layout, navigation, touch behavior, or responsive composition changes.
|
||||
- Reconstructing missing worktree inventory from filesystem state.
|
||||
- Changing the environment-first task-session API projection.
|
||||
- Direct mutation of a live user's database as part of the code change.
|
||||
|
||||
## Technical approach
|
||||
|
||||
### Workspace-path precedence
|
||||
|
||||
Update `computeWorkspacePath` in
|
||||
`apps/backend/internal/orchestrator/executor/executor_execute.go` so a non-empty
|
||||
`LaunchAgentResponse.WorkspacePath` wins. Retain `WorktreePath` as the fallback
|
||||
for legacy lifecycle responses and `LaunchAgentRequest.RepositoryPath` as the
|
||||
last repository-backed compatibility fallback.
|
||||
|
||||
The response workspace is `AgentExecution.WorkspacePath`, which is the actual
|
||||
agentctl working directory surfaced by `backendapp.lifecycleAdapter`. This
|
||||
keeps fresh launch, workspace-only promotion, SSH recovery, and quick-chat
|
||||
recovery on one authority rule.
|
||||
|
||||
### Regression boundaries
|
||||
|
||||
Add a subtest to
|
||||
`apps/backend/internal/orchestrator/executor/executor_execute_workspace_path_test.go`
|
||||
with a source repository request, an empty response `WorktreePath`, and a
|
||||
correct response `WorkspacePath`. It must expect the response workspace and
|
||||
fail before the production correction.
|
||||
|
||||
Extend
|
||||
`apps/web/e2e/tests/session/session-resume.spec.ts` with a Worktree-executor
|
||||
scenario that records the original session worktree, keeps Files active,
|
||||
restarts the backend, waits for automatic resume, and asserts the visible Files
|
||||
workspace path remains unchanged. Add a stable path selector in
|
||||
`apps/web/components/task/file-browser-toolbar.tsx`; no presentation behavior
|
||||
changes.
|
||||
|
||||
## Tests
|
||||
|
||||
- `AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.1`: the unit regression
|
||||
proves recovered execution workspace precedence over the source checkout.
|
||||
- `AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.2`: the restart E2E proves
|
||||
the Files projection remains the canonical Worktree path after promotion.
|
||||
- `AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.3`: existing repository
|
||||
fallback coverage remains green when the response has neither workspace nor
|
||||
worktree path.
|
||||
|
||||
## E2E tests
|
||||
|
||||
Add `preserves the worktree Files path after backend restart` to
|
||||
`apps/web/e2e/tests/session/session-resume.spec.ts` in the `chromium` project.
|
||||
It uses the existing `backend.restart()` fixture and session recovery page
|
||||
object patterns. The shared Files component supplies the same normalized path
|
||||
to desktop and phone; because this fix does not alter layout or interaction,
|
||||
no new mobile-only scenario is required.
|
||||
|
||||
## Work orders
|
||||
|
||||
- [x] [Task 01: Preserve recovered workspace path](task-01-preserve-recovered-workspace-path.md)
|
||||
|
||||
## Verification results
|
||||
|
||||
- RED: the new `recovered execution keeps lifecycle workspace` subtest failed
|
||||
because `computeWorkspacePath` returned `/source/kandev` instead of
|
||||
`/tasks/task-1/kandev`.
|
||||
- RED: Chromium collected the named restart scenario and failed on the absent
|
||||
`file-browser-workspace-path` selector before the selector was added. A
|
||||
DOM-coupled prototype already kept the path in the normal E2E timing; the Go
|
||||
regression is the defect-specific precedence gate.
|
||||
- `cd apps && pnpm install --frozen-lockfile` passed.
|
||||
- `cd apps/backend && go test ./internal/orchestrator/executor -run '^TestResolveTaskEnvWorkspacePath$'`
|
||||
passed with 7 tests.
|
||||
- `cd apps/backend && go test ./internal/orchestrator/executor` passed with 527
|
||||
tests.
|
||||
- `cd apps/web && pnpm e2e:run tests/session/session-resume.spec.ts -- --grep 'preserves the worktree Files path after backend restart'`
|
||||
collected and passed 1 Chromium test.
|
||||
- `cd apps/web && pnpm exec prettier --check components/task/file-browser-toolbar.tsx e2e/tests/session/session-resume.spec.ts`
|
||||
passed.
|
||||
- `cd apps/web && pnpm run typecheck` passed.
|
||||
- `python3 scripts/lint-spec-files.py --all` and `git diff --check` passed.
|
||||
|
||||
## Risks
|
||||
|
||||
- `WorkspacePath` and `WorktreePath` historically overlap. The test matrix must
|
||||
preserve legacy responses that supply only `WorktreePath`.
|
||||
- The E2E stimulus must open Files before restart so reload reconstructs the
|
||||
workspace-only execution before automatic resume promotes it.
|
||||
- Previously corrupted rows are repaired only after a successful resume under
|
||||
the corrected binary and only while canonical worktree inventory remains.
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
---
|
||||
id: "01-preserve-recovered-workspace-path"
|
||||
title: "Preserve recovered workspace path"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-001
|
||||
- REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002
|
||||
acceptance_criteria:
|
||||
- AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.1
|
||||
- AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.2
|
||||
- AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.3
|
||||
system_design:
|
||||
- ../../specs/tasks/system-design/additional-session-workspace-reuse.md
|
||||
---
|
||||
|
||||
# Task 01: Preserve Recovered Workspace Path
|
||||
|
||||
## Summary
|
||||
|
||||
Make the lifecycle execution workspace authoritative when launch or resume
|
||||
persistence selects the task environment path. Prove the correction with a
|
||||
failing unit regression and the existing browser-level backend-restart flow.
|
||||
|
||||
## In scope
|
||||
|
||||
- Add the recovered workspace-only precedence case to
|
||||
`TestResolveTaskEnvWorkspacePath` and confirm RED.
|
||||
- Change `computeWorkspacePath` to prefer the lifecycle execution workspace,
|
||||
while retaining worktree-only and source-repository fallbacks.
|
||||
- Add a stable selector for the visible Files workspace path.
|
||||
- Add a Worktree-executor restart/resume scenario to the existing session
|
||||
resume Playwright specification.
|
||||
- Record RED and final command results in this work order and `plan.md`.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Schema or data migrations.
|
||||
- Lifecycle promotion redesign or new worktree metadata requirements.
|
||||
- Frontend state derivation, layout, mobile composition, or copy changes.
|
||||
- Filesystem-based repair of missing canonical worktree inventory.
|
||||
- Manual repair of the reported live task.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- A response with the recovered execution workspace and no worktree metadata
|
||||
persists that workspace instead of the request's source repository.
|
||||
- Existing worktree-only, SSH, quick-chat, and empty-response fallbacks retain
|
||||
their intended paths.
|
||||
- After backend restart and automatic resume, the Files path shown for a
|
||||
Worktree task is unchanged from its original canonical worktree.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps && pnpm install --frozen-lockfile
|
||||
cd apps/backend && go test ./internal/orchestrator/executor -run '^TestResolveTaskEnvWorkspacePath$'
|
||||
cd apps/backend && go test ./internal/orchestrator/executor
|
||||
cd apps/web && pnpm e2e:run tests/session/session-resume.spec.ts -- --grep 'preserves the worktree Files path after backend restart'
|
||||
```
|
||||
|
||||
The new unit subtest and E2E assertion must fail for the diagnosed path mismatch
|
||||
before the production correction and pass afterward. Confirm Playwright
|
||||
collects and executes the named `chromium` test.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/backend/internal/orchestrator/executor/executor_execute.go`
|
||||
- `apps/backend/internal/orchestrator/executor/executor_execute_workspace_path_test.go`
|
||||
- `apps/web/components/task/file-browser-toolbar.tsx`
|
||||
- `apps/web/e2e/tests/session/session-resume.spec.ts`
|
||||
- `docs/plans/recovered-workspace-path-persistence/plan.md`
|
||||
- `docs/plans/recovered-workspace-path-persistence/task-01-preserve-recovered-workspace-path.md`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Risks
|
||||
|
||||
- An E2E test that reloads into Chat instead of Files may miss the
|
||||
workspace-only reconstruction and fail to exercise the production sequence.
|
||||
- Adding a selector must not change the Files toolbar's responsive structure or
|
||||
accessible name.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- `REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002` and its acceptance
|
||||
criteria.
|
||||
- `docs/specs/tasks/system-design/additional-session-workspace-reuse.md`.
|
||||
- `docs/decisions/2026-08-08-task-owned-worktree-lifetime.md`.
|
||||
- `computeWorkspacePath`, `backendapp.lifecycleAdapter.LaunchAgent`, and
|
||||
`lifecycle.Manager.promoteWorkspaceExecution`.
|
||||
- Existing restart patterns in
|
||||
`apps/web/e2e/tests/session/session-resume.spec.ts`.
|
||||
|
||||
## Results
|
||||
|
||||
- Made `LaunchAgentResponse.WorkspacePath` authoritative, with
|
||||
`WorktreePath` and `RepositoryPath` retained as legacy fallbacks.
|
||||
- Added the defect-specific Go regression for a recovered execution that has
|
||||
no worktree metadata and confirmed it failed before the production change.
|
||||
- Added the Files path selector and a Chromium restart/resume test that checks
|
||||
the original worktree path before restart and after promotion plus a fresh
|
||||
boot-payload reload.
|
||||
- The first E2E draft used a DOM relationship and stayed green before the
|
||||
backend correction under normal fixture timing. The final E2E RED was the
|
||||
missing stable selector; the Go test remains the direct regression for the
|
||||
response-precedence defect.
|
||||
- All verification commands in this work order passed. The executor package
|
||||
reported 527 passing tests, and the named Chromium run reported 1 passing
|
||||
test.
|
||||
- No mobile-only test was added because the change does not alter Files layout,
|
||||
controls, touch behavior, or responsive composition; both desktop and mobile
|
||||
render the same normalized path component.
|
||||
@@ -109,6 +109,7 @@ signals, and task-scoped scheduling contracts.
|
||||
|
||||
### System design
|
||||
|
||||
- [Additional Session Workspace Reuse](system-design/additional-session-workspace-reuse.md)
|
||||
- [Attach Workspace Sources](system-design/attach-workspace-sources.md)
|
||||
- [Quick Chat Agent Titles](system-design/quick-chat-agent-titles.md)
|
||||
- [Quick Chat Session Resumption](system-design/quick-chat-session-resumption.md)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
status: draft
|
||||
system: tasks
|
||||
created: 2026-08-19
|
||||
updated: 2026-08-30
|
||||
owners:
|
||||
- kandev
|
||||
---
|
||||
@@ -34,6 +35,24 @@ preserving independent session runtime state.
|
||||
reuse shall fail with a typed, recoverable API error without creating a
|
||||
session or replacement workspace.
|
||||
|
||||
### REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002: Canonical Workspace Identity Continuity
|
||||
|
||||
**Intent:** Keep the task's effective workspace identity stable when Kandev
|
||||
reconstructs or resumes its runtime.
|
||||
|
||||
#### Acceptance criteria
|
||||
|
||||
- **AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.1:** When Kandev recovers
|
||||
or resumes a task with a ready canonical environment, the persisted and
|
||||
projected workspace path shall remain the materialized workspace used by the
|
||||
recovered runtime.
|
||||
- **AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.2:** When the task reloads
|
||||
after recovery, Files and later attached sessions shall resolve the same
|
||||
canonical workspace; the repository's source checkout shall not replace it.
|
||||
- **AC-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002.3:** When no materialized
|
||||
or recovered runtime workspace exists, a legacy repository-backed session
|
||||
can continue to use its source checkout as a compatibility fallback.
|
||||
|
||||
## Migrated source detail
|
||||
|
||||
## Why
|
||||
@@ -111,3 +130,4 @@ an optional session name remains best effort after a successful launch.
|
||||
- A trusted filesystem read-only agent mode.
|
||||
- Automatic workspace repair, reset, branch switching, or replacement during
|
||||
session spawn.
|
||||
- Reconstructing a missing physical worktree from filesystem guesses.
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
status: current
|
||||
system: tasks
|
||||
requirements:
|
||||
- REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-001
|
||||
- REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002
|
||||
created: 2026-08-30
|
||||
owners:
|
||||
- kandev
|
||||
---
|
||||
|
||||
# Additional Session Workspace Reuse System Design
|
||||
|
||||
## Purpose and boundaries
|
||||
|
||||
The task system owns the canonical task environment, its physical worktree
|
||||
inventory, and the execution lifecycle that reuses those resources. The
|
||||
workspace system supplies repository and worktree resources, while the agent
|
||||
runtime creates the process that consumes the resolved task workspace.
|
||||
|
||||
This design preserves one effective workspace identity across initial launch,
|
||||
additional-session attachment, backend restart, workspace-only reconstruction,
|
||||
and agent promotion or resume. It follows
|
||||
[ADR-2026-08-08-task-owned-worktree-lifetime](../../../decisions/2026-08-08-task-owned-worktree-lifetime.md).
|
||||
|
||||
## Requirement mapping
|
||||
|
||||
| Requirement | Design section |
|
||||
| --- | --- |
|
||||
| `REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-001` | [Canonical environment and inventory](#canonical-environment-and-inventory) |
|
||||
| `REQ-TASKS-ADDITIONAL-SESSION-WORKSPACE-REUSE-002` | [Recovery and path authority](#recovery-and-path-authority) |
|
||||
|
||||
## Canonical environment and inventory
|
||||
|
||||
`task_environments` owns the task-level execution environment.
|
||||
`task_environment_repos` owns the ordered physical repository and worktree
|
||||
inventory. `task_sessions.task_environment_id` attaches a session to that
|
||||
environment; an execution ID or repository source path is not workspace
|
||||
identity.
|
||||
|
||||
The initial materializer publishes a ready environment and complete repository
|
||||
inventory. Additional sessions validate and attach to that environment without
|
||||
running repository preparation or creating another physical owner. Missing,
|
||||
creating, inconsistent, or unsupported environments fail through the existing
|
||||
typed reuse errors.
|
||||
|
||||
## Recovery and path authority
|
||||
|
||||
`task/service.GetWorkspaceInfoForSession` reconstructs the runtime workspace
|
||||
from the canonical environment-repository inventory. For a single repository,
|
||||
the effective workspace is its worktree. For multiple repositories, it is their
|
||||
task root. The lifecycle manager uses that value as the recovered
|
||||
`AgentExecution.WorkspacePath` and as the agentctl working directory.
|
||||
|
||||
The lifecycle adapter returns both the execution workspace and any optional
|
||||
worktree metadata. Workspace-only reconstruction can legitimately return the
|
||||
correct `WorkspacePath` without a `worktree_path` metadata key when the
|
||||
execution is promoted to an agent execution.
|
||||
|
||||
The orchestrator persists an effective workspace using this authority order:
|
||||
|
||||
1. The lifecycle response's non-empty `WorkspacePath`, which is the actual
|
||||
execution working directory.
|
||||
2. The response's non-empty `WorktreePath` for legacy adapters that do not
|
||||
expose an execution workspace.
|
||||
3. The request's `RepositoryPath` only when no materialized runtime workspace
|
||||
is available.
|
||||
|
||||
The source repository path is preparation input. It must not overwrite a
|
||||
materialized task workspace reported by the lifecycle response.
|
||||
|
||||
## Persistence and projection
|
||||
|
||||
After successful launch or resume, the orchestrator refreshes
|
||||
`task_environments.workspace_path` with the authoritative effective workspace.
|
||||
No schema migration is required. A successful resume under this contract also
|
||||
self-heals an environment path written incorrectly by the former fallback
|
||||
order, provided the canonical worktree inventory remains valid.
|
||||
|
||||
Task-session reads project the linked environment workspace before the legacy
|
||||
session-local path. The Files panel and other workspace consumers use that
|
||||
effective `workspace_path`, with `worktree_path` retained as the primary
|
||||
repository and compatibility fallback.
|
||||
|
||||
## Failure and recovery
|
||||
|
||||
- An empty execution workspace and empty worktree path preserve the existing
|
||||
source-repository fallback for legacy or non-materialized local sessions.
|
||||
- A recovered execution workspace always wins over source checkout input,
|
||||
including when optional worktree metadata is absent.
|
||||
- Missing or ambiguous physical inventory fails closed. This path does not
|
||||
inspect the filesystem to guess a replacement owner.
|
||||
- Persistence failure follows the existing launch/resume rollback and task
|
||||
environment failure handling.
|
||||
|
||||
## Verification
|
||||
|
||||
The orchestrator unit boundary covers all workspace-path precedence cases,
|
||||
including workspace-only recovery with a source repository present. A session
|
||||
resume Playwright scenario uses the Worktree executor, restarts the backend,
|
||||
allows automatic promotion/resume, and verifies that the Files path remains
|
||||
the original worktree path.
|
||||
|
||||
The Files path component is shared by desktop and phone layouts. This repair
|
||||
changes backend state normalization, not layout, navigation, scrolling, or
|
||||
touch behavior, so the focused desktop recovery scenario plus existing shared
|
||||
component coverage provides mobile parity.
|
||||
Reference in New Issue
Block a user