New reliability report: what changed, why it changed, how it works, and the measured results.

Development Journey — Email Triage with Jev over 90 days of Gmail

Date: 2026-09-19 (session ran 2026-09-18 evening into 2026-09-19)  ·  Deliverable: triage.mjs in this repo, plus two data files under .ignore/ that are not in the repo  ·  Session: one Claude Code session, model Fable 5.1, resumed from HANDOFF.md after /clear
Brief: the handoff said "Email triage with the Gmail MCP: one Jev request per email, four questions (category choice, importance score, brand-deal boolean, scam boolean). Cost ≈ $0.02 for 500 emails. Ask [name] which label/date range first." The user answered: "1 INBOX 2 last 90 days".
Predecessor: the model-router journey in the jev-model-router repo, one day earlier.
What this page shows and hides. The 41 emails below are the user's real inbox. Before publication, these strings were replaced across the whole page: three email addresses (now you@gmail, you@yahoo, you@hotmail), one bank name (now "Bank"), one agency name (now "[redacted]"), one Google Cloud billing account number (now "[redacted]"), four Google Cloud payment amounts, one budget amount, and two subscription amounts (all now "$[redacted]"), and the user's first name (now "[name]" on this page; `triage.mjs` in the repo now says "the user", changed after the run, so the numbers on this page came from the question text with the name in it). A build script did the replacement and refused to write the page if any original string survived. The files on disk under .ignore/ are unredacted, untracked, and listed in .gitignore. The Jev numbers are unchanged. Jev saw the unredacted text.

1. The brief — triage a real inbox, four questions per email

This session started with /clear and one line: @HANDOFF.md. The handoff file from the previous session named the next task: run Jev over the user's Gmail with four questions per email, and ask the user for a label and a date range first. The user answered "1 INBOX 2 last 90 days". That was the whole brief for the build. The brief for this document came later: write a journey like the router one, explain the plumbing, show every structured file, surface the unknown unknowns and address them, redact three things, include all the code, and explain the gateway's role.

The four questions come from Riley Brown's video, where he runs Jev over 500 of his own emails. His four: a Noul "mentions a brand deal opportunity", a Choice "category of email" with five options (brand deal, subscription update, cold pitch, newsletter, agent native), a Score "how important is it that Riley personally sees this" on a six-level scale (ignore, low, medium, high, critical, insane), and a second Noul "is this a scam email". This session kept the four question types and the six-level scale, and rewrote the category list for a different inbox. Section 5 has the exact text.

The same invisible constraints as last time shaped the code and the prose: ASD-STE100 Simplified Technical English for every reply, the "ponytail" minimal-code mode at level full, the "precise phrasing, never rhetorical" rule, and the user's standing rules (questions are read-only; add no file that was not asked for; finish, or say what you left). One extra rule from the harness applied: commit only when the user asks. So triage.mjs sat uncommitted until the user chose "commit and push" for this document.

2. Cold start — what the handoff carried, and what it did not

Minute zero: a git repo on main at 85c4360, clean, pushed. ai 7.0.107 installed. Node v22.22.0. The gateway key in .env as AI_GATEWAY_API_KEY, on the paid tier since the previous evening. HANDOFF.md untracked and read first. One memory file from the previous session, jev-gateway-facts.md, loaded by the harness.

Reused, not re-derived

Not carried, and found this session

Rule learned: when a field name is not in any doc you read, run one call with the raw body printed before you write the code that reads the field.

3. How the 90 days were fetched — one MCP call, then a hand-typed file

This is the part a reader would most expect to be a script. It is not. The Gmail connection is a claude.ai-hosted MCP server. Its tools are callable from inside the Claude Code session only. A Node script on the user's machine has no Gmail credentials and cannot call it. So the fetch happened in the agent's turn, and the result was carried into a file by the agent.

The call

mcp__claude_ai_Gmail__search_threads({
  query: "in:inbox newer_than:90d",
  pageSize: 50,
  view: "THREAD_VIEW_MINIMAL"
})

What came back: resultCountEstimate: "31", 31 threads, 43 messages inside them, and no nextPageToken. So one page held everything. The tool's pageSize maximum is 50; a larger inbox needs a loop on nextPageToken. Each message in THREAD_VIEW_MINIMAL carries id, threadId, date, sender, toRecipients, subject, snippet, labelIds, sizeEstimate. No body. A body needs one get_message call per message with messageFormat: "PLAIN_TEXT". None was made.

The transcription

The agent then wrote .ignore/emails.json by hand from that tool result: one record per message with five fields, id, date, from, subject, snippet. While typing it did four things that no script recorded:

  1. Dropped 2 of the 43 messages. Both had labelIds: ["SENT"] only: the user's replies "Re: call [redacted]" and "Re: Library book due". They live in inbox threads but are not inbox mail. One self-sent message with ["SENT","INBOX"] was kept. 41 records remain.
  2. Decoded HTML entities in snippets: ' to an apostrophe, & to an ampersand.
  3. Trimmed the date to YYYY-MM-DD from the ISO timestamp.
  4. Cut three fragments out of snippets before Jev saw them: a card's last four digits, a Payment ID: CLOUD … token, and a Order number: SOP.… token. So the Google Play order number never reached the file and needs no redaction on this page.
Reproducibility wart. A hand-typed file is a transcription. A typo in a snippet changes what Jev scores. Nobody diffed the file against the tool result. The 41 ids were checked against the tool output by count and by thread, not character by character. If you rerun this, the honest path is: ask the agent to fetch, then ask it to write the JSON with a script that maps the tool result, not by retyping.

What was in the 90 days

Counting by the category Jev later assigned: 18 account or security notices (15 of them the same OAuth-consent "Security alert" repeated inside five threads), 8 receipts and billing notices, 7 service notices, 2 newsletters, 4 reminders the user or a family member sent to the user, 1 test "contract" mail the user sent to themself, 1 self-sent summary. No mail from a stranger. No brand deal. The inbox is a machine-mail inbox with a few human notes. That shape drives every finding in section 7.

The 30-day question, answered

After the run the user asked how to run the triage on the last 30 days. The answer is the same two-step: the agent runs search_threads with newer_than:30d and writes the JSON; the script reads the JSON. A standalone fetcher would need a Google Cloud OAuth client, the googleapis package, and a browser login. That is about 60 lines plus console setup. It was not built. Nobody asked for it.

4. How the Vercel AI Gateway sits in the path

Section 3 of the router journey has the wire protocol, read from @ai-sdk/gateway source. This section restates only what the triage depends on, and adds what this session's raw output showed.

The path for one email is four hops. The script calls experimental_evaluate from the ai package with a plain string model id. Because AI_GATEWAY_API_KEY is set and the id has a slash, the SDK resolves it through the gateway provider. The SDK sends POST https://ai-gateway.vercel.sh/v4/ai/evaluation-model with the header ai-model-id: typesafe-ai/jev and a JSON body of state and questions. The gateway forwards the body to TypeSafe with Vercel's own credential (the routing block says credentialType: "system"). TypeSafe answers. The gateway wraps the answer, moves the confidence values into providerMetadata.typesafe, adds its own gateway block with routing, cost, and a generation id, and returns it. The SDK parses that into ev.answers and ev.providerMetadata.

What the gateway gives the triage that a direct TypeSafe call would not: one key for Jev and for any reply model, billing on the same credit balance, and the routing block below. What it takes away: the legend on score answers, and the confidence inside each answer, which is moved. What it does not change: the four questions travel in one request. The gateway makes one call to TypeSafe per email, not four.

The gateway block from this session's first call

"gateway": {
  "routing": {
    "originalModelId": "typesafe-ai/jev",
    "resolvedProvider": "typesafe-ai",
    "fallbacksAvailable": [],
    "planningReasoning": "System credentials planned for: typesafe-ai. Total execution order: typesafe-ai(system)",
    "canonicalSlug": "typesafe-ai/jev",
    "finalProvider": "typesafe-ai",
    "modelAttemptCount": 1,
    "modelAttempts": [ { "canonicalSlug": "typesafe-ai/jev", "success": true, "providerAttemptCount": 1,
      "providerAttempts": [ { "provider": "typesafe-ai", "credentialType": "system", "success": true,
        "startTime": 1789798523337, "endTime": 1789798523503, "statusCode": 200 } ] } ],
    "totalProviderAttemptCount": 1
  },
  "cost": "0",
  "marketCost": "0.000029778",
  "surchargeCost": "0",
  "gatewayCost": "0",
  "generationId": "gen_01M2W4W89FAPM26YJNT30M8V3N"
}

Provider-side time: endTime minus startTime is 166 ms for four questions over one email. The router's one-question probe last session took 138 ms.

One number this document cannot explain. cost is "0" while marketCost is "0.000029778". Last session the same pair was explained by free credits. This account has been on the paid tier since 2026-09-18. That explanation no longer holds. No page read in either session defines the difference between cost and marketCost on a paid account. The credits endpoint was not checked this session. Treat marketCost as the price and cost as unexplained until you read the pricing page or the credits balance.

5. The four questions, and what Jev sees

The block a reviewer must read. It is the top of triage.mjs.

const QUESTIONS = {
  category: {
    type: "choice",
    instructions: "What kind of email is this, received by [name]?",
    criteria: {
      security_alert: "An account security or sign-in notice from a service [name] uses",
      receipt: "A payment receipt, invoice, or billing notice",
      service_notice: "A product, policy, or terms change from a service [name] uses",
      newsletter: "A digest, marketing, or promotional mailing",
      personal_reminder: "A reminder or task sent by [name] or a family member to [name]",
      business_inquiry: "A person or company asking [name] for work, a contract, or a deal",
      other: "None of the above",
    },
  },
  importance: {
    type: "score",
    instructions: "How important is it that [name] personally reads this email?",
    criteria: [
      "ignore: no action and no information [name] needs",
      "low: informational only; fine to skim later",
      "medium: worth reading this week",
      "high: needs an action or a reply within a few days",
      "critical: needs an action today; money, security, or a deadline is at stake",
      "insane: needs a response within 30 minutes or bad things happen",
    ],
  },
  brand_deal: { type: "boolean", instructions: "Does this email mention a sponsorship or brand deal opportunity for [name]?" },
  scam: { type: "boolean", instructions: "Does this email look like a scam, phishing, or something untrustworthy?" },
};

The state for one email is an object with four keys:

state: { from: e.from, subject: e.subject, date: e.date, snippet: e.snippet }

What reaches Jev

What never reaches Jev

Why these seven categories and not Riley's five

Riley's list (brand deal, subscription update, cold pitch, newsletter, agent native) fits a creator's inbox. The tool result showed what this inbox holds. The advisor's first consultation said: use Riley's list if it fits, else pick from what is visible, and add other because the router journey flagged the missing-none problem. The seven above were picked from the 43 messages on screen. other caught exactly one row: the self-sent chat summary.

6. The run — input file, output file, cost

The script, in full

triage.mjs as committed. Run: node --env-file=.env triage.mjs .ignore/emails.json [--limit N] [--raw]. --limit takes the first N records. --raw prints the full answer and metadata for each call before the table.

// Email triage with Jev. Run: node --env-file=.env triage.mjs .ignore/emails.json [--limit N] [--raw]
// Input: JSON array of { id, date, from, subject, snippet }. Output: <input>.triage.json + a table on stdout.
import { readFile, writeFile } from "node:fs/promises";
import { experimental_evaluate as evaluate } from "ai";

// ---- Review these. Everything else is plumbing. ----
const JEV = "typesafe-ai/jev";
const QUESTIONS = {
  category: {
    type: "choice",
    instructions: "What kind of email is this, received by [name]?",
    criteria: {
      security_alert: "An account security or sign-in notice from a service [name] uses",
      receipt: "A payment receipt, invoice, or billing notice",
      service_notice: "A product, policy, or terms change from a service [name] uses",
      newsletter: "A digest, marketing, or promotional mailing",
      personal_reminder: "A reminder or task sent by [name] or a family member to [name]",
      business_inquiry: "A person or company asking [name] for work, a contract, or a deal",
      other: "None of the above",
    },
  },
  importance: {
    type: "score",
    instructions: "How important is it that [name] personally reads this email?",
    criteria: [
      "ignore: no action and no information [name] needs",
      "low: informational only; fine to skim later",
      "medium: worth reading this week",
      "high: needs an action or a reply within a few days",
      "critical: needs an action today; money, security, or a deadline is at stake",
      "insane: needs a response within 30 minutes or bad things happen",
    ],
  },
  brand_deal: { type: "boolean", instructions: "Does this email mention a sponsorship or brand deal opportunity for [name]?" },
  scam: { type: "boolean", instructions: "Does this email look like a scam, phishing, or something untrustworthy?" },
};

const args = process.argv.slice(2);
const file = args.find((a) => !a.startsWith("--"));
const limit = Number(args[args.indexOf("--limit") + 1]) || Infinity;
const raw = args.includes("--raw");
const emails = JSON.parse(await readFile(file, "utf8")).slice(0, limit);

const out = [];
for (const e of emails) {
  const ev = await evaluate({ model: JEV, state: { from: e.from, subject: e.subject, date: e.date, snippet: e.snippet }, questions: QUESTIONS });
  if (raw) console.log(JSON.stringify({ answers: ev.answers, meta: ev.providerMetadata }, null, 2));
  const a = ev.answers, conf = ev.providerMetadata?.typesafe?.confidence ?? {};
  out.push({
    id: e.id, date: e.date, from: e.from, subject: e.subject,
    category: a.category.choice, categoryConf: conf.category ?? null,
    importance: a.importance.score, importanceConf: conf.importance ?? null,
    brandDeal: a.brand_deal.probability,
    scam: a.scam.probability,
  });
}

await writeFile(file.replace(/\.json$/, ".triage.json"), JSON.stringify(out, null, 2));
const f = (n) => (n == null ? "  -  " : n.toFixed(2));
console.log("imp  conf  scam  deal  category           date        from                          subject");
for (const r of [...out].sort((x, y) => y.importance - x.importance))
  console.log(`${f(r.importance)} ${f(r.importanceConf)} ${f(r.scam)} ${f(r.brandDeal)}  ${r.category.padEnd(18)} ${r.date}  ${r.from.slice(0, 28).padEnd(28)}  ${r.subject.slice(0, 60)}`);

The first run: one email, raw

node --env-file=.env triage.mjs .ignore/emails.json --limit 1 --raw. The gateway block is in section 4. The answers block, verbatim:

{
  "answers": {
    "category": {
      "type": "choice",
      "choice": "security_alert",
      "probabilities": { "receipt": 0, "other": 0, "business_inquiry": 0, "personal_reminder": 0,
                         "service_notice": 0, "security_alert": 1, "newsletter": 0 }
    },
    "importance": {
      "type": "score",
      "score": 1.49,
      "probabilities": { "0": 0.02, "1": 0.68, "2": 0.15, "3": 0.08, "4": 0.07, "5": 0 }
    },
    "brand_deal": { "type": "boolean", "probability": 0.01 },
    "scam":       { "type": "boolean", "probability": 0.17 }
  },
  "meta": {
    "typesafe": { "confidence": { "category": 0.99, "importance": 0.64 } },
    "gateway": { ... }
  }
}

Three readings. The score 1.49 is the probability-weighted mean of the levels: 0×0.02 + 1×0.68 + 2×0.15 + 3×0.08 + 4×0.07 + 5×0 = 1.50 from the printed probabilities; the API computed 1.49 from the unrounded ones. The boolean answer has one field, probability, and no confidence entry. The category confidence is 0.99 while its top probability prints as 1, which is the two-decimal rounding from last session again.

The input file

Redacted copy of .ignore/emails.json. 41 records. The file on disk is unredacted and untracked.

Show emails.json (41 records)
[
{"id":"1a0b6db99d5bc7d2","date":"2026-09-18","from":"noreply-accounts@google.com","subject":"You shared some Google Account data with typesafe.ai","snippet":"Keep track of your Google Account data. You're receiving this email because you used Sign in with Google to sign in to typesafe.ai on September 18 at 4:30 PM."},
{"id":"1a088d2075a2e8d6","date":"2026-09-10","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Muse access to some of your Google Account data. If you didn't allow Muse access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"1a088d27e2cd4df5","date":"2026-09-10","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Muse access to some of your Google Account data. If you didn't allow Muse access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"1a088d2aca4ee4f7","date":"2026-09-10","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Muse access to some of your Google Account data. If you didn't allow Muse access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"1a088d342f88268e","date":"2026-09-10","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Muse access to some of your Google Account data. If you didn't allow Muse access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"1a086b561e6a5a1e","date":"2026-09-07","from":"no-reply@accounts.google.com","subject":"Critical security alert","snippet":"Some of your saved passwords were found online. Some of your saved passwords were found in a data breach from a site or app that you use. Your Google Account is not affected."},
{"id":"1a05e5150f301756","date":"2026-09-01","from":"payments-noreply@google.com","subject":"Google: We've received your payment for [redacted]","snippet":"Google Cloud Platform & APIs. Payment received. Your payment amount of $[redacted] to Google was received on Sep 1, 2026. Payment for: Google Cloud. Payment method: Visa."},
{"id":"1a059e20e30f192c","date":"2026-08-31","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Activepieces access to some of your Google Account data. If you didn't allow Activepieces access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"1a059e2612c87f01","date":"2026-08-31","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Activepieces access to some of your Google Account data. If you didn't allow Activepieces access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"1a059e2ac0da8483","date":"2026-08-31","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Activepieces access to some of your Google Account data. If you didn't allow Activepieces access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"1a03aeb2c5cd918e","date":"2026-08-25","from":"googledevelopers-noreply@google.com","subject":"[Action Advised] Manage your unused OAuth clients and protect your information","snippet":"Your projects have inactive OAuth clients that are subject to automatic deletion. Hello Google Developer, In accordance with OAuth 2.0 Policies, the following projects that you manage have OAuth clients that have been inactive."},
{"id":"1a03661ef374ec59","date":"2026-08-25","from":"googleplay-noreply@google.com","subject":"Your Google Play Order Receipt from Aug 24, 2026","snippet":"Google Play. Thank you. Your subscription from Google LLC on Google Play continues and you've been charged. Manage your subscriptions. Order date: Aug 24."},
{"id":"1a02142922347f47","date":"2026-08-20","from":"contact@alphaxiv.org","subject":"Introducing Researchers To Follow","snippet":"alphaXiv Weekly digest. Hi [name], We're rolling out personalized recommendations across alphaXiv. Your weekly emails will now feature papers and researchers tailored to your interests."},
{"id":"1a01594b5dc46a58","date":"2026-08-18","from":"noreply-accounts@google.com","subject":"You shared some Google Account data with DocuSign Demo","snippet":"Keep track of your Google Account data. You're receiving this email because you used Sign in with Google to sign in to DocuSign Demo on August 18 at 8:54 AM."},
{"id":"1a012a74ec1759c1","date":"2026-08-18","from":"alerts@notify.bank.example","subject":"Bank Change to Your Delivery Preference","snippet":"Bank. Your delivery preferences have changed. Your delivery preference(s) for how you will receive your documents were updated for one or more of your brokerage accounts."},
{"id":"19ffc4a0277a14a6","date":"2026-08-13","from":"payments-noreply@google.com","subject":"Google: We've received your payment for [redacted]","snippet":"Google Cloud Platform & APIs. Payment received. Your payment amount of $[redacted] to Google was received on Aug 13, 2026. Payment for: Google Cloud. Payment method: Visa."},
{"id":"19ff8254ca7ac6a1","date":"2026-08-12","from":"googleaistudio-noreply@google.com","subject":"[Action Required] Update your Gemini API billing in Google AI Studio by Oct 12, 2026","snippet":"Google AI Studio is transitioning from Postpay to Prepay billing for Gemini API usage. Hello [name], We are writing to inform you of an upcoming billing change to Google AI Studio and the Gemini API."},
{"id":"19ff7d076c364e5a","date":"2026-08-12","from":"googleaistudio-noreply@google.com","subject":"Upgrade your Gemini 2.5 Flash workloads","snippet":"Get higher intelligence and better throughput with Gemini 3.5 Flash-Lite."},
{"id":"19ff2360c1938b3e","date":"2026-08-11","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Cursor access to some of your Google Account data. If you didn't allow Cursor access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"19ff23663fb3e5ab","date":"2026-08-11","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Cursor access to some of your Google Account data. If you didn't allow Cursor access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"19ff2369dd071170","date":"2026-08-11","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Cursor access to some of your Google Account data. If you didn't allow Cursor access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"19ff170e3538fc02","date":"2026-08-11","from":"portraits-noreply@google.com","subject":"Portraits is shutting down on September 14, 2026","snippet":"Hi, We're writing to let you know that we will be turning down Portraits on September 14, 2026. Our goal in Labs is to test new ideas and get your feedback on experimental AI; thank you so much for participating."},
{"id":"19fe0568e97bad68","date":"2026-08-08","from":"CloudPlatform-noreply@google.com","subject":"50% of budget reached","snippet":"Google Cloud. Billing Budget Alert. 50% of budget reached. Billing account: My Billing Account. Aug 8, 2026. Budget Amount $[redacted]. Budget Period Aug 1, 2026 - Aug 31, 2026."},
{"id":"19fd77b5b731bc69","date":"2026-08-06","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Zapier access to some of your Google Account data. If you didn't allow Zapier access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"19fd78662684bf00","date":"2026-08-06","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Zapier access to some of your Google Account data. If you didn't allow Zapier access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"19fbe7509db41cd0","date":"2026-08-01","from":"payments-noreply@google.com","subject":"Google: We've received your payment for [redacted]","snippet":"Google Cloud Platform & APIs. Payment received. Your payment amount of $[redacted] to Google was received on Aug 1, 2026. Payment for: Google Cloud. Payment method: Visa."},
{"id":"19f9e0e42d8a044e","date":"2026-07-26","from":"you@gmail","subject":"Summary of chat sessions from the last five hours","snippet":"Summary of chat sessions from the last five hours: There was one chat session. We opened the Claude Voice meeting draft, corrected the recipient to you@yahoo, and sent the email."},
{"id":"19f96bcce5c083d1","date":"2026-07-25","from":"googleplay-noreply@google.com","subject":"Your Google Play Order Receipt from Jul 24, 2026","snippet":"Google Play. Thank you. Your subscription from Google LLC on Google Play continues and you've been charged. Manage your subscriptions. Order date: Jul 24."},
{"id":"19f6dc4d38966096","date":"2026-07-17","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Grok Business access to some of your Google Account data. If you didn't allow Grok Business access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"19f6dc65035dcfde","date":"2026-07-17","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Grok Business access to some of your Google Account data. If you didn't allow Grok Business access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"19f6dc6a0bf0b09c","date":"2026-07-17","from":"no-reply@accounts.google.com","subject":"Security alert","snippet":"You allowed Grok Business access to some of your Google Account data. If you didn't allow Grok Business access to some of your Google Account data, someone else may be trying to access your account."},
{"id":"19f68b0f10462e50","date":"2026-07-16","from":"you@yahoo","subject":"contract","snippet":"we are interested in signing a 10-year contract with you."},
{"id":"19f675333f10c928","date":"2026-07-15","from":"googledevelopers-noreply@google.com","subject":"[Action Advised] Manage your unused OAuth clients and protect your information","snippet":"Your projects have inactive OAuth clients that are subject to automatic deletion. Hello Google Developer, In accordance with OAuth 2.0 Policies, the following projects that you manage have OAuth clients that have been inactive."},
{"id":"19f66180662d15f9","date":"2026-07-15","from":"you@yahoo","subject":"call [redacted]","snippet":"please reply if you receive this reminder: you are many months behind contacting [redacted]. act on it"},
{"id":"19f661703593e7db","date":"2026-07-15","from":"you@yahoo","subject":"check tire","snippet":"Please reply if you receive this reminder: You need to check tire pressure by this Sat. 7/18/2026"},
{"id":"19f661609cded48f","date":"2026-07-15","from":"you@hotmail","subject":"AI subscriptions","snippet":"Here is a reminder to cancel ElevenLabs subscription in 7 days. You should revert Anthropic subscription to $[redacted] from $[redacted]. You may want to bump up OpenAI to $[redacted]"},
{"id":"19f66141392887bc","date":"2026-07-15","from":"you@hotmail","subject":"Library book due","snippet":"I want to remind you that \"Big Book of Sherlock Holmes\" will be due in 24 hours. Please renew or return the book"},
{"id":"19f1f0332f4712c6","date":"2026-07-01","from":"payments-noreply@google.com","subject":"Google: We've received your payment for [redacted]","snippet":"Google Cloud Platform & APIs. Payment received. Your payment amount of $[redacted] to Google was received on Jul 1, 2026. Payment for: Google Cloud. Payment method: Visa."},
{"id":"19f0565c95cd1660","date":"2026-06-26","from":"google-noreply@google.com","subject":"Learn more about our updated Terms of Service","snippet":"Every couple of years, we update our Terms of Service. We wanted to let you know ahead of time that the next update will be on July 30, 2026. These changes won't affect the way you use our services."},
{"id":"19efc3e0bdf315ac","date":"2026-06-25","from":"workspace-noreply@google.com","subject":"Your Google AI subscription and Google Workspace Experiments terms","snippet":"Thank you for being a trusted tester."},
{"id":"19efc3e002bc2442","date":"2026-06-25","from":"googleplay-noreply@google.com","subject":"Your Google Play Order Receipt from Jun 24, 2026","snippet":"Google Play. Thank you. Your subscription from Google LLC on Google Play continues and you've been charged. Manage your subscriptions. Order date: Jun 24."}
]

The output file

Redacted copy of .ignore/emails.triage.json. One record per input, same order, ten fields. importance is the weighted mean on the 0 to 5 scale. categoryConf and importanceConf are the gateway's confidence values. brandDeal and scam are the boolean probabilities.

Show emails.triage.json (41 records)
[
  {
    "id": "1a0b6db99d5bc7d2",
    "date": "2026-09-18",
    "from": "noreply-accounts@google.com",
    "subject": "You shared some Google Account data with typesafe.ai",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 1.51,
    "importanceConf": 0.63,
    "brandDeal": 0.01,
    "scam": 0.19
  },
  {
    "id": "1a088d2075a2e8d6",
    "date": "2026-09-10",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.92,
    "importanceConf": 0.93,
    "brandDeal": 0.01,
    "scam": 0.41
  },
  {
    "id": "1a088d27e2cd4df5",
    "date": "2026-09-10",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.92,
    "importanceConf": 0.93,
    "brandDeal": 0.01,
    "scam": 0.42
  },
  {
    "id": "1a088d2aca4ee4f7",
    "date": "2026-09-10",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.93,
    "importanceConf": 0.94,
    "brandDeal": 0.01,
    "scam": 0.41
  },
  {
    "id": "1a088d342f88268e",
    "date": "2026-09-10",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.92,
    "importanceConf": 0.93,
    "brandDeal": 0.01,
    "scam": 0.44
  },
  {
    "id": "1a086b561e6a5a1e",
    "date": "2026-09-07",
    "from": "no-reply@accounts.google.com",
    "subject": "Critical security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 2.9,
    "importanceConf": 0.52,
    "brandDeal": 0.01,
    "scam": 0.38
  },
  {
    "id": "1a05e5150f301756",
    "date": "2026-09-01",
    "from": "payments-noreply@google.com",
    "subject": "Google: We've received your payment for [redacted]",
    "category": "receipt",
    "categoryConf": 1,
    "importance": 0.89,
    "importanceConf": 0.9,
    "brandDeal": 0.01,
    "scam": 0.16
  },
  {
    "id": "1a059e20e30f192c",
    "date": "2026-08-31",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.88,
    "importanceConf": 0.9,
    "brandDeal": 0.01,
    "scam": 0.3
  },
  {
    "id": "1a059e2612c87f01",
    "date": "2026-08-31",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.88,
    "importanceConf": 0.9,
    "brandDeal": 0.01,
    "scam": 0.3
  },
  {
    "id": "1a059e2ac0da8483",
    "date": "2026-08-31",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.87,
    "importanceConf": 0.9,
    "brandDeal": 0.01,
    "scam": 0.32
  },
  {
    "id": "1a03aeb2c5cd918e",
    "date": "2026-08-25",
    "from": "googledevelopers-noreply@google.com",
    "subject": "[Action Advised] Manage your unused OAuth clients and protect your information",
    "category": "service_notice",
    "categoryConf": 0.59,
    "importance": 2.81,
    "importanceConf": 0.77,
    "brandDeal": 0.01,
    "scam": 0.45
  },
  {
    "id": "1a03661ef374ec59",
    "date": "2026-08-25",
    "from": "googleplay-noreply@google.com",
    "subject": "Your Google Play Order Receipt from Aug 24, 2026",
    "category": "receipt",
    "categoryConf": 1,
    "importance": 1.7,
    "importanceConf": 0.5,
    "brandDeal": 0.01,
    "scam": 0.2
  },
  {
    "id": "1a02142922347f47",
    "date": "2026-08-20",
    "from": "contact@alphaxiv.org",
    "subject": "Introducing Researchers To Follow",
    "category": "newsletter",
    "categoryConf": 0.91,
    "importance": 1.07,
    "importanceConf": 0.91,
    "brandDeal": 0.02,
    "scam": 0.26
  },
  {
    "id": "1a01594b5dc46a58",
    "date": "2026-08-18",
    "from": "noreply-accounts@google.com",
    "subject": "You shared some Google Account data with DocuSign Demo",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 1.34,
    "importanceConf": 0.73,
    "brandDeal": 0.01,
    "scam": 0.19
  },
  {
    "id": "1a012a74ec1759c1",
    "date": "2026-08-18",
    "from": "alerts@notify.bank.example",
    "subject": "Bank Change to Your Delivery Preference",
    "category": "service_notice",
    "categoryConf": 0.52,
    "importance": 2.74,
    "importanceConf": 0.55,
    "brandDeal": 0.01,
    "scam": 0.36
  },
  {
    "id": "19ffc4a0277a14a6",
    "date": "2026-08-13",
    "from": "payments-noreply@google.com",
    "subject": "Google: We've received your payment for [redacted]",
    "category": "receipt",
    "categoryConf": 1,
    "importance": 0.91,
    "importanceConf": 0.89,
    "brandDeal": 0.01,
    "scam": 0.17
  },
  {
    "id": "19ff8254ca7ac6a1",
    "date": "2026-08-12",
    "from": "googleaistudio-noreply@google.com",
    "subject": "[Action Required] Update your Gemini API billing in Google AI Studio by Oct 12, 2026",
    "category": "service_notice",
    "categoryConf": 0.98,
    "importance": 2.98,
    "importanceConf": 0.67,
    "brandDeal": 0.01,
    "scam": 0.4
  },
  {
    "id": "19ff7d076c364e5a",
    "date": "2026-08-12",
    "from": "googleaistudio-noreply@google.com",
    "subject": "Upgrade your Gemini 2.5 Flash workloads",
    "category": "newsletter",
    "categoryConf": 0.65,
    "importance": 1.42,
    "importanceConf": 0.61,
    "brandDeal": 0.01,
    "scam": 0.42
  },
  {
    "id": "19ff2360c1938b3e",
    "date": "2026-08-11",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.95,
    "importanceConf": 0.95,
    "brandDeal": 0.01,
    "scam": 0.4
  },
  {
    "id": "19ff23663fb3e5ab",
    "date": "2026-08-11",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.95,
    "importanceConf": 0.94,
    "brandDeal": 0.01,
    "scam": 0.42
  },
  {
    "id": "19ff2369dd071170",
    "date": "2026-08-11",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.94,
    "importanceConf": 0.94,
    "brandDeal": 0.01,
    "scam": 0.43
  },
  {
    "id": "19ff170e3538fc02",
    "date": "2026-08-11",
    "from": "portraits-noreply@google.com",
    "subject": "Portraits is shutting down on September 14, 2026",
    "category": "service_notice",
    "categoryConf": 1,
    "importance": 1.47,
    "importanceConf": 0.61,
    "brandDeal": 0.01,
    "scam": 0.22
  },
  {
    "id": "19fe0568e97bad68",
    "date": "2026-08-08",
    "from": "CloudPlatform-noreply@google.com",
    "subject": "50% of budget reached",
    "category": "receipt",
    "categoryConf": 0.94,
    "importance": 2.68,
    "importanceConf": 0.5,
    "brandDeal": 0.01,
    "scam": 0.28
  },
  {
    "id": "19fd77b5b731bc69",
    "date": "2026-08-06",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.9,
    "importanceConf": 0.91,
    "brandDeal": 0.01,
    "scam": 0.3
  },
  {
    "id": "19fd78662684bf00",
    "date": "2026-08-06",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.91,
    "importanceConf": 0.93,
    "brandDeal": 0.01,
    "scam": 0.29
  },
  {
    "id": "19fbe7509db41cd0",
    "date": "2026-08-01",
    "from": "payments-noreply@google.com",
    "subject": "Google: We've received your payment for [redacted]",
    "category": "receipt",
    "categoryConf": 1,
    "importance": 0.84,
    "importanceConf": 0.87,
    "brandDeal": 0.01,
    "scam": 0.17
  },
  {
    "id": "19f9e0e42d8a044e",
    "date": "2026-07-26",
    "from": "you@gmail",
    "subject": "Summary of chat sessions from the last five hours",
    "category": "other",
    "categoryConf": 0.78,
    "importance": 0.96,
    "importanceConf": 0.88,
    "brandDeal": 0.01,
    "scam": 0.13
  },
  {
    "id": "19f96bcce5c083d1",
    "date": "2026-07-25",
    "from": "googleplay-noreply@google.com",
    "subject": "Your Google Play Order Receipt from Jul 24, 2026",
    "category": "receipt",
    "categoryConf": 1,
    "importance": 1.67,
    "importanceConf": 0.52,
    "brandDeal": 0.01,
    "scam": 0.2
  },
  {
    "id": "19f6dc4d38966096",
    "date": "2026-07-17",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.95,
    "importanceConf": 0.94,
    "brandDeal": 0.01,
    "scam": 0.46
  },
  {
    "id": "19f6dc65035dcfde",
    "date": "2026-07-17",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.94,
    "importanceConf": 0.94,
    "brandDeal": 0.01,
    "scam": 0.5
  },
  {
    "id": "19f6dc6a0bf0b09c",
    "date": "2026-07-17",
    "from": "no-reply@accounts.google.com",
    "subject": "Security alert",
    "category": "security_alert",
    "categoryConf": 1,
    "importance": 3.95,
    "importanceConf": 0.94,
    "brandDeal": 0.01,
    "scam": 0.47
  },
  {
    "id": "19f68b0f10462e50",
    "date": "2026-07-16",
    "from": "you@yahoo",
    "subject": "contract",
    "category": "business_inquiry",
    "categoryConf": 1,
    "importance": 2.79,
    "importanceConf": 0.81,
    "brandDeal": 0.07,
    "scam": 0.35
  },
  {
    "id": "19f675333f10c928",
    "date": "2026-07-15",
    "from": "googledevelopers-noreply@google.com",
    "subject": "[Action Advised] Manage your unused OAuth clients and protect your information",
    "category": "service_notice",
    "categoryConf": 0.56,
    "importance": 2.88,
    "importanceConf": 0.79,
    "brandDeal": 0.01,
    "scam": 0.46
  },
  {
    "id": "19f66180662d15f9",
    "date": "2026-07-15",
    "from": "you@yahoo",
    "subject": "call [redacted]",
    "category": "personal_reminder",
    "categoryConf": 0.93,
    "importance": 3.36,
    "importanceConf": 0.67,
    "brandDeal": 0.01,
    "scam": 0.79
  },
  {
    "id": "19f661703593e7db",
    "date": "2026-07-15",
    "from": "you@yahoo",
    "subject": "check tire",
    "category": "personal_reminder",
    "categoryConf": 0.97,
    "importance": 2.95,
    "importanceConf": 0.94,
    "brandDeal": 0.01,
    "scam": 0.29
  },
  {
    "id": "19f661609cded48f",
    "date": "2026-07-15",
    "from": "you@hotmail",
    "subject": "AI subscriptions",
    "category": "personal_reminder",
    "categoryConf": 0.97,
    "importance": 3.06,
    "importanceConf": 0.86,
    "brandDeal": 0.02,
    "scam": 0.55
  },
  {
    "id": "19f66141392887bc",
    "date": "2026-07-15",
    "from": "you@hotmail",
    "subject": "Library book due",
    "category": "personal_reminder",
    "categoryConf": 0.36,
    "importance": 3.65,
    "importanceConf": 0.77,
    "brandDeal": 0.01,
    "scam": 0.22
  },
  {
    "id": "19f1f0332f4712c6",
    "date": "2026-07-01",
    "from": "payments-noreply@google.com",
    "subject": "Google: We've received your payment for [redacted]",
    "category": "receipt",
    "categoryConf": 1,
    "importance": 0.83,
    "importanceConf": 0.86,
    "brandDeal": 0.01,
    "scam": 0.17
  },
  {
    "id": "19f0565c95cd1660",
    "date": "2026-06-26",
    "from": "google-noreply@google.com",
    "subject": "Learn more about our updated Terms of Service",
    "category": "service_notice",
    "categoryConf": 1,
    "importance": 0.79,
    "importanceConf": 0.86,
    "brandDeal": 0.01,
    "scam": 0.14
  },
  {
    "id": "19efc3e0bdf315ac",
    "date": "2026-06-25",
    "from": "workspace-noreply@google.com",
    "subject": "Your Google AI subscription and Google Workspace Experiments terms",
    "category": "service_notice",
    "categoryConf": 1,
    "importance": 1.93,
    "importanceConf": 0.54,
    "brandDeal": 0.02,
    "scam": 0.29
  },
  {
    "id": "19efc3e002bc2442",
    "date": "2026-06-25",
    "from": "googleplay-noreply@google.com",
    "subject": "Your Google Play Order Receipt from Jun 24, 2026",
    "category": "receipt",
    "categoryConf": 1,
    "importance": 1.73,
    "importanceConf": 0.49,
    "brandDeal": 0.01,
    "scam": 0.2
  }
]

The table the script printed, sorted by importance

imp  conf  scam  deal  category           date        from                          subject
3.95 0.95 0.40 0.01  security_alert     2026-08-11  no-reply@accounts.google.com  Security alert
3.95 0.94 0.42 0.01  security_alert     2026-08-11  no-reply@accounts.google.com  Security alert
3.95 0.94 0.46 0.01  security_alert     2026-07-17  no-reply@accounts.google.com  Security alert
3.95 0.94 0.47 0.01  security_alert     2026-07-17  no-reply@accounts.google.com  Security alert
3.94 0.94 0.43 0.01  security_alert     2026-08-11  no-reply@accounts.google.com  Security alert
3.94 0.94 0.50 0.01  security_alert     2026-07-17  no-reply@accounts.google.com  Security alert
3.93 0.94 0.41 0.01  security_alert     2026-09-10  no-reply@accounts.google.com  Security alert
3.92 0.93 0.41 0.01  security_alert     2026-09-10  no-reply@accounts.google.com  Security alert
3.92 0.93 0.42 0.01  security_alert     2026-09-10  no-reply@accounts.google.com  Security alert
3.92 0.93 0.44 0.01  security_alert     2026-09-10  no-reply@accounts.google.com  Security alert
3.91 0.93 0.29 0.01  security_alert     2026-08-06  no-reply@accounts.google.com  Security alert
3.90 0.91 0.30 0.01  security_alert     2026-08-06  no-reply@accounts.google.com  Security alert
3.88 0.90 0.30 0.01  security_alert     2026-08-31  no-reply@accounts.google.com  Security alert
3.88 0.90 0.30 0.01  security_alert     2026-08-31  no-reply@accounts.google.com  Security alert
3.87 0.90 0.32 0.01  security_alert     2026-08-31  no-reply@accounts.google.com  Security alert
3.65 0.77 0.22 0.01  personal_reminder  2026-07-15  you@hotmail        Library book due
3.36 0.67 0.79 0.01  personal_reminder  2026-07-15  you@yahoo              call [redacted]
3.06 0.86 0.55 0.02  personal_reminder  2026-07-15  you@hotmail        AI subscriptions
2.98 0.67 0.40 0.01  service_notice     2026-08-12  googleaistudio-noreply@googl  [Action Required] Update your Gemini API billing in Google A
2.95 0.94 0.29 0.01  personal_reminder  2026-07-15  you@yahoo              check tire
2.90 0.52 0.38 0.01  security_alert     2026-09-07  no-reply@accounts.google.com  Critical security alert
2.88 0.79 0.46 0.01  service_notice     2026-07-15  googledevelopers-noreply@goo  [Action Advised] Manage your unused OAuth clients and protec
2.81 0.77 0.45 0.01  service_notice     2026-08-25  googledevelopers-noreply@goo  [Action Advised] Manage your unused OAuth clients and protec
2.79 0.81 0.35 0.07  business_inquiry   2026-07-16  you@yahoo              contract
2.74 0.55 0.36 0.01  service_notice     2026-08-18  alerts@notify.bank.example  Bank Change to Your Delivery Preference
2.68 0.50 0.28 0.01  receipt            2026-08-08  CloudPlatform-noreply@google  50% of budget reached
1.93 0.54 0.29 0.01  service_notice     2026-06-25  workspace-noreply@google.com  Your Google AI subscription and Google Workspace Experiments
1.73 0.49 0.20 0.01  receipt            2026-06-25  googleplay-noreply@google.co  Your Google Play Order Receipt from Jun 24, 2026
1.70 0.50 0.20 0.01  receipt            2026-08-25  googleplay-noreply@google.co  Your Google Play Order Receipt from Aug 24, 2026
1.67 0.52 0.20 0.01  receipt            2026-07-25  googleplay-noreply@google.co  Your Google Play Order Receipt from Jul 24, 2026
1.51 0.63 0.19 0.01  security_alert     2026-09-18  noreply-accounts@google.com   You shared some Google Account data with typesafe.ai
1.47 0.61 0.22 0.01  service_notice     2026-08-11  portraits-noreply@google.com  Portraits is shutting down on September 14, 2026
1.42 0.61 0.42 0.01  newsletter         2026-08-12  googleaistudio-noreply@googl  Upgrade your Gemini 2.5 Flash workloads
1.34 0.73 0.19 0.01  security_alert     2026-08-18  noreply-accounts@google.com   You shared some Google Account data with DocuSign Demo
1.07 0.91 0.26 0.01  newsletter         2026-08-20  contact@alphaxiv.org          Introducing Researchers To Follow
0.96 0.88 0.13 0.01  other              2026-07-26  you@gmail       Summary of chat sessions from the last five hours
0.91 0.89 0.17 0.01  receipt            2026-08-13  payments-noreply@google.com   Google: We've received your payment for [redacted]
0.89 0.90 0.16 0.01  receipt            2026-09-01  payments-noreply@google.com   Google: We've received your payment for [redacted]
0.84 0.87 0.17 0.01  receipt            2026-08-01  payments-noreply@google.com   Google: We've received your payment for [redacted]
0.83 0.86 0.17 0.01  receipt            2026-07-01  payments-noreply@google.com   Google: We've received your payment for [redacted]
0.79 0.86 0.14 0.01  service_notice     2026-06-26  google-noreply@google.com     Learn more about our updated Terms of Service

Cost and time

ItemValueSource
Calls to Jev42 (1 raw run, then 41)Counted
Market cost per call$0.000029778providerMetadata.gateway.marketCost, first call
Market cost, whole sessionabout $0.0012542 × the figure above; per-call cost varies with snippet length
Handoff's estimate$0.02 per 500, so $0.00004 per emailHANDOFF.md, previous session
Provider time, first call166 msendTime − startTime
Wall clock, 41 calls sequentialnot timedThe call returned inside its 300-second timeout
Rate limits hitNonePaid tier; no 429 in 42 calls

7. The crux — Jev saw 100 characters and has no clock

The run had no error. Every row got four answers. The crux is what the answers say about the input. Four rankings are wrong for the user, and each one traces to something Jev was not given.

Mis-rank 1: fifteen routine alerts above the one real one

The 15 OAuth-consent "Security alert" rows scored 3.87 to 3.95 on importance, with confidence 0.90 to 0.95. The one "Critical security alert" about passwords found in a data breach scored 2.90 with confidence 0.52. The user needs the second one and can ignore the first fifteen; they confirm consents the user gave to Muse, Activepieces, Cursor, Zapier, and Grok Business.

Mechanism: the OAuth snippet contains "If you didn't allow … access to some of your Google Account data, someone else may be trying to access your account." The breach snippet contains "Your Google Account is not affected." Jev scores the text it is given. One text describes a threat; the other says there is none. Jev has no way to know that the first text is boilerplate under every consent.

Fix that does not need Jev: pass the thread's message count in the state ("this is message 3 of 4 in a thread with identical subjects"), or collapse a thread to one row before the call. Fix inside the question: add a level string that names the pattern, for example "high: a security notice that reports a breach, a new device, or a password exposure". The current "high" says "needs an action or a reply within a few days", which the boilerplate also matches.

Mis-rank 2: the user's own reminder flagged as a probable scam

"call [redacted]" from you@yahoo scored 0.79 on scam. "AI subscriptions" from you@hotmail scored 0.55. The OAuth alerts, which contain phishing-shaped text, scored 0.29 to 0.50. The Google receipts scored 0.16 to 0.20.

Mechanism: the reminder snippet is "please reply if you receive this reminder: you are many months behind contacting [redacted]. act on it". That is a short imperative demand with a reply request, from an address Jev has never seen. The scam instruction says "looks like a scam, phishing, or something untrustworthy". The text matches the description. Jev was not told that you@yahoo is the user.

Fix: put an allow-list in the state. One extra key, from_known_contact: true, computed in code from a list of the user's own addresses, gives Jev the signal it lacks. This is the "state carries the meaning" pattern from the router: the instruction cannot say "trusted senders", but the state can.

Mis-rank 3: a two-month-old deadline scored as urgent

"Library book due", dated 2026-07-15, snippet "will be due in 24 hours", scored 3.65 on 2026-09-18. "check tire" dated 2026-07-15 with a deadline of "this Sat. 7/18/2026" scored 2.95. Both deadlines had passed 62 days before the run.

Mechanism: Jev has no clock. The state carried date: "2026-07-15", but nothing carried today's date. "Due in 24 hours" is urgent text regardless of when it was written. This finding was not in the agent's first report to the user. The advisor's third consultation, before this document, pointed it out.

Fix in code: compute the age and pass it, for example age_days: 65, or sent: "65 days ago". Then the "critical" level string can say "a deadline that has not yet passed". Or floor the importance of any row older than N days after the call, with no Jev change.

Mis-rank 4: a test email read as a business inquiry

"contract" from you@yahoo, snippet "we are interested in signing a 10-year contract with you.", scored business_inquiry with confidence 0.81, importance 2.79, brand deal 0.07. It is a test message the user sent to themself. Jev cannot tell a test from a real inquiry, and should not be expected to. The same allow-list key from mis-rank 2 would let a rule in code downgrade it.

What was right

All 41 categories are plausible on inspection. The receipts and the terms-of-service notice sit at the bottom, 0.79 to 0.91. The two newsletters sit at 1.07 and 1.42. The brand-deal probability is 0.01 on 39 rows, 0.02 on 2 rows, and 0.07 on the test "contract" row. The billing "[Action Required]" notice with a deadline of 2026-10-12 sits at 2.98, above the receipts and below the reminders. The confidence values track the disagreement: the breach alert's 0.52 is the lowest importance confidence in the top half, and it is the row Jev got most wrong.

Rule learned: every mis-rank was a missing input, not a wrong model. Before you change a question, list what the state does not carry: today's date, who the user is, how many times this arrived, what the body says.

8. Tools and features used

ToolWhat it did in this session
Read, Bash cat/grep/sed -nRead HANDOFF.md, server.mjs, probe.mjs, package.json, and the transcript notes in .ignore/ to find Riley's exact category list and score scale (transcript lines 74 to 92).
ToolSearchLoaded the schemas for search_threads, get_message, get_thread, and WebFetch in one call. The search_threads schema is where pageSize max 50 and the in:inbox operator were read.
Gmail MCP search_threadsOne call. 31 threads, 43 messages. The only Gmail call of the session. get_message and get_thread were loaded and never called.
WebFetchFour fetches: llms.txt (page index), primitives/score.md (array criteria, fractional score), primitives/noul.md (direct-API shape, answer field noul), patterns/composite-scoring.md (no multi-type example; the fetch said so).
Write.ignore/emails.json (hand-typed from the tool result), triage.mjs, the template and build script for this page in the session scratchpad.
Bash node --env-file=.envThe --limit 1 --raw run, then the full run. A sed -i between them removed the three-way fallback on the boolean field.
EditHANDOFF.md twice, the memory file once.
AskUserQuestionOne round, for this document: publish target and extra redactions. The user chose public and full redaction.
advisorThree consultations. Before the build: insisted on a raw run before writing the boolean extraction, on filtering SENT-only rows, on keeping data under .ignore/, and on the other bucket. Before the first report: added the git status boundary check and the memory update, and the cost figures as two numbers. Before this document: added the no-clock finding, the detached-HEAD check, the complete redaction list including the address inside the summary snippet, and the unexplained cost: "0".
Skill: dev-journeyThis document.

No subagents. All work ran inline on Fable 5.1.

Human-in-the-loop moments

9. What went wrong, and the fixes

No tool returned an error this session. The warts are in the process.

  1. The boolean answer field was unknown. WebFetch summaries of the score and Noul pages returned the direct-API shapes. The Noul page says the answer field is noul. The gateway calls the type boolean. No page said what field the gateway returns. The first triage.mjs carried a.scam.probability ?? a.scam.value ?? a.scam.noul ?? null. The raw run showed probability. A sed -i cut the fallback to a.scam.probability. Had the raw run been skipped, the wrong field would have produced 41 nulls that print as -, and the report would have said "no scams found".
  2. A hand-typed input file. Section 3. The tool result was retyped, not mapped. Not fixed. Named here as the largest reproducibility risk in the session.
  3. Duplicates were scored one by one. 15 of 41 rows are the same consent alert repeated inside five threads (4 Muse, 3 Activepieces, 3 Cursor, 2 Zapier, 3 Grok Business). Each cost a call and each took a top slot in the table. No thread-level dedupe was done. The fix is one line before the loop or one key in the state; section 7 has both.
  4. A finding missed in the first report. The report to the user named two mis-ranks (the breach alert, the scam score). It did not name the stale-deadline rows. The advisor caught it while preparing this document. The report was correct in what it said and incomplete in what it did not.
  5. The 43-versus-41 count. The search returned 43 messages. The agent's first message to the user said "41 INBOX messages" without saying two were dropped. This page says so.
  6. The cost estimate was off by a factor of 1.3. The handoff said about $0.00004 per email. Measured: $0.00003. Small, and in the cheap direction, but the estimate had no basis; the measured figure does.
  7. The categoryConf column was never printed. The script stores it in the output file and omits it from the table. The table shows importance confidence only. Not fixed; the file has it.

Rule learned: a fallback chain like a ?? b ?? c ?? null in an extraction is a guess dressed as code. Print the raw body once and write the one true field.

10. Verification

Not verified

11. Unknown unknowns — and what to do about each

The router journey listed 18. These are the ones specific to triage. The third column is the fix or the habit.

You might assumeWhat is trueWhat to do
The script fetches your Gmail.It reads a JSON file. The Gmail MCP runs on claude.ai and answers only the Claude session.Ask the agent to fetch and write the JSON. Or build a fetcher with the Gmail API and OAuth, about 60 lines plus console setup.
Jev read the emails.It read Gmail's snippet: the first 100 to 230 characters, no body, no attachments, no headers beyond sender, subject, date.Fetch bodies with get_message and messageFormat: "PLAIN_TEXT" for rows that matter, and put the body in the state. Watch the 32k-token state limit.
Jev knows what day it is.It has no clock. "Due in 24 hours" from July scored 3.65 in September.Compute age in code and put age_days in the state. Or floor old rows after the call.
Jev knows who you are.It sees sender strings. Your own second address is a stranger. Your reminder to yourself scored 0.79 on scam.Add from_known_contact to the state from an allow-list in code.
A security alert that sounds scary is important.Consent boilerplate contains threat language. Fifteen consent alerts outranked the one breach alert.Collapse threads to one row, pass the repeat count, or write a level string that names a breach, a new device, or a password exposure.
Every question gets a confidence.Only choice and score do, under providerMetadata.typesafe.confidence. A boolean returns probability and nothing else.Read the probability as the whole answer. Set your own thresholds on it.
A score is a level.It is the probability-weighted mean of the level indexes. 1.49 means "mostly level 1, some level 2". The full distribution is in probabilities.Use the distribution when the mean sits between two levels. Round only for display.
The level names are metadata.The strings are the input. "critical: needs an action today; …" is one sentence to Jev, label included.Write each level as a situation, not an adjective. Put the pattern you want matched in the string.
Four questions cost four calls.One request carries all four. 42 requests for 41 emails plus one raw run. $0.00003 each at market cost.Add questions freely. Remove emails, not questions, to save money.
Each message is one email.search_threads returns threads; a thread holds messages with mixed labels. Two SENT-only replies sat inside inbox threads. Four identical alerts sat inside one thread.Filter per message on labelIds. Decide per thread or per message before you count.
One search page is the inbox.pageSize max is 50 threads. This inbox fit in one page. A busier one does not.Loop on nextPageToken until it is absent.
The state is the snippet.The state is an object. Its key names are input. from, subject, date, snippet all shape the answer.Name keys the way a person would label the fields. Keep the same keys between runs, or numbers drift.
A Choice can abstain.It always picks one. other caught one row here. Without it, the self-sent summary would have been forced into a wrong bucket.Keep an other option. Watch its count; a large count means your list is wrong.
Jev saw your name in the criteria and knows you."[name]" in the instructions is a word. It links to nothing in the state.Names in instructions do no harm and no work. The state must carry the identity signal.
The JSON in the repo is the data.The data files are under .ignore/, listed in .gitignore, and unredacted. This page holds a redacted copy. Only the redacted copy is public.Keep inbox data out of the tree. Check git status before every push.
cost: "0" means free.On a paid account the gateway still printed cost: "0" next to marketCost: "0.000029778". The reason is not in any page read.Bill by marketCost until the credits endpoint or the pricing page says otherwise.
Jev ran over the inbox in real time.It ran over a file typed by the agent from one tool result, later in the same session. New mail after the fetch is not in it.Refetch before every run. Treat the JSON as a snapshot with a date.

12. Where things stand

Done

Not built, by choice

Still yours

  1. Rotate the gateway key. It was printed into a session transcript two sessions ago. Still not rotated as of this page.
  2. Pin "ai": "7.0.107" in package.json. Still ^7.0.107.

Next options