Skip to content

park failing drained input and resume only work whose holder was gone - #452

Merged
xandreeddev merged 1 commit into
mainfrom
fix/m3
Nov 2, 2026
Merged

xandreeddev merged 1 commit into
mainfrom
fix/m3

Conversation

@xandreeddev

@xandreeddev xandreeddev commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

The inbox drainer retried a queued input that kept failing or ending partial forever, ahead of every later input in its session, and resumed any interrupted user turn at any time: a turn its time limit stopped, or one the host interrupted in this process, was restarted again and again. Each run cost a model call and journal rows.

The drainer now parks an input after InboxDrainConfig.attempts (3) failed or partial runs, or after a deliberate stop (a time limit, or the host interrupting it in this process). A parked input stays queued and counted, is never dropped, and later input runs instead; the host runs it with BeginTurn.Queued. A root is resumed only when its holder was gone (turn.lease, turn.takeover, turn.shutdown, or an open turn nobody holds), when an earlier process left it interrupted before the startup sweep, or for a failed resume within the same limit. The default resume message is now a host note, pinned exactly.

Startup takeover names the open turn the sweep saw: BeginTurn.takeOver also accepts { turn, runId }, which closes only that turn, so a turn begun since, or one this instance holds, is never taken over. Sessions.drain passes its takeOver to its first turn only. The sessions kit adds takeover-named and drain-takeover, each with a careless self-test. A runner that interrupts itself is counted as having tried its work, so a notice uses up its attempts.

A turn's own appends no longer signal Sessions.waiting, and a drainer scan reads the queue history only of sessions whose sequence number or open turn changed: one 32-commit turn among 20 sessions went from 640 history reads to at most 2. WaitingInput keeps sessions with waiting input in a persistent map and only inbox.recentSessions (256) recent quiet ones, instead of copying a map of every session ever observed on each read, which made each refresh quadratic. Agent.activity and Agent.shutdown count the caller's DrainActivity, since a drainer is built beside the agent and never in its runtime.

No stored shape changes; no new codes. SessionsConfig.inbox.recentSessions is a new required field (spread sessionsDefaults), noted under Migration.

The sessions kit's waiting-input check now lets the clock move a few more times before deciding, so a store with real IO is not judged by a single poll (it was flaky over the file log under load, on main as well).

The inbox drainer retried a queued input that kept failing or ending partial forever, ahead of every later input in its session, and resumed any interrupted user turn at any time, so a turn its time limit stopped, or one the host interrupted in this process, was restarted with "Continue." again and again. It now parks an input after a host-set number of unsuccessful runs (InboxDrainConfig.attempts, 3) or after a deliberate stop: the input stays queued and counted, never dropped, and later input runs. A root is resumed only when its holder was gone (a lease, a takeover, a shutdown), when an earlier process left it interrupted before the startup sweep, or for a failed resume within the same limit. The default resume message is a host note.

Startup takeover now names the open turn the sweep saw. BeginTurn.takeOver accepts { turn, runId }, which closes only that turn, so a turn begun since or one this instance holds is never taken over, and Sessions.drain passes its takeOver to its first turn only. The sessions kit checks both. A runner that interrupts itself is counted as having tried its work, so a notice uses up its attempts.

A turn's own appends no longer signal Sessions.waiting, and a drainer scan reads the queue history only of sessions whose sequence number or open turn changed. WaitingInput keeps sessions with waiting input in a persistent map and only the most recent quiet ones (inbox.recentSessions, 256) instead of copying and refreshing every session ever observed. Agent.activity and Agent.shutdown count the caller's DrainActivity, since a drainer is built beside the agent and never in its runtime.

The waiting-input kit check now lets the clock move a few more times before it decides, so a store with real IO is not judged by a single poll.
@xandreeddev
xandreeddev merged commit f333e85 into main Nov 2, 2026
1 check passed
@xandreeddev
xandreeddev deleted the fix/m3 branch November 2, 2026 12:36
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.

1 participant