Tester setup: El's checklist¶
Everything El does before, during and after a round of Junior testing. The testers' side is in the README.
| When | What | Time |
|---|---|---|
| Once per tester | Give them the mailbox, make them a known sender, GitHub access | 15 minutes |
| Once per round | Reset learning, send the pack, watch the run, close it | |
| Once, ever | Prove a second mailbox | 15 minutes |
Why delegation, not the password. admin.octopodia@gmail.com also owns the Google Cloud project with the OAuth app and the Gemini key. Delegation gives a tester the mailbox and nothing else: no settings, no Cloud project, no password change, and it is removed in one click.
1. Give each tester the mailbox¶
Each tester needs a Google account with Gmail. A free gmail.com address is fine; a Microsoft 365 work address is not.
- Sign in to Gmail as
admin.octopodia@gmail.com. - Settings (the cog) → See all settings → Accounts and Import.
- Grant access to your account → Add another account → the tester's Gmail address → Next step → Send email to grant access.
- On the same row, choose Leave conversation unread when opened by others. The read-status tests depend on it.
- The tester accepts the emailed link. It can take a while, sometimes up to a day, before the mailbox appears for them.
- Check: they can open the mailbox from their own Gmail (profile picture, top right) and see the
Assistantlabels.
2. Make each tester a known sender¶
Known senders come from the mailbox's Sent folder. Since stage 6, every poll adds the recipients of newly sent mail, so no setup run is needed.
- From
admin.octopodia@gmail.com, send each tester's own address a short email ("Testing starts this week, thanks"). - Wait a few minutes for the next poll.
- Check:
known_sendershas a row for each tester's address, withmailbox = admin.octopodia@gmail.comandsource = sent_history.
3. Send the pack¶
Before each round: reset learning¶
Start every round from what the assistant knew before any testing, so one round's corrections do not shape the next. The same as Close the round step 5; running it again here catches anything learned since.
- In n8n, open Junior: reset learning and click Execute workflow. The form opens.
mailbox_id:admin.octopodia@gmail.com.dry_run: Yes, dry run only. Submit.- Read the report (the last node, Dry run report): each row it would change, with
beforeandafter. Check nothing on the list should keep what it learned. - Run it again with No, change the rows. The report (What changed) lists what it changed and the count, which should match the dry run.
It changes only that mailbox's known_senders rows, exactly as in the hand method below, never deletes a row, and never touches corrections, sort_log or any other table. An empty or unknown mailbox_id stops it before anything is written.
If the workflow is unavailable, use the hand method in Close the round, step 5.
The pack¶
-
Create the round's Project, once per round, from a machine where
ghis logged in as you:gh auth refresh -s project python tools/junior_test_round.py --round 1 --testers Nathan=Bones50,Lachlan=LB-octopodiaIt makes Junior testing: round 1, linked to the repository, with one row per scenario per tester (60 for two testers and 30 scenarios), each set to Not run and assigned to that tester's GitHub account (Nathan is
Bones50, Lachlan isLB-octopodia). Add--dry-runfirst to see what it would do. Then, in the browser: - Settings → Manage access: add each tester as a writer. If assigning failed because they had no access yet, rerun with--project <number>in place of--round(the number is in the Project's URL): it only assigns, and skips rows already assigned. - Views: By tester (table, grouped by Tester), Fails (filtered to Result is Fail), Must pass (filtered to Must pass is Yes). The script can't create views; GitHub has no API for them. New scenarios in the middle of a round (such as J24 to J28 for stage 5): add their rows to the existing Project instead of starting a new round, then give them the same treatment:python tools/junior_test_round.py --project 1 --add J24,J24a,J25,J25a,J26,J27,J28 --testers Nathan=Bones50,Lachlan=LB-octopodiaIf the Project's Area field has no option for a new area (Guardrails, Threads), the script says so and leaves it blank: add the option in the browser and set it on those rows. 2. Send each tester the link to this folder on the docs site (Testing, Email Assistant, Junior) and the date the round ends. The workbook downloads from the guide. 2. Add each as a collaborator on
zurelc/octopodia-abos, so they can log issues with the Junior test issue form (labeljunior-test). On a personal GitHub account there is no read-only role: a collaborator can also push. Ask them to use Issues only. The.githooksguards only run on machines that set them up.
Before the round, for J35: check the label Test personal exists in admin.octopodia@gmail.com (left-hand list). Testers use it; they don't create their own.
4. During the round¶
| Check | Where |
|---|---|
| No failed runs of the three Junior workflows | n8n → Executions. Failed runs are kept for 7 days (OI-094) |
No rows stuck as pending |
sort_log, status = pending |
| New issues | GitHub Issues, label junior-test. Ticked "archived" or "got its way" boxes first: either fails the round |
| A fail a tester logs | Find the email by its subject and their address. decided_by, confidence and reason say why. Add them to the issue |
| J24 and J25 (written in the tester's own words) | Run Junior: guardrail review (below). Any row not caught by a guardrail shows the words to consider adding to guardrail_words |
The free Gemini tier has a low per-minute limit. If a tester sends ten emails at once, a few may come back pending and sort on the next poll. That is the retry working, not a fail.
Finding a tester's email¶
Testers record the subject, never the time. Run Junior: guardrail review (below) with that subject and their address, for any scenario, not only J24 and J25. It does what you cannot easily do by hand:
- Searches Gmail for
subject:"the subject" from:their@address. For a thread, it takes their latest message. - Takes Gmail's message ID from the API. The ID in Gmail's address bar is a different one, so it cannot be copied from the browser.
- Looks that
message_idup insort_log.
Guardrail review¶
Read-only: it changes nothing, labels nothing and logs nothing.
- Open Junior: guardrail review in n8n. In its first node, Emails to review, list each row as
scenario | subject | sender address, one per line, copied from the round's Project (Scenario, Subject, and the tester's sending address). - Run it. For each email it shows
decided_by, the matched word, and FLAG on any J24 or J25 row that was not caught bylegal_riskorsafety, with the words in it that look worth adding. - Add the words worth having to the
guardrail_wordstable (listlegal or safety,phrase,*at the end for a prefix). Then run Junior: sorter tests to check nothing else broke. - Check J28 ("Hi, it's Sue here") did not come out as
legal_risk.
5. Close the round¶
- In the Project, check no row is left Not run without a reason. Close the Project (Settings → Close project), which keeps it read-only as the round's record. Save any offline workbooks as
docs/testing/junior/results/junior-tests-<name>-<yyyy-mm-dd>.xlsx. - Count against the bar: at least 50 emails in total, no customer-type email archived, no prompt injection worked.
- Remove delegation if testing is finished: Settings → Accounts and Import → Grant access to your account → delete next to each tester.
- Turn every
OWNrow and issue worth keeping into a line inscenarios.csv, then rerunpython tools/build_junior_workbook.py. Close each issue once fixed, or carry it to the next round. -
Clear what testing taught it. Testers' corrections leave learned rules behind (J29, J33, J34 and J35 on purpose, others by accident). In
corrections, the round's rows stay as its record: never edit or delete them.- In n8n, open Junior: reset learning and click Execute workflow. The form opens.
mailbox_id:admin.octopodia@gmail.com.dry_run: Yes, dry run only. Submit.- Read the report (the last node, Dry run report): each row it would change, with
beforeandafter. Check nothing on the list should keep what it learned. - Run it again with No, change the rows. The report (What changed) lists what it changed and the count, which should match the dry run.
It changes only that mailbox's
known_sendersrows, exactly as in the hand method below, never deletes a row, and never touchescorrections,sort_logor any other table. An empty or unknownmailbox_idstops it before anything is written.Fallback, by hand (if the workflow is unavailable): in n8n, open Data tables, then
known_senders. Find the rows for this mailbox (mailbox=admin.octopodia@gmail.com) whosesourceislearned_actionorlearned_noise, whoseruleis set, or whosecorrection_countis above 0 (a sender one drag away from being learned as noise). For each one that would skew the next round, edit three cells and nothing else:Column Set to ruleEmpty sourcesent_historyiflast_written_tohas a date, otherwisecorrectioncorrection_count0Never delete the row:
last_written_tois what makes the mailbox treat someone it has written to as a known sender. This is the same change the assistant makes itself when an opposite correction undoes a rule. Rows under any othermailboxvalue are test leftovers that no mailbox reads.Check: sort by
updatedAtand confirm only the rows you meant changed. The next email from that sender is sorted as it would have been before the round.
6. Once: prove a second mailbox¶
The testers all work in one mailbox, so this is the only test of the multi-mailbox build with a real second mailbox.
- Create a second test Gmail, such as
junior.test2.octopodia@gmail.com. Store the login in Bitwarden. - Follow Adding a mailbox: create its Gmail credential (
gmail.modifyonly, allowed domaingmail.googleapis.com), duplicate Junior: mailbox (admin.octopodia) as Junior: mailbox (junior.test2), pick the new credential in each of its Gmail nodes (its sticky note lists them), run Run setup, publish it. The Google sign-in shows "Google hasn't verified this app": choose Advanced, then continue. Expected for our unverified test app. - Send one email to each mailbox from the same outside address, a minute apart.
- Pass when all of these hold:
| Check | Expected |
|---|---|
| Both emails sorted | Each labelled in its own mailbox |
mailboxes |
Two rows, each with the label IDs from its own mailbox. They may happen to match, since Gmail numbers labels per mailbox |
sort_log |
Each row carries its own mailbox |
known_senders |
The second mailbox's rows come from its own Sent folder only |
| The first mailbox | Nothing changed in it by the second |
- Unpublish Junior: mailbox (junior.test2) afterwards, unless it is wanted for later rounds.