Add rpa-bot-handoff skill: ops guide for bot handover

The skill scans a bot with subagents (never runs it) and writes
HANDOVER.md, an 11-section ops guide in the operator's order: where is
what, how to run, how long to wait, after the run, how to see an
exception, exception list, how you will know, how to tell us, don'ts,
issues seen before. Dev material (check against 12 failure classes from
Tebra Auto Post, gaps with file:line, incidents) goes to HANDOVER-DEV.md.

Also adds ADR-0004, glossary terms, five fixture bots and a handoff eval
runner (--resume, --regrade) with results from 47 runs of the earlier
13-section version. The grader still expects that structure and must be
updated before the next eval run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Opus 5.5 committed 2026-10-28 15:01:42 +05:30
1 parent 3d2db6aca6
commit 3942fad6cd
143 files changed
+11815 -2

No files matched your search

@@ -0,0 +1,173 @@
# Mail intake bot - ops handover
Version: not set (the bot has no version number yet, see section 13) · Written 2026-10-23 · Escalation: RPA dev team, Teams channel **#rpa-support**
## 1. What the bot does
Every 10 minutes Windows Task Scheduler starts the bot. It reads the unread emails in the referrals mailbox and saves every attachment. It checks that each one is a PDF of 20 MB or less, then copies the PDF to the shared intake folder. It records every attachment as one item in its queue. It has no window or screen of its own. It works with three things: the **referrals mailbox** (it marks emails as read), the **shared intake folder**, where its output goes, and its own **queue database**, which ops cannot open. The bot's own log file is the only thing it writes for ops to read.
## 2. How it works
```
Windows Task Scheduler (every 10 min)
└─ starts → Mail intake bot (no window, runs in the background)
├─ reads + marks read → Referrals mailbox (INBOX, unread mail only)
├─ saves attachments → data/tmp/ (working folder)
├─ copies good PDFs → Shared intake folder (the output folder)
├─ records items → Queue database (data/queue.db - dev only)
└─ writes → logs/intake.log
```
- **Task Scheduler** starts the bot every 10 minutes. Nobody clicks anything.
- The **bot** runs once, from start to finish, then exits. The next run starts 10 minutes later.
- The **referrals mailbox** is where the bot finds its input. Once the bot fetches an email, the mailbox treats it as read.
- **data/tmp/** holds a raw copy of every attachment. Nothing cleans this folder.
- The **shared intake folder** is the output folder. Only good PDFs are copied there, and each keeps the file name the sender gave it.
- The **queue database** holds one row per attachment: sender, subject, file, status and note. Ops cannot open it.
- **logs/intake.log** gets one line per item and one line per mailbox problem. This is where ops looks.
**One run, step by step:**
1. The bot checks whether any item is still marked *Processing* from an earlier run. If one is, it writes "Previous item still Processing - aborting" to the log and stops. Nothing else happens in that run (see E02).
2. It signs in to the mailbox. If that fails it tries again, up to 3 tries with 30 seconds between them (see E01).
3. It fetches every unread email and saves each attachment. Each attachment becomes one item with status *New*.
4. It works through each *New* item: it sets the item to *Processing* and checks it, then copies the file to the shared intake folder.
5. It records the result (*Done*, *Quarantined* or *Failed*), then writes `item <number> -> <status> (<note>)` to the log.
## 3. Before a run
This bot runs by itself, so "before a run" means before the go-live and after any change to the machine. Dev sets these items up. Ops can confirm the ones marked (ops).
- [ ] The bot's settings file (`.env`) is in the bot folder and has the current mailbox password. If it is missing, or the password has expired, the bot logs "IMAP connect failed" or writes nothing at all (E01, E06).
- [ ] The folders `data/`, `data/tmp/` and `logs/` exist, and so does the queue database `data/queue.db`. The bot does not create them. If one is missing, the bot stops at the very start and writes nothing (E06).
- [ ] (ops) You can open the shared intake folder from this machine. If it is unreachable, items become *Failed* and stay that way (E05).
- [ ] (ops) `logs/intake.log` has gained new lines in the last 20 minutes. That shows the scheduled task is running.
- [ ] Input rules for the people who send referrals (ops passes these on):
- Send a **PDF**. A Word file or a photo is not accepted. Bad: `referral.docx`, `scan.jpg`. Good: `referral.pdf`.
- Keep each file **20 MB or smaller**.
- **No password on the PDF.** A protected PDF is not copied to the shared intake folder, but its item still shows *Done* (E04).
- **Give each file a unique name.** If two referrals are both called `scan.pdf`, the second one overwrites the first in the shared intake folder (E08). Bad: `scan.pdf`. Good: `referral_2026-10-23_01.pdf`.
- Every attachment becomes an item, including signature images and logos. Those always end up *Quarantined*. That is expected (E03).
## 4. During a run
- Don't open or move emails in the referrals mailbox while the bot runs. Never mark referral emails as read by hand. The bot only fetches **unread** emails, so an email you mark as read is skipped for good.
- Don't rename, move or delete files in `data/tmp/`. The bot needs them until each item is finished.
- To stop the bot: ask the RPA dev team on #rpa-support to **disable the scheduled task**. Don't end the bot's process (python) halfway through a run. That leaves an item stuck in *Processing*, which blocks every later run (E02). It can also lose emails the bot had already marked as read (E07).
- One run normally takes well under a minute. If the mailbox is down, a run can take about 90 seconds (3 tries × 30 s).
- **A run looks stuck** when the log shows "Previous item still Processing - aborting" on two runs in a row, or gets no new lines for more than 20 minutes. **The bot never clears this by itself.** It has no watchdog, so don't wait. Escalate right away (E02, E06).
## 5. Where to look when something goes wrong
Ops can open `logs/intake.log` and the shared intake folder. The queue database holds the sender, subject and status of each item, but ops cannot open it. The queue viewer spreadsheet named in the bot's README is not delivered (section 13).
| Question | Where to look |
|---|---|
| Is the bot running at all? | `logs/intake.log`: there should be new lines at least every 10-20 minutes while mail comes in. No new lines → E06. |
| Did the last run finish? | `logs/intake.log`: the last lines are `item <number> -> ...` lines, with no "aborting" line after them. |
| Which items failed? | `logs/intake.log`: search for `-> Failed`, `-> Quarantined` and `encrypted`. |
| Why did one item fail? | `logs/intake.log`: search for `item <number> ->`. The text in brackets is the reason. |
| Did the bot already retry? | `logs/intake.log`: "IMAP connect failed (1/3)", "(2/3)" and "(3/3)" are the mailbox retries. Nothing else is ever retried. |
| Did the PDF arrive? | The shared intake folder: look for the file name. |
| Which email / sender was it? | Ops cannot see this. Ask dev on #rpa-support and quote the item number. |
| Which version is running? | There is no version shown anywhere yet (section 13). |
## 6. Statuses
The bot has no screen, so statuses have no colours. Each status appears in the log line `item <number> -> <status> (<note>)`. Log lines are marked INFO (normal), WARNING (a retry) or ERROR (a run stopped).
| Status (note) | Colour | Meaning | What ops does |
|---|---|---|---|
| New (no note) | none (never in the log) | The attachment is saved and waiting. The next run picks it up. | nothing |
| Processing | none (never in the log) | The bot is working on this item right now. If it is still Processing after the run has ended, the item is stuck and blocks the bot. | If "Previous item still Processing - aborting" repeats, see E02 |
| Done - "<n> pages saved" | INFO line | The PDF was copied to the shared intake folder. | nothing |
| Done - "encrypted - not readable" | INFO line | **Needs follow-up.** The PDF has a password. It was **not** copied to the shared intake folder, even though the status says Done. | See E04 |
| Quarantined - "not a PDF or over 20 MB" | INFO line | Not a PDF, or too big. It was not copied. One note covers both reasons. | See E03 |
| Failed - "share unreachable: <error text>" | INFO line | The copy to the shared intake folder failed. **The bot never retries it.** | See E05 |
## 7. Exception catalogue
| ID | What you see | How the bot identifies it | Where it is recorded | Type | Who acts | What to do | Escalate when |
|---|---|---|---|---|---|---|---|
| E01 | Log shows "IMAP connect failed (1/3)", then (2/3) and (3/3), and no `item` lines after them. New referrals stop arriving in the shared intake folder. | The bot cannot sign in to the mailbox: network down, mail server down, or a wrong or expired password. It tries 3 times, 30 s apart, then the run ends. It cannot tell a password problem from a network problem. | `logs/intake.log`, WARNING lines. The final "Mailbox unreachable" is **not** written to the log. | System | Bot, then Dev | Wait for the next run (10 min). If the text after the colon mentions login, authentication or credentials, the password has probably expired, so escalate at once. | The warnings appear on 3 runs in a row (about 30 min), or the text points to a password problem. Send: the time of the first and last warning and the text after the colon. |
| E02 | Log shows "ERROR Previous item still Processing - aborting" on every run. No new items, and nothing new in the shared intake folder. | At the start of each run the bot checks for an item still marked Processing. This happens when an earlier run crashed in the middle of an item, for example on a damaged PDF, or when someone ended the bot by hand. The bot then stops the run, every time. | `logs/intake.log`, ERROR line. The stuck item is visible only in the queue database. | System | **Dev** | One single line followed by normal runs can be two runs overlapping. Ignore it. If it repeats, the bot is fully blocked and **does not recover by itself**. Only dev can release the stuck item, because ops cannot open the database. | Two "aborting" lines in a row. Send: the time of the first "aborting" line and the last `item <number> ->` line before it. |
| E03 | `item <number> -> Quarantined (not a PDF or over 20 MB)` | The attachment's name does not end in .pdf, or the file is bigger than 20 MB. | `logs/intake.log`; status Quarantined | Business | Ops (dev looks up the sender) | Most of these are signature images or logos. Ignore them. If a referral is expected and missing, ask dev on #rpa-support for the sender and subject of that item number. Then ask the sender to resend it as a PDF of 20 MB or less. | You cannot find who sent it. Send: the item number and the time of the log line. |
| E04 | `item <number> -> Done (encrypted - not readable)`, and the file is **not** in the shared intake folder | The PDF has a password, so the bot stops before copying it. It still marks the item Done. | `logs/intake.log`; status Done with the note "encrypted" | Business | Ops (dev looks up the sender) | Treat it as **not delivered**. Ask dev for the sender and subject of that item number, then ask the sender for a copy with no password. | You cannot find who sent it. Send: the item number and the time of the log line. |
| E05 | `item <number> -> Failed (share unreachable: ...)` | The copy to the shared intake folder fails: the folder is offline, full, or the bot has no permission to write there. | `logs/intake.log`; status Failed | System | Ops, then Dev | 1. Check that you can open the shared intake folder. 2. Check whether a file with that name is already there. It may be half-copied, and if so, tell dev. 3. The bot **never retries a Failed item**. Only dev can set it back to New. | Always, once the folder works again. Send: the item numbers, the time, and the text after "share unreachable:". |
| E06 | `logs/intake.log` gets no new lines for more than 20 minutes, even though mail is arriving | The bot stops before it writes anything. Possible causes: the scheduled task is off; the settings file is missing; the database or a folder is missing; the mailbox gave up after its retries; or an attachment it could not save crashed the run. None of these is written to the log. | Nowhere ops can see. The reason is only in the Task Scheduler's last-run result. | System | Dev | Nothing ops can fix. | After 20 minutes with no new lines. Send: the time of the last line in `logs/intake.log`. |
| E07 | A sender says they emailed a referral, but it is not in the shared intake folder and no `item` line matches it | Three possible causes. The bot crashed while fetching mail, after the mailbox had already marked the email as read, so it is never fetched again. Or the email had no named attachment, which the bot skips without a word. Or someone marked the email as read by hand. | Nowhere | System | Dev | Don't resend from your side yet. Escalate so dev can check the mailbox and the working folder. | Always. Send: the approximate time the email was sent and the sender's organisation. Never paste patient details in chat. |
| E08 | The file in the shared intake folder is the wrong referral, or one of two referrals is missing | Two attachments with the same file name (for example `scan.pdf`). The second overwrites the first, both in the working folder and in the shared intake folder. Both items can still show Done. | Two `item` lines Done; only one file in the shared intake folder | System | Dev | Escalate so dev can find both emails. Ask senders to give files unique names (section 3). | Always. Send: the file name and the two item numbers. |
## 8. What the bot recovers by itself
| Problem | What the bot does | Limit | After the limit |
|---|---|---|---|
| Can't sign in to the mailbox | Tries again | 3 tries × 30 s (about 90 s) | The run ends with nothing logged. The next scheduled run tries again in 10 min (E01). |
| Items left New (e.g. a run ended early) | The next run picks them up | no limit | n/a |
| Item stuck in Processing | **Nothing.** Every run stops at once | no automatic recovery, no watchdog | Blocked until dev releases it (E02) |
| Copy to the shared intake folder failed | **Nothing.** Never retried | none | Item stays Failed until dev resets it (E05) |
| Damaged PDF, or attachment that can't be saved | **Nothing.** The run crashes | none | Damaged PDF → stuck Processing (E02). Save failure → emails lost (E07). |
## 9. Known incidents
Newest first. All three are dated 2026-10-23, the date they were recorded at import. **None of them is fixed in the code.** Each "fix" only added a note, so all three can happen again.
| When / version | What ops saw | Cause | What to do now | Ops could handle? |
|---|---|---|---|---|
| 2026-10-23 / no version | Mailbox password expired; no mail was processed and nothing clear appeared in the log | A login failure is treated like a network blip: 3 retries, then the run ends without logging the final failure. If the bot is also blocked (E02), the mailbox is never even tried, so the password problem stays hidden. | Watch for "IMAP connect failed" lines (E01). If they mention login or authentication, escalate for a password reset. | Partly |
| 2026-10-23 / no version | Password-protected referrals never reached the shared intake folder | Protected PDFs are skipped before the copy step, but the item is marked Done | Search the log for "encrypted" and get an unprotected copy (E04) | Partly |
| 2026-10-23 / no version | Nothing was picked up for a whole day; the log repeated "Previous item still Processing - aborting" | A crash in the middle of an item, for example a damaged PDF, left it stuck in Processing. Every later run stopped at once. Someone released it by hand in the database. | Escalate after two "aborting" lines in a row (E02). Ops cannot release it. | Partly |
## 10. Releases and version check
- How an update arrives: dev replaces the bot files in the bot folder on the bot machine. There is no launcher or auto-update.
- How to confirm it applied: there is **no version number** in the bot or its log, so ops cannot check this yet. Ask dev on #rpa-support to confirm the change and its date (section 13).
- If the version did not change: report it on #rpa-support.
## 11. Escalating to dev
Post in Teams **#rpa-support**. Send:
- the exception ID from section 7 (e.g. "E02")
- the time of the first and last related log lines
- the item numbers
- the relevant lines copied from `logs/intake.log`
Never send: passwords, the settings file (`.env`), the PDFs themselves, or patient or sender details in chat.
## 12. Failure-class check
- F1 covered in section 3 (settings file, folders, database, shared intake folder) and E06; gaps in section 13 #16.
- F2 not applicable: the bot drives no windows. It only talks to the mailbox, files and its database.
- F3 covered in E01 (mailbox) and E05 (shared intake folder), with limits in section 8. No mail-server timeout: section 13 #19.
- F4 covered in section 3 input rules (PDF only, 20 MB, no password, unique names), E03 and E08; gaps in section 13 #8 and #9.
- F5 covered in E04; section 13 #3.
- F6 covered in E05 step 2 (check for a half-copied file before dev resets the item).
- F7 covered in section 6 ("Done - encrypted" needs follow-up) and E04; section 13 #3.
- F8 covered in E02 and section 8: the bot stops every 10 minutes, forever, with no limit. There are no re-run loops or watchdog.
- F9 covered in section 4 (disable the scheduled task; never end the process mid-run).
- F10 covered in E02 (one stuck item blocks every run); section 13 #1.
- F11 covered in section 5, E06 and E07; section 13 #5, #10 and #16.
- F12 covered in section 10; section 13 #11.
## 13. Fix before handover (dev team)
| # | Gap | Why it hurts ops | Where (file:line) |
|---|---|---|---|
| 1 | A stuck Processing item stops every run forever. There is no try/except around `process()`, no automatic reset and no age limit. Commit b2ca175 added only a comment. | The bot is blocked for hours until dev edits the DB. Ops cannot release it. | `intake.py:74-76`, `intake.py:79-81`, `intake.py:89` |
| 2 | A damaged PDF makes `PdfReader` raise, which crashes the run and leaves the item in Processing. A missing temp file does the same in `p.stat()`. | Leads straight to #1 | `intake.py:59`, `intake.py:61`, `intake.py:68` |
| 3 | An encrypted PDF returns status `Done` without being copied | Ops thinks it was delivered (F5/F7) | `intake.py:62-63` |
| 4 | `Failed` items are never retried. Only `New` rows are selected. | Every share outage needs a DB fix by dev | `intake.py:66-67`, `intake.py:78` |
| 5 | The final `RuntimeError("Mailbox unreachable")` is not logged, and a login failure is retried just like a network error. Commit 610a536 added only a comment. | The run fails silently, and an expired password looks like a network blip | `intake.py:34-37`, `intake.py:91` |
| 6 | The RFC822 fetch marks mail as Seen, but rows are committed only after the whole loop. A crash mid-fetch loses those emails. | Referrals vanish with no trace (E07) | `intake.py:44`, `intake.py:54` |
| 7 | `write_bytes(None)` on an empty attachment payload crashes the fetch | Triggers #6 | `intake.py:51` |
| 8 | The attachment name is used as-is. Duplicate names overwrite each other in tmp and in the share, and a name containing a path can write outside `data/tmp`. | Wrong or missing referral (E08); security risk | `intake.py:50`, `intake.py:65` |
| 9 | Parts with no filename are skipped silently. Inline images and signatures create Quarantined noise. The Quarantined note mixes "not PDF" and "too big". | Ops can't tell real problems from noise | `intake.py:47-49`, `intake.py:59-60` |
| 10 | `queue_viewer.xlsx` is named in the README but is not in the repo. Ops cannot open the DB, and the log line has only the item id, with no sender, subject or file name. | Ops cannot act on E03/E04 without dev looking up every item | `README.md:6`, `intake.py:84` |
| 11 | No version string anywhere, in the code or the log | Ops cannot confirm an update applied | `intake.py:1-2` (add one and log it at the start of each run) |
| 12 | `.env` (mailbox credentials) is committed to git in the initial import | Credentials exposed in history: rotate them and remove the file from git | `.env` (commit 15fcd88) |
| 13 | `logs/intake.log` holds a line with a sender's email address, written in a format the current code does not produce. That suggests an older or different build. | Personal data in the log; unclear which build ran | `logs/intake.log` |
| 14 | `data/tmp/` is never cleaned | Referral PDFs pile up on the bot machine (privacy, disk space) | `intake.py:50-51` |
| 15 | The `BOT_WAS_RUN` marker file is written every run and nothing documents it or reads it | Unknown purpose. Best reading: a "ran at least once" flag, not a health check. | `intake.py:72` |
| 16 | Errors that happen before the first log line (missing `.env` key, missing DB, table or folders) and "Mailbox unreachable" go only to the Task Scheduler result | Ops sees just silence (E06) | `intake.py:16-18`, `intake.py:23`, `intake.py:31`, `intake.py:73-74` |
| 17 | No lock against overlapping runs. A slow run plus the next scheduled run gives a false "still Processing" line. | False alarm that looks like E02 | `intake.py:74` |
| 18 | Commits b2ca175, 78e06c9 and 610a536 are labelled "fix" but only add comments | Readers of the history believe the incidents are fixed | `intake.py:89-91` |
| 19 | No timeout on the IMAP connection | A hanging mail server could stall a run with no log line | `intake.py:31` |