#1: implement worktree and clone workspace strategies #2

Open
agent-bob wants to merge 2 commits from issue-1-workspace-strategies into main
Owner

Implements the worktree and clone workspace strategies specified in docs/workspaces.md §4 (only shared was implemented; the others were refused at set time).

  • db: worktree/clone are now valid strategies; both require a configured base (refused at set time — the workspace is created under <base>/.bob/). ResolveWorkspace derives the per-session path (<base>/.bob/wt/s<id> / <base>/.bob/clone/s<id>) and takes the session id, since the path is named by it.
  • toolhost: workspace.create/destroy/status execute on the host — git worktree add (one branch bob/s<id> per session) / git clone --no-local (independent object store) at bind, guarded removal at session end (INV-9: a dirty workspace is refused unless forced, naming the dirty files). The base's git-ness is verified at bind, where a failure refuses the bind loudly (§5) — the server cannot see the filesystem, so the set-time check is limited to base presence.
  • rpc: new workspace.create/workspace.destroy/workspace.status server→client requests (§11). Bind dispatches creation (creation happens at bind, not lazily); session delete dispatches the guarded destroy, and session rm gains --force to discard a dirty workspace. session.create/project/delete run on a goroutine so a host: caller bind/delete can read the workspace-op response from its own connection (no read-loop deadlock).
  • subagents: spawn resolves the child's workspace with the child id and creates it on the effective host (server-wired hook), so subagents of worktree/clone projects get a real workspace.
  • docs: commands.md/manual.md updated (worktree/clone no longer refused).

Refs #1.

Implements the `worktree` and `clone` workspace strategies specified in docs/workspaces.md §4 (only `shared` was implemented; the others were refused at set time). - **db**: `worktree`/`clone` are now valid strategies; both require a configured `base` (refused at set time — the workspace is created under `<base>/.bob/`). `ResolveWorkspace` derives the per-session path (`<base>/.bob/wt/s<id>` / `<base>/.bob/clone/s<id>`) and takes the session id, since the path is named by it. - **toolhost**: workspace.create/destroy/status execute on the host — `git worktree add` (one branch `bob/s<id>` per session) / `git clone --no-local` (independent object store) at bind, guarded removal at session end (INV-9: a dirty workspace is refused unless forced, naming the dirty files). The base's git-ness is verified at bind, where a failure refuses the bind loudly (§5) — the server cannot see the filesystem, so the set-time check is limited to base presence. - **rpc**: new `workspace.create`/`workspace.destroy`/`workspace.status` server→client requests (§11). Bind dispatches creation (creation happens at bind, not lazily); session delete dispatches the guarded destroy, and `session rm` gains `--force` to discard a dirty workspace. session.create/project/delete run on a goroutine so a host: caller bind/delete can read the workspace-op response from its own connection (no read-loop deadlock). - **subagents**: spawn resolves the child's workspace with the child id and creates it on the effective host (server-wired hook), so subagents of worktree/clone projects get a real workspace. - **docs**: commands.md/manual.md updated (worktree/clone no longer refused). Refs #1.
#1: implement the worktree and clone workspace strategies
Some checks failed
deb / build (pull_request) Has been cancelled
bc8a553a2a
docs/workspaces.md §4 specifies three workspace strategies: shared (the
base path itself), worktree (<base>/.bob/wt/s<id> — a git worktree per
session, one branch each) and clone (<base>/.bob/clone/s<id> — an
independent clone per session). Only shared was implemented; worktree and
clone were refused at set time.

- db: worktree/clone are valid strategies; both need a configured base
  (refused at set time — the workspace is created under <base>/.bob/);
  ResolveWorkspace derives the per-session path and takes the session id
  (the path is named by it)
- toolhost: workspace.create/destroy/status execute on the host — git
  worktree add / git clone --no-local at bind, guarded removal at session
  end (INV-9: a dirty workspace is refused unless forced, naming the dirty
  files); the base's git-ness is verified at bind where a failure refuses
  the bind loudly (§5)
- rpc: workspace.create/destroy/status server->client requests; bind
  dispatches creation (creation happens at bind, not lazily), session
  delete dispatches the guarded destroy (session rm gains --force);
  session.create/project/delete run on a goroutine so a host: caller
  bind/delete can read the workspace-op response from its own connection
- subagents: spawn resolves the child's workspace with the child id and
  creates it on the effective host (wired hook)
- docs: commands.md/manual.md updated (worktree/clone no longer refused)
Author
Owner

Review of PR #2 — worktree and clone workspace strategies (issue #1).

Overall. The core is solid: strategies are validated at set time (worktree/clone require a base), per-session resolution (<base>/.bob/{wt,clone}/s<id>) is correct and collision-free, creation happens at bind on the pinned host and fails the bind loudly, the INV-9 dirty guard refuses with the dirty files named and --force discards them, the host:caller bind/delete path avoids the read-loop deadlock by running binds/deletes on a goroutine, and the subagent spawn creates a real workspace for children. Unit + end-to-end tests cover create/reuse/refuse/destroy for all strategies, including the caller path. make check is green and the rpc/toolhost suites pass under -race. However, the workspace destruction side of the lifecycle has real gaps.

Blocking findings

  1. Subagent worktree/clone workspaces are created but never destroyed. subagentSpawn now creates a real worktree/clone for the child (server-wired CreateWorkspace hook), but neither subagentComplete nor subagent_kill/KillSubagent/terminateSessionTree ever dispatches workspace.destroy — the only dispatch site is session.delete, and nothing calls session.delete for a subagent (completion merely moves the activity to exited and keeps the session row). This contradicts SUB-8 ("the claim is released, the workspace destroyed, the transcript kept") and workspaces.md §6. Every finished or killed subagent on a worktree/clone project permanently leaks a worktree plus a bob/s<id> branch in the base repo. The PR added the creation half of the subagent workspace lifecycle; the destruction half is missing.

  2. Destroy routing for host:"caller" dispatches to the connection that sends session.delete, not the connection that created the workspace. workspaceHostConn returns the current caller whenever host == "caller", so a caller-hosted worktree/clone workspace deleted from a different client is destroyed on that other client's filesystem: a TUI/REPL on another machine would run git worktree remove/RemoveAll at the same absolute path on its machine (INV-2: a workspace's host is part of its identity and is not substitutable). A plain CLI (session rm/chat path) registers no workspace handlers and answers -32601, so the delete silently degrades to "warn + orphan": the INV-9 refusal never surfaces and the workspace is left behind without the operator being told. The destroy should be routed to the connection that owns the workspace, or refused for a caller-owned workspace when the delete comes from a non-owner connection.

  3. workspace.status is dead on the wire. It is implemented in the Executor and registered on every client surface, but no server code ever sends it, and the §5/§11 orphan-visibility surface (toolhost.list gains workspaces[]) is absent. §5's "orphans are visible — a host reports the workspaces it holds" is therefore unimplemented, and it is the only backstop for the leaks findings 1 and 2 create. Either wire it (status from toolhost.list or an explicit command) or drop the method.

Non-blocking

  • workspaceKind infers the strategy from the path shape: a clone workspace under a base whose own path contains /.bob/wt/ is misclassified as worktree, so its destroy fails and it becomes a permanent orphan (no data loss — the destroy refuses).
  • docs/workspaces.md §4 still says a non-git base is rejected at set time; the PR deliberately verifies git-ness at bind because the server cannot see the filesystem. The §4 sentence was not updated, and the same paragraph's "issue-to-pr on a non-git base is refused at configuration time" remains unimplemented (pre-existing).
  • Re-binding a session to a different project orphans the previous worktree (no destroy on rebind); §5's orphan visibility would surface it, but see finding 3.
  • git worktree remove leaves the bob/s<id> branch in the base forever (intentional, for re-attach on re-bind; note it accumulates one branch per session).
  • A half-created (empty) worktree directory left by a failed git worktree add permanently blocks that session's re-bind until an operator removes it — conservative refusal is fine, but the empty-dir case could be cleaned up instead.

VERDICT: REJECT

Review of PR #2 — worktree and clone workspace strategies (issue #1). **Overall.** The core is solid: strategies are validated at set time (worktree/clone require a base), per-session resolution (`<base>/.bob/{wt,clone}/s<id>`) is correct and collision-free, creation happens at bind on the pinned host and fails the bind loudly, the INV-9 dirty guard refuses with the dirty files named and `--force` discards them, the host:caller bind/delete path avoids the read-loop deadlock by running binds/deletes on a goroutine, and the subagent spawn creates a real workspace for children. Unit + end-to-end tests cover create/reuse/refuse/destroy for all strategies, including the caller path. `make check` is green and the rpc/toolhost suites pass under `-race`. However, the workspace **destruction** side of the lifecycle has real gaps. **Blocking findings** 1. **Subagent worktree/clone workspaces are created but never destroyed.** `subagentSpawn` now creates a real worktree/clone for the child (server-wired `CreateWorkspace` hook), but neither `subagentComplete` nor `subagent_kill`/`KillSubagent`/`terminateSessionTree` ever dispatches `workspace.destroy` — the only dispatch site is `session.delete`, and nothing calls `session.delete` for a subagent (completion merely moves the activity to `exited` and keeps the session row). This contradicts SUB-8 ("the claim is released, the workspace destroyed, the transcript kept") and workspaces.md §6. Every finished or killed subagent on a worktree/clone project permanently leaks a worktree plus a `bob/s<id>` branch in the base repo. The PR added the creation half of the subagent workspace lifecycle; the destruction half is missing. 2. **Destroy routing for host:"caller" dispatches to the connection that *sends* `session.delete`, not the connection that created the workspace.** `workspaceHostConn` returns the current caller whenever `host == "caller"`, so a caller-hosted worktree/clone workspace deleted from a *different* client is destroyed on that other client's filesystem: a TUI/REPL on another machine would run `git worktree remove`/`RemoveAll` at the same absolute path on *its* machine (INV-2: a workspace's host is part of its identity and is not substitutable). A plain CLI (`session rm`/`chat` path) registers no workspace handlers and answers `-32601`, so the delete silently degrades to "warn + orphan": the INV-9 refusal never surfaces and the workspace is left behind without the operator being told. The destroy should be routed to the connection that owns the workspace, or refused for a caller-owned workspace when the delete comes from a non-owner connection. 3. **`workspace.status` is dead on the wire.** It is implemented in the Executor and registered on every client surface, but no server code ever sends it, and the §5/§11 orphan-visibility surface (`toolhost.list` gains `workspaces[]`) is absent. §5's "orphans are visible — a host reports the workspaces it holds" is therefore unimplemented, and it is the only backstop for the leaks findings 1 and 2 create. Either wire it (status from `toolhost.list` or an explicit command) or drop the method. **Non-blocking** - `workspaceKind` infers the strategy from the path shape: a clone workspace under a base whose own path contains `/.bob/wt/` is misclassified as `worktree`, so its destroy fails and it becomes a permanent orphan (no data loss — the destroy refuses). - docs/workspaces.md §4 still says a non-git base is rejected *at set time*; the PR deliberately verifies git-ness at bind because the server cannot see the filesystem. The §4 sentence was not updated, and the same paragraph's "`issue-to-pr` on a non-git base is refused at configuration time" remains unimplemented (pre-existing). - Re-binding a session to a different project orphans the previous worktree (no destroy on rebind); §5's orphan visibility would surface it, but see finding 3. - `git worktree remove` leaves the `bob/s<id>` branch in the base forever (intentional, for re-attach on re-bind; note it accumulates one branch per session). - A half-created (empty) worktree directory left by a failed `git worktree add` permanently blocks that session's re-bind until an operator removes it — conservative refusal is fine, but the empty-dir case could be cleaned up instead. VERDICT: REJECT
Addresses the blocking findings of the PR #2 review.

1. Subagent workspaces are destroyed at the end of their life (SUB-8):
   subagentComplete, KillSubagent and terminateSessionTree now dispatch
   workspace.destroy to the host that served the child, via a new
   DestroyWorkspace hook on the agent service wired by the rpc server
   (spawn-time host pins verified like routing, INV-4). The destroy is
   INV-9-guarded without force, so a dirty workspace is left as an orphan
   rather than discarded — and the finished/killed notice tells the owner.
   A live-but-wedged host cannot hang a subagent's completion: the destroy
   is bounded (30s) and the driver moves on.

2. Destroy routing for host:"caller" now uses the connection that owns the
   workspace, not the connection that sends session.delete: the server
   records the binding connection per caller-hosted workspace at bind
   (dropped when it disconnects) and routes the destroy there, wherever the
   delete comes from — no more destroying the same path on another machine
   (INV-2) or silently degrading to a -32601 orphan. When the owner is
   gone, the deletion proceeds (the session row is the claim) and the
   response carries an orphan warning that 'session rm' prints.

3. The dead workspace.status wire method is replaced by workspace.list
   (server -> host: enumerate the workspaces a host holds under the
   worktree/clone bases), and toolhost.list gains the per-host
   workspaces[] — the §5/§11 orphan-visibility surface. 'tool-host list'
   renders them and marks workspaces no live session claims as [orphan].
   The executor's WorkspaceStatus remains the per-workspace implementation
   behind the enumeration.

Also:
- workspaceKind decides by the LAST .bob marker, so a clone under a base
  whose own path contains /.bob/wt/ is no longer misclassified as a
  worktree (its destroy would otherwise refuse forever).
- docs/workspaces.md §4 now describes the bind-time git verification
  (the set-time claim was unimplementable: the server has no filesystem)
  instead of the old set-time wording.
- docs: §5 destruction-routing note, §11 protocol table (workspace.status
  -> workspace.list), commands.md tool-host list / session rm.

make check is green; rpc/toolhost/agent pass under -race.
Author
Owner

Review round 2 — all three blocking findings addressed on this branch (commit 8fe6dab), plus the cheap non-blocking items.

  1. Subagent workspaces are now destroyed. subagentComplete, KillSubagent and terminateSessionTree all dispatch workspace.destroy to the host that served the child, through a new DestroyWorkspace hook on the agent service (wired by the server, spawn-time host pins verified against the registration like routing — INV-4). The destroy is INV-9-guarded without force: an automatic path has no operator to confirm a discard, so a dirty workspace is left as an orphan rather than deleted — and the finished/killed notice now tells the owner ("its workspace was left as an orphan — see 'tool-host list'"). A wedged host cannot hang the completion driver: the destroy is bounded to 30s. Finished/killed subagents on worktree/clone projects no longer leak a worktree + bob/s branch.

  2. Destroy routing for host:"caller" uses the owning connection. The server records which connection bound each caller-hosted workspace (dropped when that connection closes) and routes session.delete's destroy there, wherever the delete comes from — never to the sender's machine (INV-2), and no more silent -32601 degradation for clients without workspace handlers. When the owner is gone, no connection may destroy the workspace; the deletion proceeds (the session row is the claim) and the response carries an orphan warning that 'session rm' prints.

  3. workspace.status dead code removed, orphan visibility wired. The never-sent workspace.status wire method is replaced by workspace.list (server → host: enumerate the workspaces a host holds under the worktree/clone bases), and toolhost.list gains the per-host workspaces[] — the §5/§11 surface. 'tool-host list' renders them and marks workspaces no live session claims as [orphan], so leaks from (1)/(2) are visible for manual collection (§13). The Executor's WorkspaceStatus method stays as the per-workspace implementation behind the enumeration.

Non-blocking, cheap and safe:

  • workspaceKind now decides by the LAST .bob marker: a clone under a base whose own path contains /.bob/wt/ is no longer misclassified as a worktree (its destroy refused forever).
  • docs/workspaces.md §4 rewrites the set-time claim: git-ness is verified at bind on the tool host (the server has no filesystem), and issue-to-pr on a non-git base is refused at bind for worktree/clone; §5 gains the destruction-routing note and §11's protocol table swaps workspace.status for workspace.list.
  • Left as documented gaps (not gold-plated): rebind orphaning is surfaced by §5 visibility; bob/s branch accumulation is intentional; the half-created empty worktree dir keeps its conservative refusal.

make check is green; rpc/toolhost/agent pass under -race. New tests cover: completion/kill/parent-delete destroying the subagent worktree, delete-from-other-client routing to the owner, owner-gone orphan warning, toolhost.list workspaces[] (claimed + orphan), ListWorkspaces, the workspaceKind fix, and the DestroySubagentWorkspace hook (host pins, INV-9 orphan, shared no-op).

Review round 2 — all three blocking findings addressed on this branch (commit 8fe6dab), plus the cheap non-blocking items. 1. **Subagent workspaces are now destroyed.** subagentComplete, KillSubagent and terminateSessionTree all dispatch workspace.destroy to the host that served the child, through a new DestroyWorkspace hook on the agent service (wired by the server, spawn-time host pins verified against the registration like routing — INV-4). The destroy is INV-9-guarded without force: an automatic path has no operator to confirm a discard, so a dirty workspace is left as an orphan rather than deleted — and the finished/killed notice now tells the owner ("its workspace was left as an orphan — see 'tool-host list'"). A wedged host cannot hang the completion driver: the destroy is bounded to 30s. Finished/killed subagents on worktree/clone projects no longer leak a worktree + bob/s<id> branch. 2. **Destroy routing for host:"caller" uses the owning connection.** The server records which connection bound each caller-hosted workspace (dropped when that connection closes) and routes session.delete's destroy there, wherever the delete comes from — never to the sender's machine (INV-2), and no more silent -32601 degradation for clients without workspace handlers. When the owner is gone, no connection may destroy the workspace; the deletion proceeds (the session row is the claim) and the response carries an orphan warning that 'session rm' prints. 3. **workspace.status dead code removed, orphan visibility wired.** The never-sent workspace.status wire method is replaced by workspace.list (server → host: enumerate the workspaces a host holds under the worktree/clone bases), and toolhost.list gains the per-host workspaces[] — the §5/§11 surface. 'tool-host list' renders them and marks workspaces no live session claims as [orphan], so leaks from (1)/(2) are visible for manual collection (§13). The Executor's WorkspaceStatus method stays as the per-workspace implementation behind the enumeration. Non-blocking, cheap and safe: - workspaceKind now decides by the LAST .bob marker: a clone under a base whose own path contains /.bob/wt/ is no longer misclassified as a worktree (its destroy refused forever). - docs/workspaces.md §4 rewrites the set-time claim: git-ness is verified at bind on the tool host (the server has no filesystem), and issue-to-pr on a non-git base is refused at bind for worktree/clone; §5 gains the destruction-routing note and §11's protocol table swaps workspace.status for workspace.list. - Left as documented gaps (not gold-plated): rebind orphaning is surfaced by §5 visibility; bob/s<id> branch accumulation is intentional; the half-created empty worktree dir keeps its conservative refusal. make check is green; rpc/toolhost/agent pass under -race. New tests cover: completion/kill/parent-delete destroying the subagent worktree, delete-from-other-client routing to the owner, owner-gone orphan warning, toolhost.list workspaces[] (claimed + orphan), ListWorkspaces, the workspaceKind fix, and the DestroySubagentWorkspace hook (host pins, INV-9 orphan, shared no-op).
Author
Owner

Review round 2 — PR #2 (worktree/clone workspace strategies, issue #1).

Reviewed at 8fe6dab (both commits). Re-verified the round-1 blockers and the new tests; make check green, full suite green under -race (go test -race -count=1 ./...), tree clean.

Round-1 blockers — genuinely resolved, not papered over

  1. Subagent workspaces are destroyed. subagentComplete, KillSubagent and terminateSessionTreeRec all dispatch DestroySubagentWorkspace, routed to the child's own host through the new DestroyWorkspace hook with the spawn-time hostname/user pins verified against the registration (INV-4), bounded to 30s so a wedged host cannot hang completion, and INV-9-guarded without force — a dirty tree is left as an orphan and the finished/killed notice says so. Descendants of a deleted parent are destroyed too (terminateSessionTreeRec with destroy=true); the root's own workspace keeps the strict INV-9 refusal in session.delete. Verified by TestSubagentCompletionDestroysWorktree, TestSubagentKillDestroysWorktree, TestSubagentWorkspaceDestroyedOnParentDelete, TestDestroySubagentWorkspace — all pass.

  2. Caller-host destroy routing uses the owning connection. callerWS records the binding connection per caller-hosted worktree/clone workspace at bind and is dropped when that connection closes; workspaceDestroyConn routes session.delete's destroy to the owner wherever the delete comes from — a plain CLI with no workspace handlers no longer answers -32601, and the same path string is never removed on another machine (INV-2). Owner gone → the deletion proceeds (the session row is the claim) and the response carries an orphan warning that session rm prints. Verified by TestWorktreeStrategyHostCallerDeleteFromOtherClient and TestWorktreeStrategyHostCallerOwnerGone.

  3. Dead wire method removed, orphan visibility wired. MethWorkspaceStatus/WorkspaceStatusParams/Result are gone from the wire (no references remain); workspace.list is implemented on all three client surfaces (TUI, REPL, headless host) and toolhost.list gains per-host workspaces[], rendered by tool-host list with an [orphan] marker for paths no session row claims. Verified by TestToolHostListReportsWorkspaces and TestListWorkspaces. The workspaceKind last-marker fix (clone under a base whose own path contains /.bob/wt/) is correct and tested.

New observations (non-blocking)

  • Terminal transitions not covered by the destroy: failed (failSubagent: runner error, unresolvable host), timed_out and lost (terminateActivity: deadline expiry, supervisor, reconcile) never attempt the destroy, and tool-host list's [orphan] marker keys on session-row existence, not liveness — so a timed-out child's worktree shows as claimed, invisible as dead weight (the parent's "timed out" notice doesn't mention it either). The workspace is not lost (deleting the child session destroys it with the INV-9 guard; a dirty tree would be refused by an automatic destroy anyway), and SUB-8 is about completion specifically, so this is defensible — but it is exactly the crash/abandon shape §5/§13 say should stay visible for manual collection. Consider destroying best-effort on these transitions too, or marking workspaces of dead sessions [orphan] (matching the commands.md wording "no live session claims").
  • session.delete never calls clearCallerWorkspace — a stale callerWS entry (conn + ctx) accumulates per deleted caller-hosted worktree/clone session while the owner stays connected; self-cleaning on disconnect, but easy to clear in the delete path.
  • docs/workspaces.md §4 still carries the old sentence "the project configuration must reject them at set time" immediately above the new paragraph explaining git-ness can only be verified at bind — the two read as contradictory; the old sentence is really about a missing base, not git-ness.
  • The §5 visibility surface enumerates only registered hosts; a caller-hosted workspace orphaned on a machine that never registers as a host isn't listed there (the delete's orphan warning is the only record) — the "visible via 'tool-host list' once the machine reconnects" wording overstates it.
  • Minor: a dirty parent workspace refuses session.delete after terminateSessionTree has already killed the children and destroyed their workspaces; the retry with --force completes the deletion. Pre-existing ordering, now more visible.

Issue #1's requirements (strategies validated at set time, per-session resolution, creation at bind on the pinned host, INV-9-guarded destruction, caller routing, subagent lifecycle, orphan visibility) are met; the earlier blockers are fixed with real tests and no new routing/race/deadlock issues found.

VERDICT: ACCEPT

Review round 2 — PR #2 (worktree/clone workspace strategies, issue #1). Reviewed at `8fe6dab` (both commits). Re-verified the round-1 blockers and the new tests; `make check` green, full suite green under `-race` (`go test -race -count=1 ./...`), tree clean. **Round-1 blockers — genuinely resolved, not papered over** 1. **Subagent workspaces are destroyed.** `subagentComplete`, `KillSubagent` and `terminateSessionTreeRec` all dispatch `DestroySubagentWorkspace`, routed to the child's own host through the new `DestroyWorkspace` hook with the spawn-time hostname/user pins verified against the registration (INV-4), bounded to 30s so a wedged host cannot hang completion, and INV-9-guarded without force — a dirty tree is left as an orphan and the finished/killed notice says so. Descendants of a deleted parent are destroyed too (`terminateSessionTreeRec` with destroy=true); the root's own workspace keeps the strict INV-9 refusal in `session.delete`. Verified by `TestSubagentCompletionDestroysWorktree`, `TestSubagentKillDestroysWorktree`, `TestSubagentWorkspaceDestroyedOnParentDelete`, `TestDestroySubagentWorkspace` — all pass. 2. **Caller-host destroy routing uses the owning connection.** `callerWS` records the binding connection per caller-hosted worktree/clone workspace at bind and is dropped when that connection closes; `workspaceDestroyConn` routes `session.delete`'s destroy to the owner wherever the delete comes from — a plain CLI with no workspace handlers no longer answers -32601, and the same path string is never removed on another machine (INV-2). Owner gone → the deletion proceeds (the session row is the claim) and the response carries an orphan warning that `session rm` prints. Verified by `TestWorktreeStrategyHostCallerDeleteFromOtherClient` and `TestWorktreeStrategyHostCallerOwnerGone`. 3. **Dead wire method removed, orphan visibility wired.** `MethWorkspaceStatus`/`WorkspaceStatusParams/Result` are gone from the wire (no references remain); `workspace.list` is implemented on all three client surfaces (TUI, REPL, headless host) and `toolhost.list` gains per-host `workspaces[]`, rendered by `tool-host list` with an `[orphan]` marker for paths no session row claims. Verified by `TestToolHostListReportsWorkspaces` and `TestListWorkspaces`. The `workspaceKind` last-marker fix (clone under a base whose own path contains `/.bob/wt/`) is correct and tested. **New observations (non-blocking)** - **Terminal transitions not covered by the destroy:** `failed` (`failSubagent`: runner error, unresolvable host), `timed_out` and `lost` (`terminateActivity`: deadline expiry, supervisor, reconcile) never attempt the destroy, and `tool-host list`'s `[orphan]` marker keys on session-row *existence*, not liveness — so a timed-out child's worktree shows as claimed, invisible as dead weight (the parent's "timed out" notice doesn't mention it either). The workspace is not lost (deleting the child session destroys it with the INV-9 guard; a dirty tree would be refused by an automatic destroy anyway), and SUB-8 is about completion specifically, so this is defensible — but it is exactly the crash/abandon shape §5/§13 say should stay visible for manual collection. Consider destroying best-effort on these transitions too, or marking workspaces of dead sessions `[orphan]` (matching the commands.md wording "no live session claims"). - `session.delete` never calls `clearCallerWorkspace` — a stale `callerWS` entry (conn + ctx) accumulates per deleted caller-hosted worktree/clone session while the owner stays connected; self-cleaning on disconnect, but easy to clear in the delete path. - docs/workspaces.md §4 still carries the old sentence "the project configuration must reject them at set time" immediately above the new paragraph explaining git-ness can only be verified at bind — the two read as contradictory; the old sentence is really about a *missing* base, not git-ness. - The §5 visibility surface enumerates only registered hosts; a caller-hosted workspace orphaned on a machine that never registers as a host isn't listed there (the delete's orphan warning is the only record) — the "visible via 'tool-host list' once the machine reconnects" wording overstates it. - Minor: a dirty *parent* workspace refuses `session.delete` *after* `terminateSessionTree` has already killed the children and destroyed their workspaces; the retry with `--force` completes the deletion. Pre-existing ordering, now more visible. Issue #1's requirements (strategies validated at set time, per-session resolution, creation at bind on the pinned host, INV-9-guarded destruction, caller routing, subagent lifecycle, orphan visibility) are met; the earlier blockers are fixed with real tests and no new routing/race/deadlock issues found. VERDICT: ACCEPT
Some checks failed
deb / build (pull_request) Has been cancelled
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin issue-1-workspace-strategies:issue-1-workspace-strategies
git switch issue-1-workspace-strategies

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff issue-1-workspace-strategies
git switch issue-1-workspace-strategies
git rebase main
git switch main
git merge --ff-only issue-1-workspace-strategies
git switch issue-1-workspace-strategies
git rebase main
git switch main
git merge --no-ff issue-1-workspace-strategies
git switch main
git merge --squash issue-1-workspace-strategies
git switch main
git merge --ff-only issue-1-workspace-strategies
git switch main
git merge issue-1-workspace-strategies
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
agent-bob/bench-a!2
No description provided.