Add progressive Hub workspace affinity - #3994
michaelmwu wants to merge 27 commits into
Conversation
|
Companion Hub PR: getpaseo/hub#86 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0d541459e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
paseo/packages/server/src/server/hub/daemon-executions.ts
Lines 190 to 194 in bcd5ec4
If the daemon exits after the affinity-owned agent record is durably written but before WorkspaceAffinityManager.create() persists the resulting workspaceId, restart loads a provisional mapping with no timer. This early replay return bypasses resolvePlacement, so the mapping is not repaired or armed even though the response acknowledges affinity; without a later archive control, the workspace misses retainUntil. Fresh evidence beyond the earlier pre-affinity replay case is this interrupted first-create state, which already has an affinity owner but an incomplete mapping.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Addressed the remaining provisional-mapping review in |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41af713825
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bbc9a8cbfa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c5070b9562
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e396105e31
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b8c23fddf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5d0cec4bb5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ac535722a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3997c3593d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 65d4b4c2e3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0b164ec113
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0b36a20364
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4ad9affd87
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb642e8a5b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee37f98b62
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…260829 # Conflicts: # packages/server/src/server/bootstrap.ts
|
| Filename | Overview |
|---|---|
| packages/server/src/server/hub/workspace-affinity.ts | Adds the durable affinity state machine, serialized binding, recovery, timer management, restoration, and retryable expiry cleanup. |
| packages/server/src/server/hub/daemon-executions.ts | Integrates affinity validation and acknowledgement with execution creation, duplicate replay, ownership, and archive control. |
| packages/server/src/server/agent/agent-manager.ts | Adds workspace-scoped and unresolved leases that serialize registration, terminal creation, relationship mutation, activation, and archival. |
| packages/server/src/server/workspace-archive-service.ts | Strengthens archive exclusion and structured partial-failure reporting for retryable workspace and backing-directory cleanup. |
| packages/protocol/src/messages.ts | Adds backward-compatible optional affinity lease and acknowledgement fields with boundary validation. |
| packages/server/src/server/agent/agent-loading.test.ts | Adds stored-agent loading/archive race coverage, but uses inline event-loop waits instead of explicit synchronization helpers. |
| packages/server/src/server/agent/agent-manager.test.ts | Adds broad lease and archive-exclusion regression coverage, with several repeated timing-dependent test assertions. |
| docs/hub.md | Documents affinity reuse, retention, progressive capability negotiation, archival behavior, and restart ownership. |
Sequence Diagram
sequenceDiagram
participant Hub
participant Exec as DaemonExecutions
participant Affinity as WorkspaceAffinityManager
participant Workspace
participant Agent
participant Archive as WorkspaceArchiveService
Hub->>Exec: create(executionId, affinity key, deadline)
Exec->>Affinity: hash and bind affinity
Affinity->>Affinity: persist provisional mapping
Affinity->>Workspace: reuse active or restore archived workspace
Affinity->>Agent: create affinity-owned agent under registration lease
Agent-->>Affinity: workspaceId and cwd
Affinity->>Affinity: persist placement and arm deadline
Exec-->>Hub: agent + workspaceAffinityApplied
Hub->>Exec: matching later execution
Exec->>Affinity: validate target and extend retention
Affinity->>Workspace: reuse exact workspace
Affinity->>Agent: create separate agent
Exec-->>Hub: affinity acknowledged
Affinity->>Archive: archive at retained deadline
Archive->>Agent: classify and archive affinity ancestry
Archive->>Workspace: archive workspace when no unrelated blockers
Archive-->>Affinity: success, blocked, or retryable failure
Reviews (1): Last reviewed commit: "Document trigger affinity and Linear iss..." | Re-trigger Greptile
| await new Promise((resolve) => setImmediate(resolve)); | ||
| expect(archiveStarted).toBe(false); | ||
|
|
||
| finishLookup.resolve(); | ||
| await resumeStarted.promise; | ||
| await new Promise((resolve) => setImmediate(resolve)); | ||
| expect(archiveStarted).toBe(false); |
There was a problem hiding this comment.
Timing-dependent archive race assertions
These concurrency tests use setImmediate and mutable booleans to infer that archival remains blocked. This depends on incidental event-loop progress and repeats across the new loading, detach, and reparent tests; use an explicit reusable synchronization signal so the regressions remain deterministic and clearly express the lifecycle boundary.
Rule Used: # Code Review Pattern Reference: Slop, Tests, Feat... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
Closing in favor of the implementation merged in #4354. |
Linked issue
Companion Hub change: getpaseo/hub#86. There is no standalone issue.
Type of change
Reasoning
A Hub workflow responding to multiple messages in one Slack thread, Discord thread, GitHub issue, or GitHub pull request should be able to keep using the checkout created for that conversation. Hub can provide a stable custom affinity key, but the Paseo daemon owns the actual workspace, worktree, agent records, restoration path, and archive operation.
This PR adds the optional daemon-side capability needed for exact reuse while keeping deployment progressive. Existing Hub versions never send the field. A new Hub talking to an older daemon accepts the missing acknowledgement and gets the existing fresh-workspace behavior, so operators do not need to update every daemon before updating Hub.
Usage
In the trigger form, open Workspace affinity under Where it runs and enter a custom key. Empty disables reuse. The equivalent complete single-run trigger is:
Legacy bundles put
workspace_affinityon a step. The complete usage reference is Workspace affinity, updated alongside Hub's single-run editor and Linear issue support. Linear Agent Sessions remain a separate Hub integration; issue identity should select the workspace while session identity controls replies and stop signals.The key is custom: it may be a literal, a finite authored expression, or a prefix plus the provider-authenticated
paseo.trigger.conversation_key. Hub sends the daemon:{ "workspaceAffinity": { "key": "review:<opaque authenticated conversation key>", "retainUntil": "<workflow max_runtime deadline>", "autoArchive": true } }A supporting daemon acknowledges successful binding with
workspaceAffinityApplied: true. It hashes the raw key, reuses or restores the mapped workspace, and extends retention to the latest matching workflow deadline.idle_timeoutremains the liveness deadline for one execution; it does not define the gap between executions. This lets an agent auto-archive after finishing while its workspace stays reusable until the retained workflow deadline.Disconnecting or revoking the Hub relationship fences new execution authority but does not abandon an already acknowledged retention deadline. The daemon keeps those cleanup timers independently and reconstructs them from persisted mappings after restart.
Why the daemon change is necessary
Hub can select the affinity key and deadline, but it cannot safely own daemon-local workspace lifecycle. Only the daemon can:
Trying to do this with only Hub changes would require Hub to discover and persist private daemon-local workspace IDs, would not restore an archived workspace safely, and would race daemon creation and archival. Older daemons have no key-to-workspace primitive to invoke. Keeping the mapping in Paseo preserves the existing ownership boundary, while the optional acknowledgement gives Hub a forward-progressive capability negotiation.
Goals
max_runtimedeadline.open_project_requestunder an unresolved activation lease before its first filesystem or registry lookup, covering exact-path archived workspace reuse through durable activation and response publication.workspace.create.requestand MCPcreate_workspaceprovisioning under an unresolved lease before source lookup, so expiry cannot remove an affinity-owned backing worktree before the sibling workspace is durable.Non-goals
idle_timeoutas workspace inactivity.QA
September 7 follow-up:
0a1fbb4af1587a35fb654a6eff72e52619a8bb73changes only the public affinity guide. The complete Linear trigger example compiles against the updated Hub compiler; the repository formatter check passes. No daemon/runtime code or dependencies changed. The all-workspace typecheck hook was excluded for this documentation-only commit; existing daemon evidence below is from the implementation commits. The current-main merge conflict is unchanged and is not resolved by the docs update.Behavior verified locally on Linux:
workspaceAffinityAppliedacknowledgementNo UI changed, so screenshots and recordings are not applicable. macOS, Windows, iOS, and Android were not tested because the change is isolated to protocol and server behavior.
Companion cross-repository integration runs in getpaseo/hub#86 against exact commit
bb3a46346c7e781bc963ffb2e0fdbd3f5ed3efa3.Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatpasses