This repository has been archived on 2026-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
orchestrator/agent-team/agent_team/db/schema.sql
Adam Moussa 1b4d30e47f feat(agent-team): add pending_questions.kind discriminator + migration (Phase B1)
The plan-review gate (coming next) needs to tell its decision questions apart
from clarifier questions in the durable ledger. Add a `kind` column to
pending_questions (values 'clarify' | 'plan_decision').

- Fresh DBs: `kind TEXT NOT NULL DEFAULT 'clarify'` (+ CHECK) in the DDL.
- Live ledger: idempotent additive migration (SCHEMA_VERSION 3→4) — a guarded
  ALTER (PRAGMA table_info) run from both migrate() and init_db; legacy rows
  take the 'clarify' default, never null. (SQLite can't add a CHECK via ALTER,
  so the migrated column is NOT NULL DEFAULT only; value constraint is enforced
  on fresh DBs by the CHECK and on all writes by the typed helper.)
- ledger.post_question gains a keyword-only `kind="clarify"` (backward
  compatible — existing callers unchanged); PendingQuestion.from_row reads it.

Tests: fresh-DB column+default, idempotent init_db, legacy-DB backfill to
'clarify', plan_decision round-trip. 1396 passed.
2026-06-23 21:03:53 -04:00

93 lines
4.1 KiB
SQL

-- R720 agent-team durable SQLite schema (design §3.3.1, §6.7).
--
-- This file holds the raw DDL statements ONLY. The authoritative copies live
-- as string constants in agent_team/db/schema.py; this companion file mirrors
-- them verbatim for tooling / direct inspection. SQL DDL lives only in these
-- two places.
--
-- The LangGraph SqliteSaver checkpointer creates its OWN tables against this
-- same database file/connection; those are intentionally NOT declared here.
-- pending_questions: the durable human-interaction lifecycle ledger.
-- The LangGraph checkpoint holds graph state; this table holds the question
-- lifecycle (delivery, duplicate/late answers, expiry) and is what delivery,
-- the responder, and restart recovery read. Every race resolves via an atomic
-- compare-and-set against the `status` column under BEGIN IMMEDIATE.
CREATE TABLE IF NOT EXISTS pending_questions (
question_id TEXT PRIMARY KEY,
thread_id TEXT NOT NULL,
turn INTEGER NOT NULL,
status TEXT NOT NULL
CHECK (status IN ('open', 'answered', 'expired', 'superseded')),
transport TEXT NOT NULL,
channel_ref TEXT,
posted_at TEXT,
deadline_at TEXT,
answer_json TEXT,
answered_at TEXT,
answered_via TEXT,
-- kind: discriminates the human gate this question belongs to —
-- 'clarify' (the clarifier) or 'plan_decision' (the plan-review gate).
-- Defaults to 'clarify' so an in-place ALTER on a legacy ledger and any
-- existing rows take the clarifier value.
kind TEXT NOT NULL DEFAULT 'clarify'
CHECK (kind IN ('clarify', 'plan_decision'))
);
CREATE INDEX IF NOT EXISTS idx_pending_questions_thread
ON pending_questions (thread_id, turn);
CREATE INDEX IF NOT EXISTS idx_pending_questions_status
ON pending_questions (status);
-- Defense-in-depth: at most one OPEN question may carry a given non-null
-- channel_ref, so a thread reply's thread_ts can never map to two open rows
-- (a partial unique index; NULL channel_refs and closed rows are excluded).
CREATE UNIQUE INDEX IF NOT EXISTS uq_pending_questions_open_channel_ref
ON pending_questions (channel_ref)
WHERE channel_ref IS NOT NULL AND status = 'open';
-- budget_ledger: the persistent shared Claude budget ledger (§6.1, §6.6).
-- One row per accounted spend event; the shared daily cap and the
-- interactive-first reserve are computed by summing over a UTC day. Spend is
-- recorded across ALL R720 Claude work (pipeline + Plane-1 sweeps).
CREATE TABLE IF NOT EXISTS budget_ledger (
entry_id INTEGER PRIMARY KEY AUTOINCREMENT,
thread_id TEXT,
stage TEXT,
model TEXT NOT NULL,
billing_mode TEXT NOT NULL,
input_tokens INTEGER NOT NULL DEFAULT 0,
output_tokens INTEGER NOT NULL DEFAULT 0,
usd_cost REAL NOT NULL DEFAULT 0.0,
recorded_at TEXT NOT NULL,
day_bucket TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_budget_ledger_day
ON budget_ledger (day_bucket);
CREATE INDEX IF NOT EXISTS idx_budget_ledger_thread
ON budget_ledger (thread_id);
-- ingested_issues (schema v2): durable de-dup ledger for the GitHub-issue intake
-- poller. The in-memory poller set does not survive a restart, so a scheduled
-- intake (each run a fresh process) would re-ingest every still-open labeled
-- issue and spawn duplicate tasks. This table records, per (source, issue_id),
-- the issues already turned into tasks so intake is idempotent across restarts.
-- The box is read-only (no write token to remove the intake label), so durable
-- de-dup is the only correct guard. `source` namespaces by repo (e.g.
-- "github:owner/repo") so per-repo issue numbers from different repos cannot collide.
CREATE TABLE IF NOT EXISTS ingested_issues (
source TEXT NOT NULL,
issue_id TEXT NOT NULL,
ingested_at TEXT NOT NULL,
PRIMARY KEY (source, issue_id)
);
-- schema_meta: single-row table recording the applied schema version so
-- migrate() can detect and step forward.
CREATE TABLE IF NOT EXISTS schema_meta (
id INTEGER PRIMARY KEY CHECK (id = 1),
schema_version INTEGER NOT NULL
);