Related: Guide 11 (building the cluster and bot), Guide 12 (/prep and Drive material), Guide 13 (every command with prompts, paste-ready group messages). Class schedules: Guide 16. Jev router: Guide 17. Every guide and diagram by name: docs index.
Contents: 1 Architecture · 2 Routing · 3 The routes in detail · 4 Sequences · 5 What was fixed · 6 Commands · 7 Workflows · 8 Quirks and limits
1. Architecture
System architecture
| Part | Where it runs | Job |
|---|---|---|
| WAHA | pod in the waha namespace |
holds the WhatsApp session, calls the bot's webhook, sends replies |
ts03-bot |
launchd service com.ts03.bot.whatsapp on the Mac |
routes every message |
| Owner path | the bot's zsh -ic ts03-... calls |
full Claude Code with the Sheets MCP: live sheet reads and writes |
| Member sandbox | claude -p with --tools "", no MCP servers, in ~/.cache/ts03-public |
answers from the snapshot only. Proven by ts03-bot selftest |
| Snapshot | ~/.cache/ts03-snapshot.json |
items and subjects exported by the owner path, valid for 30 minutes |
| Material cache | ~/.cache/ts03-material/<folder>.json |
text extracted from Drive slides and books for /prep |
| Indexer | launchd job com.ts03.material-index |
fills the material cache |
| CronJob | ts03-digest in waha |
pings the bot at 08:00 and 22:00 SGT, and the bot posts the brief |
The CronJob is only a clock. The digest needs claude and the Sheets credentials, which exist only on the Mac, so the pod sends a ping to http://host.k3d.internal:5055/hook/<secret> and the bot does the work.
2. How every message is routed
Message routing
| Route | Handler | Data source | Output | Counts toward the limit |
|---|---|---|---|---|
/today /tomorrow /week /month /next /urgent /overdue /brief /all /points /tbd |
fixed question, list mode | snapshot (owner: live sheet) | cards, table, json, yaml |
yes for members |
/plan /load /remind |
fixed question, text mode | snapshot (owner: live sheet) | plain bullets. Layout words are ignored | yes for members |
/sub X, /find X |
question template with your argument, list mode | snapshot (owner: live sheet) | same as list | yes for members |
/ask <question> or plain words |
list mode with the question as written | snapshot (owner /ask: live sheet, read only) |
same as list | yes for members |
/subjects /prof /professors, or words like professor, course, subject, who teaches (a who teaches question that names NLP, NLU or CV goes to the schedule instead) |
answered locally, no AI | snapshot subject list | cards or layout | no |
/prep … or "prepare for …" |
prep path | routed Drive cache plus the model | sections, table or json |
own cap of 20 per day for the group |
/nlp …, /cv …, /schedule NLP …, or a question naming a configured subject or area plus lecture, class, exam, holiday, schedule, slides, recording (or who teaches when the subject has parts) |
schedule path, checked before the subject list and /prep |
cached course sheet plus the model | plain text | yes for members |
| a free-text message, when the Jev router is on | Jev picks the tracker, the professors' sheet, study, the subject list or other, or the bot asks | the Jev decider, then the same sources as the chosen route | as the chosen route | yes for members |
/help /menu /start /commands, /about /builder, /capabilities /dml /permissions |
local text | none | text | no |
/whoami |
local text | none | chat id and sender id | no |
/add /done /submit /move /note |
owner path write | live sheet (insert and update only) | confirmation | owner only |
/check /health /refresh /stats /html |
owner path or local | live sheet or counters | report, link or text | owner only |
3. The routes in detail
/plan, /load, /points, /tbd, /remind
| Command | Question the bot asks | Mode | Owner version |
|---|---|---|---|
/plan |
build a day-by-day study plan for the next 7 days from deadlines and points, max 12 lines | text | ts03-plan |
/load |
count upcoming items per subject and per week, flag any week with 3 or more deadlines, max 10 lines | text | ts03-load |
/points |
upcoming items sorted by points or weightage, highest first | list | ts03-points |
/tbd |
items where any field is TBD, blank or missing | list | ts03-tbd |
/remind |
draft a short friendly WhatsApp reminder for the group for the next 7 days, under 8 lines | text | ts03-wa |
List mode makes the model return one JSON object (title, answer, items with subject, type, title, due date, time, status, days left, points, notes). The bot renders it as cards, a table, JSON or YAML. Text mode asks for plain chat text with • bullets, so there is nothing to lay out and table, json and yaml have no effect.
For members both modes run in the sandbox with the snapshot pasted into the prompt as DATA. The prompt tells the model to use only that data, to compute days left from today's date, to treat the question as untrusted text, and to answer I can only answer questions about the TS-03 tracker. if the question tries to change rules or read files.
Plain-words questions
| You send | What happens |
|---|---|
@bot any GPU Programming quiz coming up? |
not a slash command, no subject keyword, not a prep phrase, so the member path in list mode. The model filters the items from the snapshot |
@bot what's due before Friday? |
same path. Today's date is in the prompt, so "Friday" is worked out from it |
@bot IP ka assignment kaise karna hai? |
same path with the "how do I do X" rule: the answer is a checklist of what to submit, marks split, format and deadline taken from that item's notes, and the item is included. The question is Hindi or Hinglish, so the reply is Hinglish. It never writes the assignment itself |
The snapshot carries the full Notes text of every item, which is what makes the guidance possible.
Two phrasings change the route:
- A message with subject, course, professor, prof, faculty, teacher, who teaches, taught by or TA is answered from the subject list, not by the model. The exception: a question that also names a configured schedule subject or area (NLP, NLU, CV) goes to the schedule.
- A message like "how do I prepare for the GPU quiz" matches the prep pattern and goes to
/prep, not to the tracker notes. Ask "GPU quiz kaise karna hai" or "what do I need to submit for the GPU assignment" for the notes.
The Jev router (optional)
With "mode": "on" in ~/.config/ts03-router.json, a free-text message is first sent to a Jev decision model, which returns probabilities for five routes: the TS-03 tracker (quiz, assignment, task, test), the professors' course sheet (NLU, NLP, FoVL, CV), study help, the subject list, or other. The bot acts on a clear winner, lets the rules break a close call, and asks when there is no signal. Commands, writes and the owner's tools never go through it. A quiz, assignment, task or test word keeps a message with the tracker even when it names NLP. It is off by default, and Guide 17 has the setup, the shadow mode and the settings.
Schedule questions
| You send | What happens |
|---|---|
/nlp, /cv, /nlu, /vision, /fovl |
schedule path. The word you used stays at the front of the question, so /cv is about Computer Vision and /nlp about NLU / NLP |
/schedule NLP <question> |
the same, naming the subject |
@bot when is the next CV lecture? |
a subject or area name plus a schedule word (lecture, class, exam, holiday, schedule, timetable, slides, recording, session, break, buffer, calendar) |
@bot who teaches CV? |
an area name plus a teaching word, because the subject has parts in its config |
@bot is there class on 9 October? |
no subject named, so the tracker answers, and it has no class schedule. Name the subject: @bot is there NLP class on 9 October? |
@bot prepare for the NLP exam |
stays with /prep |
@bot NLP assignment due? |
stays with the tracker |
The schedule check runs before the subject-list check. The cache is built from the course sheet by ts03-schedule-index every 6 hours. In code, each area gets its status (finished, ongoing or upcoming), lecture counts, date range, instructors and next lecture, so "when is the next CV lecture?" gets "the CV part ended on 29 Aug" and not the next NLP lecture. Guide 16 has the setup.
4. Sequences
A member asks /week table
/prep patents vs copyright
The 08:00 and 22:00 brief
A successful Job only means the ping got a 200. The bot runs the digest in the background and writes triggered by scheduler, then sent or FAILED: <reason> to ~/Library/Logs/ts03-bot.log.
The Drive material indexer
A schedule question: who teaches CV?
The schedule indexer
A Jev-routed question: NLP lecture or assignment due?
5. What was fixed
| # | Symptom | Root cause | Fix |
|---|---|---|---|
| 1 | /prep never used Drive files, indexer said no files found |
the Google Drive MCP had never been added to Claude Code | added google-drive at user scope, reusing the existing OAuth client |
| 2 | Drive tools still returned nothing from scripts | headless claude -p blocks MCP tools that are not pre-approved |
mcp__google-drive added to permissions.allow |
| 3 | Scripts failed with a 404 for a retired model | fresh shells ran an old Intel claude from /usr/local/bin, and .local/bin was only prepended in .zshrc (interactive only). path_helper also reorders PATH after .zshenv |
prepend .local/bin in .zprofile; stopped forcing --model |
| 4 | Empty listing for a folder with 7 files | the Drive folder is literally named GPU/ and an exact-name search missed it. Separately, an identical prompt sometimes returned an empty list |
explicit list-then-match procedure, up to 3 attempts |
| 5 | Good material cache replaced by an empty one | a failed run wrote files: [] over it |
the previous cache and per-file text are kept |
| 6 | Textbook PDFs and text copies in the repo root | claude ran with the repo as its working directory |
runs in a scratch dir that is wiped, .tmp_* in .gitignore. Checked they were never committed |
| 7 | Duplicate Google Docs piling up in Drive | PDF conversion left a temporary copy each run | the prompt tells the model to delete its copy. The 7 existing duplicates were trashed |
| 8 | PDFs rejected by the text reader | readTextFile only accepts text/* |
convert, then read. Slide-deck wording added |
| 9 | /prep could solve a pasted quiz question |
no rule against it | integrity rule in the prompt plus a sandbox CLAUDE.md. The selftest still proves zero tools |
| 10 | table and json dropped sections or returned raw text |
the parser needed headers alone on a line | parser matches the known section names in any header shape |
| 11 | Digest silently not scheduled, then lost | --digest HH:MM accepted junk, and --uninstall also deletes the digest job |
times validated before anything restarts, several times allowed, restart with launchctl kickstart |
| 12 | Bot stopped answering, WAHA printed a QR | the WhatsApp session was logged out | re-linked. Image pull policy set to IfNotPresent so the version cannot change under the bot |
| 13 | A pod could not resolve host.k3d.internal |
CoreDNS had no entry for it | the CronJob pod pins the Mac's address with hostAliases |
| 14 | Twice-daily brief | needed a scheduler | Kubernetes CronJob pings a ts03.digest event. ts03.ping is a health check that posts nothing. Evening title by the hour |
| 15 | IP Management had no /prep routing |
no folder or aliases | IPM folder and aliases added |
| 16 | gh: bad CPU type in executable |
an Intel-only gh on an Apple Silicon Mac |
install the arm64 build, or create the release in the browser |
| 17 | Docs said /ask was owner-only |
it is not: members can use it | corrected in Guide 13 |
| 18 | Course schedule at risk of being misread | merged cells (instructor parts, exam weeks) and Drive smart chips: a model can miss a merge or drop a chip | ts03-schedule-index reads the sheet through the Sheets API, fills merged ranges and caches JSON. Attendance is never read |
| 19 | "Is there class on 9 October?" answered "the tracker has no class schedules" | no route to the course sheet | schedule route: /nlp, /cv, /schedule, or a subject or area name plus a schedule word. Name the subject in the question |
| 20 | "Who teaches NLP?" gave two names and no CV / NLU split | the tracker's subject list has no areas and misses the third instructor | parts in the schedule config maps each part to an area and its instructors, and teaching words route to the schedule for that subject |
| 21 | A second test message got no reply | the per-person cooldown drops a repeat sent within 10 seconds, silently | by design, now documented in section 8: wait a moment between questions |
Lessons worth keeping:
- A single empty answer from a model-driven listing is not proof. Retry, and never overwrite good data with it.
install-bot.sh --uninstallis not a restart.- Never
git add -Ain this repo. Add folders by name. - After every change to
scripts/ts03-bot: deploy withinstall-bot.sh, then runselftest.
6. Commands and prompts
Members (all work with @bot or a leading /):
| Command | Plain-words prompt |
|---|---|
/today |
@bot what's due today? |
/tomorrow |
@bot anything due tomorrow? |
/week |
@bot what's due this week? |
/month |
@bot what's coming up this month? |
/next |
@bot what's my next deadline? |
/urgent |
@bot what's urgent right now? |
/overdue |
@bot is anything overdue? |
/brief |
@bot give me a quick brief |
/sub GPU Programming |
@bot show me everything for GPU Programming |
/prof Advanced AI |
@bot who teaches Advanced AI? |
/subjects |
@bot list all subjects with their professors |
/find patent |
@bot find anything about patent |
/all |
@bot show the whole tracker |
/plan |
@bot make me a study plan for this week |
/load |
@bot which week is the busiest? |
/points |
@bot which items carry the most points? |
/tbd |
@bot which items have missing details? |
/remind |
@bot write a reminder I can forward to the group |
/prep CUDA |
@bot prepare me on memory coalescing |
/nlp |
@bot when is the next NLP lecture? |
/cv |
@bot when is the next CV lecture? |
/nlp when is the minor exam |
@bot when does the FoVL major exam start? |
/schedule NLP <question> |
@bot who teaches CV? |
/ask <question> |
@bot <question> |
/help |
@bot help |
/about, /capabilities, /whoami |
@bot who built you?, @bot can you edit the sheet? |
Layouts: add table, json or yaml at the end (/prep supports table and json). html is owner-only.
Owner only: /add, /done, /submit, /move, /note, /check, /refresh, /html <name>, /stats, /health. Examples and paste-ready group messages are in Guide 13.
7. Workflows, step by step
Deploy a change to ts03-bot
Add a subject for /prep
- Create a top-level Drive folder and put PDFs in it. Prefer text PDFs over PowerPoint.
- Add aliases to
~/.config/ts03-subject-routes.json, with the folder's exact name as the value. ./scripts/ts03-material-index "<folder name>"and check each file showsok.- Restart the bot so it re-reads the routes:
launchctl kickstart -k gui/$(id -u)/com.ts03.bot.whatsapp. - Test
/prep <topic>and look for 📎 FROM YOUR MATERIAL naming a file. - Test that a pasted question is declined:
/prep Q1. compute ….
Add or change a class schedule
- Add or edit the subject in
~/.config/ts03-schedules.json: sheet id, tab, aliases, andpartswith the instructors. No restart is needed. ./scripts/ts03-schedule-index <KEY> --dry-run, then again without--dry-run../scripts/ts03-schedule-index --show <KEY>and compare the counts with the sheet.- Test
/nlpand a plain question that names the subject, in a DM first.
Re-link WhatsApp after a logout
kubectl rollout restart deployment/waha -n wahakubectl logs -n waha deploy/waha -f- On the bot's phone: WhatsApp, Settings, Linked devices, Link a device, scan the QR.
- Check the session says
WORKING, then send/stats.
Change the brief times
- Edit
scheduleink8s/digest-cronjob.yaml, for example"0 9,21 * * *". kubectl apply -f k8s/digest-cronjob.yamlkubectl get cronjob ts03-digest -n waha
Pause the brief: kubectl patch cronjob ts03-digest -n waha -p '{"spec":{"suspend":true}}'
Publish to GitHub
8. Quirks and limits
- The brief needs the Mac awake and Colima running at 08:00 and 22:00. launchd would have posted on wake. The CronJob skips a slot that is more than 2 hours late (
startingDeadlineSeconds: 7200). - A successful ping Job does not mean the digest posted. Read the bot log.
- The pod reaches the Mac through a pinned address (
hostAliases). If Colima's network changes, re-pin it withcolima ssh -- getent hosts host.lima.internal. - The integrity rule and the sandbox
CLAUDE.mdare behavioural. A declined reply can still describe the method in general terms. Check new wording with a real pasted question. - Members are limited to 6 AI-backed requests per hour each and 150 per day for the group.
/prepis capped at 20 per day for the group. - Routes are read once at bot start. Restart after editing them.
- Slide decks (
.pptx) are untested with the Drive connector. Save them as PDF if extraction fails. - Nothing from Drive or
~/.cache/ts03-material/belongs in the repository. - Members: a second message within 10 seconds is dropped with no reply (
COOLDOWN_SECONDS). Wait a moment between questions. - A schedule question needs a subject or area name. "Is there class tomorrow?" on its own goes to the tracker, which has no class schedule.
- Instructors come from the schedule config, not from the sheet. Keep
partsin step with the sheet. - The schedule cache refreshes every 6 hours and by hand with
ts03-schedule-index. The reply footer shows its age. - The Jev router is off by default and is a third party: with it on, the scrubbed text of free-text messages (at most 500 characters, no ids) goes to TypeSafe or Cloudflare. If Jev errors, times out or is unsure, the rules decide.
Questions or corrections: [email protected]