Skip to content

Session message-writer self-collides (UNIQUE constraint messages.session_id, msg_uid) under concurrency even with fully isolated per-process sessions.db #41

Description

@Broccolito

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions