Files
PaperClipAI/packages/plugins/sandbox-providers/daytona/DIRECTORY-CONSTRAINT-FINDINGS.md
Nicky LeachandPaperclip cfed36ea6b feat(plugin-daytona): persistent session model with plain command dispatch (#10941)
## Thinking Path

> - Paperclip is the control plane for autonomous AI companies
> - One core subsystem runs agent work inside sandboxes
> - The Daytona provider uses that path to run user commands
> - The current one-shot model does not keep a shell alive across
commands
> - This pull request adds an opt-in persistent session model for
Daytona
> - The benefit is faster command dispatch with the same sandbox
boundaries

## Linked Issues or Issue Description

### Subsystem affected

Cross-cutting. This change touches `packages/adapters`, provider tests,
span names, and sandbox command behavior.

### Problem or motivation

The Daytona provider needs a persistent shell for repeated command
dispatch.
The old advisory wrapper path does not reach that goal.
It also adds cost and removes the session speed gain.

### Proposed solution

Add a `useSessions` driver flag.
Keep it off by default.
Open one Daytona session per lease when the flag is on.
Send each user command into that session.
Read stdout and stderr from the session logs endpoint.
Run each command in a subshell so `exit` does not stop the shell.
Remove the advisory `bwrap` wrapper path and its lease metadata.
Add session setup and teardown spans.
Keep a hard delete on teardown.

### Alternatives considered

Keep the advisory `bwrap` wrapper.
That path does not give a real persistent session.
It also keeps extra command overhead.
Keep a one-shot fallback for user commands.
That would weaken the session model and hide a missing session case.

### Roadmap alignment

This work fits the `Cloud / Sandbox agents` milestone in `ROADMAP.md`.
It also supports the control plane goal of safe remote sandbox
execution.

### Additional context

The handoff verification reported `tsc --noEmit` clean and 119 Daytona
unit tests passing.
The handoff also reported a clean host span allowlist test and five
expected commits on the branch.
The security review gate remains required before merge.

## What Changed

- Added an opt-in persistent session model for the Daytona sandbox
provider.
- Routed user commands through `executeSessionCommand` when sessions are
enabled.
- Removed the advisory `bwrap` command wrapper path and the lease
metadata it used.
- Added session lifecycle spans and span allowlist coverage.
- Documented the leak bound in `DIRECTORY-CONSTRAINT-FINDINGS.md`.

## Verification

- `tsc --noEmit` clean for the Daytona plugin, per handoff verification.
- Daytona unit suite passes, with 119 tests, per handoff verification.
- Host span allowlist test passes, per handoff verification.

## Risks

- Persistent sessions can leak if teardown fails.
- Session logs must keep stdout and stderr separate.
- The flag stays off by default to limit rollout risk.

## Model Used

OpenAI GPT-5, Codex, tool use enabled.

## 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>
2026-08-06 17:38:00 -07:00

3.6 KiB

Directory-constraint (bwrap) findings for the Daytona session path

Summary

The Daytona provider ran an advisory bubblewrap (bwrap) wrapper around each user command. The wrapper gave an agent real-time feedback when the agent wrote to a directory that the ephemeral sandbox does not keep. The wrapper was advisory only. It added no security boundary; the ephemeral sandbox is the only boundary.

The provider now runs each user command plain, both on the persistent-session path and on the one-shot fallback path. This note records why the provider drops the directory-constraint goal for now, and what a future re-introduction needs.

Why the goal is dropped for now

The session model runs one persistent shell per lease and feeds every command into that shell. The plan aimed to enter the bwrap sandbox one time per session and amortize its cost across every command in the session. A live validation proved this aim is not reachable with the current Daytona session daemon.

  • An exec-replace of the session shell with bwrap stops the session from running any later command. A side-channel file proved the inner shell never ran the next command. A repeat without --new-session gave the same result, so --new-session is not the cause. The exec-replace itself breaks the session daemon's per-command delivery.
  • A per-command bwrap wrap works inside a session (separated stdout and stderr, correct exit codes, the session stays alive), but it costs about 1.75 s per command. An un-wrapped session command takes about 0.29 s; a bwrap-wrapped session command takes about 2.05 s. The wrap adds the full cost back on every command, which removes the session model's speed win.

The wrapper was advisory, not a security boundary. The provider already ran a command plain when the bwrap capability probe failed, so the plain path is an accepted, shipped behavior. Dropping the wrapper drops only the advisory feedback; it does not change the command's privilege or isolation. The command runs as the unprivileged sandbox user (daytona) in both cases.

Removal scope

  • The session dispatch runs the plain login-shell script, wrapped in a subshell so a top-level exit cannot end the persistent session shell.
  • The one-shot fallback (used when the session model is off) also runs the plain login-shell script.
  • The provider removed the per-command bwrap apply, the bwrap capability probe, the bwrap command builder, and the bwrapAvailable / sandboxUsername lease metadata that only the removed path used.
  • The provider keeps the advisory writable-directory set, which a sync operation records from each access: "rw" mapping. Nothing reads the set today. It stays in place so a future isolation wrapper can consume it without a new sync change.

What a future re-introduction needs

  • A persistent-namespace approach needs its own spike. Two candidates are:
    • Enter one namespace per session and route each command into it with nsenter.
    • Feed each command to the namespace shell through a FIFO instead of an exec-replace.
  • Both candidates need a live test against the Daytona session daemon, because the exec-replace model is proven unreachable and the per-command wrap is too slow.
  • The re-introduction must keep the session speed win. It must not pay the full bwrap setup cost on every command.
  • The writable-directory set is already tracked per lease, so a new wrapper can bind those directories read-write without a sync change.
  • The wrapper stays advisory. It must not become a security boundary; the ephemeral sandbox stays the only boundary.