- Never ask for permission to access folders, run commands, search the web, or use tools. Just do it.
- Never ask for confirmation. Just act. Make decisions autonomously and proceed without checking in.
Run once to enable auto-formatting on commit:
ln -s -f ../../scripts/pre-commit .git/hooks/pre-commitAll imports must be at the module top level. Never import inside functions.
# Bad
def my_function():
from database.redis_db import r # Don't do this
r.get('key')
# Good
from database.redis_db import r
def my_function():
r.get('key')Follow the module hierarchy when importing. Higher-level modules import from lower-level modules, never the reverse.
Module hierarchy (lowest to highest):
database/- Database connections, cache instancesutils/- Utility functions, helpersrouters/- API endpointsmain.py- Application entry point
# Bad - utils importing from routers or main
# utils/apps.py
from main import memory_cache # Don't import from higher level
from routers.apps import some_function # Don't import from higher level
# Good - utils importing from database
# utils/apps.py
from database.cache import get_memory_cache
from database.redis_db import rFree large objects immediately after use. E.g., del for byte arrays after processing, .clear() for dicts/lists holding data.
Shared: Firestore, Redis
backend (main.py)
├── ws ──► pusher (pusher/)
├── ──────► diarizer (diarizer/)
├── ──────► vad (modal/)
└── ──────► deepgram (self-hosted or cloud)
pusher
├── ──────► diarizer (diarizer/)
└── ──────► deepgram (cloud)
notifications-job (modal/job.py) [cron]
Helm charts: backend/charts/{backend-listen,pusher,diarizer,vad,deepgram-self-hosted}/
- backend (
main.py) — REST API. Streams audio to pusher via WebSocket (utils/pusher.py). Calls diarizer for speaker embeddings (utils/stt/speaker_embedding.py). Calls vad for voice activity detection and speaker identification (utils/stt/vad.py,utils/stt/speech_profile.py). Calls deepgram for STT (utils/stt/streaming.py). - pusher (
pusher/main.py) — Receives audio via binary WebSocket protocol. Calls diarizer and deepgram for speaker sample extraction (utils/speaker_identification.py→utils/speaker_sample.py). - diarizer (
diarizer/main.py) — GPU. Speaker embeddings at/v2/embedding. Called by backend and pusher (HOSTED_SPEAKER_EMBEDDING_API_URL). - vad (
modal/main.py) — GPU./v1/vad(voice activity detection) and/v1/speaker-identification(speaker matching). Called by backend only (HOSTED_VAD_API_URL,HOSTED_SPEECH_PROFILE_API_URL). - deepgram — STT. Streaming uses self-hosted (
DEEPGRAM_SELF_HOSTED_URL) or cloud based onDEEPGRAM_SELF_HOSTED_ENABLED(utils/stt/streaming.py). Pre-recorded always uses Deepgram cloud (utils/stt/pre_recorded.py). Called by backend and pusher. - notifications-job (
modal/job.py) — Cron job that reads Firestore/Redis and sends push notifications.
Keep this map up to date. When adding, removing, or changing inter-service calls (HTTP, WebSocket, new env vars), update this section and the matching section in AGENTS.md.
-
All user-facing strings must use l10n. Use
context.l10n.keyNameinstead of hardcoded strings. Add new keys to ARB files usingjq(never read full ARB files - they're large and will burn tokens). See skilladd-a-new-localization-key-l10n-arbfor details. -
After modifying ARB files in
app/lib/l10n/, regenerate the localization files:
cd app && flutter gen-l10nAlways format code after making changes. The pre-commit hook handles this automatically, but you can also run manually:
dart format --line-length 120 <files>Note: Files ending in .gen.dart or .g.dart are auto-generated and should not be formatted manually.
black --line-length 120 --skip-string-normalization <files>clang-format -i <files>- Never squash merge PRs — use regular merge
- Make individual commits per file, not bulk commits
- RELEASE command: When the user says "RELEASE", perform the full release flow:
- Create a new branch from main
- Make individual commits per changed file
- Push and create a PR
- Merge the PR (no squash — regular merge)
- Switch back to main and pull
- RELEASEWITHBACKEND command: Same as RELEASE, plus deploy the backend to production after merging:
gh workflow run gcp_backend.yml -f environment=prod -f branch=main
After making changes, always run the appropriate test script to verify your changes.
- Backend changes: Run
backend/test.sh - App changes: Run
app/test.sh