Repository navigation
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Use case. A plugin posts short notices into a user's chat with ctx.session.synthetic({ sessionID, text, delivery?, resume? }). It should start a turn only when the session is idle and its current agent is the one the notice is meant for. Otherwise it should not interrupt the user's work or wake the wrong agent.
Problem. There is no way to make the wake conditional. On dev (0e655ff), Session.synthetic admits the item and then wakes unconditionally (packages/core/src/session/session.ts:323):
if (input.resume !== false && !(yield* get(sessionID)).revert) yield* execution.wake(sessionID)RunCoordinator.wake (packages/core/src/session/run-coordinator.ts:131-140) sets pendingWake on an active execution, and settle (:111-115) starts a successor for a doorbell that survives the loop. So a plugin that checks idle/agent first (with get, events, or the active() query proposed in #56115) and then calls synthetic is check-then-act. A user prompt or agent switch between the two calls makes the notice wake a turn nobody asked for, or wake under the wrong agent.
resume: false avoids the wake, but the notice then waits in the inbox until the next prompt and is not visible until then (documented in packages/plugin/src/promise/session.ts:177-178; we saw the same on 2.2.5). experimental.idle only runs at the end of model work, so it cannot post into a session that is already idle.
Proposal. An atomic guard on admission and wake, evaluated where the coordinator decides whether an execution exists, for example:
const result = await ctx.session.synthetic({
sessionID,
text,
resume: { ifIdle: true, agent: "build" }, // or wake: "if-idle"
})
// result.woke: boolean- Idle and the agent matches: admit and start the execution,
woke: true. - Otherwise: either do not admit (typed result or error), or admit without waking. Either works for us as long as the result says which happened.
Related: #56115 (busy query and inbox cancel for plugins), #48644 (visible notices that do not start the agent).
Tested upstream
anomalyco/opencode dev@0e655ff2a2b7ee2234a6ebd5e61048b58a188b7c source read 2026-11-06: session.ts:323 unconditional wake after admission, run-coordinator.ts:111-140 pendingWake/successor; live probes on v2.2.5 2026-11-06 (resume:false synthetic stays in session_inbox until next prompt)
Activity
opencodejr commentedon Nov 6, 2026
Thanks for the detailed write-up, @mradwankhalil!
To restate the request:
ctx.session.syntheticshould be able to wake the session only if it is idle and its current agent matches, checked atomically where the run coordinator decides whether an execution exists. The result should say whether it woke (or whether the item was admitted at all).I checked this against
dev(0e655ff) and found what you describe.Session.syntheticwakes after admission wheneverresume !== false.RunCoordinator.wakemarks a running execution withpendingWake, andsettlethen starts a new execution when the current one ends. A plugin can only check state before callingsynthetic, so the race is real, and it can't be fixed from inside a plugin today.resume: falseandexperimental.idledon't cover it either, for the reasons you gave. So this needs a small addition to the plugin API: a conditional wake option onsynthetic, checked in the coordinator's wake path, that reports what happened. Maintainers will consider it.Related issues:
experimental.idlecontinuations when the user stops the session.holny commentedon Nov 6, 2026
Confirmed on dev (0e655ff) — the wake after admission is unconditional, and
RunCoordinator.wake'spendingWakesurvives the running execution, so an idle/agent check beforesyntheticis check-then-act: a prompt or agent switch in between still starts a successor turn nobody asked for.I'd like to take this. Plan: a conditional wake option on
syntheticshaped likeresume: { ifIdle: true, agent }, evaluated in the coordinator right where it decides whether an execution exists — busy or agent mismatch still admits the item but skips the wake, and the result reportswoke(and why not) so the caller can fall back.revertkeeps skipping the wake. PR in the next day or two.mradwankhalil commentedon Nov 6, 2026
Thanks @opencodejr for confirming the race, and @holny for offering to work on it. The proposed approach fits our plugin's use case.
Admitting the item without waking is fine for us. We understand that it may still be consumed by an ongoing execution or a later prompt; the requirement is to avoid an unsolicited wake, not to cancel delivery.
Two details are important:
Keeping the existing no-wake behavior for reverted sessions also makes sense. We have a best-effort workaround for now; this would let us replace it with an explicit host guarantee.