420f00494f
Sets up the Cargo workspace (harness-core, harness-tools, harness-providers, harness-mcp, harness-lsp, harness-app, harness-tui) and the ten architecture docs under docs/. harness-core gets its foundational types (session/message/part/model ids), an in-process event bus, a table-driven permission evaluate function, and a SQLite-backed storage actor with schema and roundtrip coverage. Every crate compiles empty and a bin stub prints its version.
2.4 KiB
2.4 KiB
08 — Storage
harness-core/src/store/. SQLite per project: ~/.local/share/ai-harness/db/<project-slug>.sqlite (slug = sanitized worktree path + hash), WAL mode.
Schema
CREATE TABLE session (id TEXT PRIMARY KEY, parent_id TEXT, created_at INTEGER, updated_at INTEGER,
data TEXT NOT NULL); -- full Session JSON
CREATE TABLE message (id TEXT PRIMARY KEY, session_id TEXT NOT NULL, created_at INTEGER,
data TEXT NOT NULL);
CREATE TABLE part (id TEXT PRIMARY KEY, message_id TEXT NOT NULL, session_id TEXT NOT NULL,
idx INTEGER NOT NULL, data TEXT NOT NULL);
CREATE TABLE job (task_id TEXT PRIMARY KEY, parent_session_id TEXT NOT NULL, data TEXT NOT NULL);
CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT); -- schema_version, cache stamps
CREATE INDEX message_session ON message(session_id);
CREATE INDEX part_message ON part(message_id);
CREATE INDEX session_parent ON session(parent_id);
JSON data columns (opencode's approach): schema evolution without a migration per field. IDs are ULIDs, so ORDER BY id is chronological.
Storage actor
One dedicated thread owns the rusqlite::Connection; commands arrive over mpsc::Sender<StoreCmd>, each carrying a oneshot reply channel. store::api exposes typed async facades: upsert_session, upsert_message, upsert_part, messages(session), parts(message), sessions(), jobs(parent), …
Rationale vs sqlx/async drivers: single writer, embedded DB, tiny schema — an actor thread is simpler, avoids async-driver complexity, and serializes writes for free.
Write cadence
- Messages and tool-state transitions: written immediately.
- Streaming text deltas: buffered by the processor's
DeltaFlusher— flushed to the part row every 50 ms or 2 KB, whileAppEvent::PartDeltagoes to the UI immediately. UI is realtime; SQLite writes are throttled. - Synthetic parts (job board injection, reminders) are not persisted.
Session resume
- Session list from the DB; opening a session replays its messages/parts into the TUI view.
- Crash recovery: on load, an unfinished assistant message (no
finished, no error) is marked aborted; in-flight tool parts becomeError { "interrupted" }. - Job records reload from the
jobtable so the board survives restarts (running jobs whose runs died are markedErroron load).