feat(observability): rename sandbox provider spans and add run-time wrapper spans (#10999)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip uses adapter and sandbox code to start agents and run
sandbox work
> - The current sandbox spans use mixed names and do not group related
run-time work
> - Mixed names make traces harder to read and compare across providers
> - This pull request renames provider spans, adds run-time wrapper
spans, and keeps the host allowlist closed
> - The benefit is clearer traces with the same sandbox behavior and
trust boundary

## Linked Issues or Issue Description

**What existing behavior does this improve?**

This improves OpenTelemetry span names and grouping for sandbox startup,
execution, callback relay, and agent session work.

**Subsystem affected**

Cross-cutting (multiple of the above): adapter utilities, sandbox
providers, shared telemetry documentation, and server instrumentation.

**Current behavior**

Sandbox provider spans use mixed names. Related run-time operations
expose inner `sandbox.exec` spans without a named wrapper span. The host
mapper uses a closed allowlist for provider span names.

**Proposed behavior**

Use descriptive provider-scoped span names. Add wrapper spans for agent
session input, agent session output polling, and callback relay. Keep
the host mapper allowlist closed and map unknown names to `other`.

**Reason and benefit**

Clear names make traces easier to read and reduce ambiguity during
sandbox operation analysis. Wrapper spans show the full operation while
preserving the inner execution spans.

**Breaking changes**

None. This change updates telemetry span names and grouping only. It
does not change sandbox behavior, endpoint behavior, or the host trust
boundary.

**Additional context**

Related prior work:
[#10758](https://github.com/paperclipai/paperclip/pull/10758).

## What Changed

- Rename Daytona provider sync and session spans with descriptive
provider-scoped names.
- Add three run-time wrapper spans for agent session input, output
polling, and callback relay.
- Add a shared span runner that preserves no-op behavior without a real
tracer.
- Keep the host mapper allowlist closed and map unknown names to
`other`.
- Update telemetry documentation and span-name tests.

## Verification

- Focused adapter-utils span tests pass for startup timing, callback
relay, and sandbox execution.
- Focused Daytona plugin span tests pass for renamed leaf spans and
session open or close spans.
- Focused server tests pass for host mapping and instrumentation.
- The stacked diff contains one commit on top of
`feat/daytona-persistent-session-model`.

## Risks

- Span names change for existing telemetry consumers.
- The wrapper spans add trace structure but do not change sandbox
execution.
- The host mapper keeps the existing closed allowlist and `other`
bucket.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI GPT-5 (Codex agent); exact deployment revision and context window
are not exposed in this run; tool use and code execution 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] 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>
This commit is contained in:
Nicky LeachandPaperclip authored and GitHub committed 2026-08-06 22:31:15 -07:00
1 parent 03cfad7ceb
commit 9ace548fd2
15 files changed
+539 -123

No files matched your search

+19 -15
View File
@@ -507,29 +507,33 @@ const SESSION_EVENT_SUBSCRIPTION_TIMEOUT_MS = 30 * 60 * 1_000; // 30 minutes
const SPAN_ATTRS = SANDBOX_STARTUP_SPAN_ATTRS;
/** The closed set of provider span names a plugin may emit. `pack` and
* `transfer` are the host-local build and the byte upload. `mkdir`, `guard`,
* `rename`, `extract`, and `provision` are the per-round-trip command spans in
* the inbound sync path. `session.setup` and `session.teardown` are the short
* spans that wrap a persistent-session create and delete. */
/** The closed set of provider span leaf names a plugin may emit. `pack` and
* `transfer` are the host-local build and the byte upload. `ensureDirectory`,
* `checkSymlinkEscape`, `promote`, `extractTarball`, and `postUploadCommand`
* are the per-round-trip command spans in the inbound sync path. `session.open`
* and `session.close` are the short spans that wrap a persistent-session create
* and delete. */
const KNOWN_PROVIDER_SPAN_NAMES: ReadonlySet<string> = new Set([
"pack",
"transfer",
"mkdir",
"guard",
"rename",
"extract",
"provision",
"session.setup",
"session.teardown",
"ensureDirectory",
"checkSymlinkEscape",
"promote",
"extractTarball",
"postUploadCommand",
"session.open",
"session.close",
]);
/** Clamp the span name to a closed, namespaced set. A known name maps to
* `sandbox.provider.<name>`; any other value maps to `sandbox.provider.other`,
* so a span name never carries free-form data. */
* `sandbox.daytona.<name>`; any other value maps to `sandbox.daytona.other`, so
* a span name never carries free-form data. Only the daytona provider emits
* these spans today, so the segment is the literal `daytona`. When a second
* provider emits provider spans, derive the segment from the normalized
* `provider` family attribute on the span instead of this literal. */
function clampProviderSpanName(raw: unknown): string {
const name = typeof raw === "string" && KNOWN_PROVIDER_SPAN_NAMES.has(raw) ? raw : "other";
return `sandbox.provider.${name}`;
return `sandbox.daytona.${name}`;
}
/** The closed allowlist of attribute keys a provider span may carry. The host