Add pluggable workspace runtimes - #3360
Open
boudra wants to merge 53 commits into
Open
Conversation
Establish the pipe-mode vertical slice before migrating terminals, files, Git, and providers. Runtime selection is immutable per workspace, and external runtimes remain unavailable to path-based daemon surfaces until those capabilities move behind the boundary.
Keep terminal state in Paseo while allocating PTYs and running terminal commands in the selected workspace runtime. Runtime exit and cleanup remain authoritative across worker and wrapper failures.
Keep browsing, streaming, editing, downloads, and live file observation inside the selected workspace runtime. Helper processes own cancellation and teardown so remote workspaces never fall back to host paths.
Bind Git callers to workspace identity so selected repositories cannot collapse onto cwd-keyed state. Run commands and observation through runtime capabilities while preserving explicit cwd-bound behavior for legacy records.
Bind provider discovery, state, and subprocess launches to workspace identity so selected runtimes cannot fall back to host execution. Preserve explicit legacy behavior and isolate provider snapshots by runtime generation.
Reconcile runtime placement and lifecycle by workspace identity, preserve state through archive and recovery, and make deletion, observation replay, scripts, and resource cleanup convergent across local, worktree, and Docker runtimes.
Publish the rootless workspace runtime protocol, enforce module and host-execution boundaries, and prove an independently authored command runtime across lifecycle, PTY, files, Git, providers, and CI.
Expose the runtime catalog and carry an explicit runtime ID through workspace creation while preserving omitted-field compatibility for older clients. The runtime POC made useGitActions require WorkspaceGitBoundary, but Command Center mounted that consumer without the boundary and crashed. Bind the active workspace at that consumer. Keep provisional Worktree reservations unpublished until final placement so observers never bind the source checkout as the new workspace. Gate legacy cwd Git compatibility by hostVisiblePath instead of runtime kind because Local and Worktree remain host-visible runtimes.
Pre-creation provider truth now comes from an invisible real runtime workspace and reuses the normal provider snapshot pipeline. Probe records are owned separately, keeping them structurally absent from ordinary workspace projections. Setup eligibility follows fresh content materialization rather than lifecycle resource ownership. Adopted Local directories never execute repository setup, while fresh Worktree, Docker, and external content may; provider probes never execute repository setup.
Exercise probe, creation, terminals, file observation, Git, scripts, and agent edits through the Desktop Docker runtime while proving host decoys remain untouched. Selected-runtime creation has a longer request budget because materialization and setup can legitimately exceed the legacy RPC ceiling; legacy creation keeps its existing timeout. Materializing records remain durable but private until final placement is published. Scope Docker resources to the daemon owner and validate both container and volume before every lifecycle operation and creation race. Explicit Local and Worktree selection remains behaviorally unchanged, including reusable Local script terminals and the shared common-Git watcher. Real-provider manual QA uses ignored checkout-local configuration and is intentionally not committed.
Keep lifecycle policy in provider-probe while WorkspaceRuntimeService remains the sole lifecycle executor. Idle probes pause without discarding runtime state; source and runtime fingerprints recreate probes and rotate provider bindings, while restart and project removal converge persisted records. Close provider snapshot and helper bindings before lifecycle transitions to avoid leaks. Keep scriptTerminal in the exact bound-runtime surface because reusable local script terminals were intentionally introduced in the preceding accepted runtime slice.
Route probe and user materialization through one projectRuntimeSource seam, where persisted Git source overrides the host-only convenience root and must belong to Git project data. Lock default revision, explicit revision, and subdirectory semantics with real Docker acceptance. Enforce provider-probe internals structurally and document that the current Docker POC needs a locally built image because its default reference is unpublished.
Electron-hosted Local helpers bypassed the sanitized self-Node launch policy, so they lacked ELECTRON_RUN_AS_NODE and provider probes hung. Command-runtime adapters lacked exact detached POSIX process-group ownership, so forced lifecycle cleanup could leave adapter descendants alive.
The server build, typecheck, and dry-run package pass with the Docker implementation deleted from the tree. paseo-workspace-helper remains a mandatory generic runtime contract requirement.
Keep the daemon dependent only on the public runtime contract and helper, while optional runtime implementations remain external command registrations. Workspace setup continues asynchronously through the bound runtime without coupling production packages to an implementation.
Contributor
|
Too many files changed for review (336 files, 100 file limit). Bypass the limit by tagging |
# Conflicts: # nix/npm-deps.hash # package.json # packages/client/src/daemon-client.ts # packages/server/src/server/bootstrap.ts # packages/server/src/server/websocket-server.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Paseo does not need to become a sandbox, container manager, or remote-workspace platform. It needs one stable workspace-runtime boundary so those capabilities can be supplied by independent implementations.
This PR makes Local, Worktree, and registered external runtimes first-class implementations of the same workspace abstraction. Paseo owns the contract and workspace helper; runtime authors own materialization, isolation, process lifecycle, and implementation-specific configuration.
The architecture
Paseo core knows that a runtime is registered and what generic capabilities it exposes. It does not know whether an external runtime uses Docker, a local sandbox, a VM, SSH, or a hosted service.
What crosses the boundary
Paseo routes workspace-scoped behavior through the selected runtime:
The runtime owns physical placement. Paseo addresses the workspace by
workspaceId;cwdin public placement is descriptive compatibility data, not execution authority.Workspace setup remains asynchronous. A runtime only blocks creation until usable workspace content exists. Paseo then publishes the workspace, starts the existing Setup lifecycle, streams its steps and output, and allows the initial agent to run while setup continues.
Runtime authoring reference
Runtime authors depend on two small Paseo packages:
@getpaseo/workspace-runtime-contract— strict versioned schemas and TypeScript types for the runtime CLI.@getpaseo/workspace-helper— the official root-confined executable for structured files and watching. Runtimes place it on the workloadPATH; they do not reimplement its protocol.The configured executable receives one operation:
describereports protocol version and pipe/PTY support. Lifecycle commands exchange one JSON request and response over stdin/stdout.execgives file descriptors 0–2 to the workload, receives a newline-delimited spawn envelope on fd 3, and emits orderedstarted → eof → exitevents on fd 4. PTY resize and signal controls also use fd 3.The important invariants are deliberately small:
workspaceIdand the opaqueruntimeInstanceId;paseo-workspace-helperinside the runtime environment.The complete executable schema and byte-level framing examples live in
packages/workspace-runtime-contract.Registration example
{ "workspaceRuntimes": { "docker": { "type": "command", "label": "Docker", "command": ["/absolute/path/to/runtime-executable"], "options": { "image": "my-workspace-image:dev", "bindMounts": [] } }, "sandboxed": { "type": "command", "label": "Sandboxed", "command": ["/absolute/path/to/another-runtime"], "options": { "policy": "strict" } } } }Both entries are identical to Paseo: registered command runtimes with an ID, label, command argv, and opaque options. Their implementation details are entirely outside core.
Goals
Non-goals
Package and deletion boundary
Production server, CLI, Desktop, release, and daemon-image packages depend only on the contract and helper. Optional runtime implementations are not production dependencies and are not automatically registered.
The repository retains one private generic fixture for executable contract tests. Removing any external runtime repository leaves Paseo production coherent; removing runtime support from Paseo is localized to the contract/helper, generic registration, and workspace-runtime consumers rather than implementation-specific branches.
Related work
Refs #2453. That work explores a specific bundled container workflow; this PR provides the generic boundary but does not ship or supersede a runtime implementation.