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

+2
View File
@@ -0,0 +1,2 @@
BILLING_API_URL=https://billing.example.test/api/v2
BILLING_API_KEY=sk_test_FAKE_1234567890abcd
+5
View File
@@ -0,0 +1,5 @@
# Invoice sync
Every hour, pushes approved invoices from the finance export (`data/input/invoices.csv`) into the
billing API. Writes `data/output/sync_report_<time>.csv` (Status, Message) and `logs/sync.log`.
Needs `.env` with BILLING_API_URL and BILLING_API_KEY. Version 0.9.1.
+5
View File
@@ -0,0 +1,5 @@
[
{"msg": "fix: rate limit 429 crashed the hourly run - add retries with backoff", "file": "sync.py"},
{"msg": "fix: duplicate invoices when the API timed out (504) after it had already created the invoice - check before retry", "file": "sync.py"},
{"msg": "chore: bump to 0.9.1", "file": "README.md"}
]
+2
View File
@@ -0,0 +1,2 @@
invoice_no,customer_id,customer_name,amount,currency
INV-7781,C-19,Acme Placeholder Ltd,120.00,USD
1 invoice_no customer_id customer_name amount currency
2 INV-7781 C-19 Acme Placeholder Ltd 120.00 USD
+4
View File
@@ -0,0 +1,4 @@
2026-09-03 09:00:02 INFO INV-7781 Synced
2026-09-03 09:00:05 INFO INV-7782 Invalid
2026-09-03 09:00:40 INFO INV-7783 Failed
2026-09-03 09:00:41 INFO run done -> sync_report_20260903_0900.csv
+2
View File
@@ -0,0 +1,2 @@
requests
python-dotenv
+85
View File
@@ -0,0 +1,85 @@
"""Invoice sync: pushes approved invoices from the finance export CSV into the billing API.
python sync.py data/input/invoices.csv (runs every hour from Task Scheduler)
"""
import csv
import logging
import os
import sys
import time
from datetime import datetime
from pathlib import Path
import requests
from dotenv import load_dotenv
ROOT = Path(__file__).parent
load_dotenv(ROOT / ".env")
API = os.environ.get("BILLING_API_URL", "https://billing.example.test/api/v2")
HEADERS = {"Authorization": f"Bearer {os.environ.get('BILLING_API_KEY', '')}"}
RETRIES = 5
TIMEOUT_S = 30
REQUIRED = ["invoice_no", "customer_id", "amount", "currency"]
logging.basicConfig(filename=ROOT / "logs" / "sync.log", level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("sync")
def validate(row):
missing = [f for f in REQUIRED if not row.get(f)]
if missing:
return f"missing {', '.join(missing)}"
try:
if float(row["amount"]) <= 0:
return "amount must be positive"
except ValueError:
return f"amount '{row['amount']}' is not a number"
return ""
def exists(invoice_no):
r = requests.get(f"{API}/invoices/{invoice_no}", headers=HEADERS, timeout=TIMEOUT_S)
return r.status_code == 200
def push(row):
"""Returns (status, message). Retries 429 and 5xx with backoff; checks for a duplicate before each retry."""
for attempt in range(1, RETRIES + 1):
try:
r = requests.post(f"{API}/invoices", json=row, headers=HEADERS, timeout=TIMEOUT_S)
except requests.RequestException as e:
print(f"network error on {row['invoice_no']}: {e}")
r = None
if r is not None and r.status_code in (200, 201, 207):
return "Synced", "Created" if r.status_code != 207 else "Created (some lines rejected)"
if r is not None and r.status_code == 409:
return "Duplicate", "Invoice already exists in billing"
if r is not None and r.status_code == 422:
return "Invalid", r.json().get("detail", "Rejected by billing")
if exists(row["invoice_no"]):
return "Synced", "Created (confirmed after retry)"
time.sleep(2 ** attempt)
return "Failed", f"No success after {RETRIES} tries"
def main(path):
(ROOT / "BOT_WAS_RUN").write_text("1")
report = ROOT / "data" / "output" / f"sync_report_{datetime.now():%Y%m%d_%H%M}.csv"
with open(path, newline="", encoding="utf-8") as f, open(report, "w", newline="", encoding="utf-8") as out:
w = csv.writer(out)
w.writerow(["invoice_no", "customer", "Status", "Message"])
for row in csv.DictReader(f):
problem = validate(row)
status, msg = ("Invalid", problem) if problem else push(row)
log.info("%s %s", row.get("invoice_no"), status)
w.writerow([row.get("invoice_no"), row.get("customer_name"), status, msg])
log.info("run done -> %s", report.name)
if __name__ == "__main__":
try:
main(sys.argv[1])
except Exception as e:
print(f"CRASH: {e!r}")
raise