v2: split session DB into inbound/outbound for write isolation

Eliminates SQLite write contention across the host-container mount
boundary by splitting the single session.db into two files, each with
exactly one writer:

  inbound.db  — host writes (messages_in, delivered tracking)
  outbound.db — container writes (messages_out, processing_ack)

Key changes:
- Host uses even seq numbers, container uses odd (collision-free)
- Container heartbeat via file touch instead of DB UPDATE
- Scheduling MCP tools now emit system actions via messages_out
  (host applies them to inbound.db during delivery)
- Host sweep reads processing_ack + heartbeat file for stale detection
- OneCLI ensureAgent() call added (was missing from v2, caused
  applyContainerConfig to reject unknown agent identifiers)

Verified: tsc clean, 327 tests pass, real e2e through Docker works.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-09 12:17:31 +03:00
parent 320176e7e8
commit 82cb363f84
19 changed files with 738 additions and 347 deletions

View File

@@ -5,14 +5,18 @@
* No stdin, no stdout markers, no IPC files.
*
* Config:
* - SESSION_DB_PATH: path to session SQLite DB (default: /workspace/session.db)
* - SESSION_INBOUND_DB_PATH: path to host-owned inbound DB (default: /workspace/inbound.db)
* - SESSION_OUTBOUND_DB_PATH: path to container-owned outbound DB (default: /workspace/outbound.db)
* - SESSION_HEARTBEAT_PATH: heartbeat file path (default: /workspace/.heartbeat)
* - AGENT_PROVIDER: 'claude' | 'mock' (default: claude)
* - NANOCLAW_ASSISTANT_NAME: assistant name for transcript archiving
* - NANOCLAW_ADMIN_USER_ID: admin user ID for permission checks
*
* Mount structure:
* /workspace/
* session.db ← session SQLite DB
* inbound.db ← host-owned session DB (container reads only)
* outbound.db ← container-owned session DB
* .heartbeat ← container touches for liveness detection
* outbox/ ← outbound files
* agent/ ← agent group folder (CLAUDE.md, skills, working files)
* .claude/ ← Claude SDK session data
@@ -80,7 +84,9 @@ async function main(): Promise<void> {
command: 'node',
args: [mcpServerPath],
env: {
SESSION_DB_PATH: process.env.SESSION_DB_PATH || '/workspace/session.db',
SESSION_INBOUND_DB_PATH: process.env.SESSION_INBOUND_DB_PATH || '/workspace/inbound.db',
SESSION_OUTBOUND_DB_PATH: process.env.SESSION_OUTBOUND_DB_PATH || '/workspace/outbound.db',
SESSION_HEARTBEAT_PATH: process.env.SESSION_HEARTBEAT_PATH || '/workspace/.heartbeat',
},
},
},