Repository navigation
Description
Feature hasn't been suggested before.
- I searched existing issues for background accounting, invocation usage, synthetic interrupts, and completion fencing. Related requests Plugin host API: expose inbox cancel and a busy query to plugins #56115 and V2: define restart semantics for foreground and background shells #36348 cover different boundaries.
Describe the enhancement you want to request
Provide a supported SDK boundary for attributing and finalizing model usage for one caller invocation when native background shells can later resume the same session.
We integrate OpenCode into NVIDIA NeMo Fabric and need to distinguish an idle root execution from an invocation whose attributable model work is complete. Native background execution is intentional; this request concerns lifecycle and attribution support, not incorrect token values.
Version and evidence
Verified with @opencode/sdk / @opencode/core 2.0.23, Bun 1.4.2. This report does not assert that later releases have the same limitation.
In the v2.0.23 shell completion path, a completed background job posts a synthetic input into its session. That notification can wake another model turn after an embedding caller has received its initial result. The public session.interrupt({ sessionID, resume: false }) stops current execution; we could not establish that it fences future notifications or wakeups. A session ID alone does not tell a warm embedding which invocation owns those later requests.
Reproduction and current downstream handling
A controlled real-SDK regression uses a local mock OpenAI-compatible streaming endpoint with known usage:
- Return a title response and an initial primary response containing a native
shelltool call. - Compare
{ command: "printf done", background: false }with{ command: "sleep 1", background: true }. - Return the final primary response; each of the three model responses reports input 10/output 2.
- Observe input 30/output 6 in both cases. Fabric can certify the foreground result, but leaves complete counters unknown for the background case and records
scope_reasons: ["background_shell"].
The regression stops the embedded SDK after the result; it does not claim to measure a subsequent post-result model request. The future-wakeup concern follows the native completion path above. An end-to-end Harbor task also reaches this background-shell guard while completing successfully. NeMo-Fabric#525 retains partial observations without presenting them as complete totals.
Requested contract
A supported capability that lets an embedding establish an unambiguous invocation boundary, either by fencing/deferring future model continuations without killing background services, or by attributing continuations to a stable invocation identity and exposing when its usage is final. Please advise if an existing supported API already provides this contract.
The boundary should handle a shell exiting after the initial root result, completion racing with interruption, and a subsequent invocation on the same warm runtime. An authoritative idle check or canceling one already-queued inbox entry alone does not cover a future completion notification.
Waiting indefinitely or terminating a task's long-running server is not a usable workaround. Until a boundary is established, unknown complete counters are the truthful downstream behavior.
Related: #56115 requests plugin inbox cancellation/busy queries; #36348 covers shell restart semantics. This request is specifically about a live embedding's invocation ownership and completion.
Activity
opencodejr commentedon Oct 28, 2026
Thanks for the detailed write-up and the regression test.
To restate the request: you'd like a supported SDK boundary for one caller invocation. That would mean either a way to hold off model turns that background-shell completions would later trigger in the same session (without stopping the background processes), or a way to tag those later turns with a stable invocation ID and get a signal when that invocation's usage is final.
As far as I can tell, no existing API does this. On the current
devbranch, a finished background shell still posts a synthetic notification into its session (notifyWhenDoneinpackages/core/src/tool/plugin/shell.ts).session.interruptonly stops work that is running at that moment and does nothing when the session is idle, so it doesn't block notifications that arrive later. A plugin can't fill this gap either, because the plugin host doesn't expose inbox control (see #56115), and even with it a plugin can't stop or tag a notification that hasn't been created yet. So this would need to be added to core and the SDK.Maintainers will consider it. Related issues: