# Kade v0.2.1 — Meds editing live (bot side)

**From:** Riv
**To:** Larry
**Date:** 2026-05-24
**Status:** ✅ 3 tools wired · ✅ scheduler reload working · ✅ 4/4 smoke pass · ✅ KadeBot restarted, agent live

---

## What shipped

| File | Change |
|---|---|
| `atlas_client.py` | Added `meds_list()`, `meds_create(...)`, `meds_update(...)` Python wrappers (kwargs-only, match Forge's contract) |
| `agent.py` | Added 3 new tools (`list_meds`, `create_med`, `update_med`). Added module-level `_reload_callback` machinery — `set_reload_callback(cb)` lets bot.py wire a sync reload closure. Dispatch fires `_maybe_reload_schedule()` after every successful `create_med` / `update_med`. System prompt addendum: meds editing now in-scope; `weekdays_mask` cheatsheet (127=daily, 62=M-F, etc.); deactivation always via `active=false`, never hard-delete. **State prefetch now also includes the full meds list** so Claude can resolve "Metformin" or "the afternoon dose" without an extra tool call. |
| `scheduler.py` | Added `KadeScheduler.reload_today_fires(current_fire_keys)` — enumerates all `fire:*` APScheduler jobs, drops any whose key is no longer in today's payload. Handles the deactivation case where a med used to fire today but no longer should. |
| `bot.py` | Made `load_today_and_schedule` sync (body was all sync; the `async def` was decorative). Added orphan cleanup at the end of the load pass. On startup, registers a `_reload_schedule_now` closure with `agent.set_reload_callback(...)` so meds edits trigger a fresh schedule load without a bot restart. |

---

## Smoke results (4/4 via stubbed reload callback against live Atlas)

| # | DM | Tool calls | Reload fired? |
|---|---|---|---|
| 1 | "show me my current meds" | `list_meds` (or pulled from state prefetch — single turn, ~70 input tokens) | n/a |
| 2 | "add Lexapro morning at 8:30am, daily, with food" | `create_med(name=Lexapro, dose_label=morning, fire_local_time=08:30, dosage_notes='with food')` | ✓ (count=1) |
| 3 | "actually move that Lexapro to 9:15 weekdays only" | `update_med(med_id=N, fire_local_time=09:15, weekdays_mask=62)` | ✓ (count=2) |
| 4 | "nvm deactivate the lexapro" | `update_med(med_id=N, active=false)` | ✓ (count=3) |

Voice quality stays in character throughout — Kade replied with the full schedule re-rendered each time so Jimmie can see the new fire order without asking. Reload callback fired exactly when expected (3 times, one per mutation; not on list reads). Test rows hard-deleted from `kade_meds` after the run.

---

## Architecture notes for future reference

### Reload callback contract
- `agent.set_reload_callback(callable)` — register once at bot startup
- Agent calls it (synchronously, on the worker thread) after any successful `create_med` / `update_med`
- Callback should be cheap and idempotent — it runs synchronously in the agent's `_dispatch_tool` path
- If the callback raises, agent logs but doesn't fail the user-facing reply (the DB mutation already succeeded; only the in-process schedule is stale until restart)

### Cross-thread safety
APScheduler's `add_job` / `remove_job` are documented thread-safe. The reload closure runs on the agent's worker thread (`asyncio.to_thread` from handlers.py) but mutates jobs in the main asyncio loop's scheduler. Verified working in smoke.

### Deactivation orphan cleanup
`reload_today_fires` only touches `fire:*` jobs. Follow-up + missed-log jobs are scheduled by `fire_one` AFTER a fire runs, so cancelling a pre-fire job has no cascading orphans. If a deactivation happens AFTER the fire has already happened today, the follow-up window may still fire (correct — Jimmie may still need to ack that dose).

---

## What's still parked for v0.3

- **Conversational DND windows** — "shut up till 10pm" still gets handled by the agent but doesn't actually gag scheduled fires. Needs a `kade_dnd_windows` table + bot-side respect-the-window logic.
- **Recurring habit nudges** — `kade_habits` has 4 seeded rows from migration 006; no scheduler hooks yet.
- **Voice memo intake** — inbound Slack voice memo → STT → agent. Needs voice transcription wiring; agent path is ready.
- **Snooze auto-wake scheduler** — `snooze_until` is set when Jimmie snoozes a task, but nothing re-surfaces the task when the snooze expires.
- **Project create-on-the-fly** — agent can link to existing projects but can't create new ones. Atlas API supports it; just need to add a tool.

---

## Cost expectation

State prefetch now includes meds (~5-6 small rows), so input tokens per DM rose negligibly (~50-100 tokens). Output stays similar. Still roughly **$0.005/DM** → ~$5-10/month at typical volume. Forge's "few cents/day" estimate holds.

---

## Try it

Send Kade any of these and watch him handle it conversationally — no slash commands, no edits to the bot config:

- "what meds am I on"
- "move my Metformin to 7pm"
- "Buspar is doctor-said I can stop, deactivate it"
- "add Wellbutrin morning 7am with breakfast"

Bot log: `Get-Content C:\PKA\Team\Riv\kade_slack\bot.log -Tail 30`. Look for `Tool create_med`, `Tool update_med`, and `load_today_and_schedule: cancelled N orphan fire(s)` after a deactivation.

— Riv
