Skip to content

fix: isolate concurrent Pi replays and committed tool caches - #945

Merged
rynfar merged 4 commits into
mainfrom
codex/fix-pi-concurrent-replay
Sep 4, 2026
Merged

fix: isolate concurrent Pi replays and committed tool caches#945
rynfar merged 4 commits into
mainfrom
codex/fix-pi-concurrent-replay

Conversation

@rynfar

@rynfar rynfar commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Oh My Pi sends its main loop, title generation and side questions under one session ID. A request queued behind another caller could receive a hard 400 and trigger model fallback. Allow the Pi adapter to declare this concurrency pattern, serialize the requests, and replay a conflicting late caller from its own complete body. Explicit fork/subagent signals retain their intended undo behavior; other adapters retain stale-request protection.

Incorporates #922's two original commits with Mate Remias's author and authored dates preserved, followed by separate maintainer corrections and validation.

Real Oh My Pi 18.0.3 validation exposed another required correction: when the main request ran first, the title request inherited cached read/write tools and returned a read call instead of a title. Cache tool declarations only after successful durable publication and associate them with that exact SDK session. Fresh requests use their own declarations, empty fresh branches do not inherit old tools, and failed side requests cannot poison the main branch's cache. The bounded MCP instance cache continues to reuse identical tool registrations.

Validation:

  • Corrected the imported undo fixtures to actually reach a valid rollback boundary under the current lineage rules, with observed queue admission instead of timing sleeps. On main, both Pi admission cases fail and the explicit-fork control passes.
  • Four separate-process tests fail on main and pass with the proposal: both arrival orders in streaming/non-streaming, followed by a main-conversation continuation. Existing non-Pi conflict and cross-process resume controls pass.
  • Real SDK before/after: main rejects the late caller in both orders; the candidate answers each caller from its own history, preserves source histories through the supported SDK API and keeps the next main turn correct.
  • Actual Oh My Pi 18.0.3 session/title APIs, provider serialization and read/write tool loop: title-first on main reproduces the reported 400; the candidate completes the title and file-copy task. Main-first exposed the tool inheritance failure; after correction it yields a parseable title and copies the random fixture value correctly. This exercises the client engine, not its terminal UI.
  • Three HTTP tool-cache regressions fail before: fresh side requests in both modes inherit main tools, and a failed side request replaces the committed tool set. The corrected targeted suite passes 28 tests. Independent typecheck and build pass.
  • Final full suite: 3,404 pass / one existing skip / zero failures, exit 0. All four real E41 passthrough controls pass. Both corrected actual-client orders pass. Exact-head CI passes at 554b531134dfb092382383592356c2f6f19714e5: test, Windows smoke, Docker smoke and build (runs 33922862600 / 33922862613).

The durable mapping follows the last completed caller. If a side call finishes last, the next main turn may also replay; the change does not promise a single prompt-cache miss or immunity from upstream errors. Headerless clients still have the existing cache-identity limitations. OpenCode-specific conflict proposals in #869 remain under separate review.

Fixes #870. Supersedes #922 with contributor credit.

Co-authored-by: Mate Remias materemias@gmail.com

materemias and others added 4 commits September 4, 2026 15:29
…870)

Oh My Pi drives one conversation from several callers at once: the main
turn, title generation, and side questions asked mid-turn. All of them
carry the same metadata.user_id.session_id, because the protocol has no
per-flow signal to send. Turn coordination serializes them correctly,
but whichever one lost the commit race arrived holding a branch the
mapping had moved past, and was refused with a 400.

That 400 is a hard error on the client side. In omp it triggers a model
fallback, so a turn that could have run switched models and threw away
the prompt cache instead.

Adapters can now declare that their clients share one session key across
concurrent turns, which routes them into the existing
declaresConcurrentFlow escape that fork and subagent sources already
use. The loser is answered by replaying its own history: one
prompt-cache miss instead of a failed turn. Nothing else changes, and
every other adapter still refuses a stale arrival.

Admitting such a turn is not the same as trusting its lineage. A short
side call carries a prefix of the main history, so after the main turn
commits the loser reads as an undo against it, and honouring that would
resume and rewind the session that just committed. A loser admitted by
the protocol declaration is therefore reclassified as diverged before
the undo path sees it. Per-request fork and subagent signals keep their
lineage, since those callers name their own session boundary.

Two tests cover it. The first reproduces the reported shape, two
branches sharing a prefix and differing at the last message, and fails
on main with the request never reaching the SDK. The second commits a
winning turn with SDK message UUIDs while a prefix-carrying side call
waits, and fails without the reclassification, with resume pointing at
the winner's session.
The undo downgrade added for the pi adapter applied to every request that
adapter serves, including the ones that carry their own concurrency
declaration. A fork source or subagent signal names its own session
boundary, so an undo from it is deliberate and honouring it is the point;
only pi's unmarkable side calls need their arrival-order undo replaced by
a replay. Gate the downgrade on the absence of a per-request signal.

Also corrects docs/agents.md: the loser misses the prompt cache, it does
not cost a hit.
@rynfar
rynfar marked this pull request as ready for review September 4, 2026 21:54
@rynfar
rynfar merged commit 04d1c93 into main Sep 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

400 "This session advanced while the request was waiting" for every request from oh-my-pi (omp)

2 participants