Each starter now reacts to a busy session in its own way:
- An integration message gets a busy notice. The bridge deletes its
stored attachments and does not run the message.
- An integration button resume waits for the session for 5 seconds.
Then it posts "This action has already been handled". A Preview
resume waits the same time and then gets `turn_already_running`.
- A workflow-tool resume tries again with a backoff from 1 s to 30 s.
It stops when the run is no longer suspended.
- A background wake skips without a failure count. The turn that holds
the session requests a new wake when it settles.
- A busy sub-agent resume is retryable, so the parent stays suspended.
The session lease replaces the in-process HITL guard and the running
check of the wake, so this removes both and `existsRunningByThread`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A turn can now wait a short time while another turn holds the session.
Callers set the wait with sessionWaitMs; the default is no wait.
Before each start attempt, a resume checks that its checkpoint is still
suspended. A resume that another resume already handled gets
AgentResumeAlreadyHandledError and records nothing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Remove the Preview admission lock and its running-execution check. The
session lease now admits Preview turns like all other turns, so the
automatic Preview continuation no longer needs its own admission path.
Stop no longer takes a lock. When a stopped turn suspended, settle
cancels the suspension while the turn still holds the lease, then
finalizes. The checkpoint compare-and-set decides between a cancel and a
resume.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Take the session lease in the transaction that records the running
execution. All four start paths use this point: chat turns, failed starts,
foreground sub-agents, and workflow agents. A turn on a busy session gets
AgentTurnAlreadyRunningError and nothing is recorded.
The execution heartbeat renews the lease. Finalization releases it. When
a start takes over an expired lease, it marks the previous execution as
interrupted. The lease signal is combined with the abort signal of the
turn, so a lost lease stops the run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add the repository and service that take, renew, and release the lease
of an agent session. The lease uses the database clock. A lease that is
not renewed can be taken over by another main after two minutes.
The service keeps a registry of the leases that this main holds. It
aborts the local turn after two failed renewals, and it refuses a session
while an older local turn on that session is still settling.
Move AgentTurnAlreadyRunningError to its own file, so the lease can
throw it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add the agent_session_lease table with one row for each agent session.
A turn will take the lease in the transaction that records its execution,
so that only one turn runs on a session at a time.
The row keeps its epoch after release and after session deletion, so the
epoch of a thread ID only increases.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Store the active session of an integration conversation in the database
instead of the cache. The default cache is in memory. A restart sent each
conversation that used /new or an idle timeout back to its first session.
The pointer is a key in the metadata of the agents_resources row for the
base thread ID. Session deletion does not remove that row.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ali Elkhateeb <ali.elkhateeb@n8n.io>
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ali Elkhateeb <ali.elkhateeb@n8n.io>