ADR-0032: Session data model and retention
Accepted — Phase 2 sessions + append-only checkpoints (hashes/metadata); FORCE RLS; soft delete; extraction cursor for Phase 3; fuller DATABASE_SCHEMA.md model deferred.
ADR-0032: Session data model and retention strategy
- Status: Accepted (schema in 2.4.1; store in 2.4.2)
- Date: 2026-07-24
- Authors: IBEX Harness team
- Milestone: 2.4.1 Sessions and checkpoints migrations
Context
A session is one conversation between a client and an agent (shared context across LLM turns). A checkpoint is an immutable record of a single turn: operational metadata plus content hashes for later memory dedup. Phase 3 memory extraction reads completed sessions' checkpoints; without this schema there is nothing to extract.
DATABASE_SCHEMA.md describes a richer sessions model (heartbeat, loop fingerprints, recovery fields). Phase 2 needs a minimal subset that the proxy can write on the chat path and that Phase 3 can query incrementally.
Decision
- Session vs checkpoint:
sessionsholds conversation aggregate state (status, model/provider from first turn, token/latency totals,last_extracted_turn).checkpointsholds one row per turn withUNIQUE(session_id, turn_index). - Phase 2 column subset: Hashes and metadata only —
messages_hash, optionalcompletion_hash, tokens, latency, streaming flags. Full message/completion JSON is not stored in these tables in Phase 2 (payload archival is a later concern). - Lifecycle: Status enum is
active|completed|abandoned|error. Soft delete viadeleted_at. Idle abandonment is implemented in 2.4.4; this ADR only reserves theabandonedstatus. - Client session key: Optional
external_id(fromX-IBEX-Session-ID) with partial unique(org_id, agent_id, external_id) WHERE external_id IS NOT NULLfor lookup in 2.4.2+. - Tenant isolation: Both tables have
org_id,ENABLE+FORCE ROW LEVEL SECURITY, andibex_core.rls_org_visible(same as directives). Ownership is structural via composite FKs(agent_id, org_id) → agents,(session_id, org_id) → sessions, and(directive_version_id, org_id) → directive_versions. - Append-only checkpoints:
ibex_apphasSELECT, INSERTonly oncheckpoints; aBEFORE UPDATEtrigger rejects mutations. Session parentON DELETE CASCADEcleanup runs as table owner. - Directive pointer: Nullable
directive_version_idwith composite org-scoped FK (ON DELETE RESTRICT). Postgres cannotON DELETE SET NULLa composite FK that includes NOT NULLorg_id; aBEFORE DELETEtrigger ondirective_versionsnulls referencingsessions.directive_version_idso version cleanup still clears the pointer. - Phase 3 extraction: Worker selects completed, non-deleted sessions with unextracted turns via partial index
idx_sessions_agent_extractionon(agent_id, last_extracted_turn) WHERE status = 'completed' AND deleted_at IS NULL AND last_extracted_turn < turn_count, then advanceslast_extracted_turn. Dedup usesmessages_hash/completion_hash. - Retention: Soft-deleted sessions are excluded from the extraction index. Org-scoped retention days and hard-delete GC are deferred to a later ADR; until then soft delete is the operator-facing delete path.
Consequences
- Migration
000010_create_sessionsapplies the Phase 2 subset underibex_core. - 2.4.2 can implement typed CRUD without schema churn for the hot path.
- Expanding toward the full
DATABASE_SCHEMA.mdsessions model requires an explicit follow-on ADR and additive migrations.
Alternatives considered
- Store full messages JSON in checkpoints now — simplifies Phase 3; balloons storage and privacy surface on the hot path. Rejected for Phase 2; hashes only.
- Implement full DATABASE_SCHEMA.md sessions table — heartbeat/loop/recovery fields unused until later goals. Deferred.
- Mutable checkpoints — allows correcting token counts after stream end; risks silent corruption of extraction inputs. Rejected; finalize metadata on insert (or insert incomplete then never update — stream incomplete is
is_complete=falseat insert time).
Was this page helpful?
Last updated on