Architecture & Data Flow
Diagrams are written in Mermaid. They render automatically on GitHub and in VS Code (install the "Mermaid Preview" extension). You can also paste the code blocks into mermaid.live to view and export them.
Component Architecture
High-level view of how the major pieces fit together.
graph TB
subgraph Users["Interfaces"]
DiscordClient["Discord Client\n(user)"]
TelegramClient["Telegram Client\n(user)"]
AdminCLI["manage.py\n(admin CLI)"]
end
subgraph BotLayer["Bot Layer"]
subgraph DiscordBot["Discord Bot — bot/"]
ConvCog["conversation.py\non_message handler\nMOTD · onboarding · routing"]
AdminCog["admin.py\n/profile · /reset · /link\n/set_platform · /set_timezone\n/delete_my_data"]
SlashCogs["tasks.py · appointments.py\nslash command interfaces"]
end
subgraph TgBot["Telegram Bot — tg_bot/"]
TgConv["handlers/conversation.py\nmessage handler\nMOTD · onboarding · routing"]
TgCmds["handlers/commands.py\n/start · /link · /set_platform\n/profile · /delete_my_data"]
end
Notifier["notifier.py\nNotifier\nroutes per preferred_platform\n(discord · telegram · both)"]
end
subgraph AILayer["AI Layer — ai/"]
Coach["coach.py\nbuild prompt → call Claude\nexecute tools → return text"]
Memory["memory.py\nrolling history window\nsummarization at 60 msgs"]
Tools["tools.py\ntool definitions +\nexecution handlers"]
HabitsCatalog["habits_catalog.py\nin-code habit definitions\n(action + check-in types)"]
subgraph Prompts["prompts/"]
Base["system_base.py\nBASE_PROMPT (ADHD coaching)\nBASE_PROMPT_TASKONLY (neutral)\nHABIT_SETUP_MODE · triggers"]
Context["task_context.py\ntask + appointment\nhabit + missed-task fragments"]
Modes["coaching_modes.py\noverwhelm · focus\ncelebration · planning"]
end
end
subgraph SchedLayer["Scheduler — scheduler/"]
Daemon["daemon.py\nAPScheduler\nAsyncIOScheduler"]
Reminders["reminders.py\ntask + appointment\nreminders"]
ReminderQueue["reminder_queue.py\nfire-once push\nnotification queue"]
Checkins["checkins.py\ndaily morning\ncheck-in"]
HabitReview["habit_review.py\n60-day periodic\nhabit review"]
WeeklySummary["weekly_summary.py\nFriday win summary"]
WeeklyReview["weekly_review.py\nMonday weekly review"]
Recurring["recurring.py\ngenerate daily task + appt instances\nmark missed · bump urgency"]
end
subgraph DataLayer["Data Layer — db/"]
Repos["repos/\nuser · task · appt · message · habit\nrecurring_task · recurring_appt\noccasion · reminder · link_code"]
Crypto["crypto.py\nFernet encryption\nat rest"]
DB[("SQLite or PostgreSQL\n(DATABASE_URL)")]
end
subgraph WebLayer["Web Layer — web/"]
WebServer["server.py\nFastAPI + uvicorn\nport 8000"]
StaticSite["web/dist/\nMkDocs static site\n(baked into Docker image)"]
HealthCheck["/health\nhealth check endpoint"]
end
subgraph External["External Services"]
DiscordAPI["Discord API"]
TelegramAPI["Telegram API"]
AnthropicAPI["Anthropic API\nclaude-sonnet-4-6"]
Browser["Browser\n(docs / landing page)"]
end
DiscordClient <-->|"WebSocket"| DiscordAPI
TelegramClient <-->|"long polling"| TelegramAPI
DiscordAPI --> ConvCog
DiscordAPI --> AdminCog
TelegramAPI --> TgConv
TelegramAPI --> TgCmds
AdminCog --> Repos
TgCmds --> Repos
AdminCLI -->|"direct DB access\nno bot needed"| Repos
ConvCog --> Coach
ConvCog --> Repos
TgConv --> Coach
TgConv --> Repos
Coach --> Memory
Coach --> Tools
Coach --> Prompts
Coach <-->|"messages.create()"| AnthropicAPI
Memory --> Repos
Tools --> Repos
Tools --> HabitsCatalog
Daemon --> Reminders
Daemon --> ReminderQueue
Daemon --> Checkins
Daemon --> HabitReview
Daemon --> WeeklySummary
Daemon --> WeeklyReview
Daemon --> Recurring
Reminders --> Repos
Reminders --> Notifier
ReminderQueue --> Repos
ReminderQueue --> Notifier
Checkins --> Coach
Checkins --> Notifier
HabitReview --> Coach
HabitReview --> Notifier
WeeklySummary --> Coach
WeeklySummary --> Notifier
WeeklyReview --> Coach
WeeklyReview --> Notifier
Recurring --> Repos
Notifier -->|"DM user"| DiscordAPI
Notifier -->|"send_message()"| TelegramAPI
Repos --> Crypto
Crypto <--> DB
WebServer --> StaticSite
WebServer --> HealthCheck
Browser -->|"HTTP :8000"| WebServer
Message Data Flow
Sequence diagram for a single user message through the system.
sequenceDiagram
actor User
participant Discord
participant Conv as conversation.py
participant Coach as coach.py
participant Memory as memory.py
participant Tools as tools.py
participant Repos as db/repos
participant Claude as Anthropic API
User->>Discord: sends message / DM
Discord->>Conv: on_message event
Conv->>Repos: get_or_create_user()
Repos-->>Conv: (user, is_new)
alt First contact (is_new)
Conv->>Discord: send MOTD
end
alt Onboarding not complete
Conv->>Claude: onboarding system prompt + history
Claude-->>Conv: next question or ONBOARDING_COMPLETE
Conv->>Repos: save messages
Conv->>Repos: update user profile (on completion)
Conv->>Discord: send onboarding reply
else Normal conversation
Conv->>Coach: respond(user, message)
Coach->>Repos: save_user_message()
Coach->>Repos: list_active tasks
Coach->>Repos: list_upcoming appointments
Note over Coach: assemble system prompt:<br/>base (ADHD or task-only) + profile +<br/>tasks + appts + habit context +<br/>mode fragment (skipped if task-only)
alt habits_setup_complete = False
Note over Coach: inject HABIT_SETUP_MODE fragment<br/>Aria walks user through habit setup
else Normal coaching mode
Note over Coach: inject coaching_mode fragment<br/>(overwhelm / focus / celebration / planning)
end
Coach->>Memory: get_context_messages()
Memory->>Repos: get_history(limit=20)
Repos-->>Memory: recent messages + summary row
Memory-->>Coach: formatted history
loop Tool-use loop (max 5 rounds)
Coach->>Claude: messages.create(system, tools, history)
Claude-->>Coach: response (text and/or tool_use blocks)
alt Response contains tool_use blocks
loop Each tool call
Coach->>Tools: execute_tool(name, input)
Tools->>Repos: create / update / read
Repos-->>Tools: result
Tools-->>Coach: tool result string
end
Note over Coach: append assistant + tool_result turns<br/>to local message list, loop again
else No tool calls — final response
Coach-->>Conv: final text
end
end
Coach->>Repos: save_assistant_message()
Coach->>Memory: maybe_summarize()
Note over Memory: if msg count ≥ 60, purge old rows,<br/>call Claude for summary, save summary row
Conv->>Discord: send response (chunked if > 2000 chars)
Discord-->>User: bot reply
end
Proactive Message Flow
How the scheduler sends reminders, morning check-ins, and habit reviews without a user triggering anything.
sequenceDiagram
participant APScheduler
participant Recurring as recurring.py
participant Reminders as reminders.py
participant Checkins as checkins.py
participant HabitReview as habit_review.py
participant WeeklySummary as weekly_summary.py
participant WeeklyReview as weekly_review.py
participant Coach as coach.py
participant Repos as db/repos
participant Notifier as Notifier
Note over APScheduler: recurring: hourly<br/>task/appt reminder: every 5 min / 1 min<br/>reminder queue: every 1 min<br/>morning check-in: every minute (match user time)<br/>habit review: daily at 10am UTC<br/>weekly summary/review: every minute (match user day+time)
APScheduler->>Recurring: hourly instance generation
Recurring->>Repos: list_active recurring tasks
loop Each applicable recurring task
Recurring->>Repos: mark previous-day open instances "missed"
Recurring->>Repos: count_consecutive_missed → bump urgency (cap 85)
Recurring->>Repos: create today's fresh Task instance
end
APScheduler->>Reminders: poll for due tasks
Reminders->>Repos: list_due_for_reminder(now)
Repos-->>Reminders: overdue tasks
loop Each due task
Reminders->>Notifier: send(user, reminder text)
Reminders->>Repos: mark_reminder_sent(task_id)
end
APScheduler->>Reminders: poll for due appointments
Reminders->>Repos: list_due_for_reminder(now)
Repos-->>Reminders: appointments in reminder window
loop Each due appointment
Reminders->>Notifier: send(user, reminder text)
Reminders->>Repos: mark_reminder_sent(appt_id)
end
APScheduler->>Checkins: fire morning check-in for user
Checkins->>Repos: get user
Checkins->>Coach: respond(user, CHECKIN_TRIGGER)
Note over Coach: builds fresh context: occasion countdowns (lead window) +<br/>tasks + appts + recently-missed recurring tasks +<br/>today's recurring instances + habit check-in<br/>questions (active, paused, struggling)
Coach-->>Checkins: check-in message text
Checkins->>Notifier: send(user, check-in text)
APScheduler->>HabitReview: daily habit review check
HabitReview->>Repos: query users where habits_setup_complete=True<br/>AND last_habit_review_at < 60 days ago
Repos-->>HabitReview: users due for review
loop Each user due for review
HabitReview->>Coach: respond(user, HABIT_REVIEW_TRIGGER)
Note over Coach: builds context with habit stats +<br/>struggling habits, injects HABIT_REVIEW_INSTRUCTIONS
Coach-->>HabitReview: review message text
HabitReview->>Notifier: send(user, review text)
Note over HabitReview: complete_habit_setup tool call<br/>resets last_habit_review_at
end
APScheduler->>WeeklySummary: every minute — check if any user's summary time matches now
WeeklySummary->>Repos: get users due for weekly summary
Repos-->>WeeklySummary: matching users
loop Each user due for weekly summary
WeeklySummary->>Coach: respond(user, WEEKLY_SUMMARY_TRIGGER)
Coach-->>WeeklySummary: summary message text
WeeklySummary->>Notifier: send(user, summary text)
end
APScheduler->>WeeklyReview: every minute — check if any user's review time matches now
WeeklyReview->>Repos: get users due for weekly review
Repos-->>WeeklyReview: matching users
loop Each user due for weekly review
WeeklyReview->>Coach: respond(user, WEEKLY_REVIEW_TRIGGER)
Coach-->>WeeklyReview: review message text
WeeklyReview->>Notifier: send(user, review text)
end
Habit Tracking Data Flow
How check-in habit questions flow through the system during a morning check-in.
sequenceDiagram
participant APScheduler
participant Checkins as checkins.py
participant Coach as coach.py
participant Claude as Anthropic API
participant HabitRepo as habit_repo
participant Notifier as Notifier
actor User
APScheduler->>Checkins: fire check-in for user
Checkins->>Coach: respond(user, CHECKIN_TRIGGER)
Coach->>HabitRepo: get_pause() for each enabled habit
Coach->>HabitRepo: get_struggling_habits() — ≥60% no in last 14 days
Coach->>HabitRepo: get_stats() — yes/no ratios
Note over Coach: format_habit_context() builds prompt fragment<br/>listing active/paused/struggling habits
Coach->>Claude: check-in system prompt + habit context
Claude-->>Coach: check-in message with habit questions
Checkins->>Notifier: send(user, check-in + habit questions)
Notifier-->>User: morning check-in (Discord or Telegram)
User->>Notifier: "Yes, I woke up on time. No on screens."
Notifier->>Coach: respond(user, reply)
loop Each habit answer
Claude->>Coach: tool_use: log_habit_checkin(habit_key, response)
Coach->>HabitRepo: log_response(user_id, habit_key, True/False)
Note over HabitRepo: upserts by (user_id, habit_key, date)<br/>one row per habit per day
end
alt Habit is struggling AND user said no
Note over Claude: gently explores barrier<br/>(one open question, no judgment)
User->>Notifier: explains barrier
Claude->>Coach: tool_use: pause_habit_checkin(habit_key, pause_days, reason)
Coach->>HabitRepo: set_pause() — encrypted reason stored
Note over HabitRepo: habit skipped in check-ins until paused_until date
end
Database Schema
erDiagram
users {
int id PK
str discord_id "nullable; unique"
str discord_username "nullable"
str telegram_id "nullable; unique"
str telegram_username "nullable"
str display_name
str timezone
bool onboarding_complete
str coaching_style "gentle|direct|cheerleader|none"
str preferred_check_in_time "HH:MM local"
int wins_count
int focus_session_minutes "default 25"
int checkin_max_items "tasks shown in morning check-in; default 5"
str preferred_platform "discord|telegram|both"
date vacation_until "nullable; suppresses proactive messages"
str quiet_hours_start "nullable; HH:MM local"
str quiet_hours_end "nullable; HH:MM local"
int task_switch_buffer_minutes "nullable; injected into day-planning prompt"
int break_interval_minutes "nullable"
int break_duration_minutes "nullable"
bool habits_setup_complete
str habits_checkin_config "JSON; nullable"
datetime last_habit_review_at "nullable"
int weekly_summary_day "0=Mon…6=Sun; default 4 (Fri)"
str weekly_summary_time "HH:MM local; default 16:00"
int weekly_review_day "0=Mon…6=Sun; default 0 (Mon)"
str weekly_review_time "HH:MM local; default 09:00"
str last_seen_changelog "ISO datetime; nullable"
str bot_persona "nullable; overrides Aria persona"
bool is_test "synthetic accounts; excluded from broadcasts and stats"
datetime created_at
}
tasks {
int id PK
int user_id FK
str title "encrypted"
str status "pending|in_progress|done|snoozed|missed|skipped|abandoned"
str priority "low|normal|high|urgent"
str source "manual|ai_suggested|calendar|recurring"
datetime due_at
datetime snoozed_until
datetime reminder_sent_at
int parent_task_id FK
int estimated_minutes
int recurring_task_id FK "nullable"
date recurrence_date "nullable; set on recurring instances"
str skip_reason "encrypted; set when status=skipped"
int urgency_base
int escalation_hours
datetime completed_at
datetime created_at
}
recurring_tasks {
int id PK
int user_id FK
str title "encrypted"
str recurrence_type "daily|weekdays|weekends|weekly|monthly_date|monthly_weekday|yearly|quarterly|semi_annual"
str recurrence_days "weekday list or day-of-month"
int recurrence_week_of_month "nullable; for monthly_weekday"
int recurrence_month "nullable; anchor month for yearly/quarterly/semi_annual"
int urgency_base
int escalation_hours
str default_reminder_time "nullable; HH:MM local"
bool active
datetime created_at
}
appointments {
int id PK
int user_id FK
str title "encrypted"
str description "encrypted; nullable"
str location "encrypted; nullable"
datetime starts_at
datetime ends_at "nullable"
int reminder_minutes_before
int travel_minutes "nullable; added to reminder window"
bool transition_reminder
bool reminder_sent
int recurring_appointment_id FK "nullable"
date recurrence_date "nullable; set on recurring instances"
str google_event_id "nullable; Phase 2"
datetime created_at
}
recurring_appointments {
int id PK
int user_id FK
str title "encrypted"
str start_time "HH:MM local"
int duration_minutes "nullable"
str location "encrypted; nullable"
int reminder_minutes_before
bool transition_reminder
int travel_minutes "nullable"
str recurrence_type "daily|weekdays|weekends|weekly|monthly_date|monthly_weekday|yearly|quarterly|semi_annual"
str recurrence_days "nullable"
int recurrence_week_of_month "nullable"
int recurrence_month "nullable; anchor month"
bool active
datetime created_at
}
occasions {
int id PK
int user_id FK
str title "encrypted"
str recurrence_type "none|yearly"
date target_date "nullable; for one-shot occasions"
int recurrence_month "nullable; 1-12 for yearly"
str recurrence_days "nullable; day of month for yearly"
int lead_days "days ahead to start countdown; default 7"
datetime handled_at "nullable; silences countdown for this occurrence"
str handled_note "encrypted; nullable"
bool active
datetime created_at
}
reminders {
int id PK
int user_id FK
str message "encrypted"
datetime fire_at "UTC datetime to send"
datetime sent_at "nullable; set when delivered"
bool active "false = cancelled"
datetime created_at
}
messages {
int id PK
int user_id FK
str role "user|assistant|tool"
str content "encrypted"
str tool_name "nullable"
str tool_call_id "nullable"
bool is_summary
datetime created_at
}
platform_link_codes {
int id PK
int user_id FK
str code "6-char; unique; 15-min TTL"
datetime expires_at
datetime created_at
}
habit_logs {
int id PK
int user_id FK
str habit_key
date date
bool response
datetime created_at
}
habit_pauses {
int id PK
int user_id FK
str habit_key
date paused_until
str reason "encrypted"
}
api_call_logs {
int id PK
int user_id FK "nullable"
str call_type "chat|checkin|habit_review|weekly_review|weekly_summary|onboarding|summarization"
str model
int input_tokens
int output_tokens
int latency_ms
int tool_calls
datetime created_at
}
audit_logs {
int id PK
int user_id FK
str action "read|create|update|delete"
str resource_type
int resource_id
str performed_by "user|ai|scheduler"
datetime created_at
}
bug_reports {
int id PK
int user_id FK
str platform "discord|telegram"
str report_type "bug|feature_request"
str description "encrypted"
str context "encrypted JSON: messages+tasks+appts snapshot"
str app_version
datetime created_at
}
users ||--o{ tasks : owns
users ||--o{ recurring_tasks : owns
users ||--o{ appointments : owns
users ||--o{ recurring_appointments : owns
users ||--o{ occasions : owns
users ||--o{ reminders : owns
users ||--o{ messages : owns
users ||--o{ platform_link_codes : owns
users ||--o{ habit_logs : owns
users ||--o{ habit_pauses : owns
users ||--o{ api_call_logs : owns
users ||--o{ audit_logs : owns
users ||--o{ bug_reports : files
tasks }o--o| recurring_tasks : "instance of"
tasks }o--o| tasks : "subtask of"
appointments }o--o| recurring_appointments : "instance of"