## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Sandboxed agents use provider capabilities to select safe execution paths > - Session output still depends on three operator flags that duplicate capability data > - Duplicate flags can drift from the verified sandbox capability snapshot > - This pull request makes the capability snapshot the only streaming decision and removes the obsolete flags > - The benefit is default streaming with a poll fallback when a capability or stream fails ## Linked Issues or Issue Description **What existing behavior does this improve?** ACP sandbox session-output streaming and sandbox execution configuration. **Subsystem affected** Cross-cutting (multiple of the above): server/, packages/shared/, packages/adapter-utils/, and packages/plugins/. **Current behavior** Session-output streaming requires operator flags in the server and Daytona plugin configuration. Saved configurations can retain a removed key. **Proposed behavior** The verified capability snapshot selects streaming. The Daytona plugin uses persistent sessions by default, keeps bypass commands one-shot, and falls back from the log stream to polling. Removed configuration keys become inert. **Reason and benefit** One capability source prevents configuration drift. The fallback keeps output available when capability resolution or log streaming fails. **Breaking changes** The three operator flags no longer control session-output streaming. Existing saved keys load but have no effect. ## What Changed - Remove `useSessions` and `useLogStream` from the Daytona plugin configuration and manifest. - Remove `streamAgentSessionOutput` from server configuration, shared types, and execution-target plumbing. - Select streaming from `persistentProcessSessions` and `independentControlCommands`. - Keep poll fallback on capability resolution failure and stream failure. - Strip removed keys from strict fake-sandbox and catchall plugin configuration. - Update the sandbox capability documentation and focused tests. ## Verification - `tsc --noEmit` passed in `packages/shared`, `packages/adapter-utils`, `server`, and the Daytona plugin. - Daytona `plugin.test.ts` passed 139 tests. - Server capability, configuration, route, and runtime suites passed 160 tests. - `packages/adapter-utils` `execution-target-sandbox.test.ts` passed 44 tests. - The capability matrix covers stream, poll, and resolution-failure paths. - Removed-key tests cover strict fake-sandbox and catchall plugin schemas. ## Risks - A capability snapshot that lacks either required session capability uses polling. - A log stream failure uses polling and can increase request count. - Existing removed configuration keys no longer change behavior. - The isolated-worktree Daytona Vitest run has a pre-existing missing `packages/adapters/droid-local` reference. CI and standard checkouts use the committed configuration. ## Model Used OpenAI Codex, GPT-5, tool use and code review assistance. The exact runtime context window is managed by the Codex platform. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes: #` / `Refs: #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
8.9 KiB
Sandbox provider capability contract
A sandbox provider plugin declares an environment driver with
kind: "sandbox_provider". Each driver can declare a set of optional sandbox
capabilities. This document is the contract for a third-party provider author.
It states what to declare, which worker methods each capability needs, what an
omitted key means, and when the host narrows or denies a capability.
Read Sandbox file-sync lifecycle hooks for the native file-transfer hooks. Read the driver declaration shape in the plugin specification.
How the host resolves an effective capability
The host never trusts a declaration alone. For every run it resolves each capability as the intersection of three inputs:
effective = verified ∩ declared ∩ narrowing
- verified — the methods the live worker advertised in
InitializeResult.supportedMethods. The host maps each capability to the worker methods it needs (see the table below). A capability is verified only when the worker advertises every required method. An empty or missing method list verifies nothing, so every capability resolvesfalse. - declared — the values in the driver declaration. A declared key is optional (see the next section).
- narrowing — a per-run restriction from the lease policy or the resolved provider config. A narrowing can only remove a capability, never add one.
A capability is effective only when all three allow it. A declaration therefore never grants a capability that the worker did not verify.
The declaration is optional and partial
Declare capabilities through the nested sandboxCapabilities object on the
driver declaration:
environmentDrivers: [
{
driverKey: "my-provider",
kind: "sandbox_provider",
displayName: "My Provider",
configSchema: { type: "object", properties: {} },
sandboxCapabilities: {
reusableLeases: true,
nativeSyncIn: true,
nativeSyncOut: true,
persistentProcessSessions: false,
independentControlCommands: false,
incrementalSessionOutput: false,
},
},
]
Every key is optional. For a valid, identified provider each key has one of three states:
- Omitted — the host defers to verified worker discovery. The capability is
effective when the worker advertises the required methods and no narrowing
removes it. Omission is the correct default for a provider that follows the
standard method contract.
reusableLeasesandincrementalSessionOutputare the two exceptions: an omitted key never grants either capability. Both are opt-in (see the next two sections). false— the host narrows the capability to off. The capability is never effective, even when the worker advertises the required methods.true— the host still requires the verified prerequisites. Atruevalue never grants a capability without them. It documents intent and lets the host present the capability, but the worker must still advertise the required methods.
Reusable leases need an explicit opt-in
Reusable leases are the exception to the omission rule above. The host grants
reusable-lease acquisition only when the declaration sets reusableLeases to
true. The provider opts in through one of two fields:
- the nested
sandboxCapabilities.reusableLeases: true, or - the legacy
supportsReusableLeases: true.
An omitted key leaves reusableLeases unset. An unset key does not make the
provider eligible for reusable-lease acquisition, and it does not advertise
provider-level reusable support. The host then always creates an ephemeral lease.
The opt-in never removes the other prerequisites. The worker must still verify
all three lifecycle methods, environmentResumeLease, environmentReleaseLease,
and environmentDestroyLease, and per-run narrowing still applies.
The two opt-in fields have a fixed precedence. The host keeps the legacy
supportsReusableLeases field for backward compatibility, and it folds the field
into sandboxCapabilities.reusableLeases.
- When only
supportsReusableLeasesis present, the host reads it asreusableLeases. - When both
supportsReusableLeasesandsandboxCapabilities.reusableLeasesare present, the nested value wins.
A manifest with legacy true and nested false therefore resolves to false.
Prefer the nested sandboxCapabilities.reusableLeases in a new manifest.
Incremental session output needs an explicit opt-in
Incremental session output is the second exception to the omission rule. The host
selects the session-output streaming path only when the declaration sets
incrementalSessionOutput to true. An omitted key resolves the capability to
false, so the host keeps the output-file poll path.
The reason is that this key is a behavioral guarantee, not a worker-method
property. A generic one-shot provider can keep persistent process sessions and run
independent control commands, yet it never emits incremental stdout and stderr
from a live session. The two broad capabilities do not imply incremental output,
so the host requires the provider to declare the behavior. A provider that streams
incremental session output declares sandboxCapabilities.incrementalSessionOutput: true; every other provider omits the key and keeps the poll path.
The opt-in never removes the prerequisites. The worker must still verify
environmentExecute, and per-run narrowing still applies. A config-resolution
failure narrows the capability to off (see Failure behavior).
The capabilities and their worker-method prerequisites
| Capability | Required worker methods | Meaning |
|---|---|---|
reusableLeases |
environmentResumeLease, environmentReleaseLease, and environmentDestroyLease |
The host retains a provider lease and resumes it across runs. |
nativeSyncIn |
environmentSyncIn |
The host transfers files into the sandbox through the native inbound hook. |
nativeSyncOut |
environmentSyncOut |
The host transfers files out of the sandbox through the native outbound hook. |
persistentProcessSessions |
environmentExecute |
The provider keeps a persistent process session open across commands. |
independentControlCommands |
environmentExecute |
The provider runs a one-shot control command beside a long-lived command. |
incrementalSessionOutput |
environmentExecute |
The provider streams incremental stdout and stderr from a live session. Opt-in: an omitted key resolves false. |
Reusable leases need all three lifecycle methods. The host resumes a lease with
environmentResumeLease, ends it with environmentReleaseLease, and tears down a
stale lease with environmentDestroyLease. The reuse path destroys a stale lease
when a resume fails, so a provider that cannot destroy a lease would strand it. A
worker that omits any of the three methods never gets reusable leases, even with a
positive declaration. The host then always creates an ephemeral lease.
The host advertises and consumes the two native sync methods as a pair. Define both or neither. See Sandbox file-sync lifecycle hooks.
Target and config narrowing
A narrowing removes a capability that the provider verified and declared but that this run cannot use.
- Ephemeral lease policy. A lease that the host does not retain never reuses.
The host narrows
reusableLeasesto off for an ephemeral lease and keeps it on only for a reuse-by-environment lease. - Kubernetes Job backend. A Job-backed lease, or a lease the host marked as
unable to run native file sync, disables native sync. The host narrows
nativeSyncInandnativeSyncOutto off and keeps the base64-over-exec fallback.
Failure behavior
The host fails closed on two failure states. It never grants a capability from an unknown state.
- Config-resolution failure. A provider whose config the host cannot resolve
is untrusted. The host narrows
persistentProcessSessionsandincrementalSessionOutputto off instead of allowing either through an empty config. - Exact-plugin identity failure. A retained lease pins the exact plugin that
acquired it. When that plugin is absent, or when it no longer declares this
provider key with the
sandbox_providerkind, the host cannot establish the declaration. It resolves every effective capability tofalse, no matter what methods a stale worker still advertises. An omittedsandboxCapabilitiesobject on a valid, identified plugin is a different state; the host defers to verified discovery for that case.
Concurrency capabilities are not part of the contract
Earlier drafts listed two concurrency keys, concurrentSyncAndExec and
concurrentSyncOperations. The runtime never exposed a scheduling choice that
read either key, so a declaration had no effect. The host removed both keys. The
strict capability validator now rejects them as unknown keys. The host can
reintroduce a concurrency capability when a runtime path enforces it.