name: review-session
description: Review session data model for the v2 two-phase monthly review (Reflect + Plan). Use when building review session CRUD, phase orchestration, conversation entry storage, or the review frontend. Status: BUILT (Phase 3, 2026-04-02).
allowed-tools: [Read, Grep, Bash(python*)]
Review Session
STATUS: BUILT (Phase 3, 2026-04-02) — review_sessions and review_conversation_entries tables exist and are live.
Architecture: Two Phases, Five Steps
Reflect phase (act=1):
Step 1: Overview — financial snapshot, spending vs budget, income
Step 2: Goals — check-in on goals set in previous review
Step 3: Patterns — Fogo identifies spending patterns and anomalies
Step 4: Open Reflection — Fogo asks reflective questions
Plan phase (act=2):
Step 5: Savings Goal — set or confirm savings rate target
Step 6: Budget & Goals — allocate budget categories, set behavioral goals
Step 7: Wrap Up — sinking fund contributions, closing narrative
The old three-act (Truth → Reflection → Plan) structure is deprecated.
Session model schema is unchanged — act field maps: Reflect=1, Plan=2, Wrap-up=3.
Core Tables
review_sessions
CREATE TABLE review_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT NOT NULL,
month INTEGER NOT NULL,
year INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'in_progress',
current_act INTEGER NOT NULL DEFAULT 1,
snapshot_total_spent INTEGER,
snapshot_total_income INTEGER,
snapshot_savings_rate FLOAT,
snapshot_categories JSONB,
act1_completed_at TIMESTAMPTZ,
act2_completed_at TIMESTAMPTZ,
act3_completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, month, year)
);
review_conversation_entries
CREATE TABLE review_conversation_entries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES review_sessions(id),
act INTEGER NOT NULL,
sequence_num INTEGER NOT NULL,
entry_type TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
data JSONB,
trace_id TEXT,
superseded_by UUID REFERENCES review_conversation_entries(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(session_id, sequence_num)
);
CREATE INDEX ON review_conversation_entries (session_id, sequence_num);
CREATE INDEX ON review_conversation_entries (session_id, act);
Entry Types
Each entry_type has a specific JSONB schema in data:
| entry_type | role | data keys | notes |
|---|
buddy_insight | buddy | insight_type, categories, amounts | Reflect — narrative insight |
income_confirmation | buddy | detected_income, sources | Reflect — income check |
user_income_response | user | confirmed_amount, confirmed | Reflect — user confirms |
category_truth | buddy | category, spent, budget, pct_used | Reflect — per-category truth |
user_category_response | user | category, response, note | Reflect — user reaction |
buddy_question | buddy | question_type, question_text, options | Reflect — reflection question |
user_reflection | user | question_type, response_text, option_selected | Reflect — user response |
buddy_summary | buddy | themes, emotional_arc | Reflect — end of reflection |
commitment | user | commitment_text, metric_type, metric_params | Plan — behavioral goal |
fund_contribution | user | fund_id, fund_name, amount | Plan — sinking fund |
savings_target | user | target_rate, previous_rate | Plan — rate target |
plan_summary | buddy | commitments, funds, savings_rate | Plan — Fogo confirms plan |
Append-Only Supersede Pattern
Entries are never deleted. If an insight is revised or a user changes their response:
old_entry.superseded_by = new_entry.id
db.add(new_entry)
db.commit()
active_entries = session.entries.filter(superseded_by=None).order_by(sequence_num)
Act Transitions
Reflect → Plan: all key Reflect steps complete (or user advances manually)
Plan → Wrap-up: savings goal + budget set (or user advances manually)
Wrap-up → complete: at least one commitment OR explicit "done"
Never block on perfect data — degrade gracefully:
- No budget → Reflect still runs (patterns instead of compliance)
- No income set → Fogo asks during Reflect instead of blocking
- Partial data → Fogo acknowledges "I have X weeks of data"
Services
async def create_session(user_id, month, year) -> ReviewSession
async def get_session(session_id, user_id) -> ReviewSession
async def get_current_session(user_id, month, year) -> ReviewSession | None
async def add_entry(session_id, entry_type, role, content, data, trace_id) -> ReviewConversationEntry
async def advance_act(session_id) -> ReviewSession
async def load_session_entries(session_id) -> list[ReviewConversationEntry]
API Endpoints
Reflect phase:
POST /api/v2/review-session/ — Create session for month/year
GET /api/v2/review-session/active — Find in-progress session
GET /api/v2/review-session/{id} — Load session + all entries
POST /api/v2/review-session/{id}/... — Reflect step endpoints
Plan phase:
POST /api/v2/review-session/{id}/plan/... — Plan step endpoints
Common:
PATCH /api/v2/review-session/{id}/advance-act — Move to next phase
POST /api/v2/review-session/{id}/complete — Finalize session
GET /api/v2/review-session/history — Past completed sessions
Last Updated
2026-04-02 — Updated for two-phase Reflect+Plan architecture