A skeptical accountability companion. Not a cheerleader.
One commitment, verified evidence, no self-report. Earn the trust.
Expand
"It doesn't believe you. Yet."
Most habit apps take you at your word. Tap a checkbox, keep the streak, lie to yourself for free. Kawan (Malay for "friend") is the opposite: a companion that holds you to one commitment, asks for real evidence, and only believes you once you've shown it.
You commit to a single deliverable with a deadline. Kawan checks in on a schedule, reviews the evidence you submit — a screenshot, a file, or commits in a GitHub repo — and returns a verdict: pass, fail, or unclear. Self-report is never accepted. Trust is earned check-in by check-in.
The catch that makes it work: Kawan can never change the terms of your deal. Your goal, deadline, and how you're verified are yours alone. The AI reads them, reasons about them, and nudges you — but it is structurally incapable of editing them. That guarantee is enforced in the schema, not just the prompt (see The trust boundary).
It's wrapped in a warm, expressive interface: pick one of three Live2D companions, each with a distinct personality, voice, and model line-up, who reacts to your progress in real time.
| Landing | Sign in |
|---|---|
![]() |
![]() |
| Guided tour | Home |
![]() |
![]() |
| Commitments | Analytics |
![]() |
![]() |
A commitment moves through a single, deterministic lifecycle — from drafting the deal to a verified (or honestly un-verified) outcome.
I will [complete] [a deliverable] by [a deadline]. One goal, one deadline. No room to be vague.
Choose your evidence source (a GitHub repo to watch, or screenshot/file uploads), optionally name a witness who gets emailed if you miss, and a reminder email. Only you can change these. Kawan reads them but never edits them.
Three personalities, same backbone:
| Companion | Archetype | Tone |
|---|---|---|
| Kawan | Skeptical Concierge | Candid, warm, slightly dry. Believes you because you proved it. |
| Adik | Gentle Cheerleader | Encouraging and kind. Celebrates every step. |
| Cik Maid | Playful Taskmaster | Brisk, playful, expects results — with a wink. |
Your companion enters the workspace as a live, animated avatar. It gathers context (why, obstacles, time), then checks in on schedule and waits for evidence.
A focused room around the conversation: captured context, an advisory plan, recent activity, a live countdown to the next check-in, and the Submit final evidence action.
Every commitment has a detail page: verified count, check-ins, latest verdict and reasoning, the immutable terms, and a full timeline.
When the evidence passes, the commitment is closed as done. No participation trophies — a win counts because it was shown.
- 🎯 One real commitment — a single action + deliverable + deadline. Hard fields you set and only you can change.
- 🔎 Evidence over self-report — verdicts come from a GitHub repo's commits, an uploaded file, or a screenshot judged by a vision model. There is no "mark as done" button you can lie to.
- ⚖️ Honest verdicts — every check-in resolves to
pass/fail/unclear.unclearnever punishes; a flaky or slow model degrades to it instead of guessing. - 🎭 Three Live2D companions — Kawan, Adik, and Cik Maid, each a stateless preset of tone + animated model + voice + inference model. Switching the companion changes the messenger, never your commitment.
- 🔐 TEE inference via Chutes — real check-in lines and evidence judgments run on Chutes' Trusted Execution Environment chutes, with per-persona model routing and an automatic secondary judge on failure. A deterministic offline stub backend runs the whole app with zero keys.
- 🪜 Reliable delivery — notifications walk a ladder: live WebSocket → Web Push → persisted in-app timeline, so a check-in is never lost.
- 📣 Off-device reminders — opt-in email (Resend), Web Push (VAPID), and a Telegram check-in channel.
- 🤝 Stakes & witnesses — name someone who's emailed if you miss the deadline. That's the whole mechanism.
- 📈 Analytics & achievements — a productivity meter, identity titles, and 15 behavioral achievements that reward how you won (verified without a skip-day, finished early, came back after a miss…).
- ⏰ Scheduled & on-demand check-ins — APScheduler drives the cadence; one code path serves both the cron tick and an instant "check now," and rebuilds its jobs from the DB after a restart.
- 🪶 Guided walkthrough — an optional tour that teaches the commitment flow on real components, not a fake demo.
- 🌗 Polished UX — light/dark themes, responsive shell, optional Piper neural TTS with a WebSpeech fallback.
Kawan is a single-process FastAPI backend plus a React SPA. The frontend is organized in three zones: public pages (Zone 0), the SaaS shell (Zone 1 — home, commitments, analytics, settings), and the full-screen AI workspace (Zone 2 — the compose flow and live companion).
flowchart TD
subgraph Client["React SPA (Vite)"]
UI["Shell + Workspace"]
L2D["Live2D avatar<br/>PixiJS v6"]
SW["Service Worker<br/>(Web Push)"]
end
subgraph Server["FastAPI (single process)"]
API["REST /api/*"]
WS["WebSocket /ws"]
SCHED["APScheduler<br/>cadence ticks"]
PIPE["Check-in pipeline<br/>+ final verifier"]
DB[("SQLite / Postgres<br/>hard fields · soft context")]
end
subgraph External["External services"]
CHUTES["Chutes TEE<br/>inference + SIWC auth"]
NOTIFY["Email · Web Push · Telegram"]
end
UI -->|"/api, /ws"| API
UI <-->|"live check-ins"| WS
SCHED --> PIPE
PIPE --> DB
PIPE -->|"judge evidence /<br/>check-in line"| CHUTES
PIPE -->|"WS → Push → timeline"| NOTIFY
API --> DB
API -->|"OAuth2 PKCE"| CHUTES
WS <--> L2D
NOTIFY --> SW
The core idea is a hard separation between what you own and what the AI can touch — enforced in the data model, not just convention:
- Hard fields (
commitmentstable): action, deliverable, deadline, cadence, evidence type, stake. Written only by GUI handlers and the scheduler/verifier. No AI code path can update them. - Soft context (
soft_contexttable): the why, obstacles, and constraints. The only table the AI is allowed to write. - Proposals: the AI can propose a change to a hard field, but only you can apply it.
- Audit log: every hard-field mutation records an actor — and
'ai'is unrepresentable by a databaseCHECKconstraint. The AI literally cannot be the author of a change to your deal.
That is why the UI can promise "Only you can change these. Kawan reads them but never edits them." and mean it.
One code path (app/pipeline.py) runs for both a scheduled cadence tick and an on-demand check:
- Fetch new evidence through the adapter for the commitment's evidence type (
github/screenshot/file). - Judge it into a
Verdict(pass/fail/unclear) — primary call on a Chutes TEE model, with a bounded timeout that fails fast to a secondary judge rather than hanging. - Persist the evidence, check-in line, and escalation state.
- Deliver down the ladder: WebSocket → Web Push → in-app timeline.
A commitment's status machine (draft → active → verifying → grace → completed / missed, plus lapsed / returned) is the only thing that moves state — derived snapshots feed the AI as read-only prompt context and can never write back.
| Layer | Technologies |
|---|---|
| Frontend | React 18 · TypeScript · Vite · React Router v7 · PixiJS v6 + pixi-live2d-display · Recharts · Lucide · Biome |
| Backend | FastAPI · SQLAlchemy 2 (async) · APScheduler · Pydantic Settings · httpx · uv |
| Database | SQLite (dev) · PostgreSQL via Supabase pooler (prod) |
| AI / Inference | Chutes (OpenAI-compatible TEE inference) + Sign in with Chutes (OAuth2 PKCE) · deterministic stub backend |
| Realtime / Notify | WebSocket · Web Push (VAPID) · Telegram Bot API · Email (Resend) |
| Avatars / Voice | Live2D Cubism (Haru, Hiyori, LiveroiD) · Piper neural TTS (optional) |
| Deploy | Backend on Render · Frontend on Vercel |
The app runs fully offline out of the box — the default AI backend is a deterministic stub, so you need no API keys to try it locally.
- Python 3.12+ and
uv - Bun (the frontend lockfile is
bun.lock; npm/pnpm also work) - A POSIX shell (the asset scripts are bash)
1. Configure the environment
cp .env.example .env # in the kawan/ folder; sensible dev defaults are pre-filledThe dev defaults use local SQLite, the Vite proxy, and KAWAN_AI_BACKEND=stub. No secrets required.
2. Fetch the Live2D companion models (gitignored; one-time after clone)
./scripts/download_models.sh # Haru + Hiyori auto-download; LiveroiD is a manual BOOTH step3. Run the backend (FastAPI on :8000)
cd backend
uv sync
uv run uvicorn app.main:app --reload4. Run the frontend (Vite on :5173, proxies /api and /ws to the backend)
cd frontend
bun install
bun devOpen http://localhost:5173 and choose Continue as guest to start.
Optional — voices: run
./scripts/download_voices.shto fetch the three Piper persona voices. Without them, the frontend falls back to the browser's WebSpeech voice.
All settings use the KAWAN_ prefix and load from kawan/.env. See .env.example for the fully annotated list. The most important knobs:
| Variable | What it does |
|---|---|
KAWAN_AI_BACKEND |
stub (deterministic, offline — default) or chutes (real TEE inference) |
KAWAN_DATABASE_URL |
SQLite by default; a Supabase pooler URL in prod |
KAWAN_CHUTES_API_KEY |
Chutes token — enables guest-mode inference and app registration |
KAWAN_SIWC_* |
Sign in with Chutes (OAuth2 PKCE) client credentials |
KAWAN_SESSION_SECRET / KAWAN_FERNET_KEY |
Cookie signing + token-at-rest encryption (must be set in prod) |
KAWAN_VAPID_* |
Web Push keypair — blank disables push (delivery falls back to the timeline) |
KAWAN_RESEND_API_KEY |
Stake/reminder email — blank uses a log-only outbox so the miss path still runs |
KAWAN_TELEGRAM_BOT_TOKEN |
Telegram check-in channel — blank makes every send a no-op |
KAWAN_PIPER_VOICES_DIR |
Directory of Piper voice models — blank returns 204 and the client uses WebSpeech |
To use real inference, set KAWAN_AI_BACKEND=chutes and provide KAWAN_CHUTES_API_KEY (and the KAWAN_SIWC_* values for Sign in with Chutes).
- Backend → Render.
backend/render.yamldefines the web service (uv sync→uvicorn). Secrets and the cross-origin cookie settings (KAWAN_COOKIE_SAMESITE=none,KAWAN_COOKIE_SECURE=true) are set in the Render dashboard. Database notes (Supabase session vs. transaction pooler) live inbackend/DEPLOY.md. - Frontend → Vercel.
frontend/vercel.jsonrewrites/api/*to the Render backend and serves the SPA. In production the WebSocket connects directly to Render, which is why prod runsSameSite=None; Securecookies.
kawan/
├── backend/ # FastAPI single-process service
│ ├── app/
│ │ ├── main.py # app + lifespan (scheduler, telegram poller)
│ │ ├── models.py # hard fields / soft context / audit log
│ │ ├── pipeline.py # check-in + final verify (the one code path)
│ │ ├── personas.py # Kawan / Adik / Cik Maid presets
│ │ ├── adapters/ # github · screenshot · file evidence
│ │ ├── routes/ # auth · commitments · push · telegram · voice · ws
│ │ └── … # scheduler, chutes client, notify, state machine
│ ├── render.yaml # Render deploy
│ └── DEPLOY.md # DB / pooler notes
├── frontend/ # React + Vite SPA
│ ├── src/
│ │ ├── shell/ # Zone 1 — SaaS shell + pages
│ │ ├── zone2/ # Zone 2 — workspace, Live2D, new-commitment flow
│ │ ├── timeline/ # analytics, achievements, productivity meter
│ │ └── … # auth, notifications, ui, share
│ └── public/ # Live2D models, banner, icons, service worker
├── scripts/ # download_models.sh · download_voices.sh · helpers
├── docs/screenshots/ # the images in this README
└── .env.example # annotated configuration
Distributed under the MIT License. See LICENSE for details.
Built by Team CHJL with 💖 for Chutes Hack Malaysia 2026.
![]() ZJ @AlaskanTuna |
![]() YK @kymil4 |
![]() Jeremy @WhiteAvocad0 |
![]() KH @c3638 |
| Frontend, the Live2D companion stage, and the workspace UI. | Backend core: auth, billing, the scheduler, and realtime. | The AI agent layer, Chutes integration, and evidence judging. | Voice, notifications, deployment, and demo integration. |
- Chutes — Trusted Execution Environment inference and Sign in with Chutes
- Live2D Cubism & pixi-live2d-display — the animated companions
- Piper — neural text-to-speech voices
- Shields.io — the badges above
















