Skip to content

[Pi/OMP] A busy-database refusal kills a subagent, because nothing resends its turn (0.45.0) #640

Closed

Description

@TheOrdinaryWow

What happened

When another process holds the shared context.db write lock, Magic Context refuses the turn, as designed since #601. A refused turn of a subagent spawned by OMP's task tool is never resent. The only later turns are OMP's own "no tool call" reminders, and they are refused the same way. After three reminders OMP disposes the subagent, and the parent gets status="cancelled" with the reason Interrupted by user.

Our magic-context.log from 2026-10-08, 07:44 to 14:57 UTC, combined with the OMP transcripts:

count
turn refused log lines 51
Sessions with at least one refusal 22 (16 subagents, 6 primary)
Subagents that ended aborted after refusals 6, in 3 projects

The six subagents that died:

Subagent Assistant turns before the first refusal Fallback outcome logged for its refusals
T13ControlSeam 0 (new) lkg_miss ×4
T14PanelAdapter 0 (new) lkg_miss ×4
MomusS02Round2 32 lkg_model_mismatch ×4, raw_fallback_refused ×1
T13SettingsCentre 287 lkg_content_mismatch, raw_fallback_refused
T5SupplyModels 71 lkg_content_mismatch, raw_fallback_refused
T12PanelConfig 6 lkg_invalidated_reshape ×3, raw_fallback_refused ×1

Five of them made no successful provider request after their first refusal, and every later assistant message is an empty stopReason: "aborted". T12PanelConfig got one more turn through, then died on later refusals.

One subagent, start to finish

T13ControlSeam is session 01a11bee-6b1a-7461-8914-bd2ef3c107d4. It was spawned at 14:32:52 together with one sibling, T14PanelAdapter, which failed the same way.

14:34:52.584Z transform: usage=4.2% (39108 tokens, limit=922000) decision=execute
14:34:52.838Z sqlite writer site=BEGIN IMMEDIATE lane=foreground acquire_ms=252 attempts=1 outcome=busy
14:34:52.838Z pi m[0] HARD fold pre-execution failed: SQLite writer acquisition remained busy
14:34:52.839Z TRANSIENT STORAGE FAILURE SQLITE_BUSY: LKG unavailable (lkg_miss); refusing unreduced 3-message input
14:34:52.839Z [pi] turn refused Magic Context storage is busy; send your message again
14:35:14.433Z TRANSIENT STORAGE FAILURE SQLITE_BUSY: LKG unavailable (lkg_miss); refusing unreduced 3-message input
14:35:52.656Z TRANSIENT STORAGE FAILURE SQLITE_BUSY: LKG unavailable (lkg_miss); refusing unreduced 5-message input
14:36:15.373Z TRANSIENT STORAGE FAILURE SQLITE_BUSY: LKG unavailable (lkg_miss); refusing unreduced 7-message input

Its transcript records:

  1. The assignment, as a user message.
  2. A magic-context-turn-refused entry, then an empty assistant message with stopReason: "aborted".
  3. OMP's reminders 1, 2 and 3, each answered by the same refusal entry and an aborted message.
  4. session_exit {reason: "dispose"} at 14:36:25.

The subagent used 0 tokens.

These write transactions ran during that window:

14:35:46.292Z lane=background hold_ms=2491  slow write transaction: site=message_index_incremental
14:35:48.963Z lane=background hold_ms=1065  slow write transaction: site=message_index_reconcile
14:35:51.165Z lane=foreground hold_ms=2190  slow write transaction: site=pi_compaction_queue

context.db is 1.6 GB. In this window OMP processes in three projects were writing to it.

Why this differs from #601

#601 was closed as fixed in 0.45.0 with this policy: wait at most 250 ms for the database, then resend the last good request or refuse the turn. The refusal was kept on purpose, because "a refusal costs one resend." Unreduced history could overflow the window and costs two prompt-cache rebuilds.

That holds when a person is watching and resends. It fails for a subagent:

  • Nobody resends. The host's reminders are the only later turns. They arrive while contention is still going on, and each one is refused. The refusal ends the subagent's assignment, and with it the parent's plan step. Turns that the host starts in a primary session have the same problem. The parent's next turn after receiving the failed subagents' results was refused at 14:36:59, and that session then sat idle until the user restarted OMP at 14:42.
  • Often there is no usable last-good request. A new subagent has none (lkg_miss). In the mid-run cases above the replay was rejected for a model mismatch, a content mismatch or a reshape, or it did not fit (raw_fallback_refused completeness=partial).
  • A new subagent has nothing to protect. On its first pass nothing has been dropped, the input is 3 messages at 4.2% of the window, and there is no cached prefix. Passing that input through cannot cause the overflow or cache loss that [Pi] PiStorageBusyError on SQLite lock contention triggers fail-closed ctx.abort(), blocking interactive turns #601 guards against.

Expected

A transient SQLITE_BUSY should not permanently kill a subagent. Possible fixes, in our order of preference:

  1. Fail open when there is nothing to protect. If a session has no last-good request and no applied drops or compartments, pass event.messages through instead of refusing.
  2. Wait longer when nobody will resend. For subagent sessions (an OMP subagent's session header has parentSession), retry the writer with a longer backed-off budget before refusing.
  3. Shorten foreground holds. pi_compaction_queue held the foreground lane for 2.2 s, against a 250 ms budget for everyone else.

The 0.46.0 notes shorten several lock holds, which should make this rarer. They do not change the refusal path for sessions without a usable last-good request. We are still on 0.45.0, because the v95 migration needs every OMP process stopped at once.

Environment

  • Linux 6.18, x86_64
  • OMP (oh-my-pi) 18.8.5
  • @cortexkit/pi-magic-context 0.45.0
  • context.db 1.6 GB, shared by concurrent OMP processes in several projects
  • Subagents spawned in parallel as background jobs with OMP's task tool

Activity

  1. magic-alfonso commented on Oct 8, 2026

    @magic-alfonso

    Thanks for the detailed report and the log tables. They make the failure clear: a refused subagent turn is never resent, and many of these subagents had no usable last-good request to fall back on.

    We're reproducing this on a real OMP host now and checking each of the fallback outcomes you listed (lkg_miss, lkg_model_mismatch, lkg_content_mismatch, lkg_invalidated_reshape, raw_fallback_refused). Some of those may be rejecting a replay that would have been safe, especially for subagents that had dozens or hundreds of turns. We're also looking at the 2.2 s pi_compaction_queue hold. I'll follow up here once we know which of your three proposals is safe to adopt.

  2. magic-alfonso commented on Oct 9, 2026

    @magic-alfonso

    Fixed in 0.47.0. When another process holds the database, the turn now resends the last good request if it still fits. Otherwise it waits up to about 16.5 seconds for the database, within Oh My Pi's 30-second extension limit. Two OMP-only mismatches that made a valid last good request look unusable are fixed. A turn that is still refused says what it was waiting for, and the next reminder completes it. OpenCode 1 and 2 also try the last good request before waiting.

    Update to 0.47.0 (OpenCode: opencode-magic-context@0.47.0; Pi and Oh My Pi: @cortexkit/pi-magic-context@0.47.0). There is no database migration, so restarting the host is enough. OpenCode 2 needs 2.0.22 or newer and does not update plugins on its own: open /plugins, select Magic Context and press ctrl+u. Release notes: https://github.com/cortexkit/magic-context/releases/tag/v0.47.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions