Files
PaperClipAI/packages/plugins
Nicky LeachandPaperclip 3d23c3b2c3 perf(plugin-daytona): cache the started sandbox handle per lease (#10335)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Daytona sandbox adapter spends time on repeated per-exec sandbox
lookups during a lease
> - That repeated lookup is pure overhead once the started sandbox
handle is already known and trusted for the lease
> - The adapter still needs strict isolation and fail-closed behavior
because the handle is an authenticated compute object, not an inert
value
> - This pull request memoizes the started sandbox handle per lease in a
process-scoped cache, and now advances freshness only after successful
reuse so failed commands cannot suppress stale-handle refreshes
> - The benefit is lower provider-get latency on the hot path while
keeping resume safety, teardown safety, and observability intact

## Linked Issues or Issue Description

This is a Daytona performance fix, not a standalone public GitHub issue.

### Problem / Motivation

- Repeated `client.get(sandboxId)` calls on the sandbox hot path
re-fetch a handle that is already started and trusted for the current
lease.
- The extra provider round-trip is pure overhead on repeated exec, sync,
resume, and interactive-cancel flows.
- Cache freshness also has to be tied to successful reuse, or a failed
command can make a stale sandbox look freshly used.

### Proposed Solution

- Cache the started `Sandbox` handle in process memory, keyed by a
non-secret composite lease scope.
- Enforce strict identity checks and eviction on release, destroy,
interactive cancel, and resume-sentinel mismatch.
- Block teardown cleanup until active lease operations finish so
delete/stop cannot race in-flight execute or sync work.
- Advance freshness only after successful execute, sync, or resume
reuse, so failed operations do not mask an auto-stopped sandbox.
- Preserve `getDurationMs` in exec metadata so provider-get latency
remains observable.

### Alternatives Considered

- Keep fetching the sandbox on every exec path.
- Cache only by bare lease id.

### Roadmap Alignment

- This change is part of the Daytona performance work and narrows
per-call overhead without changing the public adapter contract.

## What Changed

- Memoized the started Daytona `Sandbox` handle in a process-scoped
cache keyed by a composite lease scope.
- Added fail-closed identity checks so cache hits and single-flight
populate paths reject mismatched sandbox ids.
- Evicted cached handles on release, destroy, interactive cancel, and
resume sentinel mismatch.
- Blocked release, destroy, and interactive cancel teardown cleanup
until active lease operations drain.
- Advanced cache freshness only after successful execute, sync, and
resume reuse.
- Kept `getDurationMs` in exec metadata so provider-get latency remains
observable.
- Expanded the Daytona plugin tests to cover same-lease reuse,
cross-scope isolation, eviction paths, rejected populate handling,
concurrent single-flight behavior, cached-resume sentinel revalidation,
teardown cancellation safety, and failed-execute freshness handling.

## Verification

- `pnpm exec vitest run --config vitest.config.ts` in
`packages/plugins/sandbox-providers/daytona` — 81/81 passing, including
the teardown-cancel, snapshot-capture, syncIn-cancel, and failed-execute
freshness regressions.
- `git rev-parse origin/perf/daytona-sandbox-handle-cache` matched the
authorized submit SHA `528158f998fa88bb0748300f156b38d86d6589cd` before
the fixup commits.
- `git log --oneline
origin/master..origin/perf/daytona-sandbox-handle-cache` shows the
expected focused Daytona changes.
- GitHub duplicate/related PR search and ROADMAP review were completed
before opening the PR.
- Remote CI and Greptile completed successfully after this description
was updated; the PR is now ready for board handoff.

## Risks

- The cache is process-scoped, so correctness depends on the eviction
paths staying complete.
- A bug in the scope key or identity checks could leak reuse across the
wrong lease boundaries, but the implementation fails closed on id
mismatches.
- Teardown now waits for active operations to drain, so any missed
activity bookkeeping could delay cleanup instead of racing it.
- Freshness updates now happen after success, which is safer, but it
means any missed success-path call would trigger an extra refresh rather
than silently masking staleness.

## Model Used

OpenAI GPT-5 via Codex, tool-using coding agent; exact context window
not surfaced in the workspace.

## 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-07-27 19:14:53 -07:00
..