Files
optimclaw/src/db/CLAUDE.md
T
633b234e44 docs: add comprehensive subdirectory CLAUDE.md files and update root (#589)
* docs: add comprehensive subdirectory CLAUDE.md files and update root

The repo has grown significantly. This adds module-level CLAUDE.md files
for the five most complex subsystems, and updates the root CLAUDE.md to
reflect the actual current state of the codebase.

New files:
- src/agent/CLAUDE.md — full module map (19 files), session/thread/turn
  model, agentic loop flow, compaction strategies with correct thresholds,
  scheduler invariants, self-repair details, complete submission command
  reference table
- src/channels/web/CLAUDE.md — complete API route table (50+ endpoints),
  SSE event type reference, auth/rate limiting gotchas, connection limits,
  CORS headers, step-by-step endpoint addition guide
- src/db/CLAUDE.md — dual-backend build commands, sub-trait structure
  (7 sub-traits, ~67 methods), SQL dialect differences, boolean/timestamp
  gotchas, complete schema table, in-memory test helper, shared handle pattern
- src/llm/CLAUDE.md — corrected LlmProvider trait signatures, provider
  chain decorator order, NEAR AI dual-auth and session renewal details,
  circuit breaker thresholds, previously undocumented smart_routing.rs
  and recording.rs
- tests/e2e/CLAUDE.md — conftest fixtures and async scoping, environment
  injected into the binary, mock_llm canned responses, writing guide with
  correct asyncio usage, gotchas section

Root CLAUDE.md updates:
- Added E2E test setup and integration test commands
- Documented ~15 undocumented modules: cli/, registry/, hooks/, tunnel/,
  observability/, webhook_server.rs, cost_guard.rs, job_monitor.rs, etc.
- Corrected libSQL backend path (libsql/ directory, 8 sub-modules)
- Updated Database trait method count (~67, split across 7 sub-traits)
- Fixed stale references: config.rs → config/channels.rs, main.rs → app.rs
- Added Hook, Observer, Tunnel traits to extensibility section
- Added tunnel and observability env vars to Configuration section
- Removed resolved TODO (webhook trigger is now shipped)
- Added Module Specifications entries for all 5 new CLAUDE.md files

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* docs: address PR review comments and reduce CLAUDE.md size

- Fix 7-sub-trait count (was 6) and ~78 async methods (was ~60/~67) in
  both CLAUDE.md and src/db/CLAUDE.md
- Add missing types.rs to secrets/ file tree (CLAUDE.md)
- Add missing tls.rs to src/db/CLAUDE.md Files table
- Fix method counts: ConversationStore 12, JobStore 13, RoutineStore 15
- Add Windows venv activation note to E2E setup commands
- Collapse agent/, web/, llm/, db/ file trees to one-liners (detail
  lives in their respective CLAUDE.md files)
- Replace verbose Database and LLM Providers sections with summaries
  linking to src/db/CLAUDE.md and src/llm/CLAUDE.md
- Root CLAUDE.md: 43,868 → 35,270 chars (fixes >40k perf warning)

[skip-regression-check]

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-03-07 08:33:09 +00:00

9.1 KiB

Database Module

Dual-backend persistence layer. All new persistence features must support both backends.

Quick Reference

# Default build (PostgreSQL)
cargo build

# libSQL/Turso build
cargo build --no-default-features --features libsql

# Both backends
cargo build --features "postgres,libsql"

# Test each backend in isolation
cargo check                                           # postgres (default)
cargo check --no-default-features --features libsql   # libsql only
cargo check --all-features                            # both

Files

File Role
mod.rs Database supertrait + 7 sub-traits (~78 async methods total) — add new ops here first
postgres.rs PostgreSQL backend — delegates to Store + Repository in history/
libsql/mod.rs libSQL/Turso backend struct, connection helpers, row parsing utilities
libsql/conversations.rs ConversationStore impl
libsql/jobs.rs JobStore impl
libsql/sandbox.rs SandboxStore impl
libsql/routines.rs RoutineStore impl
libsql/settings.rs SettingsStore impl
libsql/tool_failures.rs ToolFailureStore impl
libsql/workspace.rs WorkspaceStore impl (FTS5 + vector search)
libsql_migrations.rs Consolidated libSQL schema (CREATE IF NOT EXISTS, no ALTER TABLE)
tls.rs TLS connector factory for PostgreSQL (rustls + system root certs)

PostgreSQL schema: migrations/V1__initial.sql through V9__flexible_embedding_dimension.sql (managed by refinery). V1 is the base schema; later migrations add tables, columns, and rename claude_code_eventsjob_events.

Trait Structure

The Database supertrait is composed of seven sub-traits. Leaf consumers can depend on the narrowest sub-trait they need rather than the full Database:

Sub-trait Methods Covers
ConversationStore 12 Conversations, messages
JobStore 13 Agent jobs, actions, LLM calls, estimation
SandboxStore 13 Sandbox jobs, job events
RoutineStore 15 Routines, routine runs
ToolFailureStore 4 Self-repair tracking
SettingsStore 8 Per-user key-value settings
WorkspaceStore 13 Memory documents, chunks, hybrid search

Database adds run_migrations() and combines all sub-traits.

Adding a New Persistence Operation

  1. Decide which sub-trait the method belongs to, or create a new sub-trait
  2. Add the async method signature to that sub-trait in mod.rs
  3. Implement in postgres.rs (delegate to Store or Repository)
  4. Implement in libsql/<module>.rs (SQLite-dialect SQL, use self.connect().await? per operation)
  5. Add migration if needed:
    • PostgreSQL: new migrations/VN__description.sql
    • libSQL: add CREATE TABLE IF NOT EXISTS to libsql_migrations.rs

SQL Dialect Differences

Feature PostgreSQL libSQL
UUIDs UUID type TEXT
Timestamps TIMESTAMPTZ TEXT (ISO-8601 RFC 3339 with ms precision)
JSON JSONB TEXT
Numeric/Decimal NUMERIC TEXT (preserves rust_decimal precision)
Arrays TEXT[] TEXT (JSON-encoded array)
Booleans BOOLEAN INTEGER (0/1)
Vector embeddings VECTOR (any dim, V9 removed fixed 1536) F32_BLOB(1536) via libsql_vector_idx
Full-text search tsvector + ts_rank_cd FTS5 virtual table + sync triggers
JSON path update jsonb_set(col, '{key}', val) json_patch(col, '{"key": val}')
PL/pgSQL Functions Triggers (no stored procs in SQLite)
Connection model deadpool-postgres connection pool New connection per operation (self.connect())
Concurrency Pool-based, fully concurrent WAL mode + 5 s busy timeout; write serialized
Auto-timestamp DEFAULT NOW() DEFAULT (datetime('now'))
Timestamp parsing Native type Multi-format fallback in parse_timestamp()

JSON merge patch gotcha: libSQL uses RFC 7396 JSON Merge Patch (json_patch) for metadata updates. This replaces top-level keys entirely — it cannot do partial nested updates. PostgreSQL uses jsonb_set which is path-targeted. Don't rely on partial nested metadata updates if you need libSQL compat.

Boolean storage: libSQL stores booleans as integers. When reading, use get_i64(row, idx) != 0; when writing, pass 1i64/0i64. Never pass a Rust bool directly.

Timestamp write format: Always write timestamps with fmt_ts(dt) (RFC 3339, millisecond precision). Read with get_ts() / get_opt_ts() which handle legacy naive formats too.

Vector dimension: PostgreSQL V9 migration changed the column to unbounded vector (removing the HNSW index). libSQL still uses F32_BLOB(1536) — if you use a different-dimension embedding model, the libSQL schema needs updating too.

Connection per operation: LibSqlBackend::connect() creates a fresh connection for every operation, sets PRAGMA busy_timeout = 5000, and closes it when the Connection is dropped. This is intentional — the libSQL SDK does not offer a pool. Avoid holding connections open across await points.

Schema: Key Tables

Core:

  • conversations — multi-channel conversation tracking
  • conversation_messages — individual messages within a conversation
  • agent_jobs — job metadata and status
  • job_actions — event-sourced tool executions
  • job_events — sandbox job streaming events (renamed from claude_code_events in V7)
  • dynamic_tools — agent-built tools
  • llm_calls — cost/token tracking
  • estimation_snapshots — learning data
  • repair_attempts — self-repair action log (not exposed via Database trait yet)

Workspace/Memory:

  • memory_documents — flexible path-based files
  • memory_chunks — chunked content with FTS + vector indexes
  • memory_chunks_fts — FTS5 virtual table (libSQL) / tsvector column (PostgreSQL)
  • heartbeat_state — periodic execution tracking

Security/Extensions:

  • secrets — AES-256-GCM encrypted credentials
  • wasm_tools — installed WASM tool binaries
  • tool_capabilities — per-tool HTTP allowlist, secret access, rate limits
  • leak_detection_patterns — secret regex patterns (seed data in both backends)
  • leak_detection_events — audit log of detected leaks
  • secret_usage_log — per-request credential injection audit trail
  • tool_rate_limit_state — sliding window rate limit counters

Other:

  • routines, routine_runs — scheduled/reactive execution
  • settings — per-user key-value
  • tool_failures — broken tool tracking for self-repair
  • _migrations — libSQL-only internal migration version tracking

libSQL Current Limitations

  • Secrets store — still requires PostgresSecretsStore; LibSqlSecretsStore exists but is not plumbed through the main startup path
  • Settings reloadConfig::from_db skipped (requires Store)
  • No incremental migrations — schema is idempotent CREATE IF NOT EXISTS; no ALTER TABLE support; column additions require a new versioned approach
  • No encryption at rest — only secrets (API tokens) are AES-256-GCM encrypted; all other data is plaintext SQLite
  • Hybrid search — both FTS5 and vector search (libsql_vector_idx) are implemented; however, the vector index is fixed at F32_BLOB(1536) while PostgreSQL switched to unbounded vector in V9
  • Write serialization — WAL mode allows concurrent readers but only one writer at a time; busy timeout is 5 s, which may cause timeouts under high write concurrency

Running Locally with libSQL

# Use local SQLite file (default)
DATABASE_BACKEND=libsql LIBSQL_PATH=~/.ironclaw/test.db cargo run

# Use Turso cloud (embedded replica syncs local file to cloud)
DATABASE_BACKEND=libsql LIBSQL_URL=libsql://xxx.turso.io LIBSQL_AUTH_TOKEN=xxx cargo run

# In-memory (tests only — data is lost when the process exits)
# Use LibSqlBackend::new_memory() directly in test code

Testing the libSQL Backend

Use LibSqlBackend::new_memory() in unit tests — no files, no cleanup required:

#[tokio::test]
async fn test_my_feature() {
    let backend = LibSqlBackend::new_memory().await.unwrap();
    backend.run_migrations().await.unwrap();
    // backend implements Database — call any trait method
}

For concurrency tests that require multiple connections sharing state, use LibSqlBackend::new_local(&tmp_path) with a tempfile::tempdir(). In-memory databases do not share state between connections.

Sharing the libSQL Database Handle

LibSqlBackend::shared_db() returns an Arc<LibSqlDatabase> for passing to satellite stores (e.g., LibSqlSecretsStore, LibSqlWasmToolStore) that need their own connections per-operation but should share the same underlying database file. These stores call .connect() on the shared handle themselves. This is the correct pattern — do not pass a live Connection to satellite stores.

Pattern: Fix the Pattern, Not the Instance

When fixing a bug in one backend's SQL, always grep for the same pattern in the other backend. A fix to postgres.rs that doesn't also fix the libSQL module (e.g., libsql/jobs.rs) is half a fix. The same applies to satellite types like LibSqlSecretsStore or LibSqlWasmToolStore.