Summary
Even after fully isolating each biorouter run to its own sessions.db
(a unique XDG_DATA_HOME per invocation — verified: separate DB files, the main
~/.local/share/biorouter/sessions/sessions.db untouched, no shared writer),
running several biorouter run processes concurrently still aborts a fraction of
them with:
Error: error returned from database: (code: 2067) UNIQUE constraint failed: messages.session_id, messages.msg_uid
Since each process writes only its own private DB, this is a self-collision
inside a single run — biorouter inserts two message rows with the same
(session_id, msg_uid) into its own store. It looks like an internal race in
message persistence (msg_uid assignment) that surfaces under concurrency/CPU
scheduling, and scales with session length (number of tool-call messages).
This is distinct from #31
#31 is about multiple processes sharing one sessions.db. This is different:
the DBs are provably separate (per-process XDG_DATA_HOME, confirmed via
distinct file paths + untouched main DB), yet the collision still happens. So
per-process isolation is NOT sufficient — the writer races with itself.
Environment
- biorouter
1.88.3, macOS (Apple Silicon), 16-core.
- Each run: unique
--name, unique XDG_DATA_HOME=<tmpdir>, --quiet,
--output-format json, --max-turns 50, multi-tool agent sessions (many
messages), a medcp stdio extension providing the tools.
Observed behavior (rate scales with concurrency × session length)
| concurrency |
session length |
collision rate |
| 1 (sequential) |
long (max-turns 50) |
~0% |
| 6 |
short (max-turns 4, few messages) |
0% |
| 2 |
long |
nonzero (~50% in a small sample) |
| 4 |
long |
~44% |
The aborted run emits (no text response), 0 tool calls, and an empty/invalid
--output-format json. A single sequential run never collides; the problem only
appears once ≥2 runs execute concurrently, and worsens with more messages.
Expected
msg_uid assignment / message insertion within a single session should be
race-free regardless of process concurrency or CPU scheduling. Suggestions:
- Generate
msg_uid atomically (a per-session monotonic counter under a lock, or
a UUID) so a single run cannot violate its own (session_id, msg_uid)
uniqueness.
- Open the session DB in WAL mode with a
busy_timeout, and retry the
insert on SQLITE_BUSY/constraint instead of aborting the whole run.
- On an unrecoverable DB error, surface a clear message and still emit valid
--output-format json, rather than empty output + a raw SQLite dump.
Impact
Parallel/batch use of biorouter run is unreliable: any concurrency silently
fails a fraction of runs with empty output, forcing callers to add external
retry logic. This blocks straightforward "run N agents in parallel" workflows and
makes headless throughput far lower than the hardware allows (a 16-core box
cannot safely run more than ~1–2 concurrent long agent sessions today).
Summary
Even after fully isolating each
biorouter runto its ownsessions.db(a unique
XDG_DATA_HOMEper invocation — verified: separate DB files, the main~/.local/share/biorouter/sessions/sessions.dbuntouched, no shared writer),running several
biorouter runprocesses concurrently still aborts a fraction ofthem with:
Since each process writes only its own private DB, this is a self-collision
inside a single run — biorouter inserts two message rows with the same
(session_id, msg_uid)into its own store. It looks like an internal race inmessage persistence (msg_uid assignment) that surfaces under concurrency/CPU
scheduling, and scales with session length (number of tool-call messages).
This is distinct from #31
#31 is about multiple processes sharing one
sessions.db. This is different:the DBs are provably separate (per-process
XDG_DATA_HOME, confirmed viadistinct file paths + untouched main DB), yet the collision still happens. So
per-process isolation is NOT sufficient — the writer races with itself.
Environment
1.88.3, macOS (Apple Silicon), 16-core.--name, uniqueXDG_DATA_HOME=<tmpdir>,--quiet,--output-format json,--max-turns 50, multi-tool agent sessions (manymessages), a
medcpstdio extension providing the tools.Observed behavior (rate scales with concurrency × session length)
The aborted run emits
(no text response),0tool calls, and an empty/invalid--output-format json. A single sequential run never collides; the problem onlyappears once ≥2 runs execute concurrently, and worsens with more messages.
Expected
msg_uidassignment / message insertion within a single session should berace-free regardless of process concurrency or CPU scheduling. Suggestions:
msg_uidatomically (a per-session monotonic counter under a lock, ora UUID) so a single run cannot violate its own
(session_id, msg_uid)uniqueness.
busy_timeout, and retry theinsert on
SQLITE_BUSY/constraint instead of aborting the whole run.--output-format json, rather than empty output + a raw SQLite dump.Impact
Parallel/batch use of
biorouter runis unreliable: any concurrency silentlyfails a fraction of runs with empty output, forcing callers to add external
retry logic. This blocks straightforward "run N agents in parallel" workflows and
makes headless throughput far lower than the hardware allows (a 16-core box
cannot safely run more than ~1–2 concurrent long agent sessions today).