Skip to content

fix(memory): share one SQLite connection per holographic store database - #61726

Merged
teknium1 merged 2 commits into
mainfrom
salvage/43819-holographic-shared-conn
Jul 10, 2026
Merged

fix(memory): share one SQLite connection per holographic store database#61726
teknium1 merged 2 commits into
mainfrom
salvage/43819-holographic-shared-conn

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

The holographic memory store now shares one SQLite connection (+ one RLock) per database file across all MemoryStore instances in a process, ending the database is locked contention and retry burns reported on 24/7 gateways (#55503).

Root cause: each instance opened its own connection; a mid-method exception could leave an open write transaction pinning the WAL write lock, failing every sibling's writes for the 10s busy timeout — cascading into model-visible errors and token-burning retry loops.

Salvages PR #43819 by @adambiggs (cherry-picked, authorship preserved).

Changes

  • plugins/memory/holographic/store.py: process-wide refcounted registry (_shared + _shared_guard) — one connection + RLock per DB path, autocommit (isolation_level=None) so a raise can never dangle a transaction, schema init once, last-close teardown (contributor commit)
  • follow-up (ours): registry keyed on Path.resolve() so symlinked/relative paths to the same file share one connection instead of silently reintroducing the contention; symlink regression test; AUTHOR_MAP entry
  • tests/plugins/memory/test_holographic_store.py: 14 tests — sharing, refcounting, 8-thread concurrent writers, failed-write lock release, provider shutdown, symlink identity

Validation

Before After
N instances, same DB N independent WAL writers, lock contention 1 connection, fully serialized
Symlinked path separate connection (contention returns) same registry entry
Tests 460 memory-plugin tests green (incl. 14 new)

Plugin-only diff — no core files touched. Note: this removes in-process contention; cross-process cron contention is mitigated (write lock held per-statement under autocommit) but not eliminated.

Infographic

infographic

@alt-glitch alt-glitch added type/bug Something isn't working tool/memory Memory tool and memory providers comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 10, 2026
@teknium1
teknium1 force-pushed the salvage/43819-holographic-shared-conn branch from ea5d3e9 to 6ab00f3 Compare July 10, 2026 00:56
teknium1 added a commit that referenced this pull request Jul 10, 2026
…ort (#61734)

test_accepted_at_every_position spawned 11 separate
'python -m hermes_cli.main' subprocesses, each cold-importing the full
CLI module tree under a 15s TimeoutExpired deadline. On a loaded CI
worker the import alone can exceed that (slice 2/8 flaked exactly here
on PR #61726's run, TimeoutExpired at subprocess.py:1253), failing PRs
that never touched the CLI.

Replace with ONE driver subprocess that imports hermes_cli.main once
and parses all 11 argvs in-process (catching SystemExit per argv),
reporting JSON results. Same assertions per argv, identical semantics
(verified the --help-before-unknown-flag exit behavior matches the old
method), ~11x less import work, and the 180s timeout only trips on a
genuine hang.
adambiggs and others added 2 commits July 9, 2026 18:06
Every MemoryStore instance opened its own SQLite connection guarded by
its own RLock. Several providers coexist in one process (the main agent
plus every delegate_task subagent), so instances pointing at the same
memory_store.db raced as independent WAL writers. Combined with writes
that were not rolled back on error, one connection could leave an open
write transaction that pinned the write lock and made every other
connection's writes fail with "database is locked" for the full busy
timeout.

Instances for the same database now share ONE process-wide connection
and ONE re-entrant lock, so access is fully serialized and
cross-connection contention is impossible. The shared connection is
refcounted: closing one instance never tears it out from under a live
sibling, and the last close releases it. The connection runs in
autocommit (isolation_level=None) so a write that raises mid-method can
never leave a dangling transaction holding the write lock; the existing
explicit commit() calls become harmless no-ops.

The provider's shutdown() now calls the refcount-guarded close() instead
of just dropping the reference: leaving finalization to GC kept the
connection (and its write lock) alive indefinitely on long-running
gateways, prolonging the exact contention this fix removes. The last
provider now releases the connection deterministically while siblings
stay live; regression tests fail without the wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-ups for salvaged PR #43819: the registry key was
str(Path(db_path).expanduser()) — a symlinked or relative path to the
same DB file got its own connection, silently reintroducing the exact
multi-writer contention the registry prevents. Key on Path.resolve()
(OSError-tolerant fallback). Adds a symlink regression test and the
AUTHOR_MAP entry for adambiggs.
@teknium1
teknium1 force-pushed the salvage/43819-holographic-shared-conn branch from 6ab00f3 to 2f909a6 Compare July 10, 2026 01:08
@teknium1
teknium1 merged commit a801046 into main Jul 10, 2026
31 checks passed
@teknium1
teknium1 deleted the salvage/43819-holographic-shared-conn branch July 10, 2026 01:17
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…ort (NousResearch#61734)

test_accepted_at_every_position spawned 11 separate
'python -m hermes_cli.main' subprocesses, each cold-importing the full
CLI module tree under a 15s TimeoutExpired deadline. On a loaded CI
worker the import alone can exceed that (slice 2/8 flaked exactly here
on PR NousResearch#61726's run, TimeoutExpired at subprocess.py:1253), failing PRs
that never touched the CLI.

Replace with ONE driver subprocess that imports hermes_cli.main once
and parses all 11 argvs in-process (catching SystemExit per argv),
reporting JSON results. Same assertions per argv, identical semantics
(verified the --help-before-unknown-flag exit behavior matches the old
method), ~11x less import work, and the 180s timeout only trips on a
genuine hang.
justemu pushed a commit to justemu/hermes-agent that referenced this pull request Jul 18, 2026
…ort (NousResearch#61734)

test_accepted_at_every_position spawned 11 separate
'python -m hermes_cli.main' subprocesses, each cold-importing the full
CLI module tree under a 15s TimeoutExpired deadline. On a loaded CI
worker the import alone can exceed that (slice 2/8 flaked exactly here
on PR NousResearch#61726's run, TimeoutExpired at subprocess.py:1253), failing PRs
that never touched the CLI.

Replace with ONE driver subprocess that imports hermes_cli.main once
and parses all 11 argvs in-process (catching SystemExit per argv),
reporting JSON results. Same assertions per argv, identical semantics
(verified the --help-before-unknown-flag exit behavior matches the old
method), ~11x less import work, and the 180s timeout only trips on a
genuine hang.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…ort (NousResearch#61734)

test_accepted_at_every_position spawned 11 separate
'python -m hermes_cli.main' subprocesses, each cold-importing the full
CLI module tree under a 15s TimeoutExpired deadline. On a loaded CI
worker the import alone can exceed that (slice 2/8 flaked exactly here
on PR NousResearch#61726's run, TimeoutExpired at subprocess.py:1253), failing PRs
that never touched the CLI.

Replace with ONE driver subprocess that imports hermes_cli.main once
and parses all 11 argvs in-process (catching SystemExit per argv),
reporting JSON results. Same assertions per argv, identical semantics
(verified the --help-before-unknown-flag exit behavior matches the old
method), ~11x less import work, and the 180s timeout only trips on a
genuine hang.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…ort (NousResearch#61734)

test_accepted_at_every_position spawned 11 separate
'python -m hermes_cli.main' subprocesses, each cold-importing the full
CLI module tree under a 15s TimeoutExpired deadline. On a loaded CI
worker the import alone can exceed that (slice 2/8 flaked exactly here
on PR NousResearch#61726's run, TimeoutExpired at subprocess.py:1253), failing PRs
that never touched the CLI.

Replace with ONE driver subprocess that imports hermes_cli.main once
and parses all 11 argvs in-process (catching SystemExit per argv),
reporting JSON results. Same assertions per argv, identical semantics
(verified the --help-before-unknown-flag exit behavior matches the old
method), ~11x less import work, and the 180s timeout only trips on a
genuine hang.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…ort (NousResearch#61734)

test_accepted_at_every_position spawned 11 separate
'python -m hermes_cli.main' subprocesses, each cold-importing the full
CLI module tree under a 15s TimeoutExpired deadline. On a loaded CI
worker the import alone can exceed that (slice 2/8 flaked exactly here
on PR NousResearch#61726's run, TimeoutExpired at subprocess.py:1253), failing PRs
that never touched the CLI.

Replace with ONE driver subprocess that imports hermes_cli.main once
and parses all 11 argvs in-process (catching SystemExit per argv),
reporting JSON results. Same assertions per argv, identical semantics
(verified the --help-before-unknown-flag exit behavior matches the old
method), ~11x less import work, and the 180s timeout only trips on a
genuine hang.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/memory Memory tool and memory providers type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants