169 lines
12 KiB
Markdown
169 lines
12 KiB
Markdown
# 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_reminder` JSON 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
|
||
|
||
1. Copy the example environment file and fill in your secrets:
|
||
```bash
|
||
cp .env.example .env
|
||
# edit .env to insert your real OPENROUTER_API_KEY, DISCORD_BOT_TOKEN, TARGET_USER_ID, etc.
|
||
```
|
||
2. Bring up the stack with docker-compose (recommended; includes host persistence for logs/notes):
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
- `./memory` is bind-mounted into the container (`./memory:/app/memory`), so any saved notes appear in the repo directly.
|
||
- `.env` is auto-loaded and the FastAPI service is exposed on `http://localhost:8000`.
|
||
- The compose stack now launches two services: `adhdbot` (the FastAPI/Discord gateway) and `agentic_worker`, a companion process that runs the hourly sweep loop.
|
||
3. Or build/run manually if you prefer the raw Docker commands:
|
||
```bash
|
||
docker build -t adhdbot .
|
||
docker run --rm -p 8000:8000 --env-file .env -v "$PWD/memory:/app/memory" adhdbot
|
||
```
|
||
|
||
## Local development (no Docker)
|
||
|
||
1. Create a virtual environment and install deps:
|
||
```bash
|
||
python3 -m venv .venv
|
||
source .venv/bin/activate
|
||
pip install -r requirements.txt
|
||
```
|
||
2. Copy `.env.example` to `.env` and fill in the same secrets the container expects.
|
||
3. Launch the API with reload and rich logs:
|
||
```bash
|
||
uvicorn api:app --reload --port 8000
|
||
```
|
||
4. (Optional) start the hourly worker in another shell to mirror the compose setup:
|
||
```bash
|
||
AGENTIC_INTERVAL_SECONDS=900 python agentic_worker.py
|
||
```
|
||
5. Run one-off prompts without FastAPI by calling the helper scripts:
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
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 to `TARGET_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` / `promptName` remain for backward compatibility but no longer swap entire templates.
|
||
- `GET /users/{userId}/notes?limit=10` – fetch the most recent notes (limit defaults to 10, use `limit=0` for 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`, optional `details`, `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`, optional `note`) so the hourly sweep knows the latest state.
|
||
- `POST /users/{userId}/notes/test` – quick QA helper that reuses the welcome prompt with a custom `context` JSON body.
|
||
- `GET /prompts` – inspect the currently loaded prompt catalog.
|
||
- `POST /prompts/reload` – force a reload from the `prompts/` folder.
|
||
|
||
Environment variables of interest (see `.env.example`):
|
||
- `OPENROUTER_API_KEY` – OpenRouter key used by `AIInteraction`.
|
||
- `DISCORD_BOT_TOKEN` / `TARGET_USER_ID` / `DISCORD_WEBHOOK_URL` – Discord plumbing.
|
||
- `PROMPT_CATEGORY`, `PROMPT_NAME`, `PROMPT_CONTEXT` – defaults for the `/run` endpoint.
|
||
- `LOG_PROMPTS` (default `1`) – 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 with `action: schedule_reminder` will be POSTed to ntfy.
|
||
- `NTFY_TOPIC` – optional fixed topic slug; when present every reminder is sent to exactly this channel (e.g., `adhdbot-chelsea`).
|
||
- `NTFY_TOPIC_TEMPLATE` – fallback format string for the topic when `NTFY_TOPIC` is unset (default `adhdbot-{userId}`).
|
||
- `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 (default `5`).
|
||
- `AGENTIC_OPERATOR_HINT` – optional text passed to `agentic_review.py` so 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.css` describe a quick React shell that talks directly to `/api/run` and 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:
|
||
|
||
```json
|
||
{
|
||
"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 (either the fixed `NTFY_TOPIC` or the templated `https://ntfy.scorpi.us/adhdbot-<user>` fallback), 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.json` under the `action_items` key. Each entry tracks `title`, `cadence`, optional `interval_minutes`, `details`, and a rolling `progress` history.
|
||
- Use the action API endpoints (above) to add/remove/edit entries or append `status` updates—CLI, scripts, or the UI can call them exactly like the note endpoints. The bundled `web_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.buildReviewPacket` compiles the latest notes plus the action list into a JSON blob and feeds it into the `agentic/hourly_review` prompt. The new helper script `agentic_review.py` calls this flow; point a cron/systemd timer at it (hourly) so the autopilot can look for overdue habits or opportunities.
|
||
- `agentic_worker.py` wraps the same helper in a persistent loop. The `agentic_worker` service defined in `docker-compose.yml` runs it with the same `.env` file, so deploying the stack automatically keeps the hourly sweep online. Adjust cadence via `AGENTIC_INTERVAL_SECONDS` or 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 adjust `agentic/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 with `ensureUserId`/`MemoryManager` helpers so non-FastAPI callers stay lean.
|
||
- **`Runner.py` + `AIInteraction.py`**: glue between your request and OpenRouter. `Runner` is a thin façade; `AIInteraction` composes the system prompt, trims chat history, logs prompts when `LOG_PROMPTS=1`, and post-processes responses for memory + notifications.
|
||
- **`Memory.py`**: owns all persistence under `memory/<user>_memory.json` (notes, summaries, and `action_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 for `schedule_reminder` payloads and relays them to ntfy with sanitized titles + timestamps. Leave `NTFY_BASE_URL` unset 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 through `agentic/hourly_review`. Run `agentic_worker` via 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 when `DISCORD_BOT_TOKEN` or `DISCORD_WEBHOOK_URL` is configured.
|
||
- **Prompts folder**: `prompts/defaultPrompts.json` ships with sane defaults; drop additional JSON files in the same folder and call `POST /prompts/reload` to hot-swap templates. Tooling/JSON contract lives in `prompts/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`. `AIInteraction` injects this file both into the **system prompt** and at the end of every user prompt, so any changes immediately affect how models emit `take_note`, `store_task`, or `schedule_reminder` JSON payloads.
|
||
- `PROMPTS.md` documents 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 to `memory/<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 adhdbot` to 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 `openRouterModel` in `AIInteraction.py` (or surface it via env) and rebuild the container.
|