Skip to content

feat(billing): track E2B and Daytona Function sandbox usage - #7184

Merged
icecrasher321 merged 10 commits into
stagingfrom
feat/e2b-daytona-usage-billing
Aug 29, 2026
Merged

feat(billing): track E2B and Daytona Function sandbox usage#7184
icecrasher321 merged 10 commits into
stagingfrom
feat/e2b-daytona-usage-billing

Conversation

@BillLeoutsakosvl346

@BillLeoutsakosvl346 BillLeoutsakosvl346 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • meter remote workflow Function code and shell sandboxes for E2B and Daytona
  • meter the sandbox a cloud Pi session runs in, which was billing nothing at all
  • calculate provider runtime cost in-process and emit it as ordinary Function output.cost
  • reuse the existing trace/span cost summary and workflow usage-ledger pipeline used by hosted-key tools
  • attribute direct Function cost to its Function span; fold nested Function calls into Agent or Pi toolCost
  • keep local JavaScript, custom Function tools, non-workflow calls, local-mode Pi, and sandbox个人资料: mothership unmetered

Trace-native billing flow

E2B/Daytona elapsed resource usage
  -> Function output.cost
  -> direct Function span or Agent/Pi toolCost
  -> existing cost summary
  -> existing workflow logger and usage ledger

There is no sandbox-specific outbox, reconciliation worker, billing-period machinery, database schema, or new ledger path.

Runtime begins immediately before provider creation and ends when Function cleanup starts. Pricing uses the provider-effective TTL as a cap, the configured production multiplier once, and eight-decimal final rounding. Cleanup time is Sim infrastructure overhead.

Pi sandbox metering

Pi's own sandbox was never metered: withPiSandbox called createSandbox without the meterUsage argument, so only sandboxes created through executeFunctionRequest were charged. It is the larger consumer by an order of magnitude — a Function block holds a sandbox for seconds, a Pi session for a 31-minute minimum lifetime.

The gap was worst where it was least visible. A Pi coding agent normally runs BYOK, so its model cost is zero by definition and the ledger bills a model row on total > 0. With the sandbox unmetered, such a run wrote a zero-cost model_unbilled row: Sim collected only the flat execution fee while paying its provider for the whole session.

A cost sink now travels on PiRunContext, the seam backends already receive and the only one reaching all four cloud modes. The handler owns one sink covering both sandbox sources — Function tools in local mode, the agent's own sandbox in cloud mode — so neither can be dropped where cost is folded into the block's output. It rides in toolCost, which is what survives the BYOK zeroing.

Outcome policy

Applies to both Function and Pi sandboxes; a Pi session that ends by throwing is absorbed exactly as a Function run that never completed is.

Billable completed sandbox outcomes:

  • successful code and shell execution
  • returned user-code exceptions
  • returned nonzero shell exits
  • workspace export failures after the sandbox already completed
  • every provider-completed Function retry attempt
  • a cloud Pi session whose callback returned, including one whose commands exited non-zero

Uncharged outcomes:

  • provider creation or execution throws
  • sandbox/provider crashes
  • provider-reported timeout or E2B lifetime limit
  • cancellation
  • provisioning, corrupt-output, or sandbox-file collection failure
  • an enclosing Agent or Pi provider/memory failure before the parent produces its authoritative cost output
  • a Pi session that ends by throwing — provider crash, lifetime limit, or cancellation

The parent-failure rule intentionally matches hosted-key trace collection: nested tool cost is published through the completed parent span, not billed independently from partial child execution.

Pricing and scope

  • E2B: 2 vCPU and 4 GiB RAM
  • Daytona: 2 vCPU, 4 GiB RAM, and 10 GiB provisioned disk
  • provider list rates are multiplied by the existing COST_MULTIPLIER
  • provider credits, free allowances, and negotiated discounts are intentionally excluded
  • no Mothership-profile, local-JavaScript, custom Function-tool, or historical-backfill change

Accepted limitations

  • provider allocations that never return a usable result are not charged
  • process crashes can lose usage
  • nested Agent/Pi Function costs are aggregated into the parent rather than itemized per call
  • Pi sandbox cost lands in the Pi block's toolCost, so it is not separable from that block's other non-model cost in the ledger
  • if an Agent/Pi parent fails before publishing its final output, nested Function cost is not charged
  • persisted trace UI visibility continues to follow the existing per-span display policy

Validation

Latest focused run after rebasing onto staging:

  • 3,418 tests passed across remote-sandbox, Pi handlers, Function execution, billing, and provider dispatch
  • apps/sim type-check passed
  • apps/sim lint passed
  • API validation passed
  • tool-request and monorepo boundary checks passed
  • git diff --check passed

Charged amounts, verified against live providers

The unit tests pin the arithmetic and the conformance suite proves a cost is produced and routed — but that suite stubs the provider and mocks Date.now() to advance one millisecond per call, so total > 0 is the strongest claim it can make. It would hold equally well if the metered window were anchored to the wrong instants. Two opt-in smoke suites (SANDBOX_BILLING_SMOKE=1) hold a real sandbox open for a known interval and bound the charge between what that interval must cost and what the whole call could justify:

Sandbox Provider Session Billed Charged Implied rate
Function E2B 9302 ms 9.031 s $0.00041543 $0.1656/hr
Function Daytona 8721 ms 8.435 s $0.00039054 $0.16668/hr
Pi E2B 6412 ms 6.174 s $0.00028400 $0.1656/hr
Pi Daytona 7958 ms 7.604 s $0.00035207 $0.16668/hr

Every window sits inside its session, excluding a few hundred milliseconds of Sim-side overhead, and both implied rates match the providers' published pricing exactly.

Each smoke case was also confirmed to fail against the defect it guards, rather than assumed to: the Pi case reports expected 0 to be greater than or equal to 0.00023 against the original unmetered call, and the uncharged-session case reports a non-zero charge when the fee is applied unconditionally.

Ticket: https://sim-ai.slack.com/archives/C093DF8MA21/p1787866922654329

@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment 操作 Updated (UTC)
docs Skipped Skipped Aug 29, 2026 8:11pm

Request Review

@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR meters E2B and Daytona sandboxes used by workflow Function and cloud Pi execution through the existing trace and usage-ledger pipeline.

  • Calculates provider runtime cost using effective sandbox lifetime, configured pricing, and bounded elapsed time.
  • Propagates trusted sandbox cost through successful outputs, failures, retries, streaming execution, and Pi tool cost.
  • Adds focused pricing, provider-conformance, attribution, retry, handler, and live smoke coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/lib/function-execution/execute-request.ts Gates remote sandbox metering by workflow scope and carries provider-generated cost through Function execution results.
apps/sim/lib/execution/remote-sandbox/index.ts Defines the provider-neutral metering window and attaches cost only to outcomes considered billable.
apps/sim/lib/billing/sandbox-pricing.ts Calculates E2B and Daytona runtime cost from fixed resource profiles, effective lifetime, and the configured multiplier.
apps/sim/executor/execution/block-executor.ts Preserves trusted Function cost across retries and post-handler failures so trace output receives the authoritative total.
apps/sim/executor/handlers/pi/pi-handler.ts Aggregates Sim-paid sandbox usage into Pi tool cost for successful, streaming, and backend-reported error outcomes.
apps/sim/executor/handlers/pi/local/sim-tools.ts Accumulates remote Function sandbox charges produced by Function tools invoked during local Pi sessions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[E2B or Daytona sandbox] --> B[Measure completed runtime]
  B --> C[Calculate provider cost]
  C --> D[Function output cost or Pi sandbox-cost sink]
  D --> E[Block span cost or Pi toolCost]
  E --> F[Trace cost summary]
  F --> G[Workflow usage ledger]
Loading

Reviews (8): Last reviewed commit: "fix(billing): carry the Pi charge onto a..." | Re-trigger Greptile

Comment thread apps/sim/lib/function-execution/execute-request.ts Outdated
Comment thread apps/sim/lib/billing/sandbox-usage-outbox.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/sim/lib/execution/remote-sandbox/index.ts Outdated
Comment thread apps/sim/lib/function-execution/execute-request.ts Outdated
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile-apps @cubic-dev-ai please re-review the latest changes on this PR. The prior findings have been addressed or resolved with the documented scope decision, and the branch is updated with current staging.

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 28, 2026

Copy link
Copy Markdown

@greptile-apps @cubic-dev-ai please re-review the latest changes on this PR. The prior findings have been addressed or resolved with the documented scope decision, and the branch is updated with current staging.

@BillLeoutsakosvl346 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 22 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/sim/lib/function-execution/execute-request.ts Outdated
Comment thread apps/sim/lib/execution/remote-sandbox/index.ts Outdated
Comment thread apps/sim/lib/billing/sandbox-usage-outbox.ts Outdated
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai please re-review the latest changes in dd7aa1c. The three findings from the prior run have been addressed with focused E2B/Daytona, outbox, and Function response regressions, and all threads are resolved.

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 28, 2026

Copy link
Copy Markdown

@cubic-dev-ai please re-review the latest changes in dd7aa1c. The three findings from the prior run have been addressed with focused E2B/Daytona, outbox, and Function response regressions, and all threads are resolved.

@BillLeoutsakosvl346 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 22 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/sim/lib/billing/sandbox-usage-outbox.ts Outdated
Comment thread apps/sim/lib/execution/remote-sandbox/index.ts Outdated
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai please re-review the latest changes in a3a6dd2. Both latest sandbox billing findings are fixed, focused regressions pass across E2B and Daytona, and the full requested validation set is green.

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 28, 2026

Copy link
Copy Markdown

@cubic-dev-ai please re-review the latest changes in a3a6dd2. Both latest sandbox billing findings are fixed, focused regressions pass across E2B and Daytona, and the full requested validation set is green.

@BillLeoutsakosvl346 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 22 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

Implemented the sandbox durability follow-up in c88112edf2.

Review fixes included:

  • Persist a V2 sandbox.usage.finalize intent before any E2B/Daytona create call, then correlate the provider sandbox through E2B metadata or Daytona name/labels.
  • Distinguish definitive create rejection from ambiguous timeout/transport/conflict/rate-limit/server failures. Ambiguous creates return non-retryable 503 sandbox_create_uncertain; user code never runs while identity is uncertain.
  • Bound intent/attachment DB admission, provider recovery, and termination. Pre-expiry provider outages defer for 60 seconds without consuming outbox attempts.
  • Recover ambiguous creates by correlation. Known sandboxes are terminated and billed once; known identities past TTL bill exactly to frozen TTL without provider access. Identities that can never be confirmed are never charged, remain on hourly non-consuming reconciliation, and emit structured operator alerts.
  • Persist ranked terminal evidence (provider_confirmed > not_found_observed > provider_expiry), retry terminal patches, and reload before pricing so concurrent live cleanup wins.
  • Preserve immutable V1 pricing replay while rejecting non-positive multipliers for new intents.
  • Coordinate intent admission and cycle close through the payer subscription lock; a close waits for outstanding old-period sandbox events, while post-close admission refreshes only the effective period for the same payer.

Focused validation:

  • 374 tests passed across sandbox pricing/finalization, cycle close, core outbox, E2B/Daytona conformance, and Function execution responses.
  • apps/sim type-check passed.
  • apps/sim lint passed.
  • API validation audit passed.
  • Monorepo boundary check passed.

Policy decisions remain unchanged: unresolved provider identities are no-charge; Daytona account allowances/credits are ignored in favor of configured list-rate pricing; Mothership scope is unchanged.

Out-of-scope follow-ups identified for separate work:

  1. General long-running workflow settlement across billing-period close (broader than sandbox intent coordination).
  2. Internal log-detail partial totals while independently ledgered costs are still settling.

@cubic-dev-ai please re-review the latest commit, especially the durable pre-create intent, correlation recovery, terminal evidence precedence, and billing-period lock coordination.

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Implemented the sandbox durability follow-up in c88112edf2.

Review fixes included:

  • Persist a V2 sandbox.usage.finalize intent before any E2B/Daytona create call, then correlate the provider sandbox through E2B metadata or Daytona name/labels.
  • Distinguish definitive create rejection from ambiguous timeout/transport/conflict/rate-limit/server failures. Ambiguous creates return non-retryable 503 sandbox_create_uncertain; user code never runs while identity is uncertain.
    ...

@BillLeoutsakosvl346 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 25 files

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/billing/cycle-close.ts Outdated
Comment thread apps/sim/lib/billing/sandbox-usage-outbox.ts Outdated
@BillLeoutsakosvl346
BillLeoutsakosvl346 force-pushed the feat/e2b-daytona-usage-billing branch from c88112e to e48e74f 比较 August 28, 2026 16:42

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 25 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/function-execution/execute-request.ts Outdated
@BillLeoutsakosvl346
BillLeoutsakosvl346 force-pushed the feat/e2b-daytona-usage-billing branch from e48e74f to 3125336 比较 August 28, 2026 18:28
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

Implemented the complete sandbox lifecycle review follow-up in 3125336, rebased onto staging at b094b4d.

Finding-by-finding:

  1. Durable creation boundary: added a V3 monotonic state machine (intent admitted → create dispatched → provider attached → cleanup requested → terminal/no-charge). Local failures before dispatch preserve the original error; every no-handle failure after dispatch is an uncertain non-retryable 503 and is recovered by correlation.
  2. Crash recovery: intent-only crashes complete no-charge, dispatched creates are searched while live, recovered sandboxes are terminated and billed once, and identities still unconfirmed at frozen TTL complete as auditable identity_unconfirmed no-charge.
  3. Provider bounds: Daytona now bounds the full create lifecycle, uses one absolute timeout across lookup+delete, and observes cancellation before late delete. E2B receives the lifecycle signal/request timeout and freezes the SDK-equivalent whole-second TTL.
  4. Cleanup truth: every metered live-handle kill is preceded by a durable cleanup_requested checkpoint. Failed checkpoints skip direct kill; successful cleanup records provider_confirmed evidence. Weak not-found/expiry evidence waits briefly for stronger live confirmation.
  5. Ledger durability: finalization row-locks and reloads the latest payload before pricing; usage insertion and ledgerRecordedAt are atomic and idempotent by provider+sandbox ID. Infrastructure failures defer without consuming attempts; malformed/semantic payloads retain finite dead-letter behavior.
  6. Handler lease safety: provider and database work share a 60-second sub-lease with abort checks, leaving margin before the generic 90-second outbox handler timeout.
  7. Billing-period coordination: intent admission, ledger insertion, and cycle close serialize on the payer subscription. Null-marker and initialized rollover paths preserve adjacent elapsed-period writers, exact adjacent threshold mismatches hand off to cycle close, unrelated mismatches remain errors, and cycle close revalidates its ledger snapshot under lock.
  8. Actor/payer attribution: trusted executor actorUserId is forwarded separately from attributedUserId, forged body identity is ignored, valid actor/workspace-payer splits succeed, and incomplete metered attribution fails before provider creation.

Validation after the final staging rebase:

  • 483 focused Vitest tests across the 10 requested suites
  • apps/sim type-check
  • apps/sim full Biome lint
  • API validation
  • tool-request boundary
  • monorepo boundaries
  • git diff --check

Policy remains unchanged: an unresolved provider identity is no-charge; Daytona allowances/credits are ignored; pricing and multiplier behavior are unchanged; Mothership, local JavaScript, and custom Function-tool scope are unchanged. The broader long-workflow settlement and internal terminal-log partial-total concerns remain separate follow-up work.

@cubic-dev-ai please re-review the new head 3125336.

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Implemented the complete sandbox lifecycle review follow-up in 3125336, rebased onto staging at b094b4d.

Finding-by-finding:

  1. Durable creation boundary: added a V3 monotonic state machine (intent admitted → create dispatched → provider attached → cleanup requested → terminal/no-charge). Local failures before dispatch preserve the original error; every no-handle failure after dispatch is an uncertain non-retryable 503 and is recovered by correlation.
    ...

@BillLeoutsakosvl346 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 29 files

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/execution/remote-sandbox/daytona.ts Outdated
Comment thread apps/sim/lib/billing/sandbox-usage-outbox.ts Outdated
Pi's own sandbox was never metered. withPiSandbox called createSandbox
without the meterUsage argument, so only sandboxes created through
executeFunctionRequest were charged — and Pi's is the larger consumer by an
order of magnitude. A Function block holds one for seconds; a Pi session
holds one for a minimum lifetime of 31 minutes.

The gap was worst exactly where it was least visible. A Pi coding agent
normally runs BYOK, so its model cost is zero by definition, and the ledger
bills a model row on total > 0. With the sandbox unmetered, such a run
produced a zero-cost model_unbilled row and Sim collected only the flat
execution fee while paying its provider for the whole session.

Threads a cost sink through PiRunContext, which is the seam backends
already receive and the only one that reaches all four cloud modes. The
handler owns one sink covering both sandbox sources — Function tools in
local mode, the agent's own sandbox in cloud mode — so neither can be
dropped where the cost is folded into the block's output. It rides in
toolCost for the same reason the Function tool cost already does: that is
what survives the BYOK zeroing.

Unlike the Function path this charges on creation rather than on a
completed session. A Function run is seconds long, so absorbing one the
provider failed to deliver is cheap and reads as fair; tens of minutes of
Pi compute is consumed whether the agent finished, errored, or was
cancelled, and billing only clean endings would mean paying for every other
one. A create that throws still costs nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The handler test mocks the backend and writes into the cost sink by hand,
so it proves the wiring from a backend to the block's cost and nothing
more — it would still pass if withPiSandbox never metered at all, which is
precisely the bug that path had.

Holds a real Pi sandbox open for a known interval and bounds the charge
between what that interval must cost and what the whole session could
justify. Verified to fail against the original unmetered call with
"expected 0 to be greater than or equal to 0.00023", and to pass once the
sink is threaded: 5.949s billed of a 6.141s session on E2B.

The second case pins the other half of the contract — a caller that
supplies no sink is not charged, which is what keeps mothership and other
internal Pi sandboxes free.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Aligns Pi with the outcome policy the Function path already applies rather
than keeping the divergence the previous commit introduced. A session that
ends by throwing — a provider crash, a lifetime limit, a cancellation — is
absorbed, because a charge nobody can tie to delivered work is not one
worth defending, and consistency across the two sandbox paths is worth more
than recovering the cost of runs that failed.

A command exiting non-zero is still billed: the callback returns normally
there and the agent produced its answer, which is the same reason the
Function path bills its own non-zero exits.

The window still closes at teardown, so a completed session is charged for
the whole time the provider held its sandbox.

Verified on both providers, including that the new case fails when the
charge is applied unconditionally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tion

Three paths dropped cost the sandbox had already earned.

A harvest that cannot return what the run produced — more files than the
export limit, nesting past the listing depth, or an output directory the
code deleted — was excluded from the billable-error set. All three arrive
only after the sandbox has executed and all three are the caller's to fix,
so they belong with the post-completion export failures the policy already
bills rather than the provider failures it absorbs. A completed run whose
code wrote one file too many went free.

That also left the route with nothing to read: it already consults
readTrustedSandboxOutputCost for these errors, so attaching the cost at the
sandbox layer is what carries it into the response.

Separately, a Function block whose handler succeeded could still fail in
the steps that follow it — base64 hydration, and large-value redaction that
throws rather than emit unredacted data. Those errors carry no cost of
their own, so the completed sandbox went unbilled. The handler's cost is
now held across that window, in the same way streamingPartialOutput already
is, and used only when the error has none.

The new conformance case was confirmed to fail against the narrower catch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@icecrasher321 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 39 files

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/execution/remote-sandbox/index.ts
Comment thread apps/sim/executor/handlers/pi/core/backend.ts Outdated
A backend that returns a result carrying `totals.errorMessage` has already
run: the sandbox was billed and the sink holds the charge. But that path
throws instead of reaching `buildOutput`, which is what publishes the cost,
so the charge was accumulated and then dropped — lost revenue rather than
an over-charge.

Both failure paths now carry it on the error they raise, the same way the
Function handler carries its tool cost, so `handleBlockError` can pick it
up. An agent that ran and then reported a failure consumed the same tokens
and sandbox seconds as one that succeeded, which is why the cost
computation is now shared between the two rather than duplicated.

Also corrects the sink's doc comment. Local mode does fill it — the agent
runs on the caller's own machine and costs Sim nothing, but a
`function_execute` among the Sim tools it calls bills its own remote
sandbox into the same total.

The new case was confirmed to fail without the attach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@icecrasher321 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 39 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

@icecrasher321

Copy link
Copy Markdown
Collaborator

@greptile Re: the 4/5 on workflow-scoped attribution — traced this, and the guard has not gone missing; the design it belonged to was replaced.

01fedd0d72 is not an ancestor of this branch (git merge-base --is-ancestor says no). It belongs to the PR's earlier architecture, which had SandboxUsageContext, getSandboxUsageContext(), SandboxUsageAttributionError, requireBillingAttributionHeader, and its own persistence path. None of those symbols exist in the current tree — grep returns nothing for any of them. So the reply you're comparing against is stale, which is a fair thing to flag, but the current code is not a regression from it.

Under the present design execute-request.ts writes no ledger row at all. It emits output.cost and the existing trace/span → cost-summary → workflow-logger pipeline carries it, which is the point of the PR ('no sandbox-specific outbox, reconciliation worker, billing-period machinery, database schema, or new ledger path'). Requiring billing attribution at the dispatch boundary would gate a write that no longer happens there.

Attribution is enforced fail-closed at the layer that does write, lib/logs/execution/logger.ts:

if (workflowRecord.workspaceId && !billingContext) {
  throw new Error('Billing attribution is required for workspace execution usage')
}

That guard predates this PR and applies to every billable target, model cost included — sandbox cost is not a special case. The two reachable shapes both terminate safely: a workspace-scoped run without attribution throws there rather than silently dropping the charge, and a run with no workspaceId is a personal execution whose context is derived from the user.

executionId is likewise not a gap: cost reaches the ledger through the execution's own log, so a dispatch with no execution to attach to produces no span to carry it.

Happy to be shown a path where a metered run reaches the ledger without attribution — that would be a real hole and I'd fix it — but I could not construct one against the current code.

@icecrasher321

Copy link
Copy Markdown
Collaborator

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@icecrasher321 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 39 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

You're right — the previous finding was based on the superseded architecture. In the current head, executeFunctionRequest only emits the trusted output.cost; it does not write a ledger row. The existing execution logger is the write boundary and its workspace-scoped guard requires billing attribution, while personal executions derive context from the user. executionId remains attached through the execution log/span that carries the cost into that pipeline. Adding the old dispatch-boundary guard would therefore protect a write that no longer occurs there. I’m withdrawing the workflow-attribution finding; this is not a regression in the current design.

@icecrasher321

Copy link
Copy Markdown
Collaborator

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@icecrasher321 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 39 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

@icecrasher321
icecrasher321 merged commit f607c01 into staging Aug 29, 2026
27 checks passed
@icecrasher321
icecrasher321 deleted the feat/e2b-daytona-usage-billing branch August 29, 2026 21:11
注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

标签

None yet

项目

None yet

Development

Successfully merging this pull request may close these issues.

2 participants