# Kade v0.2 — Bot handlers built, gated, smoke-tested

**From:** Riv
**To:** Larry
**Date:** 2026-05-24
**Status:** ✅ Code shipped to disk · ✅ 6/6 canonical DMs pass against live Atlas + Claude · ⏸ **Gated by env flag — needs ONE owner decision to roll live**

---

## What shipped

| File | Change |
|---|---|
| `atlas_client.py` | Added 7 task-endpoint wrappers + a structured `AtlasClientError` exception (carries the 4xx body so the agent can react to bucket-cap 409s, vague→today 400s, etc.) |
| `agent.py` | **New.** Claude Sonnet 4.6 tool-use loop. State prefetch (today rollup + vague queue + top-20 open list) → Claude with 7 tool definitions → execute → iterate (max 5 turns) → final Kade-voice text. Profile.md cached as system prompt. |
| `handlers.py` | The v0.1 `'other'` branch (which just logged and stayed silent) now routes to `agent.run()` via `asyncio.to_thread` and posts the response back as a Slack DM. **Gated by `KADE_AGENT_ENABLED` env flag — defaults off.** Ack/skip path for meds is unchanged. |

No PM2 ecosystem changes needed — same `KadeBot` Task Scheduler entry, same single Python process. Bot just needs a restart to pick up the new modules.

---

## Smoke results (6/6 pass against live Atlas + real Claude API)

| # | DM | Bot behavior |
|---|---|---|
| 1 | "remind me to test the kade v0.2 bot wiring" | First run created task #11 in `list` (correctly defaulted, no auto-promote). Second run hit `deduped: true` and Claude offered to promote rather than make a duplicate. |
| 2 | "what's on today" | Read the prefetched state, zero tool calls, replied in Kade voice with Top 3 / Today / Overdue + offered to load the day. |
| 3 | "put the kade bot test on today's list" | `update_task(bucket='today')` — explicit promote only. |
| 4 | "mark the kade bot wiring test done" | Fuzzy-matched task #11 → `bulk_complete([11])`. |
| 5 | "snooze the prospect dashboard one for an hour" | Reference not found in snapshot — Claude correctly refused to guess, gave two recovery options. |
| 6 | "look at that thing" | Asked the clarifying question in voice ("That's a vague one even by your standards 😑") instead of silently creating garbage. |

Voice quality is solid — emojis, sass, action-forward, no corporate drift. Stoic quotes didn't trigger in these contexts (correctly — none of these DMs warranted one).

**Performance per DM (Sonnet 4.6 with profile.md cached):**
- 1-2 Claude turns, 3-7 seconds end-to-end
- ~70-320 input tokens / 25-100 output tokens per turn
- Roughly **$0.005 per DM** at list prices, less with the ephemeral cache hitting

---

## Architecture notes

### State prefetch keeps cost down
Every DM pulls today rollup + vague queue + top 20 open list tasks BEFORE Claude sees it. Most simple DMs ("mark X done," "what's on today," snooze, complete) finish with zero additional tool calls because Claude already has the state. Tool calls happen for things like project-scoped queries, snoozed/done lookups, or fuzzy refs that miss the prefetched 20.

### Error handling that Claude can recover from
`AtlasClientError` carries the parsed `{ok:false, error, field}` body. So when Claude hits a 409 (bucket full) or 400 (promote-vague), it gets a structured tool result and reacts in voice ("Top 3 is full — what do you want to demote?") rather than crashing.

### Iteration cap
`MAX_AGENT_TURNS = 5`. If Claude can't resolve in 5 round-trips it gives a graceful "try saying it a different way" reply. In practice nothing's hit the cap yet.

### Voice & rules in the system prompt
Profile.md (cached ephemerally, 5-min TTL — same pattern as `voice.py`) plus a short v0.2 addendum that pins the rules:
- Default new tasks to `list` (never auto-promote)
- Ambiguous input → `clarity='vague'` + one clarifying question
- Snooze = `update_task(status='snoozed', snooze_until=...)`
- "X, Y, Z done" → `bulk_complete`
- Bucket cap → ask Jimmie to demote (no auto-demote)
- Promote-vague rejected → ask Jimmie to clarify first

---

## ONE owner decision needed

**Flip `KADE_AGENT_ENABLED=true` and restart KadeBot — yes or no?**

Cost expectation at Jimmie's volume:
- Heavy day, 50 DMs: ~$0.25
- Steady day, 20 DMs: ~$0.10
- **~$5/month at typical use, ~$10/month at heavy use**

Forge's "few cents/day" estimate matches what the smoke run showed.

Other considerations:
- ✅ All v0.1 meds behavior is unchanged (separate path, gate doesn't affect it)
- ✅ If the agent crashes or Atlas is down, the bot fails soft and posts a Kade-voiced apology (no hard crash, meds keep firing)
- ✅ Gate flip is reversible — set `KADE_AGENT_ENABLED=false`, restart, back to v0.1 behavior in one minute
- ⚠ Once on, every "other" DM Jimmie sends Kade burns ~$0.005 of API. Worth knowing if he's a midnight chatter.

**To flip it on:**
```powershell
# Add to C:\PKA\Team\Riv\kade_slack\.env
KADE_AGENT_ENABLED=true

# Restart KadeBot (no admin needed)
Stop-ScheduledTask -TaskName "KadeBot"
Start-ScheduledTask -TaskName "KadeBot"
```

---

## What's still parked for v0.3+

- **Snooze auto-wake** — `snooze_until` is set when Jimmie snoozes, but nothing currently re-surfaces the task when the snooze expires. v0.3 scheduler job will do this.
- **Project create on the fly** — if Jimmie says "remind me about the new Acadian thing" and "Acadian" isn't already a project, Kade can link to existing projects but doesn't create new ones. The contract supports `project_id` but creating projects isn't in the v0.2 tool surface. v0.3 candidate.
- **DND windows** — "shut up till 10pm" still routes through the agent but doesn't actually gag scheduled reminders. v0.3 DND concern.
- **Voice memo intake** — Slack voice memo → transcript → agent path needs voice transcription wiring. Existing `voice.py` is for OUTBOUND voice composition, not inbound STT. Separate v0.3 build.

— Riv
