Sandbox workspaces with a Nono-backed runtime #15

Open
opened 2026-08-23 01:19:55 -04:00 by jb · 0 comments
Owner

Problem Statement

Paseo runs agent sessions and their tools with the daemon user's host permissions. An agent working in one workspace can reach unrelated files, credentials, local services, and network destinations unless each provider implements its own restrictions. The daemon also owns files, Git operations, terminals, workspace scripts, provider discovery, and agent process launch through different code paths, so wrapping one provider spawn does not create a workspace boundary.

The prior workspace runtime proof of concept routed those operations through one selected runtime and defined a versioned external command contract. It did not provide a production sandbox implementation, first-class plugin registration, runtime-aware Pi MCP configuration, or Pi session import. Its process model also targeted a broader runtime abstraction than the revised requirement needs.

The revised requirement is one containment policy per workspace. Agent sessions in the same workspace may share its filesystem and policy. Active processes do not need to survive a daemon restart. Workspace files and resumable provider state must survive. Paseo must fail closed when a selected runtime is unavailable and must never substitute host execution.

Nono can enforce the boundary without a privileged container supervisor. It runs an ordinary child under Landlock on Linux or Seatbelt on macOS, preserves piped stdin, stdout, stderr, signals, and exit status, and adds network filtering, credential brokering, and selected Linux resource limits. Nono does not provide a separate kernel, mount namespace, PID namespace, reproducible image, or multi-tenant boundary. Paseo must present that distinction clearly and keep stronger isolation outside this scope.

Solution

Paseo will add a Nono-backed workspace runtime in two stages.

Minimal PoC

The Minimal PoC ports the smallest workspace runtime core and external command contract onto current main. A trusted, manually configured Nono runtime command adopts one host-visible workspace and runs Pi, provider probes, the workspace helper, Git commands, and workspace scripts through a generated Nono capability manifest. The PoC targets a host-installed Linux daemon, one pinned Nono version, one pinned Pi version, and one supported API-key model provider.

The PoC uses ordinary pipes for Pi RPC and does not use Nono detached sessions or terminal attachment. It denies network access except for the selected model route, passes no ambient daemon environment, grants only the workspace and explicit Pi state paths, applies memory and process-count limits when the host supports them, and terminates stale runtime processes during daemon startup. Users configure the runtime in daemon configuration. Pi MCP, session import, plugin registration, macOS, WSL2, and the official Docker deployment remain outside the PoC.

Usable Product

The Usable Product packages the Nono runtime as a trusted local plugin with a declarative workspace runtime contribution. The daemon validates the contribution, launches its runtime command directly, and keeps the plugin subprocess out of the process data path. Users can select the Nono runtime while creating a workspace, inspect its availability, and see actionable startup or policy failures.

The product supports Pi create, resume, import, provider discovery, adapter discovery, Paseo MCP tools, terminals, files, Git operations, and workspace scripts inside the selected workspace boundary. Paseo generates a fully resolved Nono capability manifest from trusted daemon facts rather than loading user-composed Nono profiles. It creates a route-limited MCP endpoint, derives network policy from the selected model and credential mode, scopes state by workspace, records bounded diagnostics, and supports host-installed Linux, macOS, and the official Linux Docker deployment when each platform passes runtime capability checks.

A selected Nono runtime remains authoritative until the workspace is removed. Runtime failure, plugin disablement, an unsupported kernel feature, or invalid policy makes the workspace unavailable. Paseo does not run any affected operation on the host as a fallback.

User Stories

  1. As a Paseo user, I want to select Nono when I create a workspace, so that agent activity stays within that workspace's policy.
  2. As a Paseo user, I want the selected runtime shown with the workspace, so that I know where its commands execute.
  3. As a Paseo user, I want a Nono workspace to keep its runtime selection after reconnecting, so that later operations use the same boundary.
  4. As a Paseo user, I want runtime unavailability reported before I create an agent session, so that I do not discover the problem after submitting a prompt.
  5. As a Paseo user, I want Paseo to refuse host execution when Nono fails, so that a sandbox failure cannot weaken isolation.
  6. As a Paseo user, I want Pi to edit files inside the workspace, so that sandboxing does not prevent normal coding work.
  7. As a Paseo user, I want Pi blocked from unrelated workspaces and home-directory credentials, so that one workspace cannot expose the rest of my machine.
  8. As a Paseo user, I want linked-worktree Git operations to work without granting unrelated project data, so that normal branch workflows remain usable.
  9. As a Paseo user, I want terminals opened for a Nono workspace to use its policy, so that a terminal cannot bypass the selected runtime.
  10. As a Paseo user, I want workspace scripts and services to use the selected runtime, so that setup and development commands follow the same boundary.
  11. As a Paseo user, I want file browsing and editing to work for a Nono workspace, so that the Side panel remains useful.
  12. As a Paseo user, I want Git status, diffs, and change request operations to work for a Nono workspace, so that the Changes surface remains useful.
  13. As a Paseo user, I want provider availability and models discovered inside Nono, so that the New Workspace screen reflects the environment the agent will receive.
  14. As a Paseo user, I want Pi adapter discovery to run inside Nono, so that Paseo does not advertise MCP support that the agent lacks.
  15. As a Paseo user, I want Paseo MCP tools available through a private route, so that the agent can use Paseo without reaching the complete daemon listener.
  16. As a Paseo user, I want model traffic limited to the selected provider endpoints, so that the agent cannot contact arbitrary Internet services.
  17. As a Paseo user, I want custom model endpoints represented in the runtime policy, so that private and self-hosted providers can work without broad egress.
  18. As a Paseo user, I want API keys brokered when Nono supports the provider, so that the agent process does not receive the real key.
  19. As a Paseo user, I want OAuth-backed Pi credentials stored in one scoped writable location, so that token refresh works without exposing unrelated credentials.
  20. As a Paseo user, I want the daemon environment filtered before process launch, so that unrelated tokens cannot leak through environment inheritance.
  21. As a Paseo user, I want generated Pi extension and MCP files readable only by the intended workspace runtime, so that another workspace cannot read their contents.
  22. As a Paseo user, I want Pi session state to survive agent closure and daemon restart, so that I can resume a provider session even though its old process is gone.
  23. As a Paseo user, I want to import Pi sessions that belong to the workspace, so that sandboxing does not remove the import workflow.
  24. As a Paseo user, I want sessions from other workspaces excluded from import, so that workspace state does not leak through discovery.
  25. As a Paseo user, I want cancel and stop actions to terminate the complete sandboxed process tree, so that tools cannot remain running after the agent stops.
  26. As a Paseo user, I want archive workspace to stop runtime processes while preserving workspace files, so that restore can return to the same data.
  27. As a Paseo user, I want permanent workspace removal to delete only Paseo-owned Nono state, so that adopted directories remain intact.
  28. As a Paseo user, I want daemon shutdown to terminate active Nono processes cleanly, so that a planned restart does not leave background agents.
  29. As a Paseo user, I want daemon startup to remove stale Nono processes from the previous daemon instance, so that relaxed restart continuity does not create orphaned workloads.
  30. As a Paseo user, I want same-cwd workspaces to retain the same directory-backed data behavior, so that runtime selection does not make identical directories diverge.
  31. As a Paseo user, I want workspace-owned Pi state separated even when two workspaces share a cwd, so that provider state and generated launch files do not leak between them.
  32. As a Paseo user, I want a clear explanation when my Linux kernel lacks a requested Nono capability, so that I can update the host or choose Local intentionally.
  33. As a Paseo user, I want macOS limitations stated before launch, so that I understand which Nono controls Seatbelt can enforce.
  34. As a Paseo user, I want WSL2 rejected when the required network policy cannot be enforced, so that Paseo does not offer degraded proxy confinement silently.
  35. As a Docker user, I want Nono and Pi included in the supported daemon image, so that the runtime works without mounting host package installations.
  36. As a Docker user, I want Nono policy applied inside the existing container boundary, so that agents receive path-level restrictions in addition to Docker's outer isolation.
  37. As a Docker user, I want resource controls delegated to Docker when Nono cgroups are unavailable, so that requested limits remain enforced.
  38. As a plugin user, I want installation to verify the runtime executable and protocol version, so that an incompatible plugin cannot become selectable.
  39. As a plugin user, I want disable, reload, and removal to report affected workspaces, so that runtime lifecycle changes are deliberate.
  40. As a plugin user, I want plugin failure to leave bound workspaces unavailable rather than reclassifying them as Local, so that persisted authority remains intact.
  41. As an operator, I want one pinned Nono version and one reviewed manifest schema version, so that an upstream update cannot change enforcement without review.
  42. As an operator, I want runtime policy derived from daemon facts rather than user Nono profile inheritance, so that local profile drift cannot widen permissions.
  43. As an operator, I want Nono capability elevation and unsandboxed hooks disabled, so that an agent cannot obtain permissions outside Paseo's policy.
  44. As an operator, I want bounded Nono audit and diagnostic retention, so that failures remain inspectable without unbounded host state.
  45. As an operator, I want runtime health and capability facts exposed separately from RPC availability, so that clients do not mistake a registered handler for an enforceable sandbox.
  46. As a developer, I want a real Nono contract fixture, so that protocol and sandbox behavior are tested without mocking the runtime.
  47. As a developer, I want one workspace runtime seam used by agents, terminals, files, Git, scripts, and probes, so that a new execution path cannot bypass policy accidentally.
  48. As a developer, I want runtime-specific mount, network, credential, and process facts hidden behind the Nono adapter, so that provider code remains runtime-neutral.
  49. As a security reviewer, I want every granted path and endpoint visible in the resolved manifest, so that I can audit the effective boundary.
  50. As a security reviewer, I want tests that attempt forbidden access, so that success proves kernel enforcement rather than configuration shape.
  51. As a security reviewer, I want the product to describe Nono as host capability containment, so that users do not mistake it for a gVisor, container, VM, or multi-tenant boundary.
  52. As a security reviewer, I want the Nono alpha status and platform gaps recorded in release decisions, so that production exposure requires an explicit review.

Implementation Decisions

  • Deliver the work in two independently verifiable stages named Minimal PoC and Usable Product. Do not mix product UI and plugin distribution into the PoC acceptance gate.
  • Port the workspace runtime model and versioned external command contract onto current main instead of merging the proof-of-concept branch. Preserve its useful ownership rule: one persisted runtime identifier addresses one workspace, while physical placement remains private to the runtime.
  • Use one workspace runtime seam for process execution, terminals, files, Git observation, workspace scripts, setup and archive commands, provider discovery, adapter discovery, and agent launch. Provider modules receive a bound workspace capability and do not branch on Nono.
  • Keep Nono outside the daemon process. The daemon launches a trusted runtime command, and that command launches Nono. Do not apply the Nono library or FFI sandbox to the daemon because applying it is irreversible for the current process.
  • Use Nono's CLI as the supported integration boundary. Pin the Nono binary and validate its version and required capabilities before advertising the runtime.
  • Launch Pi through ordinary piped Nono supervised execution. Do not use Nono detached sessions or attach for Pi RPC. Keep stdin, stdout, and stderr byte-preserving so the existing JSONL transport retains framing, correlation, diagnostics, and timeout ownership.
  • Treat the workspace as the containment unit. Agent sessions in one workspace may share its cwd, policy, provider state, and network grants. They do not share live process handles. Workspaces with the same cwd receive the same directory access but separate workspace-owned provider state and generated launch files.
  • Accept the relaxed daemon-restart contract. Live agent sessions do not reconnect after a daemon restart. The daemon terminates active Nono processes during graceful shutdown and performs fail-closed orphan cleanup before admitting work after startup.
  • Preserve workspace backing across daemon restart, archive, and restore. Persist enough Nono runtime identity and state ownership to inspect, stop, and delete Paseo-owned state without persisting raw PIDs as durable identity.
  • Generate a fully resolved Nono capability manifest for each workspace launch. Do not load user profiles, registry packs, inherited groups, unsandboxed hooks, capability elevation, or interactive approval flows in the daemon-managed path.
  • Build manifests from typed trusted inputs. The inputs include workspace identity, canonical cwd, separately resolved Git metadata, process purpose, selected provider/model endpoint, credential mode, generated launch files, state locations, MCP route, environment allowlist, and resource policy.
  • Validate all filesystem paths at the enforcement boundary. Grant the workspace cwd read-write, grant only required linked-worktree metadata, grant Pi and required tools read/execute access, grant workspace-owned Pi state read-write, and grant generated launch files as individual read-only files where the platform supports the distinction.
  • Give each workspace a dedicated temporary directory and set its temporary-directory environment accordingly. Do not grant the daemon's global temporary directory.
  • Start every manifest with explicit environment filtering. Pass only runtime basics, selected provider settings, Paseo agent facts, proxy settings, and variables required by the chosen toolchain. Do not inherit the daemon environment.
  • Start every manifest with restricted networking. The Minimal PoC supports one selected API-key model route. The Usable Product derives allowed provider and token-refresh endpoints from the effective provider/model configuration and rejects a provider whose network contract cannot be represented.
  • Use Nono reverse-proxy credential injection for supported API-key providers. For OAuth flows that require mutable files, grant one coordinated workspace-owned credential file and document that the agent can access that file. Do not copy one mutable OAuth file into independent workspace stores without a reconciliation rule.
  • Add a daemon-owned, route-limited MCP bridge for Nono workspaces. The sandbox may reach only the agent-scoped MCP route, not the complete daemon listener. Keep the existing bearer authorization and rotate it with daemon identity as today.
  • Generate Pi MCP configuration and adapter probes inside the selected runtime. Remove the proof-of-concept behavior that rejects MCP for runtime-selected workspaces.
  • Implement runtime-aware Pi session discovery. Store or project Pi session state under a stable workspace-owned path, preserve provider native handles across process replacement, and exclude sessions owned by another workspace.
  • Keep runtime selection optional on protocol and persistence boundaries for compatibility. Existing workspace records remain Local. New fields are additive, and new runtime RPCs use dotted namespaces with request/response suffixes.
  • Gate runtime UI once on the daemon's workspace-runtime feature and on the selected runtime's enforceable capability facts. Do not add client fallback branches.
  • The Minimal PoC registers the Nono command through trusted daemon configuration. It does not change the plugin API.
  • The Usable Product adds one declarative workspace-runtime contribution to the trusted local plugin manifest. The contribution contains stable identity, label, executable path, protocol version, and compatibility metadata. The daemon resolves the executable inside the plugin source, validates it, and launches it directly.
  • Keep the plugin subprocess out of streaming process execution. Plugin RPC remains request/response JSON for configuration and UI behavior. The runtime command owns byte streams, PTY control, signals, and exit reporting.
  • Runtime disable, reload, or removal never changes a workspace's selected runtime. Stop active runtime processes before disabling. Bound workspaces become unavailable until the same runtime identity returns or the user permanently removes and recreates the workspace under another runtime.
  • Expose availability as an enforceable runtime fact. Check operating system, Nono version, Landlock or Seatbelt support, required network mode, cgroup delegation when limits are requested, executable availability, and manifest compatibility.
  • The Minimal PoC supports host-installed Linux only and requires the exact kernel features used by its manifest. The Usable Product adds macOS after Seatbelt behavior passes the same contract tests and adds the official Linux Docker deployment after image and cgroup behavior pass dedicated tests.
  • Reject WSL2 in the first Usable Product release when the selected policy needs proxy confinement or per-port filtering. Do not enable Nono's insecure WSL2 proxy mode.
  • In Docker, bake Nono, Pi, Node, and required tools into the supported daemon image. Use Docker for the outer PID, mount, and network namespace and for CPU or I/O controls. Use Nono for path, environment, credential, and endpoint policy inside that boundary.
  • Apply Linux Nono memory and process-count limits only after capability checks prove cgroup v2 delegation. Delegate those limits to Docker when the daemon container owns them. Paseo still owns wall-time cancellation and must add separate controls for CPU, disk, and file descriptors if product requirements demand them.
  • Preserve Nono diagnostics on stderr without allowing them onto Pi stdout. Normalize policy, capability, startup, and cleanup failures into typed runtime errors with separate user-facing text and debug details.
  • Keep bounded audit and runtime diagnostic state under Paseo-owned storage. Define retention by age and count. Do not expose signing, attestation, or rollback UI in this delivery.
  • Pin Nono 0.74.0 for the first PoC evaluation. Treat every Nono update as a security-sensitive dependency change that reruns platform, policy, protocol, and escape tests. Revisit production readiness because upstream labels the current release alpha and does not recommend production use before its planned external audit.

Minimal PoC success criteria

  • A developer can configure the Nono runtime and create one Pi workspace on a supported Linux host.
  • Pi RPC starts with ordinary pipes, completes a prompt, runs a workspace command, reports output, and exits cleanly.
  • Pi can read and write its workspace and required Git metadata.
  • Attempts to read another workspace, daemon state, SSH keys, or cloud credentials fail.
  • Model traffic reaches only the configured test provider endpoint; arbitrary outbound traffic fails.
  • The agent receives no unrelated daemon environment variables.
  • Cancel terminates Pi and its descendants within a bounded timeout.
  • Graceful daemon shutdown and subsequent startup leave no runnable PoC process behind.
  • Runtime startup or policy failure returns an explicit error and never launches Pi locally.

Usable Product success criteria

  • A user can install and enable the trusted Nono plugin, select Nono during workspace creation, and see runtime availability before submitting work.
  • Pi create, subsequent turns, resume, import, provider discovery, adapter discovery, Paseo MCP tools, files, Git, terminals, and workspace scripts work through the selected runtime.
  • Linux, macOS, and the supported Docker deployment each pass their declared capability matrix. Unsupported platform features make the runtime unavailable with an actionable reason.
  • Plugin reload, disable, removal, archive, restore, daemon shutdown, daemon startup, and permanent workspace removal preserve the fail-closed lifecycle contract.
  • The resolved manifest and retained diagnostics let an operator explain every granted workspace path, provider endpoint, credential mode, and resource control.
  • Existing Local and New worktree behavior remains unchanged for workspaces without a selected runtime.

Testing Decisions

  • Test behavior through the highest existing seam: the public workspace runtime command contract exercised by a real Nono executable and a real daemon. Do not mock Nono, process spawning, the filesystem, Git, network policy, or the runtime protocol in contract and end-to-end coverage.
  • Maintain one reusable runtime contract harness. Run the same lifecycle, pipe, PTY, signal, filesystem, Git observation, archive, restore, and cleanup assertions against the fixture runtime and Nono runtime. The workspace runtime proof of concept provides prior art for this harness.
  • Keep pure unit tests only for typed manifest construction, capability resolution, path planning, compatibility projection, and error mapping. Inject small typed adapters rather than mocking module exports.
  • Add real Linux containment tests that launch an adversarial child and assert exact outcomes for allowed workspace reads/writes, unrelated workspace denial, credential-path denial, daemon-state denial, symlink escape denial, Unix-socket denial, environment filtering, endpoint allowlisting, arbitrary egress denial, memory limits, process-count limits, cancellation, and orphan cleanup.
  • Test linked-worktree access with real Git repositories. Cover gitdir, commondir, alternates, submodules, and a sibling worktree that must remain inaccessible.
  • Test Pi JSONL transport with a deterministic fake Pi executable through Nono. Assert byte framing, stderr separation, request correlation, EOF, non-zero exit, signals, timeout, and descendant cleanup. This keeps normal CI independent of provider credentials.
  • Add a real-provider Pi smoke under the existing real-provider test category. It proves one prompt, model traffic, a workspace edit, a Paseo MCP call, close, resume, and import with actual Pi authentication. Do not add authentication gates or conditional skips.
  • Test MCP through a real route-limited bridge. Assert that the agent-scoped endpoint works, another agent identity fails, unrelated daemon routes fail, and the complete daemon listener is unreachable from the sandbox.
  • Test credential brokering by proving the upstream receives the real credential while the Pi process environment, filesystem, argv, and request logs contain only the phantom credential. Add a separate OAuth-file test when that mode enters scope.
  • Test runtime availability as observable API behavior. Cover missing Nono, wrong version, unsupported manifest version, missing Landlock or Seatbelt support, unavailable cgroup delegation, unsupported WSL2 proxy policy, invalid executable, and plugin runtime disappearance.
  • Add targeted app browser coverage for runtime selection and all fallible states: immediate pending state, successful creation, unavailable runtime, startup failure, plugin disablement, and retry. Assert rendered user behavior rather than internal store state.
  • Add real Electron coverage only where plugin installation or desktop-managed daemon packaging differs from browser behavior.
  • Add a supported Docker end-to-end test that uses the production daemon image, real Nono, real workspace volume, real network boundary, and declared resource owner. The test proves that the sandbox cannot read daemon state even though both live in one outer container.
  • Add macOS contract runs for Seatbelt before advertising macOS support. Keep expected platform differences explicit instead of weakening common assertions.
  • Run security matrix tests on every pinned Nono update. Record the Nono version, operating system, kernel or macOS version, detected Landlock ABI, manifest version, and enabled resource controllers in failure artifacts.
  • Keep tests deterministic. Use fixed temporary roots, loopback test endpoints, bounded waits tied to explicit readiness, and exact error assertions. Never branch assertions based on whether enforcement happened.
  • Treat any requested control that cannot be enforced as a startup failure. Tests must not accept warnings followed by unsandboxed or degraded execution.

Out of Scope

  • Active Pi RPC continuation, output replay, or process attachment after daemon restart.
  • Per-agent isolation inside one workspace. Agent sessions in the same workspace share its policy boundary.
  • Multi-tenant isolation or protection from another malicious process running as the same host uid.
  • A separate kernel, gVisor, Firecracker, Kata, Edera, or other VM-backed execution backend.
  • Native Windows support. WSL2 remains unsupported when required Nono network controls cannot be enforced.
  • Support for providers other than Pi in the first Usable Product release.
  • Arbitrary user Nono profiles, registry packs, policy inheritance, capability elevation, interactive approval, or unsandboxed hooks in daemon-managed runs.
  • Nono detached sessions or using Nono attach as Paseo's agent transport.
  • Nono rollback review and restore UI.
  • Audit signing, Sigstore attestation, remote audit delivery, and external audit anchoring.
  • CPU, disk, and I/O quotas supplied by Nono. Docker or a future outer runtime may add them.
  • General plugin registration of agent providers or execution backends. This spec adds one declarative workspace runtime contribution.
  • Automatic conversion of an existing Local workspace to Nono. Users create a new workspace under the selected runtime.
  • Silent fallback from Nono to Local under any failure condition.

Further Notes

  • Nono 0.74.0 preserves ordinary piped stdio under supervised execution, which fits Pi's current JSONL transport. Its detached lifecycle allocates a PTY and does not fit this integration.
  • Nono enforces host capability policy. It does not create a mount or PID namespace and does not offer gVisor's guest boundary. Product copy and runtime diagnostics must use that description.
  • Landlock feature depth depends on the host kernel. Filesystem enforcement has a wider support range than TCP filtering, signal scoping, process-information scoping, and abstract Unix-socket scoping. Runtime availability must reflect the exact manifest rather than a single generic Linux-supported flag.
  • Nono networking is unrestricted unless the manifest requests a restricted mode. Paseo-generated manifests must set network policy explicitly.
  • Nono resource limits require Linux cgroup v2 and delegated controllers. The official Docker deployment may need Docker-owned limits instead of Nono-owned user-session cgroups.
  • A pathname shared by two workspace records remains shared directory-backed data under Paseo's existing model. The Nono boundary protects both from paths outside that cwd; it does not pretend the two records have separate copies.
  • The workspace runtime proof of concept remains research input. Its contract framing, fail-closed versioning, runtime selection, helper confinement, and routing tests are useful. Its branch is too divergent from current main to merge as implementation.
  • Nono is Apache-2.0. Paseo's own distribution and AGPL obligations remain unchanged.
## Problem Statement Paseo runs agent sessions and their tools with the daemon user's host permissions. An agent working in one workspace can reach unrelated files, credentials, local services, and network destinations unless each provider implements its own restrictions. The daemon also owns files, Git operations, terminals, workspace scripts, provider discovery, and agent process launch through different code paths, so wrapping one provider spawn does not create a workspace boundary. The prior workspace runtime proof of concept routed those operations through one selected runtime and defined a versioned external command contract. It did not provide a production sandbox implementation, first-class plugin registration, runtime-aware Pi MCP configuration, or Pi session import. Its process model also targeted a broader runtime abstraction than the revised requirement needs. The revised requirement is one containment policy per workspace. Agent sessions in the same workspace may share its filesystem and policy. Active processes do not need to survive a daemon restart. Workspace files and resumable provider state must survive. Paseo must fail closed when a selected runtime is unavailable and must never substitute host execution. Nono can enforce the boundary without a privileged container supervisor. It runs an ordinary child under Landlock on Linux or Seatbelt on macOS, preserves piped stdin, stdout, stderr, signals, and exit status, and adds network filtering, credential brokering, and selected Linux resource limits. Nono does not provide a separate kernel, mount namespace, PID namespace, reproducible image, or multi-tenant boundary. Paseo must present that distinction clearly and keep stronger isolation outside this scope. ## Solution Paseo will add a Nono-backed workspace runtime in two stages. ### Minimal PoC The Minimal PoC ports the smallest workspace runtime core and external command contract onto current main. A trusted, manually configured Nono runtime command adopts one host-visible workspace and runs Pi, provider probes, the workspace helper, Git commands, and workspace scripts through a generated Nono capability manifest. The PoC targets a host-installed Linux daemon, one pinned Nono version, one pinned Pi version, and one supported API-key model provider. The PoC uses ordinary pipes for Pi RPC and does not use Nono detached sessions or terminal attachment. It denies network access except for the selected model route, passes no ambient daemon environment, grants only the workspace and explicit Pi state paths, applies memory and process-count limits when the host supports them, and terminates stale runtime processes during daemon startup. Users configure the runtime in daemon configuration. Pi MCP, session import, plugin registration, macOS, WSL2, and the official Docker deployment remain outside the PoC. ### Usable Product The Usable Product packages the Nono runtime as a trusted local plugin with a declarative workspace runtime contribution. The daemon validates the contribution, launches its runtime command directly, and keeps the plugin subprocess out of the process data path. Users can select the Nono runtime while creating a workspace, inspect its availability, and see actionable startup or policy failures. The product supports Pi create, resume, import, provider discovery, adapter discovery, Paseo MCP tools, terminals, files, Git operations, and workspace scripts inside the selected workspace boundary. Paseo generates a fully resolved Nono capability manifest from trusted daemon facts rather than loading user-composed Nono profiles. It creates a route-limited MCP endpoint, derives network policy from the selected model and credential mode, scopes state by workspace, records bounded diagnostics, and supports host-installed Linux, macOS, and the official Linux Docker deployment when each platform passes runtime capability checks. A selected Nono runtime remains authoritative until the workspace is removed. Runtime failure, plugin disablement, an unsupported kernel feature, or invalid policy makes the workspace unavailable. Paseo does not run any affected operation on the host as a fallback. ## User Stories 1. As a Paseo user, I want to select Nono when I create a workspace, so that agent activity stays within that workspace's policy. 2. As a Paseo user, I want the selected runtime shown with the workspace, so that I know where its commands execute. 3. As a Paseo user, I want a Nono workspace to keep its runtime selection after reconnecting, so that later operations use the same boundary. 4. As a Paseo user, I want runtime unavailability reported before I create an agent session, so that I do not discover the problem after submitting a prompt. 5. As a Paseo user, I want Paseo to refuse host execution when Nono fails, so that a sandbox failure cannot weaken isolation. 6. As a Paseo user, I want Pi to edit files inside the workspace, so that sandboxing does not prevent normal coding work. 7. As a Paseo user, I want Pi blocked from unrelated workspaces and home-directory credentials, so that one workspace cannot expose the rest of my machine. 8. As a Paseo user, I want linked-worktree Git operations to work without granting unrelated project data, so that normal branch workflows remain usable. 9. As a Paseo user, I want terminals opened for a Nono workspace to use its policy, so that a terminal cannot bypass the selected runtime. 10. As a Paseo user, I want workspace scripts and services to use the selected runtime, so that setup and development commands follow the same boundary. 11. As a Paseo user, I want file browsing and editing to work for a Nono workspace, so that the Side panel remains useful. 12. As a Paseo user, I want Git status, diffs, and change request operations to work for a Nono workspace, so that the Changes surface remains useful. 13. As a Paseo user, I want provider availability and models discovered inside Nono, so that the New Workspace screen reflects the environment the agent will receive. 14. As a Paseo user, I want Pi adapter discovery to run inside Nono, so that Paseo does not advertise MCP support that the agent lacks. 15. As a Paseo user, I want Paseo MCP tools available through a private route, so that the agent can use Paseo without reaching the complete daemon listener. 16. As a Paseo user, I want model traffic limited to the selected provider endpoints, so that the agent cannot contact arbitrary Internet services. 17. As a Paseo user, I want custom model endpoints represented in the runtime policy, so that private and self-hosted providers can work without broad egress. 18. As a Paseo user, I want API keys brokered when Nono supports the provider, so that the agent process does not receive the real key. 19. As a Paseo user, I want OAuth-backed Pi credentials stored in one scoped writable location, so that token refresh works without exposing unrelated credentials. 20. As a Paseo user, I want the daemon environment filtered before process launch, so that unrelated tokens cannot leak through environment inheritance. 21. As a Paseo user, I want generated Pi extension and MCP files readable only by the intended workspace runtime, so that another workspace cannot read their contents. 22. As a Paseo user, I want Pi session state to survive agent closure and daemon restart, so that I can resume a provider session even though its old process is gone. 23. As a Paseo user, I want to import Pi sessions that belong to the workspace, so that sandboxing does not remove the import workflow. 24. As a Paseo user, I want sessions from other workspaces excluded from import, so that workspace state does not leak through discovery. 25. As a Paseo user, I want cancel and stop actions to terminate the complete sandboxed process tree, so that tools cannot remain running after the agent stops. 26. As a Paseo user, I want archive workspace to stop runtime processes while preserving workspace files, so that restore can return to the same data. 27. As a Paseo user, I want permanent workspace removal to delete only Paseo-owned Nono state, so that adopted directories remain intact. 28. As a Paseo user, I want daemon shutdown to terminate active Nono processes cleanly, so that a planned restart does not leave background agents. 29. As a Paseo user, I want daemon startup to remove stale Nono processes from the previous daemon instance, so that relaxed restart continuity does not create orphaned workloads. 30. As a Paseo user, I want same-cwd workspaces to retain the same directory-backed data behavior, so that runtime selection does not make identical directories diverge. 31. As a Paseo user, I want workspace-owned Pi state separated even when two workspaces share a cwd, so that provider state and generated launch files do not leak between them. 32. As a Paseo user, I want a clear explanation when my Linux kernel lacks a requested Nono capability, so that I can update the host or choose Local intentionally. 33. As a Paseo user, I want macOS limitations stated before launch, so that I understand which Nono controls Seatbelt can enforce. 34. As a Paseo user, I want WSL2 rejected when the required network policy cannot be enforced, so that Paseo does not offer degraded proxy confinement silently. 35. As a Docker user, I want Nono and Pi included in the supported daemon image, so that the runtime works without mounting host package installations. 36. As a Docker user, I want Nono policy applied inside the existing container boundary, so that agents receive path-level restrictions in addition to Docker's outer isolation. 37. As a Docker user, I want resource controls delegated to Docker when Nono cgroups are unavailable, so that requested limits remain enforced. 38. As a plugin user, I want installation to verify the runtime executable and protocol version, so that an incompatible plugin cannot become selectable. 39. As a plugin user, I want disable, reload, and removal to report affected workspaces, so that runtime lifecycle changes are deliberate. 40. As a plugin user, I want plugin failure to leave bound workspaces unavailable rather than reclassifying them as Local, so that persisted authority remains intact. 41. As an operator, I want one pinned Nono version and one reviewed manifest schema version, so that an upstream update cannot change enforcement without review. 42. As an operator, I want runtime policy derived from daemon facts rather than user Nono profile inheritance, so that local profile drift cannot widen permissions. 43. As an operator, I want Nono capability elevation and unsandboxed hooks disabled, so that an agent cannot obtain permissions outside Paseo's policy. 44. As an operator, I want bounded Nono audit and diagnostic retention, so that failures remain inspectable without unbounded host state. 45. As an operator, I want runtime health and capability facts exposed separately from RPC availability, so that clients do not mistake a registered handler for an enforceable sandbox. 46. As a developer, I want a real Nono contract fixture, so that protocol and sandbox behavior are tested without mocking the runtime. 47. As a developer, I want one workspace runtime seam used by agents, terminals, files, Git, scripts, and probes, so that a new execution path cannot bypass policy accidentally. 48. As a developer, I want runtime-specific mount, network, credential, and process facts hidden behind the Nono adapter, so that provider code remains runtime-neutral. 49. As a security reviewer, I want every granted path and endpoint visible in the resolved manifest, so that I can audit the effective boundary. 50. As a security reviewer, I want tests that attempt forbidden access, so that success proves kernel enforcement rather than configuration shape. 51. As a security reviewer, I want the product to describe Nono as host capability containment, so that users do not mistake it for a gVisor, container, VM, or multi-tenant boundary. 52. As a security reviewer, I want the Nono alpha status and platform gaps recorded in release decisions, so that production exposure requires an explicit review. ## Implementation Decisions - Deliver the work in two independently verifiable stages named Minimal PoC and Usable Product. Do not mix product UI and plugin distribution into the PoC acceptance gate. - Port the workspace runtime model and versioned external command contract onto current main instead of merging the proof-of-concept branch. Preserve its useful ownership rule: one persisted runtime identifier addresses one workspace, while physical placement remains private to the runtime. - Use one workspace runtime seam for process execution, terminals, files, Git observation, workspace scripts, setup and archive commands, provider discovery, adapter discovery, and agent launch. Provider modules receive a bound workspace capability and do not branch on Nono. - Keep Nono outside the daemon process. The daemon launches a trusted runtime command, and that command launches Nono. Do not apply the Nono library or FFI sandbox to the daemon because applying it is irreversible for the current process. - Use Nono's CLI as the supported integration boundary. Pin the Nono binary and validate its version and required capabilities before advertising the runtime. - Launch Pi through ordinary piped Nono supervised execution. Do not use Nono detached sessions or attach for Pi RPC. Keep stdin, stdout, and stderr byte-preserving so the existing JSONL transport retains framing, correlation, diagnostics, and timeout ownership. - Treat the workspace as the containment unit. Agent sessions in one workspace may share its cwd, policy, provider state, and network grants. They do not share live process handles. Workspaces with the same cwd receive the same directory access but separate workspace-owned provider state and generated launch files. - Accept the relaxed daemon-restart contract. Live agent sessions do not reconnect after a daemon restart. The daemon terminates active Nono processes during graceful shutdown and performs fail-closed orphan cleanup before admitting work after startup. - Preserve workspace backing across daemon restart, archive, and restore. Persist enough Nono runtime identity and state ownership to inspect, stop, and delete Paseo-owned state without persisting raw PIDs as durable identity. - Generate a fully resolved Nono capability manifest for each workspace launch. Do not load user profiles, registry packs, inherited groups, unsandboxed hooks, capability elevation, or interactive approval flows in the daemon-managed path. - Build manifests from typed trusted inputs. The inputs include workspace identity, canonical cwd, separately resolved Git metadata, process purpose, selected provider/model endpoint, credential mode, generated launch files, state locations, MCP route, environment allowlist, and resource policy. - Validate all filesystem paths at the enforcement boundary. Grant the workspace cwd read-write, grant only required linked-worktree metadata, grant Pi and required tools read/execute access, grant workspace-owned Pi state read-write, and grant generated launch files as individual read-only files where the platform supports the distinction. - Give each workspace a dedicated temporary directory and set its temporary-directory environment accordingly. Do not grant the daemon's global temporary directory. - Start every manifest with explicit environment filtering. Pass only runtime basics, selected provider settings, Paseo agent facts, proxy settings, and variables required by the chosen toolchain. Do not inherit the daemon environment. - Start every manifest with restricted networking. The Minimal PoC supports one selected API-key model route. The Usable Product derives allowed provider and token-refresh endpoints from the effective provider/model configuration and rejects a provider whose network contract cannot be represented. - Use Nono reverse-proxy credential injection for supported API-key providers. For OAuth flows that require mutable files, grant one coordinated workspace-owned credential file and document that the agent can access that file. Do not copy one mutable OAuth file into independent workspace stores without a reconciliation rule. - Add a daemon-owned, route-limited MCP bridge for Nono workspaces. The sandbox may reach only the agent-scoped MCP route, not the complete daemon listener. Keep the existing bearer authorization and rotate it with daemon identity as today. - Generate Pi MCP configuration and adapter probes inside the selected runtime. Remove the proof-of-concept behavior that rejects MCP for runtime-selected workspaces. - Implement runtime-aware Pi session discovery. Store or project Pi session state under a stable workspace-owned path, preserve provider native handles across process replacement, and exclude sessions owned by another workspace. - Keep runtime selection optional on protocol and persistence boundaries for compatibility. Existing workspace records remain Local. New fields are additive, and new runtime RPCs use dotted namespaces with request/response suffixes. - Gate runtime UI once on the daemon's workspace-runtime feature and on the selected runtime's enforceable capability facts. Do not add client fallback branches. - The Minimal PoC registers the Nono command through trusted daemon configuration. It does not change the plugin API. - The Usable Product adds one declarative workspace-runtime contribution to the trusted local plugin manifest. The contribution contains stable identity, label, executable path, protocol version, and compatibility metadata. The daemon resolves the executable inside the plugin source, validates it, and launches it directly. - Keep the plugin subprocess out of streaming process execution. Plugin RPC remains request/response JSON for configuration and UI behavior. The runtime command owns byte streams, PTY control, signals, and exit reporting. - Runtime disable, reload, or removal never changes a workspace's selected runtime. Stop active runtime processes before disabling. Bound workspaces become unavailable until the same runtime identity returns or the user permanently removes and recreates the workspace under another runtime. - Expose availability as an enforceable runtime fact. Check operating system, Nono version, Landlock or Seatbelt support, required network mode, cgroup delegation when limits are requested, executable availability, and manifest compatibility. - The Minimal PoC supports host-installed Linux only and requires the exact kernel features used by its manifest. The Usable Product adds macOS after Seatbelt behavior passes the same contract tests and adds the official Linux Docker deployment after image and cgroup behavior pass dedicated tests. - Reject WSL2 in the first Usable Product release when the selected policy needs proxy confinement or per-port filtering. Do not enable Nono's insecure WSL2 proxy mode. - In Docker, bake Nono, Pi, Node, and required tools into the supported daemon image. Use Docker for the outer PID, mount, and network namespace and for CPU or I/O controls. Use Nono for path, environment, credential, and endpoint policy inside that boundary. - Apply Linux Nono memory and process-count limits only after capability checks prove cgroup v2 delegation. Delegate those limits to Docker when the daemon container owns them. Paseo still owns wall-time cancellation and must add separate controls for CPU, disk, and file descriptors if product requirements demand them. - Preserve Nono diagnostics on stderr without allowing them onto Pi stdout. Normalize policy, capability, startup, and cleanup failures into typed runtime errors with separate user-facing text and debug details. - Keep bounded audit and runtime diagnostic state under Paseo-owned storage. Define retention by age and count. Do not expose signing, attestation, or rollback UI in this delivery. - Pin Nono 0.74.0 for the first PoC evaluation. Treat every Nono update as a security-sensitive dependency change that reruns platform, policy, protocol, and escape tests. Revisit production readiness because upstream labels the current release alpha and does not recommend production use before its planned external audit. ### Minimal PoC success criteria - A developer can configure the Nono runtime and create one Pi workspace on a supported Linux host. - Pi RPC starts with ordinary pipes, completes a prompt, runs a workspace command, reports output, and exits cleanly. - Pi can read and write its workspace and required Git metadata. - Attempts to read another workspace, daemon state, SSH keys, or cloud credentials fail. - Model traffic reaches only the configured test provider endpoint; arbitrary outbound traffic fails. - The agent receives no unrelated daemon environment variables. - Cancel terminates Pi and its descendants within a bounded timeout. - Graceful daemon shutdown and subsequent startup leave no runnable PoC process behind. - Runtime startup or policy failure returns an explicit error and never launches Pi locally. ### Usable Product success criteria - A user can install and enable the trusted Nono plugin, select Nono during workspace creation, and see runtime availability before submitting work. - Pi create, subsequent turns, resume, import, provider discovery, adapter discovery, Paseo MCP tools, files, Git, terminals, and workspace scripts work through the selected runtime. - Linux, macOS, and the supported Docker deployment each pass their declared capability matrix. Unsupported platform features make the runtime unavailable with an actionable reason. - Plugin reload, disable, removal, archive, restore, daemon shutdown, daemon startup, and permanent workspace removal preserve the fail-closed lifecycle contract. - The resolved manifest and retained diagnostics let an operator explain every granted workspace path, provider endpoint, credential mode, and resource control. - Existing Local and New worktree behavior remains unchanged for workspaces without a selected runtime. ## Testing Decisions - Test behavior through the highest existing seam: the public workspace runtime command contract exercised by a real Nono executable and a real daemon. Do not mock Nono, process spawning, the filesystem, Git, network policy, or the runtime protocol in contract and end-to-end coverage. - Maintain one reusable runtime contract harness. Run the same lifecycle, pipe, PTY, signal, filesystem, Git observation, archive, restore, and cleanup assertions against the fixture runtime and Nono runtime. The workspace runtime proof of concept provides prior art for this harness. - Keep pure unit tests only for typed manifest construction, capability resolution, path planning, compatibility projection, and error mapping. Inject small typed adapters rather than mocking module exports. - Add real Linux containment tests that launch an adversarial child and assert exact outcomes for allowed workspace reads/writes, unrelated workspace denial, credential-path denial, daemon-state denial, symlink escape denial, Unix-socket denial, environment filtering, endpoint allowlisting, arbitrary egress denial, memory limits, process-count limits, cancellation, and orphan cleanup. - Test linked-worktree access with real Git repositories. Cover gitdir, commondir, alternates, submodules, and a sibling worktree that must remain inaccessible. - Test Pi JSONL transport with a deterministic fake Pi executable through Nono. Assert byte framing, stderr separation, request correlation, EOF, non-zero exit, signals, timeout, and descendant cleanup. This keeps normal CI independent of provider credentials. - Add a real-provider Pi smoke under the existing real-provider test category. It proves one prompt, model traffic, a workspace edit, a Paseo MCP call, close, resume, and import with actual Pi authentication. Do not add authentication gates or conditional skips. - Test MCP through a real route-limited bridge. Assert that the agent-scoped endpoint works, another agent identity fails, unrelated daemon routes fail, and the complete daemon listener is unreachable from the sandbox. - Test credential brokering by proving the upstream receives the real credential while the Pi process environment, filesystem, argv, and request logs contain only the phantom credential. Add a separate OAuth-file test when that mode enters scope. - Test runtime availability as observable API behavior. Cover missing Nono, wrong version, unsupported manifest version, missing Landlock or Seatbelt support, unavailable cgroup delegation, unsupported WSL2 proxy policy, invalid executable, and plugin runtime disappearance. - Add targeted app browser coverage for runtime selection and all fallible states: immediate pending state, successful creation, unavailable runtime, startup failure, plugin disablement, and retry. Assert rendered user behavior rather than internal store state. - Add real Electron coverage only where plugin installation or desktop-managed daemon packaging differs from browser behavior. - Add a supported Docker end-to-end test that uses the production daemon image, real Nono, real workspace volume, real network boundary, and declared resource owner. The test proves that the sandbox cannot read daemon state even though both live in one outer container. - Add macOS contract runs for Seatbelt before advertising macOS support. Keep expected platform differences explicit instead of weakening common assertions. - Run security matrix tests on every pinned Nono update. Record the Nono version, operating system, kernel or macOS version, detected Landlock ABI, manifest version, and enabled resource controllers in failure artifacts. - Keep tests deterministic. Use fixed temporary roots, loopback test endpoints, bounded waits tied to explicit readiness, and exact error assertions. Never branch assertions based on whether enforcement happened. - Treat any requested control that cannot be enforced as a startup failure. Tests must not accept warnings followed by unsandboxed or degraded execution. ## Out of Scope - Active Pi RPC continuation, output replay, or process attachment after daemon restart. - Per-agent isolation inside one workspace. Agent sessions in the same workspace share its policy boundary. - Multi-tenant isolation or protection from another malicious process running as the same host uid. - A separate kernel, gVisor, Firecracker, Kata, Edera, or other VM-backed execution backend. - Native Windows support. WSL2 remains unsupported when required Nono network controls cannot be enforced. - Support for providers other than Pi in the first Usable Product release. - Arbitrary user Nono profiles, registry packs, policy inheritance, capability elevation, interactive approval, or unsandboxed hooks in daemon-managed runs. - Nono detached sessions or using Nono attach as Paseo's agent transport. - Nono rollback review and restore UI. - Audit signing, Sigstore attestation, remote audit delivery, and external audit anchoring. - CPU, disk, and I/O quotas supplied by Nono. Docker or a future outer runtime may add them. - General plugin registration of agent providers or execution backends. This spec adds one declarative workspace runtime contribution. - Automatic conversion of an existing Local workspace to Nono. Users create a new workspace under the selected runtime. - Silent fallback from Nono to Local under any failure condition. ## Further Notes - Nono 0.74.0 preserves ordinary piped stdio under supervised execution, which fits Pi's current JSONL transport. Its detached lifecycle allocates a PTY and does not fit this integration. - Nono enforces host capability policy. It does not create a mount or PID namespace and does not offer gVisor's guest boundary. Product copy and runtime diagnostics must use that description. - Landlock feature depth depends on the host kernel. Filesystem enforcement has a wider support range than TCP filtering, signal scoping, process-information scoping, and abstract Unix-socket scoping. Runtime availability must reflect the exact manifest rather than a single generic Linux-supported flag. - Nono networking is unrestricted unless the manifest requests a restricted mode. Paseo-generated manifests must set network policy explicitly. - Nono resource limits require Linux cgroup v2 and delegated controllers. The official Docker deployment may need Docker-owned limits instead of Nono-owned user-session cgroups. - A pathname shared by two workspace records remains shared directory-backed data under Paseo's existing model. The Nono boundary protects both from paths outside that cwd; it does not pretend the two records have separate copies. - The workspace runtime proof of concept remains research input. Its contract framing, fail-closed versioning, runtime selection, helper confinement, and routing tests are useful. Its branch is too divergent from current main to merge as implementation. - Nono is Apache-2.0. Paseo's own distribution and AGPL obligations remain unchanged.
jb added the ready-for-agent label 2026-08-23 01:19:55 -04:00
jb self-assigned this 2026-08-23 01:24:23 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jb/paseo#15