12 KiB
ADHDbot
ADHDbot is a FastAPI + Discord assistant that captures ADHD-friendly notes, breaks work into tiny steps, and pipes confirmed reminders into ntfy so your phone vibrates when it matters. The repo also bundles an hourly “agentic sweep” worker and a lightweight web console for experimenting with prompts and action items.
At a glance
- Opinionated system prompt + tooling contract wired through OpenRouter (Claude Haiku 4.5 by default).
- FastAPI surface area for chat runs, notes, and persistent action items—served by Docker or a bare Python venv.
- Notification bridge that turns
schedule_reminderJSON into ntfy pushes (single ntfy topic defined via env vars). - Hourly agentic workflow that summarizes memory + actions, then nudges the user via a dedicated prompt.
- Static React console (
web_App.tsx) for local demos: send prompts, review transcripts, and edit action items without curl.
Quick Start
- Copy the example environment file and fill in your secrets:
cp .env.example .env # edit .env to insert your real OPENROUTER_API_KEY, DISCORD_BOT_TOKEN, TARGET_USER_ID, etc. - Bring up the stack with docker-compose (recommended; includes host persistence for logs/notes):
docker compose up -d --build./memoryis bind-mounted into the container (./memory:/app/memory), so any saved notes appear in the repo directly..envis auto-loaded and the FastAPI service is exposed onhttp://localhost:8000.- The compose stack now launches two services:
adhdbot(the FastAPI/Discord gateway) andagentic_worker, a companion process that runs the hourly sweep loop.
- Or build/run manually if you prefer the raw Docker commands:
docker build -t adhdbot . docker run --rm -p 8000:8000 --env-file .env -v "$PWD/memory:/app/memory" adhdbot
Local development (no Docker)
- Create a virtual environment and install deps:
python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt - Copy
.env.exampleto.envand fill in the same secrets the container expects. - Launch the API with reload and rich logs:
uvicorn api:app --reload --port 8000 - (Optional) start the hourly worker in another shell to mirror the compose setup:
AGENTIC_INTERVAL_SECONDS=900 python agentic_worker.py - Run one-off prompts without FastAPI by calling the helper scripts:
# Runs the main conversational prompt (uses env defaults for category/name/context) python main.py # Forces the hourly sweep packet through the agentic prompt once python agentic_review.py
API usage
Once the container is running, hit the API to trigger a prompt flow:
curl -X POST http://localhost:8000/run \
-H "Content-Type: application/json" \
-d '{
"userId": "chelsea",
"context": "Remind me in 10 minutes to stretch.",
"history": [
{"role": "user", "content": "Hi"},
{"role": "assistant", "content": "Hello!"}
],
"modeHint": "Reminder"
}'
Endpoints:
GET /health– simple liveness check.POST /run– conversational entry point. Fields:userId(optional) – defaults toTARGET_USER_ID.context– the latest user message.history(optional) – array of{role:"user"|"assistant", content:"..."}representing prior turns (most recent last).modeHint(optional) – short string that nudges tone/behavior ("Planning", "Reminder", etc.).category/promptNameremain for backward compatibility but no longer swap entire templates.
GET /users/{userId}/notes?limit=10– fetch the most recent notes (limit defaults to 10, uselimit=0for all).POST /users/{userId}/notes– persist a note manually by posting{ "note": "text", "metadata": { ... } }.GET /users/{userId}/memory– full summaries + notes payload for the user.GET /users/{userId}/actions– list the modifiable daily/periodic action items tied to that user's memory.POST /users/{userId}/actions– create a new action item (title, optionaldetails,cadence,interval_minutes).PUT /users/{userId}/actions/{actionId}– update an item in-place (title, cadence, interval, or details).DELETE /users/{userId}/actions/{actionId}– remove it entirely.POST /users/{userId}/actions/{actionId}/progress– append a progress entry (status, optionalnote) so the hourly sweep knows the latest state.POST /users/{userId}/notes/test– quick QA helper that reuses the welcome prompt with a customcontextJSON body.GET /prompts– inspect the currently loaded prompt catalog.POST /prompts/reload– force a reload from theprompts/folder.
Environment variables of interest (see .env.example):
OPENROUTER_API_KEY– OpenRouter key used byAIInteraction.DISCORD_BOT_TOKEN/TARGET_USER_ID/DISCORD_WEBHOOK_URL– Discord plumbing.PROMPT_CATEGORY,PROMPT_NAME,PROMPT_CONTEXT– defaults for the/runendpoint.LOG_PROMPTS(default1) – when truthy, every outgoing prompt is logged to stdout so you can audit the final instructions sent to the LLM.NTFY_BASE_URL– when set (e.g.,https://ntfy.scorpi.us), reminder payloads withaction: schedule_reminderwill be POSTed to ntfy.NTFY_TOPIC_TEMPLATE– format string for the ntfy topic slug (defaultadhdbot-{userId}); all reminders are forced through this template so every notification lands in the same subscribed channel.NTFY_AUTH_TOKEN– optional bearer token if your ntfy server requires auth.AGENTIC_CATEGORY/AGENTIC_PROMPT_NAME/AGENTIC_MODE_HINT– control which prompt handles the hourly agentic sweep (defaults:agentic/hourly_review, hint "Agentic review").AGENTIC_NOTES_LIMIT– how many of the most recent notes to include in the sweep payload (default5).AGENTIC_OPERATOR_HINT– optional text passed toagentic_review.pyso you can bias the sweep for a given run (cron, manual nudge, etc.).AGENTIC_INTERVAL_SECONDS– cadence for the always-on worker loop (defaults to 3600 seconds/1 hour).
Frontend console
web_App.tsx+web_App.cssdescribe a quick React shell that talks directly to/api/runand the action endpoints. Drop the file into any Vite/CRA sandbox or use it as design reference for your own console.- The UI stores chat history in
localStorage, mirrors the three built-in prompts (“general”, “planning”, “reminders”), and exposes an action-item panel with CRUD + progress logging—so you can test the API without Postman. - When hosting the FastAPI server, make sure it serves static assets or proxy
/api/*so the console can fetch without CORS gymnastics.
Reminder payloads
When the assistant schedules a reminder it emits a single JSON block:
{
"action": "schedule_reminder",
"reminder": {
"message": "short friendly text",
"trigger": {
"value": "2025-11-11T02:41:42+00:00"
}
}
}
The backend automatically converts relative phrases ("in 10 minutes") into the ISO timestamp above and POSTs the message to the ntfy topic (default https://ntfy.scorpi.us/adhdbot-<user>), so subscribing to that topic on your phone is all you need for push notifications.
Daily / Periodic Action List + Hourly Agentic Sweep
- Action items share the same storage as notes inside
memory/<user>_memory.jsonunder theaction_itemskey. Each entry trackstitle,cadence, optionalinterval_minutes,details, and a rollingprogresshistory. - Use the action API endpoints (above) to add/remove/edit entries or append
statusupdates—CLI, scripts, or the UI can call them exactly like the note endpoints. The bundledweb_App.tsx(served by the static UI) now surfaces a lightweight management panel to create actions, log progress, and delete entries without touching curl. AgenticWorkflow.buildReviewPacketcompiles the latest notes plus the action list into a JSON blob and feeds it into theagentic/hourly_reviewprompt. The new helper scriptagentic_review.pycalls this flow; point a cron/systemd timer at it (hourly) so the autopilot can look for overdue habits or opportunities.agentic_worker.pywraps the same helper in a persistent loop. Theagentic_workerservice defined indocker-compose.ymlruns it with the same.envfile, so deploying the stack automatically keeps the hourly sweep online. Adjust cadence viaAGENTIC_INTERVAL_SECONDSor stop the service if you prefer to trigger sweeps manually.- Customize the autopilot without code by editing
prompts/defaultPrompts.json(or adding a sibling file) to adjustagentic/hourly_review, then reload prompts or rebuild the container.
Architecture cheat sheet
api.py(FastAPI): exposes chat, memory, action-item, and prompt-catalog routes. It uses Pydantic models for validation and wraps every handler withensureUserId/MemoryManagerhelpers so non-FastAPI callers stay lean.Runner.py+AIInteraction.py: glue between your request and OpenRouter.Runneris a thin façade;AIInteractioncomposes the system prompt, trims chat history, logs prompts whenLOG_PROMPTS=1, and post-processes responses for memory + notifications.Memory.py: owns all persistence undermemory/<user>_memory.json(notes, summaries, andaction_items). JSON blocks emitted by the model (take_note,schedule_reminder, etc.) land here before any downstream automations run.Notification.py: watches the same responses forschedule_reminderpayloads and relays them to ntfy with sanitized titles + timestamps. LeaveNTFY_BASE_URLunset to disable the bridge without touching code.AgenticWorkflow.py+agentic_worker.py: build the hourly sweep packet (latest notes, summaries, action progress) and push it throughagentic/hourly_review. Runagentic_workervia Docker Compose or your own cron/systemd timer for 24/7 coverage.DiscordGateway.py: optional DM/webhook plumbing so every assistant reply can bounce straight into Discord whenDISCORD_BOT_TOKENorDISCORD_WEBHOOK_URLis configured.- Prompts folder:
prompts/defaultPrompts.jsonships with sane defaults; drop additional JSON files in the same folder and callPOST /prompts/reloadto hot-swap templates. Tooling/JSON contract lives inprompts/tool_instructions.md.
Prompt + tooling customization
- All templates live in
prompts/defaultPrompts.json(and sibling files). Edit them and restart the service to take effect. - Shared tooling instructions live in
prompts/tool_instructions.md.AIInteractioninjects this file both into the system prompt and at the end of every user prompt, so any changes immediately affect how models emittake_note,store_task, orschedule_reminderJSON payloads. PROMPTS.mddocuments each category plus examples of the structured JSON outputs that downstream services can parse.
Memory + notes
- The memory subsystem watches LLM responses for fenced ```json payloads. When it sees
{"action": "take_note", ...}it writes tomemory/<user>_memory.json(now persisted on the host via the compose volume). - Each entry includes the note text, UTC timestamp, and the raw metadata payload, so other services can build summaries or downstream automations from the same file.
Debugging tips
- Tail the container logs with
docker compose logs -f adhdbotto see:- The final prompt (with tooling contract) sent to the model.
- Memory ingestion messages like
[memory] Recorded note for <user>: ....
- If you swap models, change
openRouterModelinAIInteraction.py(or surface it via env) and rebuild the container.