Skip to content

Add pluggable workspace runtimes - #3360

Open
boudra wants to merge 53 commits into
mainfrom
workspace-runtime-poc
Open

Add pluggable workspace runtimes#3360
boudra wants to merge 53 commits into
mainfrom
workspace-runtime-poc

Conversation

@boudra

@boudra boudra commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

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 workspace features
  agents · terminals · files · watches · Git · scripts · setup
                         │
                         ▼
              workspaceId-bound runtime API
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
        Local         Worktree      command runtime
                                      │
                             container / sandbox /
                              VM / remote machine

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.

  • Local and Worktree remain built-in, optimized implementations. They use the same workspace-ID-bound surface while retaining host-specific optimizations such as shared Git observation.
  • External runtimes are standalone executables registered through daemon configuration. They implement the public CLI contract and may live in separate repositories and release independently.
  • Runtime options are opaque. Paseo passes arbitrary JSON from trusted configuration to the runtime unchanged. Docker-specific mounts, images, networks, or sandbox policies never become Paseo schema.
  • No fallback leaks. If a selected runtime fails, Paseo reports the failure; it does not silently execute the command on the host.

What crosses the boundary

Paseo routes workspace-scoped behavior through the selected runtime:

  • provider discovery and agent processes;
  • terminals and PTYs;
  • file operations and recursive watches;
  • Git and forge commands;
  • scripts, setup, and archive commands;
  • workspace materialization, pause, resume, destruction, and reconciliation.

The runtime owns physical placement. Paseo addresses the workspace by workspaceId; cwd in 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 workload PATH; they do not reimplement its protocol.

The configured executable receives one operation:

describe
create --workspace-id <id>
inspect --workspace-id <id>
exec --workspace-id <id>
signal --workspace-id <id> --exec-id <id> --signal <signal>
pause --workspace-id <id>
resume --workspace-id <id>
destroy --workspace-id <id>
reconcile

describe reports protocol version and pipe/PTY support. Lifecycle commands exchange one JSON request and response over stdin/stdout. exec gives file descriptors 0–2 to the workload, receives a newline-delimited spawn envelope on fd 3, and emits ordered started → eof → exit events on fd 4. PTY resize and signal controls also use fd 3.

The important invariants are deliberately small:

  • validate protocol version and strict schemas;
  • make workspace lifecycle operations idempotent;
  • identify resources by workspaceId and the opaque runtimeInstanceId;
  • execute the supplied argv directly with the supplied environment;
  • never inherit daemon or wrapper environment accidentally;
  • reject workspace-relative traversal and symlink escapes;
  • forward signals and perform bounded cleanup of the exact execution;
  • provide paseo-workspace-helper inside 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

  • Preserve the Local and Worktree user experience while moving workspace execution behind one runtime surface.
  • Make container, sandbox, VM, and remote-workspace implementations possible without adding their concepts to Paseo core.
  • Keep the public contract and official workspace helper stable, narrow, independently testable, and suitable for external runtime repositories.
  • Let New Workspace select a registered runtime and discover its actual providers before creation.
  • Keep setup progress, failure, cancellation, and agent concurrency consistent across runtime implementations.
  • Maintain wire compatibility: workspace ID is authoritative when supplied; older cwd-based requests remain supported at protocol edges.

Non-goals

  • Ship, publish, or automatically register an external runtime implementation in Paseo releases.
  • Make claims about the security, isolation, networking, credentials, or policy of a particular runtime.
  • Add Docker-, sandbox-, cloud-, or provider-specific configuration to Paseo core.
  • Turn the runtime CLI into a general daemon RPC or arbitrary host capability API.
  • Change established Local or Worktree behavior.

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.

boudra added 16 commits August 10, 2026 20:30
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.
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review (336 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

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