Prompt Async Gate Rfc
# ADR: prompt-async-gate - reservation-based duplicate-injection guard
## Status
Accepted (introduced in v4.2.0)
## Context
Issue #4012 reported duplicate streaming output after OMO injected an
internal message into a live OpenCode session.
The user-visible failure was two assistant bubbles streaming the same
continuation.
The root race was not one hook making one bad decision. Multiple internal
routes could observe the same idle, completion, or error edge and each decide
that the parent session needed a wake or recovery prompt.
The most important race window was:
1. OpenCode emitted a `session.idle` event.
2. OMO started an `isSessionActive` HTTP poll.
3. OpenCode was still pacing the streaming animation for the previous answer.
4. The poll observed an inactive or idle-looking session.
5. OMO injected a continuation prompt.
6. A second hook observed the same edge and injected again.
7. The user saw two assistant bubbles.
The historical race site was visible in the built bundle at
`dist/index.js:69665-69680`. That code checked session activity before sending
an internal prompt, but the check and the prompt were not protected by a
shared reservation.
OpenCode's `prompt_async` route contributed to the failure mode because it has
fire-and-forget semantics. `session.promptAsync` can resolve before the prompt
is durably accepted by the target session. A later `session.error` event can
still arrive for the same attempt, so the caller can believe dispatch finished
while a recovery hook still treats the session as eligible for retry.
OMO has 13+ internal hook callers that can inject prompts, including:
- background task parent wakes
- runtime fallback retries
- model suggestion retries
- team mailbox live delivery
- session recovery continuations
- todo continuation resumes
- CLI run resumes
- Claude Code hook injections
- sync subagent prompts
- background subagent prompts
Route-local guards cannot close this race. Each route can be correct in
isolation and still collide with another route in the same process.
The root `AGENTS.md` now records the governing invariant in the section
"Internal message injection is dangerous": production code may call
`session.prompt` or `session.promptAsync` only inside
`packages/omo-opencode/src/shared/prompt-async-gate.ts`. Every other route must use the shared gate.
## Decision
Create `packages/omo-opencode/src/shared/prompt-async-gate.ts` as the single production owner of raw
OpenCode prompt dispatch.
The gate exposes one public dispatcher that production callers must use:when to use it
Community prompt sourced from the open-source GitHub repo er-s-an/OH-MY-MIMO-CODE (MIT). A "Prompt Async Gate Rfc" style prompt — adapt the placeholders and specifics to your task. Imported as-is and not independently retested here, so check the output before relying on it.
tags
productivitycommunitydeveloper
source
er-s-an/OH-MY-MIMO-CODE · MIT
more in Productivity
Productivity✓ tested
Summarize a doc into decisions & actions
chief of staff who extracts what to DO, not just what was said
Productivity✓ tested
Draft a reply to a hard email
calm, direct communicator who de-escalates without caving
Productivity✓ tested
Turn a brain-dump into a weekly plan
planning coach who protects your focus, not just your calendar