Skip to content
Kanishq Sharma

↑↓ to move · enter to open · esc to close

Projects

Cadence

A personal cognitive continuity system: habits, daily reflection, structured check-ins, and conversational logging that carry context from one day to the next.

FastAPI · SQLAlchemy · Alembic · SQLite · React · TypeScript

Overview

Cadence started as a habit tracker and turned into something more useful: a record of what I was doing and thinking, day by day, that does not lose the thread. Days can be closed and reopened, unfinished work carries forward as explicit threads, and a week can be reconstructed from the days that made it up.

It is the project where I took the operational parts seriously. Migrations are versioned, backups are verified and reversible, and the AI features are off until a user turns them on. Most of the work went into the parts that only matter when something goes wrong: a restore, a rate-limited model, a day left half written.

Architecture

A monorepo: a FastAPI package and a Vite React client. Routes stay thin, domain services hold the rules, SQLAlchemy models are versioned by Alembic, and SQLite is the store. External services, mail and AI, sit behind explicit consent and fall back cleanly.

  1. React client

    Vite, TypeScript, Tailwind. Dashboard, day detail, and continuity views.

  2. FastAPI routes

    Request validation and response shaping per resource.

  3. Domain services

    Habit state, day lifecycle, carry-forward threads, and summary generation.

  4. SQLAlchemy

    Alembic

    Models and the migration history that keeps schema versions explicit.

  5. SQLite

    Single-file store with WAL, online backups, and verified restores.

Generating a daily summary

  1. 1. POST /api/days/{date}/summary/generate

    The client asks for a summary of one closed day.

  2. 2. Check consent

    External AI is off per user until enabled in settings. Without consent the manual summary path is still available, so the feature degrades rather than fails.

  3. 3. Gather bounded sources

    The service collects that day's notes, check-in, and conversation entries. Bounded on purpose: the prompt is built from a known set of records, which is what makes the result traceable back to sources.

  4. 4. Redact, then call

    With redaction on, email addresses and phone-like values are replaced in the outbound prompt only. Local data is never rewritten.

  5. 5. Fall back on failure

    Models are tried in a ranked order. A 429 marks that model rate-limited and the runtime moves to the next eligible one.

  6. 6. Persist the artifact

    The summary is stored as an editable artifact, so a generated draft can be corrected by hand and still belongs to the day.

Database schema

habits / habit_logs

A discipline, and one row per day it was completed.

id
integer · primary key
user_id
integer · owner
name
text
archived_at
datetime · archive preserves history, delete does not
log.date
date · unique with habit, so a toggle is idempotent

days

The unit everything else hangs off: note, status, and links.

date
date · unique per user
note
text · nullable
status
text · open or closed, reopenable
summary
text · generated or hand-edited artifact

daily_checkins

Structured self-report: energy, focus, sleep, and similar.

day_id
integer · one per day
energy
integer
focus
integer
sleep
real

conversation_entries

Chat-style log for a day, kept as ordered entries rather than one blob.

day_id
integer
body
text
created_at
datetime · ordering

carry_forward

An unfinished thread inherited by the next day until completed or released.

origin_day
date
state
text · open, completed, released
context_id
integer · nullable link to a project or area

API design

  • GET/api/habits/month?month=YYYY-MMauthMonth grid in one request.
  • POST/api/habits/toggleauthToggle a habit log for a date.
  • PUT/api/days/{date}/checkinauthStructured check-in for a day.
  • POST/api/days/{date}/summary/generateauthSource-traceable summary through AI fallback.
  • PATCH/api/days/{date}/statusauthClose or reopen a day.
  • GET/api/days/{date}/carry-forwardauthThreads inherited by this day.
  • GET/api/continuity/weeks/{date}authReconstruct that date's week and open threads.
  • GET/api/continuity/search?q=authSearch one bounded year of owned sources.
  • GET/api/account/exportauthVersioned JSON export of everything the user owns.
  • PUT/api/account/ai-preferencesauthExternal-AI consent and outbound redaction.

Challenges

A restore that cannot quietly destroy data

Restoring a backup is the one operation that can lose everything. Restore refuses to run while the API holds its runtime lock, requires an exact schema-version match, and takes a fresh verified safety backup of the live database first. If post-install verification fails, that safety backup goes back automatically.

Backups that are actually consistent

Copying a SQLite file with WAL enabled can capture a torn state. Snapshots go through SQLite's online backup API and are integrity-checked before they get their final filename, so a file that exists is a file that restored cleanly.

AI that cannot leak by default

External AI is off per user until enabled, redaction strips emails and phone-like values from the outbound prompt without touching stored data, and nothing is sent automatically. Manual summaries and reflections work without AI at all.

Not confirming whether an account exists

A resend-verification endpoint is an account enumeration oracle if it answers differently for known and unknown addresses. It responds the same either way.

Lessons learned

  • The risky operations deserve the most design. Backup and restore took longer than the features they protect, and that was the right trade.
  • Consent is a data-flow decision, not a settings screen. Off by default, bounded prompts, redaction at the boundary.
  • Derive what you can and store what you must, but keep generated artifacts editable: a draft nobody can correct is worse than no draft.
  • A schema-version check turns an irreversible mistake into an error message.

Other case studies