<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
  <url>
    <loc>https://jonathanandrei.com/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>monthly</changefreq>
    <priority>1.0</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/about/jonathan-headshot.jpg</image:loc>
      <image:caption>Jonathan Andrei, senior software developer</image:caption>
      <image:title>Jonathan Andrei</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>monthly</changefreq>
    <priority>1.0</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/about/jonathan-headshot.jpg</image:loc>
      <image:caption>Jonathan Andrei, senior software developer</image:caption>
      <image:title>Jonathan Andrei</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.9</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.9</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/quote/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/quote/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/quote/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/quote/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/quote/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/quote/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/quote/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/quote/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/how-i-work/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.85</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/how-i-work/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/how-i-work/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/how-i-work/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/how-i-work/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.85</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/how-i-work/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/how-i-work/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/how-i-work/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/groundwork/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.9</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/groundwork/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/groundwork/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/groundwork/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/groundwork/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.9</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/groundwork/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/groundwork/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/groundwork/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/case-studies/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.9</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/case-studies/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.9</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/trainings/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.85</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/trainings/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/trainings/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/trainings/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/trainings/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.85</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/trainings/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/trainings/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/trainings/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/jonathansolvesproblems/</loc>
    <lastmod>2026-09-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.6</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/jonathansolvesproblems/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/jonathansolvesproblems/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/jonathansolvesproblems/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/jonathansolvesproblems/</loc>
    <lastmod>2026-09-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.6</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/jonathansolvesproblems/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/jonathansolvesproblems/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/jonathansolvesproblems/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/privacy/</loc>
    <lastmod>2026-09-25T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.3</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/privacy/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/privacy/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/privacy/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/privacy/</loc>
    <lastmod>2026-09-25T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.3</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/privacy/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/privacy/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/privacy/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/terms/</loc>
    <lastmod>2026-09-25T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.3</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/terms/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/terms/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/terms/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/terms/</loc>
    <lastmod>2026-09-25T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.3</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/terms/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/terms/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/terms/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/will-it-focus-camera-autofocus-manufacturer-documents/</loc>
    <lastmod>2026-09-26T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/will-it-focus-camera-autofocus-manufacturer-documents/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/will-it-focus-camera-autofocus-manufacturer-documents/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/will-it-focus-camera-autofocus-manufacturer-documents/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_01_hero.jpg</image:loc>
      <image:caption>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:caption>
      <image:title>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_02_manual.png</image:loc>
      <image:caption>Page 100, rendered from Canon's own PDF. The two highlighted sentences are the answer I needed years ago, and they are also the record the agent quotes when it explains why autofocus picked the wrong thing.</image:caption>
      <image:title>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_04_two_endpoints.png</image:loc>
      <image:caption>Two endpoints, one agent. 131 typed documents over seven types answer what is true, and each fact carries its source URL, its page and the verbatim sentence it rests on. The Knowledge Base answers why, and is never quoted.</image:caption>
      <image:title>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_03_t5i_answer.jpg</image:loc>
      <image:caption>The answer, split by shooting mode. Each quote lines up only because it matched its record word for word. A reworded one would still appear here, flagged, with the manufacturer's real sentence printed under it.</image:caption>
      <image:title>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_05_measured.png</image:loc>
      <image:caption>The same twenty questions, three ways. Verdict accuracy moves by one or two. Verbatim quoting moves from 1 of 10 to 21 of 22. That gap is the argument for typed records, measured rather than asserted.</image:caption>
      <image:title>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_06_live.png</image:loc>
      <image:caption>A live question, answered through Sanity Context. The four example questions on the page are saved answers so they load instantly, and they are re-checked against the current records every time they are served.</image:caption>
      <image:title>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_07_refusal.jpg</image:loc>
      <image:caption>No manufacturer has published an answer for this combination, so there is no verdict. An adapter that fits is not an adapter that focuses, and the app will not bridge that gap with a guess.</image:caption>
      <image:title>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_08_checks.png</image:loc>
      <image:caption>137 record quotes checked against the manufacturers' PDFs, zero failures. Then every number in the writeup checked against the data, zero failures. Both run in the build, so neither can drift quietly.</image:caption>
      <image:title>An agent that will not put words in Canon's mouth: 21 of 22 quotes found word for word in the manufacturer's own documents</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/will-it-focus-camera-autofocus-manufacturer-documents/</loc>
    <lastmod>2026-09-26T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/will-it-focus-camera-autofocus-manufacturer-documents/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/will-it-focus-camera-autofocus-manufacturer-documents/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/will-it-focus-camera-autofocus-manufacturer-documents/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_01_hero.jpg</image:loc>
      <image:caption>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:caption>
      <image:title>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_02_manual.png</image:loc>
      <image:caption>La page 100, rendue depuis le PDF de Canon. Les deux phrases surlignées sont la réponse dont j'avais besoin il y a des années, et c'est aussi la fiche que l'agent cite quand il explique pourquoi la mise au point a choisi la mauvaise chose.</image:caption>
      <image:title>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_04_two_endpoints.png</image:loc>
      <image:caption>Deux points d'accès, un agent. 131 documents typés sur sept types répondent à ce qui est vrai, et chaque fait porte l'URL de sa source, sa page et la phrase textuelle sur laquelle il repose. La base de connaissances répond au pourquoi, et n'est jamais citée.</image:caption>
      <image:title>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_03_t5i_answer.jpg</image:loc>
      <image:caption>La réponse, séparée par mode de prise de vue. Chaque citation s'aligne uniquement parce qu'elle correspond mot pour mot à sa fiche. Une citation reformulée apparaîtrait quand même ici, signalée, avec la vraie phrase du fabricant imprimée en dessous.</image:caption>
      <image:title>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_05_measured.png</image:loc>
      <image:caption>Les mêmes vingt questions, de trois façons. L'exactitude des verdicts varie d'un ou deux. La citation textuelle passe de 1 sur 10 à 21 sur 22. Cet écart est l'argument en faveur des fiches typées, mesuré plutôt qu'affirmé.</image:caption>
      <image:title>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_06_live.png</image:loc>
      <image:caption>Une question en direct, répondue par Sanity Context. Les quatre questions d'exemple sur la page sont des réponses enregistrées pour qu'elles s'affichent instantanément, et elles sont revalidées contre les fiches courantes à chaque affichage.</image:caption>
      <image:title>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_07_refusal.jpg</image:loc>
      <image:caption>Aucun fabricant n'a publié de réponse pour cette combinaison, donc il n'y a pas de verdict. Un adaptateur qui s'emboîte n'est pas un adaptateur qui fait le point, et l'application ne comblera pas cet écart par une supposition.</image:caption>
      <image:title>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/willitfocus_08_checks.png</image:loc>
      <image:caption>137 citations de fiches vérifiées contre les PDF des fabricants, zéro échec. Puis chaque chiffre du billet vérifié contre les données, zéro échec. Les deux tournent dans la compilation, donc ni l'un ni l'autre ne peut dériver en silence.</image:caption>
      <image:title>Un agent qui refuse de mettre des mots dans la bouche de Canon : 21 citations sur 22 retrouvées mot pour mot dans les documents du fabricant</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/elbow-room-mobile-arcore-scan-stair-turn/</loc>
    <lastmod>2026-09-24T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/elbow-room-mobile-arcore-scan-stair-turn/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/elbow-room-mobile-arcore-scan-stair-turn/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/elbow-room-mobile-arcore-scan-stair-turn/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_01_home.png</image:loc>
      <image:caption>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:caption>
      <image:title>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_05_fit.png</image:loc>
      <image:caption>The check no planner does. This sofa clears the front door and the hallway, then loses by 2 ft 10 in at the stair turn. The drawing stops at the pinch angle, because an animation that completed would be a lie about what happened on those stairs.</image:caption>
      <image:title>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_02_scan.png</image:loc>
      <image:caption>Walk the room with the camera. The meter fills as the floor, the walls and the ceiling settle, and the review screen shows what it actually found rather than what it hopes it found.</image:caption>
      <image:title>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_03_design.png</image:loc>
      <image:caption>Eight looks, every material, and a plan view that letters the measured length of each wall. Change the walls and the materials take-off follows, so the room's cost tracks the room rather than a guess.</image:caption>
      <image:title>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_04_furnish.png</image:loc>
      <image:caption>Every catalogue row says whether the piece can get in before you pick it, and a placed piece letters the gap from each side to whatever is nearest. Under 30 inches, the number turns terracotta.</image:caption>
      <image:title>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_06_play.png</image:loc>
      <image:caption>Your Sim walks the room you scanned, seen from behind with the roof off, or through its own eyes. Walls and furniture stop it, so the layout has to actually work rather than just look right from above.</image:caption>
      <image:title>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_07_mysim.png</image:loc>
      <image:caption>MY SIM sets build, height, skin, hair and clothes. Or one photo from the front camera, read on the phone for three colours and deleted the same second. Height matters here, because it changes what the room feels like.</image:caption>
      <image:title>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_08_pro.png</image:loc>
      <image:caption>Elbow Room Pro, built in the RevenueCat editor in the app's own palette. Every line on it maps to an entitlement check in code, and the lookup server asks RevenueCat before it spends anything on a search.</image:caption>
      <image:title>The couch that would not go down the stairs, and the phone that now says so before you buy it</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/elbow-room-mobile-arcore-scan-stair-turn/</loc>
    <lastmod>2026-09-24T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/elbow-room-mobile-arcore-scan-stair-turn/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/elbow-room-mobile-arcore-scan-stair-turn/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/elbow-room-mobile-arcore-scan-stair-turn/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_01_home.png</image:loc>
      <image:caption>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:caption>
      <image:title>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_05_fit.png</image:loc>
      <image:caption>La vérification qu'aucun planificateur ne fait. Ce divan passe la porte d'entrée et le corridor, puis perd de 2 pi 10 po au tournant d'escalier. Le dessin s'arrête à l'angle de pincement, parce qu'une animation qui se terminerait serait un mensonge sur ce qui est arrivé dans cet escalier.</image:caption>
      <image:title>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_02_scan.png</image:loc>
      <image:caption>Faites le tour de la pièce avec la caméra. La jauge se remplit à mesure que le plancher, les murs et le plafond se stabilisent, et l'écran de révision montre ce qu'il a vraiment trouvé plutôt que ce qu'il espère avoir trouvé.</image:caption>
      <image:title>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_03_design.png</image:loc>
      <image:caption>Huit styles, tous les matériaux, et une vue en plan qui inscrit la longueur mesurée de chaque mur. Changez les murs et le métré des matériaux suit, donc le coût de la pièce suit la pièce plutôt qu'une supposition.</image:caption>
      <image:title>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_04_furnish.png</image:loc>
      <image:caption>Chaque ligne du catalogue dit si le meuble peut entrer avant que vous le choisissiez, et un meuble placé inscrit l'écart entre chacun de ses côtés et ce qui est le plus proche. Sous 30 pouces, le chiffre devient terracotta.</image:caption>
      <image:title>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_06_play.png</image:loc>
      <image:caption>Votre Sim marche dans la pièce que vous avez numérisée, vue de derrière avec le toit enlevé, ou par ses propres yeux. Les murs et les meubles l'arrêtent, donc l'aménagement doit vraiment fonctionner et pas seulement bien paraître vu d'en haut.</image:caption>
      <image:title>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_07_mysim.png</image:loc>
      <image:caption>MY SIM règle la carrure, la taille, la peau, les cheveux et les vêtements. Ou une seule photo de la caméra avant, lue sur le téléphone pour trois couleurs et supprimée dans la même seconde. La taille compte ici, parce qu'elle change ce que la pièce donne.</image:caption>
      <image:title>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroommobile_08_pro.png</image:loc>
      <image:caption>Elbow Room Pro, construit dans l'éditeur RevenueCat dans la palette de l'application. Chaque ligne correspond à une vérification de droit d'accès dans le code, et le serveur de recherche interroge RevenueCat avant de dépenser quoi que ce soit sur une requête.</image:caption>
      <image:title>Le divan qui ne descendait pas l'escalier, et le téléphone qui le dit maintenant avant l'achat</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/sticker-pharmacy-cash-price-phone-agent/</loc>
    <lastmod>2026-09-13T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/sticker-pharmacy-cash-price-phone-agent/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/sticker-pharmacy-cash-price-phone-agent/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/sticker-pharmacy-cash-price-phone-agent/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_01_hero.png</image:loc>
      <image:caption>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:caption>
      <image:title>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_04_console.png</image:loc>
      <image:caption>The console running a simulated survey under its SAMPLE DATA banner: invented pharmacies, no calls placed, prices landing row by row. Labelled as simulated on the surface itself, so the sample board can never be mistaken for the real one.</image:caption>
      <image:title>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_02_board.png</image:loc>
      <image:caption>Fifteen real calls drawn as two-channel traces over 60 seconds. Magenta is the agent opening its turn while the pharmacist still speaks. The finding is visible without reading a word, and it is counted from the platform's own event stream, not from my impression of the audio.</image:caption>
      <image:title>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_03_reading.png</image:loc>
      <image:caption>Fifteen calls, seven people answered, zero prices, 24 collisions. Filed upstream as issue #415 with reproducible call IDs, where a CALL-E maintainer asked for examples the same day and is reproducing it.</image:caption>
      <image:title>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_05_refusal.jpg</image:loc>
      <image:caption>Four independent gates, each refusing on its own. Discovery prints masked numbers; the exact list goes only to a file a person edits. Every control fails closed, and every one is asserted by the 94-test suite.</image:caption>
      <image:title>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_07_merged.png</image:loc>
      <image:caption>Pull request #404 merged upstream into CALL-E's community repository: a runnable app and a reusable skill across 24 files. Merged, not drafted, which is the version of an upstream contribution that counts.</image:caption>
      <image:title>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_06_issue.jpg</image:loc>
      <image:caption>The turn-taking bug filed with reproducible call IDs. A CALL-E maintainer asked for examples the same day and is reproducing it. That is the sponsor's engineer investigating a bug their platform has, using evidence I supplied.</image:caption>
      <image:title>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_08_card.png</image:loc>
      <image:caption>Sixteen real calls, zero prices, and the measured reason why. One real trace, the four figures, nothing invented. I would rather be judged on that than on a board of prices a script put there.</image:caption>
      <image:title>Sixteen real calls to pharmacies, zero prices, and the measured reason why: the agent talks over the pharmacist</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/sticker-pharmacy-cash-price-phone-agent/</loc>
    <lastmod>2026-09-13T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/sticker-pharmacy-cash-price-phone-agent/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/sticker-pharmacy-cash-price-phone-agent/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/sticker-pharmacy-cash-price-phone-agent/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_01_hero.png</image:loc>
      <image:caption>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:caption>
      <image:title>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_04_console.png</image:loc>
      <image:caption>La console exécutant une enquête simulée sous sa bannière DONNÉES D'EXEMPLE : pharmacies inventées, aucun appel placé, prix atterrissant ligne par ligne. Étiqueté comme simulé sur la surface elle-même, pour que le tableau d'exemple ne puisse jamais être confondu avec le vrai.</image:caption>
      <image:title>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_02_board.png</image:loc>
      <image:caption>Quinze vrais appels dessinés comme des traces à deux canaux sur 60 secondes. Le magenta, c'est l'agent qui ouvre son tour pendant que le pharmacien parle encore. Le constat est visible sans lire un mot, et il est compté dans le flux d'événements de la plateforme, pas d'après mon impression de l'audio.</image:caption>
      <image:title>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_03_reading.png</image:loc>
      <image:caption>Quinze appels, sept personnes ont répondu, zéro prix, 24 collisions. Signalé en amont comme le problème no 415 avec des identifiants d'appel reproductibles, où un mainteneur de CALL-E a demandé des exemples le jour même et le reproduit.</image:caption>
      <image:title>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_05_refusal.jpg</image:loc>
      <image:caption>Quatre portes indépendantes, chacune refusant de son côté. La découverte affiche des numéros masqués ; la liste exacte ne va que dans un fichier qu'une personne édite. Chaque contrôle échoue fermé, et chacun est affirmé par la suite de 94 tests.</image:caption>
      <image:title>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_07_merged.png</image:loc>
      <image:caption>La demande de tirage no 404 fusionnée en amont dans le dépôt communautaire de CALL-E : une application exécutable et une compétence réutilisable sur 24 fichiers. Fusionnée, pas en brouillon, ce qui est la version d'une contribution en amont qui compte.</image:caption>
      <image:title>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_06_issue.jpg</image:loc>
      <image:caption>Le bogue de prise de parole signalé avec des identifiants d'appel reproductibles. Un mainteneur de CALL-E a demandé des exemples le jour même et le reproduit. C'est l'ingénieur du commanditaire qui enquête sur un bogue de sa plateforme, avec des preuves que j'ai fournies.</image:caption>
      <image:title>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sticker_08_card.png</image:loc>
      <image:caption>Seize vrais appels, zéro prix, et la raison mesurée. Une vraie trace, les quatre chiffres, rien d'inventé. Je préfère être jugé là-dessus plutôt que sur un tableau de prix qu'un script y aurait mis.</image:caption>
      <image:title>Seize vrais appels à des pharmacies, zéro prix, et la raison mesurée : l'agent parle par-dessus le pharmacien</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/day-thirty-california-insurance-appeal-agent/</loc>
    <lastmod>2026-09-13T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/day-thirty-california-insurance-appeal-agent/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/day-thirty-california-insurance-appeal-agent/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/day-thirty-california-insurance-appeal-agent/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_01_hero.png</image:loc>
      <image:caption>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:caption>
      <image:title>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_02_record.png</image:loc>
      <image:caption>California publishes every independent medical review since 2001. Overturns rose from 25 percent in 2001 to 72.34 percent in 2025, counted directly from the state's own file, not estimated.</image:caption>
      <image:title>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_03_deadline.png</image:loc>
      <image:caption>The deadline comes from the statute, not a model. Thirty days after the grievance the clock starts under HSC 1374.30(j)(3), six months under (k), every day counted the way the Civil Code says to count it.</image:caption>
      <image:title>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_04_precedent.png</image:loc>
      <image:caption>The overturn rate for denials like this one, counted over the state's own decisions, and what persuaded the reviewers in the cases that won. The rate is reported with its exact scope, because the first live run quoted a pharmacy-wide rate as if it were specific to one biologic.</image:caption>
      <image:title>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_05_draft.png</image:loc>
      <image:caption>Claude Haiku 4.5 on Amazon Bedrock drafts the appeal from those findings. Brackets mark the specifics only the patient can fill in. Grounding is checked mechanically: 18 of 18 numeric claims and 12 of 13 named authorities across 20 letters trace to the record, and a negative control catches 7 of 7 planted fabrications.</image:caption>
      <image:title>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_06_gate.png</image:loc>
      <image:caption>Then it stops. A Strands interrupt hands the exact letter to a person. Sign it, or don't. Nothing leaves without a signature, because you sign an appeal to file it.</image:caption>
      <image:title>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_07_approved.png</image:loc>
      <image:caption>Approved: the finished application, the date it must reach DMHC, and the real channels. There is no API, so the last step is yours. The software never claims to have filed.</image:caption>
      <image:title>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_08_agentcore.png</image:loc>
      <image:caption>The same agent on Amazon Bedrock AgentCore Runtime, an ARM64 container built by CodeBuild, so it works in the background rather than on a laptop. A real published denial in, 14.6 seconds round trip, stopped at the gate, nothing filed.</image:caption>
      <image:title>An insurer upholds its own denial two times in three. An independent doctor overturns it three times in four. Day Thirty is the appeal nobody files</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/day-thirty-california-insurance-appeal-agent/</loc>
    <lastmod>2026-09-13T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/day-thirty-california-insurance-appeal-agent/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/day-thirty-california-insurance-appeal-agent/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/day-thirty-california-insurance-appeal-agent/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_01_hero.png</image:loc>
      <image:caption>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:caption>
      <image:title>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_02_record.png</image:loc>
      <image:caption>La Californie publie chaque révision médicale indépendante depuis 2001. Les renversements sont passés de 25 pour cent en 2001 à 72,34 pour cent en 2025, comptés directement dans le fichier de l'État, pas estimés.</image:caption>
      <image:title>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_03_deadline.png</image:loc>
      <image:caption>Le délai vient de la loi, pas d'un modèle. Trente jours après le grief, l'horloge démarre sous HSC 1374.30(j)(3), six mois sous (k), chaque jour compté comme le Civil Code dit de le compter.</image:caption>
      <image:title>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_04_precedent.png</image:loc>
      <image:caption>Le taux de renversement pour des refus comme celui-ci, compté sur les propres décisions de l'État, et ce qui a convaincu les réviseurs dans les dossiers gagnés. Le taux est rapporté avec sa portée exacte, parce que la première exécution en direct a cité un taux pharmaceutique global comme s'il était propre à un seul biologique.</image:caption>
      <image:title>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_05_draft.png</image:loc>
      <image:caption>Claude Haiku 4.5 sur Amazon Bedrock rédige l'appel à partir de ces constats. Les crochets marquent les détails que seul le patient peut remplir. L'ancrage est vérifié mécaniquement : 18 affirmations numériques sur 18 et 12 autorités nommées sur 13 sur 20 lettres se retracent au dossier, et un contrôle négatif attrape 7 fabrications plantées sur 7.</image:caption>
      <image:title>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_06_gate.png</image:loc>
      <image:caption>Puis il s'arrête. Une interruption Strands remet la lettre exacte à une personne. Signez, ou non. Rien ne part sans signature, parce qu'on signe un appel pour le déposer.</image:caption>
      <image:title>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_07_approved.png</image:loc>
      <image:caption>Approuvé : la demande finie, la date à laquelle elle doit parvenir au DMHC, et les vrais canaux. Il n'y a pas d'API, donc la dernière étape vous appartient. Le logiciel ne prétend jamais avoir déposé.</image:caption>
      <image:title>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/daythirty_08_agentcore.png</image:loc>
      <image:caption>Le même agent sur Amazon Bedrock AgentCore Runtime, un conteneur ARM64 bâti par CodeBuild, pour qu'il travaille en arrière-plan plutôt que sur un portable. Un vrai refus publié en entrée, 14,6 secondes aller-retour, arrêté à la porte, rien de déposé.</image:caption>
      <image:title>Un assureur maintient son propre refus deux fois sur trois. Un médecin indépendant le renverse trois fois sur quatre. Day Thirty est l'appel que personne ne dépose</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/northbound-canadian-cross-border-cabotage-load-board/</loc>
    <lastmod>2026-09-11T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/northbound-canadian-cross-border-cabotage-load-board/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/northbound-canadian-cross-border-cabotage-load-board/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/northbound-canadian-cross-border-cabotage-load-board/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_01_hero.png</image:loc>
      <image:caption>A load board that knows which US freight a Canadian truck is legally allowed to haul. Built on Roadstar's own dispatch history, not on a synthetic market.</image:caption>
      <image:title>Your Canadian truck already drove that lane empty. Northbound finds the freight it could legally have carried</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_02_their_data.png</image:loc>
      <image:caption>Where the empty miles really are. In 10,479 real legs, coming home empty happened six times. Repositioning inside the US: 778. That is the pool the legal engine works on.</image:caption>
      <image:title>Your Canadian truck already drove that lane empty. Northbound finds the freight it could legally have carried</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_03_the_rule.png</image:loc>
      <image:caption>The rule the whole engine turns on, verified against two independent primary sources (law.cornell.edu and govinfo) and tabulated in LEGAL.md. The verdict turns on direction, not distance.</image:caption>
      <image:title>Your Canadian truck already drove that lane empty. Northbound finds the freight it could legally have carried</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_04_headline.png</image:loc>
      <image:caption>This is not a projection. Every leg happened, the miles are theirs, and the cost rate is ATRI's public 2025 number. Northbound counted what was already legal and already paid for in fuel.</image:caption>
      <image:title>Your Canadian truck already drove that lane empty. Northbound finds the freight it could legally have carried</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_05_amber_verdict.png</image:loc>
      <image:caption>Paste the offer as it arrived. GLM 5.2 on SPUR reads it, the regulation decides. Fairburn GA to Walton KY renders amber because it is heading home.</image:caption>
      <image:title>Your Canadian truck already drove that lane empty. Northbound finds the freight it could legally have carried</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_06_red_refusal.png</image:loc>
      <image:caption>The refusal. Columbus OH to Atlanta GA moves away from base, so it is red. Northbound names the exposure by regulation, not just by feel.</image:caption>
      <image:title>Your Canadian truck already drove that lane empty. Northbound finds the freight it could legally have carried</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_07_board.png</image:loc>
      <image:caption>34 real trucks, two rulebooks, live CBP waits. Driver 84 has 53 hours on the US cycle and 11 the moment he crosses at Bluewater.</image:caption>
      <image:title>Your Canadian truck already drove that lane empty. Northbound finds the freight it could legally have carried</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_08_ablation.png</image:loc>
      <image:caption>Ablation, not assertion. The gap between 100 percent and 40 percent is what GLM 5.2 on SPUR contributes to the verdict on messy freight offers, measured by a script the sponsor can run.</image:caption>
      <image:title>Your Canadian truck already drove that lane empty. Northbound finds the freight it could legally have carried</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/northbound-canadian-cross-border-cabotage-load-board/</loc>
    <lastmod>2026-09-11T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/northbound-canadian-cross-border-cabotage-load-board/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/northbound-canadian-cross-border-cabotage-load-board/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/northbound-canadian-cross-border-cabotage-load-board/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_01_hero.png</image:loc>
      <image:caption>Un tableau de chargements qui sait quel fret américain un camion canadien a le droit de transporter. Bâti sur l'historique de répartition de Roadstar, pas sur un marché synthétique.</image:caption>
      <image:title>Votre camion canadien a déjà roulé cette voie à vide. Northbound trouve le fret qu'il aurait pu légalement transporter</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_02_their_data.png</image:loc>
      <image:caption>Où sont vraiment les miles à vide. Sur 10 479 trajets réels, revenir à vide est arrivé six fois. Repositionnement à l'intérieur des États-Unis : 778. C'est le bassin sur lequel le moteur juridique travaille.</image:caption>
      <image:title>Votre camion canadien a déjà roulé cette voie à vide. Northbound trouve le fret qu'il aurait pu légalement transporter</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_03_the_rule.png</image:loc>
      <image:caption>La règle dont dépend tout le moteur, vérifiée contre deux sources primaires indépendantes (law.cornell.edu et govinfo) et tabulée dans LEGAL.md. Le verdict dépend de la direction, pas de la distance.</image:caption>
      <image:title>Votre camion canadien a déjà roulé cette voie à vide. Northbound trouve le fret qu'il aurait pu légalement transporter</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_04_headline.png</image:loc>
      <image:caption>Ce n'est pas une projection. Chaque trajet a eu lieu, les miles sont les leurs et le taux de coût est le chiffre 2025 public de l'ATRI. Northbound a compté ce qui était déjà légal et déjà payé en carburant.</image:caption>
      <image:title>Votre camion canadien a déjà roulé cette voie à vide. Northbound trouve le fret qu'il aurait pu légalement transporter</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_05_amber_verdict.png</image:loc>
      <image:caption>Collez l'offre telle qu'elle est arrivée. GLM 5.2 sur SPUR la lit, le règlement décide. Fairburn GA vers Walton KY affiche ambre parce que le trajet va vers la maison.</image:caption>
      <image:title>Votre camion canadien a déjà roulé cette voie à vide. Northbound trouve le fret qu'il aurait pu légalement transporter</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_06_red_refusal.png</image:loc>
      <image:caption>Le refus. Columbus OH vers Atlanta GA s'éloigne de la base, donc c'est rouge. Northbound nomme l'exposition par règlement, pas seulement au ressenti.</image:caption>
      <image:title>Votre camion canadien a déjà roulé cette voie à vide. Northbound trouve le fret qu'il aurait pu légalement transporter</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_07_board.png</image:loc>
      <image:caption>34 camions réels, deux règlements, attentes CBP en direct. Le chauffeur 84 a 53 heures sur le cycle américain et 11 au moment où il franchit à Bluewater.</image:caption>
      <image:title>Votre camion canadien a déjà roulé cette voie à vide. Northbound trouve le fret qu'il aurait pu légalement transporter</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/northbound_08_ablation.png</image:loc>
      <image:caption>Ablation, pas affirmation. L'écart entre 100 pour cent et 40 pour cent est ce que GLM 5.2 sur SPUR apporte au verdict sur des offres de fret désordonnées, mesuré par un script que le commanditaire peut exécuter.</image:caption>
      <image:title>Votre camion canadien a déjà roulé cette voie à vide. Northbound trouve le fret qu'il aurait pu légalement transporter</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/dailies-smart-glasses-continuity-clickhouse-agentic-cinema/</loc>
    <lastmod>2026-09-08T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/dailies-smart-glasses-continuity-clickhouse-agentic-cinema/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/dailies-smart-glasses-continuity-clickhouse-agentic-cinema/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/dailies-smart-glasses-continuity-clickhouse-agentic-cinema/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_01_the_catch.jpg</image:loc>
      <image:caption>The catch, called live at 0:49 in the demo. Finding the mug on the wrong side at wrap documents a reshoot. Finding it twenty seconds into the take prevents one. Median verdict on the deployed service is 4.4 seconds, and it is spoken into the wearer's ear through the glasses' own speakers because a script supervisor is watching the scene, not a screen.</image:caption>
      <image:title>A camera assistant wears smart glasses through a shoot and walks off set with the continuity paperwork already written</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_02_glasses_worn.jpg</image:loc>
      <image:caption>The capture side is a Kotlin + Jetpack Compose app on the Meta Wearables Device Access Toolkit, which is what makes the wearer's point of view reachable from my own code rather than only from Meta's app. The system also runs from any webcam in a browser, so a judge can test it without owning the hardware.</image:caption>
      <image:title>A camera assistant wears smart glasses through a shoot and walks off set with the continuity paperwork already written</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_03_findings.jpg</image:loc>
      <image:caption>The findings the system extracted on real footage: six across five takes, seven earlier fakes down to zero after reconciliation. This is not a scored benchmark, it is the artifact the crew walks off set with. The scoreable claim in the deployment is the cost (1.6 cents a take, 63 cents a day) and the median latency (4.4 s on the rolling view), both measured on real footage through the same Vertex AI path.</image:caption>
      <image:title>A camera assistant wears smart glasses through a shoot and walks off set with the continuity paperwork already written</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_07_clickhouse.png</image:loc>
      <image:caption>The receipt: row counts read fine, every write path refused with ClickHouse code 497. This is on the live judged cluster, run as the same credential the MCP subprocess uses. The report and the reality cannot disagree because /api/capabilities reads from the same function that builds the subprocess environment.</image:caption>
      <image:title>A camera assistant wears smart glasses through a shoot and walks off set with the continuity paperwork already written</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_04_agent_writes_sql.png</image:loc>
      <image:caption>The question box in action. The plain English sentence goes in, the agent composes SQL against a live ClickHouse Cloud cluster through the official mcp-clickhouse server, and every statement it ran is shown. The model is the caller, the MCP server is the authority, and neither can insert or delete a row.</image:caption>
      <image:title>A camera assistant wears smart glasses through a shoot and walks off set with the continuity paperwork already written</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_05_asks_about_itself.jpg</image:loc>
      <image:caption>The same instrument turned on itself. The question box that answers 'which takes is the smartphone missing from' also answers 'how fast is the rolling check' or 'which model answered the last question', because agent_runs is a table in the same cluster and the SQL agent can select from it. No admin dashboard, no separate observability stack. One box.</image:caption>
      <image:title>A camera assistant wears smart glasses through a shoot and walks off set with the continuity paperwork already written</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_08_architecture.png</image:loc>
      <image:caption>Every arrow in that diagram runs in the deployed service. Nothing in it is planned work. The verdict path closes the loop back to the glasses' own speakers, because a script supervisor is watching the scene rather than a screen.</image:caption>
      <image:title>A camera assistant wears smart glasses through a shoot and walks off set with the continuity paperwork already written</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/dailies-smart-glasses-continuity-clickhouse-agentic-cinema/</loc>
    <lastmod>2026-09-08T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/dailies-smart-glasses-continuity-clickhouse-agentic-cinema/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/dailies-smart-glasses-continuity-clickhouse-agentic-cinema/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/dailies-smart-glasses-continuity-clickhouse-agentic-cinema/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_01_the_catch.jpg</image:loc>
      <image:caption>Le moment clé, appelé en direct à 0:49 dans la démo. Trouver la tasse du mauvais côté à la fin documente une reprise. La trouver vingt secondes après le début en évite une. Le verdict médian sur le service déployé est de 4,4 secondes, prononcé dans l'oreille du porteur par les haut-parleurs des lunettes, parce qu'une scripte regarde la scène, pas un écran.</image:caption>
      <image:title>Un assistant caméra porte des lunettes intelligentes pendant un tournage et quitte le plateau avec la feuille de continuité déjà écrite</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_02_glasses_worn.jpg</image:loc>
      <image:caption>La partie capture est une application Kotlin + Jetpack Compose sur le Meta Wearables Device Access Toolkit, ce qui rend le point de vue du porteur accessible depuis mon propre code plutôt que seulement depuis l'app de Meta. Le système fonctionne aussi depuis n'importe quelle webcam dans un navigateur, donc un juge peut le tester sans posséder le matériel.</image:caption>
      <image:title>Un assistant caméra porte des lunettes intelligentes pendant un tournage et quitte le plateau avec la feuille de continuité déjà écrite</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_03_findings.jpg</image:loc>
      <image:caption>Les constats extraits sur du vrai métrage : six sur cinq prises, sept faux positifs plus tôt ramenés à zéro après réconciliation. Ce n'est pas un score, c'est l'artéfact avec lequel l'équipe quitte le plateau. La revendication mesurable dans le déploiement est le coût (1,6 cent par prise, 63 cents par jour) et la latence médiane (4,4 s sur la vue en direct), les deux mesurés sur du vrai métrage via le même chemin Vertex AI.</image:caption>
      <image:title>Un assistant caméra porte des lunettes intelligentes pendant un tournage et quitte le plateau avec la feuille de continuité déjà écrite</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_07_clickhouse.png</image:loc>
      <image:caption>Le reçu : les comptes de lignes se lisent bien, chaque chemin d'écriture est refusé avec le code ClickHouse 497. C'est sur le cluster jugé en direct, exécuté avec le même compte que celui utilisé par le sous-processus MCP. Le rapport et la réalité ne peuvent pas diverger parce que /api/capabilities lit depuis la même fonction qui construit l'environnement du sous-processus.</image:caption>
      <image:title>Un assistant caméra porte des lunettes intelligentes pendant un tournage et quitte le plateau avec la feuille de continuité déjà écrite</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_04_agent_writes_sql.png</image:loc>
      <image:caption>La boîte à questions en action. La phrase en anglais simple entre, l'agent compose du SQL contre un cluster ClickHouse Cloud en direct via le serveur officiel mcp-clickhouse, et chaque instruction exécutée est affichée. Le modèle est l'appelant, le serveur MCP est l'autorité, et aucun des deux ne peut insérer ou supprimer une ligne.</image:caption>
      <image:title>Un assistant caméra porte des lunettes intelligentes pendant un tournage et quitte le plateau avec la feuille de continuité déjà écrite</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_05_asks_about_itself.jpg</image:loc>
      <image:caption>Le même instrument retourné sur lui-même. La boîte à questions qui répond à 'dans quelles prises le smartphone est-il absent' répond aussi à 'à quelle vitesse répond la vérification en direct' ou 'quel modèle a traité la dernière question', parce que agent_runs est une table du même cluster et que l'agent SQL peut y faire un SELECT. Pas de tableau admin, pas de pile d'observabilité séparée. Une boîte.</image:caption>
      <image:title>Un assistant caméra porte des lunettes intelligentes pendant un tournage et quitte le plateau avec la feuille de continuité déjà écrite</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/dailies_08_architecture.png</image:loc>
      <image:caption>Chaque flèche de ce diagramme tourne dans le service déployé. Rien dedans n'est du travail planifié. Le chemin du verdict boucle jusqu'aux haut-parleurs des lunettes, parce qu'une scripte regarde la scène, pas un écran.</image:caption>
      <image:title>Un assistant caméra porte des lunettes intelligentes pendant un tournage et quitte le plateau avec la feuille de continuité déjà écrite</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/elbow-room-webmcp-canvas-spatial-planner/</loc>
    <lastmod>2026-09-02T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/elbow-room-webmcp-canvas-spatial-planner/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/elbow-room-webmcp-canvas-spatial-planner/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/elbow-room-webmcp-canvas-spatial-planner/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_01_it_does_not_go.png</image:loc>
      <image:caption>The verdict, in the units the tape measure gave. The turn allows 3 ft 9 1/2 in at that depth and the couch is 7 ft 7 in, more than twice the longest thing that corner will ever pass. Colouring by touch rather than by fit is the water-heater lesson: a thing can technically fit and still gouge the wall on the way through.</image:caption>
      <image:title>The couch took some of my basement wall on the way out. I built the planner nobody sold me</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_04_heater_goes.png</image:loc>
      <image:caption>The tank fits. It also cleared the turn with 3 ft 9 1/2 in to spare in real life and still gouged the wall going down, which is why the 3D view colours by touch and not by fit. Fitting and fitting without touching anything are two different answers.</image:caption>
      <image:title>The couch took some of my basement wall on the way out. I built the planner nobody sold me</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_05_four_corners.jpg</image:loc>
      <image:caption>Single-image metric rectification. Four corners of something whose real size you know become a homography; every pixel on that floor becomes inches. No GPU, no upload, no server. The agent walks you through it and files each reading with save_reading, so the planner then runs against your house instead of mine.</image:caption>
      <image:title>The couch took some of my basement wall on the way out. I built the planner nobody sold me</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_08_tools_themselves.png</image:loc>
      <image:caption>19 tools across three pages. 15 in the planner (six reads with readOnlyHint, six writes, record_measurement as the consequential one behind requestUserInteraction, two that register only while something is failing and unregister via AbortSignal when it stops). 4 in the measuring page. 1 shared across origins, read-only. Names under 30 chars, descriptions under 500, outputs capped at 1.5K, zero violations.</image:caption>
      <image:title>The couch took some of my basement wall on the way out. I built the planner nobody sold me</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_07_different_origin.png</image:loc>
      <image:caption>The furniture shop asks, my staircase answers, both sides consented by name. No account, no API key, no SDK, no callback URL. The whole handshake is two matching origin strings and one browser permission attribute.</image:caption>
      <image:title>The couch took some of my basement wall on the way out. I built the planner nobody sold me</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/elbow-room-webmcp-canvas-spatial-planner/</loc>
    <lastmod>2026-09-02T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/elbow-room-webmcp-canvas-spatial-planner/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/elbow-room-webmcp-canvas-spatial-planner/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/elbow-room-webmcp-canvas-spatial-planner/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_01_it_does_not_go.png</image:loc>
      <image:caption>Le verdict, dans les unités que le ruban à mesurer a données. Le virage permet 3 pi 9 1/2 po à cette profondeur et le canapé fait 7 pi 7 po, plus du double de ce que ce coin passera jamais. La coloration par contact plutôt que par ajustement vient de la leçon du chauffe-eau : une chose peut techniquement rentrer et emporter le mur au passage.</image:caption>
      <image:title>Le canapé a emporté un morceau du mur du sous-sol en sortant. J'ai construit le planificateur que personne ne m'a vendu</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_04_heater_goes.png</image:loc>
      <image:caption>Le réservoir passe. Il a aussi passé le virage avec 3 pi 9 1/2 po de marge dans la vraie vie et a quand même emporté le mur en descendant, ce qui explique pourquoi la vue 3D colore par contact et non par ajustement. Rentrer et rentrer sans rien toucher sont deux réponses différentes.</image:caption>
      <image:title>Le canapé a emporté un morceau du mur du sous-sol en sortant. J'ai construit le planificateur que personne ne m'a vendu</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_05_four_corners.jpg</image:loc>
      <image:caption>Rectification métrique d'une seule image. Quatre coins de quelque chose dont vous connaissez la vraie taille deviennent une homographie ; chaque pixel de ce sol devient des pouces. Pas de GPU, pas d'upload, pas de serveur. L'agent vous accompagne et enregistre chaque mesure avec save_reading, et le planificateur tourne alors contre votre maison au lieu de la mienne.</image:caption>
      <image:title>Le canapé a emporté un morceau du mur du sous-sol en sortant. J'ai construit le planificateur que personne ne m'a vendu</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_08_tools_themselves.png</image:loc>
      <image:caption>19 outils sur trois pages. 15 dans le planificateur (six lectures avec readOnlyHint, six écritures, record_measurement comme la consécutive derrière requestUserInteraction, deux qui ne s'enregistrent que pendant qu'il y a un échec et se désenregistrent via AbortSignal quand ça s'arrête). 4 dans la page de mesure. 1 partagé entre origines, en lecture seule. Noms sous 30 caractères, descriptions sous 500, sorties plafonnées à 1,5 Ko, zéro violation.</image:caption>
      <image:title>Le canapé a emporté un morceau du mur du sous-sol en sortant. J'ai construit le planificateur que personne ne m'a vendu</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/elbowroom_07_different_origin.png</image:loc>
      <image:caption>La boutique de meubles demande, mon escalier répond, les deux côtés ont consenti par leur nom. Pas de compte, pas de clé API, pas de SDK, pas d'URL de rappel. Toute la poignée de main tient dans deux chaînes d'origine concordantes et un attribut de permission du navigateur.</image:caption>
      <image:title>Le canapé a emporté un morceau du mur du sous-sol en sortant. J'ai construit le planificateur que personne ne m'a vendu</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/servicenow-real-client-catalog-flow-designer-now-assist-udemy/</loc>
    <lastmod>2026-09-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/servicenow-real-client-catalog-flow-designer-now-assist-udemy/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/servicenow-real-client-catalog-flow-designer-now-assist-udemy/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/servicenow-real-client-catalog-flow-designer-now-assist-udemy/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/udemy-servicenow-hero.png</image:loc>
      <image:caption>I just published my first Udemy course: the ServiceNow catalog build I've done twenty times, plus the Now Assist Skill Kit end to end</image:caption>
      <image:title>I just published my first Udemy course: the ServiceNow catalog build I've done twenty times, plus the Now Assist Skill Kit end to end</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/udemy-servicenow-catalog.png</image:loc>
      <image:caption>The very first thing the course builds, live on a Personal Developer Instance. Name, catalog, category, taxonomy, portal settings, then the two variables the flow will read. Nothing hidden, everything shown as it happens.</image:caption>
      <image:title>I just published my first Udemy course: the ServiceNow catalog build I've done twenty times, plus the Now Assist Skill Kit end to end</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/udemy-servicenow-nowassist.png</image:loc>
      <image:caption>The exact click a lot of Now Assist tutorials skip: choosing the table your new skill is allowed to be launched from. Miss this and the skill publishes fine and is invisible everywhere it should be reachable. This is one of the clicks I would not have found without spending a full day on it.</image:caption>
      <image:title>I just published my first Udemy course: the ServiceNow catalog build I've done twenty times, plus the Now Assist Skill Kit end to end</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/servicenow-real-client-catalog-flow-designer-now-assist-udemy/</loc>
    <lastmod>2026-09-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/servicenow-real-client-catalog-flow-designer-now-assist-udemy/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/servicenow-real-client-catalog-flow-designer-now-assist-udemy/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/servicenow-real-client-catalog-flow-designer-now-assist-udemy/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/udemy-servicenow-hero.png</image:loc>
      <image:caption>Je viens de publier mon premier cours Udemy : la construction de catalogue ServiceNow que j'ai faite vingt fois, plus le Now Assist Skill Kit de bout en bout</image:caption>
      <image:title>Je viens de publier mon premier cours Udemy : la construction de catalogue ServiceNow que j'ai faite vingt fois, plus le Now Assist Skill Kit de bout en bout</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/udemy-servicenow-catalog.png</image:loc>
      <image:caption>La toute première chose que le cours construit, en direct sur une Personal Developer Instance. Nom, catalogue, catégorie, taxonomie, réglages du portail, puis les deux variables que le flux va lire. Rien de caché, tout est montré au moment où ça arrive.</image:caption>
      <image:title>Je viens de publier mon premier cours Udemy : la construction de catalogue ServiceNow que j'ai faite vingt fois, plus le Now Assist Skill Kit de bout en bout</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/udemy-servicenow-nowassist.png</image:loc>
      <image:caption>Le clic exact que beaucoup de tutoriels Now Assist sautent : choisir la table depuis laquelle votre nouvelle compétence peut être lancée. Ratez cela et la compétence se publie sans problème et reste invisible partout où elle devrait être accessible. C'est un des clics que je n'aurais pas trouvés sans y passer une journée complète.</image:caption>
      <image:title>Je viens de publier mon premier cours Udemy : la construction de catalogue ServiceNow que j'ai faite vingt fois, plus le Now Assist Skill Kit de bout en bout</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/nightshift-agentic-prior-art-search/</loc>
    <lastmod>2026-08-28T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/nightshift-agentic-prior-art-search/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/nightshift-agentic-prior-art-search/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/nightshift-agentic-prior-art-search/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_01_home.png</image:loc>
      <image:caption>Public reading, gated spending. Every finished run and claim chart is open with no key and no sign-in. Starting a new search is the one action that costs real money (2,000 patents read by Gemini, about $34), so it takes two independent limits: a token gate on the run endpoint, and a daily-ceiling counter claimed in a Firestore transaction so two concurrent presses cannot both pass. The cost of a run is the central design constraint of the whole system.</image:caption>
      <image:title>Nightshift reads 2,000 patents in 4 minutes for $34 and finds the reference the examiner missed</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_04_core_log.png</image:loc>
      <image:caption>Nightshift is a judgment system. A vector pass narrows 171,695 patents down to candidates, then Gemini reads two thousand of them, not fifty, deciding for each one whether it discloses each limitation of the asserted claim. Cloud Run Jobs fans the work out across 10 tasks with `MOD(rank, TASK_COUNT) = TASK_INDEX` so the load balances by construction.</image:caption>
      <image:title>Nightshift reads 2,000 patents in 4 minutes for $34 and finds the reference the examiner missed</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_07_accuracy.png</image:loc>
      <image:caption>This is recall against the examiner, not against ground truth. An examiner's own search recall is itself 45 to 85%, so every reference Nightshift finds that the examiner missed scores here as a miss. The number is a floor on performance, not an estimate of it.</image:caption>
      <image:title>Nightshift reads 2,000 patents in 4 minutes for $34 and finds the reference the examiner missed</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_05_chart_examiner.png</image:loc>
      <image:caption>The chart is limitation-by-limitation with pin cites into the reference. This is the artifact the attorney reads: for each claim limitation, which passage of the reference discloses it, and if a passage does not exist, that gap is named too.</image:caption>
      <image:title>Nightshift reads 2,000 patents in 4 minutes for $34 and finds the reference the examiner missed</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_06_chart_missed.png</image:loc>
      <image:caption>Depth 1,129 is past every shortlist a person is ever shown. This is the reason the whole system has to read further than a top-50 list: the reference that teaches the most of the claim was not the reference the examiner used.</image:caption>
      <image:title>Nightshift reads 2,000 patents in 4 minutes for $34 and finds the reference the examiner missed</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_08_architecture.png</image:loc>
      <image:caption>Corpus and prefilter on BigQuery (171,695 CPC G06Q publications, clustered on patent id). Model: Gemini 3.5 Flash on Vertex AI. Fan-out workers: Cloud Run Jobs. Work distribution: Cloud Run task index. Run state: Firestore. The whole system is designed around the cost of a run being the central constraint, not an afterthought.</image:caption>
      <image:title>Nightshift reads 2,000 patents in 4 minutes for $34 and finds the reference the examiner missed</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/nightshift-agentic-prior-art-search/</loc>
    <lastmod>2026-08-28T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/nightshift-agentic-prior-art-search/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/nightshift-agentic-prior-art-search/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/nightshift-agentic-prior-art-search/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_01_home.png</image:loc>
      <image:caption>Lecture publique, dépense encadrée. Chaque exécution terminée et chaque tableau de revendications est ouvert sans clé ni connexion. Démarrer une nouvelle recherche est la seule action qui coûte du vrai argent (2 000 brevets lus par Gemini, environ 34 $), donc il faut deux limites indépendantes : une porte à jeton sur l'endpoint d'exécution, et un compteur de plafond quotidien réclamé dans une transaction Firestore pour que deux pressions concurrentes ne puissent pas passer toutes les deux. Le coût d'une exécution est la contrainte de conception centrale de tout le système.</image:caption>
      <image:title>Nightshift lit 2 000 brevets en 4 minutes pour 34 $ et trouve la référence que l'examinateur a manquée</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_04_core_log.png</image:loc>
      <image:caption>Nightshift est un système de jugement. Une passe vectorielle réduit 171 695 brevets à des candidats, puis Gemini en lit deux mille, pas cinquante, décidant pour chacun s'il divulgue chaque limitation de la revendication invoquée. Cloud Run Jobs répartit le travail sur 10 tâches avec `MOD(rank, TASK_COUNT) = TASK_INDEX` pour que la charge s'équilibre par construction.</image:caption>
      <image:title>Nightshift lit 2 000 brevets en 4 minutes pour 34 $ et trouve la référence que l'examinateur a manquée</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_07_accuracy.png</image:loc>
      <image:caption>C'est un rappel contre l'examinateur, pas contre une vérité terrain. Le rappel de recherche d'un examinateur est lui-même de 45 à 85 %, donc chaque référence que Nightshift trouve et que l'examinateur a manquée compte ici comme un manqué. Le chiffre est un plancher de performance, pas une estimation.</image:caption>
      <image:title>Nightshift lit 2 000 brevets en 4 minutes pour 34 $ et trouve la référence que l'examinateur a manquée</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_05_chart_examiner.png</image:loc>
      <image:caption>Le tableau est limitation par limitation avec des citations précises dans la référence. C'est l'artefact que l'avocat lit : pour chaque limitation de revendication, quel passage de la référence la divulgue, et si un passage n'existe pas, ce trou est aussi nommé.</image:caption>
      <image:title>Nightshift lit 2 000 brevets en 4 minutes pour 34 $ et trouve la référence que l'examinateur a manquée</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_06_chart_missed.png</image:loc>
      <image:caption>Profondeur 1 129 est au-delà de toute liste courte qu'on montre à un humain. C'est la raison pour laquelle tout le système doit lire plus loin qu'un top-50 : la référence qui enseigne le plus de la revendication n'était pas celle utilisée par l'examinateur.</image:caption>
      <image:title>Nightshift lit 2 000 brevets en 4 minutes pour 34 $ et trouve la référence que l'examinateur a manquée</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/nightshift_08_architecture.png</image:loc>
      <image:caption>Corpus et préfiltre sur BigQuery (171 695 publications CPC G06Q, regroupées par identifiant de brevet). Modèle : Gemini 3.5 Flash sur Vertex AI. Workers de fan-out : Cloud Run Jobs. Distribution du travail : index de tâche Cloud Run. État d'exécution : Firestore. Tout le système est conçu autour du coût d'une exécution comme contrainte centrale, pas comme réflexion tardive.</image:caption>
      <image:title>Nightshift lit 2 000 brevets en 4 minutes pour 34 $ et trouve la référence que l'examinateur a manquée</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/sentry-python-dedupe-memory-leak-weakref-fix/</loc>
    <lastmod>2026-09-25T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/sentry-python-dedupe-memory-leak-weakref-fix/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/sentry-python-dedupe-memory-leak-weakref-fix/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/sentry-python-dedupe-memory-leak-weakref-fix/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_02_the_comment.png</image:loc>
      <image:caption>The SDK's own comment narrates what is about to go wrong. `weakref.ref(exc)` raises TypeError when exc is a builtin (ValueError, KeyError, TypeError, the ones almost every real error actually is), and the fallback stores the exception itself. That is the only line the weakref existed to prevent.</image:caption>
      <image:title>Python Can't Weakref a ValueError. Sentry's Deduplication Found Out the Expensive Way: 1024 KB per Live Asyncio Task.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_01_leak_master.png</image:loc>
      <image:caption>1024 KB retained per live task, dead straight, and `sessions pinned` counts weak references to session objects that are still reachable after gc.collect(). Not a sampling artifact. The garbage collector cannot touch them because a live ContextVar genuinely still points at every one. At 200 concurrent sessions that is 205 MB that never comes back.</image:caption>
      <image:title>Python Can't Weakref a ValueError. Sentry's Deduplication Found Out the Expensive Way: 1024 KB per Live Asyncio Task.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_03_fingerprint_fails.png</image:loc>
      <image:caption>That is the flaw in the whole idea. A value fingerprint cannot tell 'the same exception object twice' apart from 'the same error raised repeatedly from the same line.' The first must deduplicate. The second must not. No fingerprint distinguishes them, because by value they are the same.</image:caption>
      <image:title>Python Can't Weakref a ValueError. Sentry's Deduplication Found Out the Expensive Way: 1024 KB per Live Asyncio Task.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_04_id_reuse.png</image:loc>
      <image:caption>2000 distinct errors collapse into two fingerprints. Each collision is a real error dropped as a duplicate. The identity-token approach avoids this by construction: the token is a real object whose lifetime is tied to the exception, so it cannot be confused with a later object at the same address.</image:caption>
      <image:title>Python Can't Weakref a ValueError. Sentry's Deduplication Found Out the Expensive Way: 1024 KB per Live Asyncio Task.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_05_the_token.png</image:loc>
      <image:caption>The whole fix. A five-byte-of-slots token exists just to be weak-referenceable so the ContextVar can stop holding the traceback. Behaviour is unchanged; the ContextVar no longer keeps a frame local alive.</image:caption>
      <image:title>Python Can't Weakref a ValueError. Sentry's Deduplication Found Out the Expensive Way: 1024 KB per Live Asyncio Task.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_06_leak_patched.png</image:loc>
      <image:caption>Same two hundred sessions. Two megabytes total, nothing pinned. Retention no longer scales with live task count.</image:caption>
      <image:title>Python Can't Weakref a ValueError. Sentry's Deduplication Found Out the Expensive Way: 1024 KB per Live Asyncio Task.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_07_seer_rootcause.jpg</image:loc>
      <image:caption>Seer's root cause is exact. Its proposed patch is wrong for the same reason attempt 2 was: id() gets reused, so 2000 distinct errors collapse to 2 fingerprints. Worth noting both together: the diagnosis and the patch are not the same problem, and getting one right does not get you the other.</image:caption>
      <image:title>Python Can't Weakref a ValueError. Sentry's Deduplication Found Out the Expensive Way: 1024 KB per Live Asyncio Task.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/sentry-python-dedupe-memory-leak-weakref-fix/</loc>
    <lastmod>2026-09-25T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/sentry-python-dedupe-memory-leak-weakref-fix/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/sentry-python-dedupe-memory-leak-weakref-fix/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/sentry-python-dedupe-memory-leak-weakref-fix/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_02_the_comment.png</image:loc>
      <image:caption>Le propre commentaire du SDK raconte ce qui est sur le point de mal tourner. `weakref.ref(exc)` lève TypeError quand exc est un builtin (ValueError, KeyError, TypeError, celles que presque chaque vraie erreur est en pratique), et le fallback retient l'exception elle-même. C'est la seule ligne que la weakref existait pour empêcher.</image:caption>
      <image:title>Python ne peut pas prendre une weakref sur un ValueError. La déduplication de Sentry l'a appris à ses dépens : 1024 Ko par tâche asyncio vivante.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_01_leak_master.png</image:loc>
      <image:caption>1024 Ko retenus par tâche vivante, tout droit, et `sessions pinned` compte les weakrefs vers des objets session encore atteignables après gc.collect(). Pas un artefact d'échantillonnage. Le garbage collector ne peut pas les toucher parce qu'un ContextVar vivant pointe encore sur chacune. À 200 sessions concurrentes c'est 205 Mo qui ne reviennent jamais.</image:caption>
      <image:title>Python ne peut pas prendre une weakref sur un ValueError. La déduplication de Sentry l'a appris à ses dépens : 1024 Ko par tâche asyncio vivante.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_03_fingerprint_fails.png</image:loc>
      <image:caption>C'est le défaut de l'idée entière. Une empreinte par valeur ne peut pas distinguer « le même objet exception deux fois » de « la même erreur levée à répétition depuis la même ligne ». La première doit dédupliquer. La seconde non. Aucune empreinte ne les distingue, parce que par valeur elles sont identiques.</image:caption>
      <image:title>Python ne peut pas prendre une weakref sur un ValueError. La déduplication de Sentry l'a appris à ses dépens : 1024 Ko par tâche asyncio vivante.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_04_id_reuse.png</image:loc>
      <image:caption>2000 erreurs distinctes s'effondrent en deux empreintes. Chaque collision est une vraie erreur supprimée comme doublon. L'approche du jeton d'identité évite ça par construction : le jeton est un vrai objet dont la durée de vie est liée à l'exception, donc il ne peut pas être confondu avec un objet ultérieur à la même adresse.</image:caption>
      <image:title>Python ne peut pas prendre une weakref sur un ValueError. La déduplication de Sentry l'a appris à ses dépens : 1024 Ko par tâche asyncio vivante.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_05_the_token.png</image:loc>
      <image:caption>Tout le correctif. Un jeton de cinq octets de slots existe juste pour être weakréférençable pour que le ContextVar cesse de tenir la traceback. Le comportement est inchangé ; le ContextVar ne garde plus un local de frame vivant.</image:caption>
      <image:title>Python ne peut pas prendre une weakref sur un ValueError. La déduplication de Sentry l'a appris à ses dépens : 1024 Ko par tâche asyncio vivante.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_06_leak_patched.png</image:loc>
      <image:caption>Mêmes deux cents sessions. Deux mégaoctets au total, rien d'épinglé. La rétention ne monte plus avec le nombre de tâches vivantes.</image:caption>
      <image:title>Python ne peut pas prendre une weakref sur un ValueError. La déduplication de Sentry l'a appris à ses dépens : 1024 Ko par tâche asyncio vivante.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sentrydedupe_07_seer_rootcause.jpg</image:loc>
      <image:caption>La cause racine de Seer est exacte. Son patch proposé est faux pour la même raison que la tentative 2 : id() est réutilisé, donc 2000 erreurs distinctes s'effondrent en 2 empreintes. À noter les deux ensemble : le diagnostic et le patch ne sont pas le même problème, et réussir l'un ne vous donne pas l'autre.</image:caption>
      <image:title>Python ne peut pas prendre une weakref sur un ValueError. La déduplication de Sentry l'a appris à ses dépens : 1024 Ko par tâche asyncio vivante.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/still-here-austin-shelter-black-dog-syndrome-99916-stays/</loc>
    <lastmod>2026-09-17T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/still-here-austin-shelter-black-dog-syndrome-99916-stays/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/still-here-austin-shelter-black-dog-syndrome-99916-stays/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/still-here-austin-shelter-black-dog-syndrome-99916-stays/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_01_hero.png</image:loc>
      <image:caption>The dog at the top of the board is Pancho, a white Cairn Terrier picked up as a stray on May 23, 2025. As of the last data pull he had been there 449 days. 99.7% of the dogs in his breed group were adopted in less time than he has already been waiting. Austin publishes that number in its open data feed. It prints it nowhere near the adoption page.</image:caption>
      <image:title>Still Here: Everyone Believes Black Dogs Get Left Behind. I Checked 99,916 Shelter Records. They Don't.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_02_chart_color.png</image:loc>
      <image:caption>Black dogs are in the fast half. 13,922 of them, leaving in a median of 9 days, quicker than white, brown or chocolate. The myth does not survive its first contact with the data. That much matches published research, a 2016 study by Hoffman and colleagues in Animal Welfare looked at nearly 16,700 records across two shelters and found black dogs left slightly faster than average at both.</image:caption>
      <image:title>Still Here: Everyone Believes Black Dogs Get Left Behind. I Checked 99,916 Shelter Records. They Don't.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_03_split.png</image:loc>
      <image:caption>The two columns do not overlap anywhere. Every bully-type cell is between 25 and 32.5 days. Every other cell is between 7 and 11. Within a breed group, coat color moves the median wait by at most 7.5 days. Changing the breed group moves it by 20. Black only looked fast in the first table because black dogs are the least likely to be bully-type, 12% of them, against 81% of the blue ones. The color signal was a breed signal the whole time. A black dog that is not a bully breed waits 8 days. A black pit bull waits 31.</image:caption>
      <image:title>Still Here: Everyone Believes Black Dogs Get Left Behind. I Checked 99,916 Shelter Records. They Don't.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_04_chart_breed.png</image:loc>
      <image:caption>The method is on the page next to the finding. Two eras of Austin's data, different schemas, joined at the animal-ID level. Length of stay in the archive is reconstructed by pairing each outcome with the most recent intake preceding it (652 outcomes could not be paired and were dropped rather than guessed at). Medians throughout, because length of stay is heavily right-skewed, the mean bully-type wait is 66 days against a median of 28.</image:caption>
      <image:title>Still Here: Everyone Believes Black Dogs Get Left Behind. I Checked 99,916 Shelter Records. They Don't.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_06_sheet_faces.jpg</image:loc>
      <image:caption>One frame per dog, ordered by how long each has been waiting, on a page that carries that wait: the longer a dog has been here, the more it has burned into the plate. Photographs come from Austin's own adoption listings via ID join (377 of 510 dogs; the 133 without a photo are mostly recent arrivals still inside a mandatory stray hold, median 20 days vs 84 for the listed ones). Clicking a dog opens the page where you could actually adopt it. 23 of them read their own record aloud, marked with a Hear button.</image:caption>
      <image:title>Still Here: Everyone Believes Black Dogs Get Left Behind. I Checked 99,916 Shelter Records. They Don't.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_07_adopt.png</image:loc>
      <image:caption>Each of the longest-waiting dogs reads its own record aloud via ElevenLabs. Nothing a dog says is invented: every clause maps to a column (name, coat, breed, intake reason, intake date, elapsed days, cohort percentile, cohort median). Phrasing varies between dogs but is selected by a hash of the animal ID, not randomly, so re-running produces byte-identical scripts. The board moves: between two runs a day apart, nine dogs came off, seven were adopted, one was reclaimed by an owner who came looking, and one was transferred to a partner rescue. Baxter had been waiting 71 days and went home on the fifteenth.</image:caption>
      <image:title>Still Here: Everyone Believes Black Dogs Get Left Behind. I Checked 99,916 Shelter Records. They Don't.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/still-here-austin-shelter-black-dog-syndrome-99916-stays/</loc>
    <lastmod>2026-09-17T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/still-here-austin-shelter-black-dog-syndrome-99916-stays/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/still-here-austin-shelter-black-dog-syndrome-99916-stays/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/still-here-austin-shelter-black-dog-syndrome-99916-stays/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_01_hero.png</image:loc>
      <image:caption>Le chien en tête du tableau est Pancho, un Cairn Terrier blanc ramassé errant le 23 mai 2025. Au dernier passage de données, il y était depuis 449 jours. 99,7 % des chiens de son groupe de race ont été adoptés en moins de temps qu'il n'attend déjà. Austin publie ce nombre dans son flux de données ouvertes. Il ne l'imprime nulle part près de la page d'adoption.</image:caption>
      <image:title>Still Here : tout le monde croit que les chiens noirs sont laissés pour compte. J'ai vérifié 99 916 dossiers de refuge. Ce n'est pas vrai.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_02_chart_color.png</image:loc>
      <image:caption>Les chiens noirs sont dans la moitié rapide. 13 922 d'entre eux, partant en médiane de 9 jours, plus vite que blancs, bruns ou chocolat. Le mythe ne survit pas à son premier contact avec les données. Cela correspond à la recherche publiée, une étude de 2016 par Hoffman et collègues dans Animal Welfare a examiné près de 16 700 dossiers dans deux refuges et a trouvé que les chiens noirs partaient légèrement plus vite que la moyenne dans les deux.</image:caption>
      <image:title>Still Here : tout le monde croit que les chiens noirs sont laissés pour compte. J'ai vérifié 99 916 dossiers de refuge. Ce n'est pas vrai.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_03_split.png</image:loc>
      <image:caption>Les deux colonnes ne se croisent nulle part. Chaque cellule bully est entre 25 et 32,5 jours. Chaque autre cellule entre 7 et 11. À l'intérieur d'un groupe de race, la couleur du pelage déplace la médiane d'au plus 7,5 jours. Changer le groupe de race la déplace de 20. Noir ne semblait rapide dans le premier tableau que parce que les chiens noirs sont les moins susceptibles d'être bully, 12 % d'entre eux, contre 81 % des bleus. Le signal couleur était un signal race depuis le début. Un chien noir qui n'est pas bully attend 8 jours. Un pit-bull noir attend 31.</image:caption>
      <image:title>Still Here : tout le monde croit que les chiens noirs sont laissés pour compte. J'ai vérifié 99 916 dossiers de refuge. Ce n'est pas vrai.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_04_chart_breed.png</image:loc>
      <image:caption>La méthode est sur la page à côté de la trouvaille. Deux ères des données d'Austin, schémas différents, jointes au niveau de l'ID animal. La durée de séjour dans l'archive est reconstruite en appariant chaque sortie avec l'entrée la plus récente qui la précède (652 sorties n'ont pas pu être appariées et ont été abandonnées plutôt que devinées). Médianes partout, parce que la durée de séjour est fortement asymétrique à droite, la moyenne d'attente bully est de 66 jours contre une médiane de 28.</image:caption>
      <image:title>Still Here : tout le monde croit que les chiens noirs sont laissés pour compte. J'ai vérifié 99 916 dossiers de refuge. Ce n'est pas vrai.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_06_sheet_faces.jpg</image:loc>
      <image:caption>Une image par chien, ordonnés selon la durée d'attente de chacun, sur une page qui porte cette attente : plus un chien est là depuis longtemps, plus il s'est gravé dans la plaque. Les photographies viennent des propres fiches d'adoption d'Austin via jointure d'ID (377 sur 510 chiens ; les 133 sans photo sont surtout des arrivées récentes encore dans une retenue obligatoire d'errance, médiane 20 jours contre 84 pour ceux listés). Cliquer sur un chien ouvre la page où on pourrait réellement l'adopter. 23 d'entre eux lisent leur propre dossier à voix haute, marqués d'un bouton Hear.</image:caption>
      <image:title>Still Here : tout le monde croit que les chiens noirs sont laissés pour compte. J'ai vérifié 99 916 dossiers de refuge. Ce n'est pas vrai.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/stillhere_07_adopt.png</image:loc>
      <image:caption>Chacun des chiens qui attendent le plus longtemps lit son propre dossier à voix haute via ElevenLabs. Rien de ce qu'un chien dit n'est inventé : chaque clause correspond à une colonne (nom, pelage, race, raison d'entrée, date d'entrée, jours écoulés, percentile de cohorte, médiane de cohorte). La formulation varie d'un chien à l'autre mais est sélectionnée par un hash de l'ID animal, pas au hasard, donc relancer produit des scripts identiques octet-pour-octet. Le tableau bouge : entre deux exécutions à un jour d'écart, neuf chiens en sont sortis, sept ont été adoptés, un réclamé par un propriétaire venu le chercher, et un transféré à un partenaire de sauvetage. Baxter attendait depuis 71 jours et est rentré chez lui le quinze.</image:caption>
      <image:title>Still Here : tout le monde croit que les chiens noirs sont laissés pour compte. J'ai vérifié 99 916 dossiers de refuge. Ce n'est pas vrai.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/assay-skincare-tracker-with-an-error-bar-youcam-api/</loc>
    <lastmod>2026-08-14T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/assay-skincare-tracker-with-an-error-bar-youcam-api/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/assay-skincare-tracker-with-an-error-bar-youcam-api/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/assay-skincare-tracker-with-an-error-bar-youcam-api/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_01_hero.png</image:loc>
      <image:caption>The whole argument in one page. Numbers from a 2,000-person AmLactin/OnePoll survey (Feb 2022) and a 2,000-person Body Shop / Professional Beauty survey, both cited in the copy. Everything downstream is that claim, made honest.</image:caption>
      <image:title>Assay: A Skincare Tracker with an Error Bar. Cropping the Same Photo Moves Texture by 5.81 Points, So a Verdict Only Fires When the Change Beats That Floor on Your Face.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_03_false_claims.png</image:loc>
      <image:caption>Same face, fifteen minutes apart, nothing applied between shots. A normal tracker would have called ten of twelve changes real. Assay's calibration measures exactly this so it can refuse to call any of them.</image:caption>
      <image:title>Assay: A Skincare Tracker with an Error Bar. Cropping the Same Photo Moves Texture by 5.81 Points, So a Verdict Only Fires When the Change Beats That Floor on Your Face.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_04_verdict.png</image:loc>
      <image:caption>Every verdict card carries the change and the floor together. Two numbers, and the verdict follows from them. No adjective without a bound.</image:caption>
      <image:title>Assay: A Skincare Tracker with an Error Bar. Cropping the Same Photo Moves Texture by 5.81 Points, So a Verdict Only Fires When the Change Beats That Floor on Your Face.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_06_live_capture.png</image:loc>
      <image:caption>Cropping moved texture by 5.81 and JPEG quality moved blemishes by 2.91, so the capture pipeline holds both constant before anything goes to the API. If you cannot control an error, at least do not add it.</image:caption>
      <image:title>Assay: A Skincare Tracker with an Error Bar. Cropping the Same Photo Moves Texture by 5.81 Points, So a Verdict Only Fires When the Change Beats That Floor on Your Face.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_08_error_budget.png</image:loc>
      <image:caption>The error budget, pulled apart by cause. Because model noise is zero, every point of the remaining spread has a name: JPEG, brightness, or pose. Assay controls the first two and calibrates against the third.</image:caption>
      <image:title>Assay: A Skincare Tracker with an Error Bar. Cropping the Same Photo Moves Texture by 5.81 Points, So a Verdict Only Fires When the Change Beats That Floor on Your Face.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/assay-skincare-tracker-with-an-error-bar-youcam-api/</loc>
    <lastmod>2026-08-14T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/assay-skincare-tracker-with-an-error-bar-youcam-api/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/assay-skincare-tracker-with-an-error-bar-youcam-api/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/assay-skincare-tracker-with-an-error-bar-youcam-api/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_01_hero.png</image:loc>
      <image:caption>Tout l'argument en une page. Chiffres d'une enquête AmLactin/OnePoll auprès de 2 000 personnes (fév. 2022) et d'une enquête Body Shop / Professional Beauty auprès de 2 000 personnes, toutes deux citées dans le texte. Tout ce qui suit, c'est cette revendication, rendue honnête.</image:caption>
      <image:title>Assay : un tracker soin de la peau avec une barre d'erreur. Recadrer la même photo déplace la texture de 5,81 points, alors un verdict ne s'affiche que si le changement dépasse ce plancher sur votre visage.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_03_false_claims.png</image:loc>
      <image:caption>Même visage, quinze minutes d'écart, rien appliqué entre les prises. Un tracker normal aurait appelé dix changements sur douze réels. La calibration d'Assay mesure exactement ça pour pouvoir refuser d'en appeler aucun.</image:caption>
      <image:title>Assay : un tracker soin de la peau avec une barre d'erreur. Recadrer la même photo déplace la texture de 5,81 points, alors un verdict ne s'affiche que si le changement dépasse ce plancher sur votre visage.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_04_verdict.png</image:loc>
      <image:caption>Chaque carte de verdict porte le changement et le plancher ensemble. Deux nombres, et le verdict en découle. Pas d'adjectif sans une borne.</image:caption>
      <image:title>Assay : un tracker soin de la peau avec une barre d'erreur. Recadrer la même photo déplace la texture de 5,81 points, alors un verdict ne s'affiche que si le changement dépasse ce plancher sur votre visage.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_06_live_capture.png</image:loc>
      <image:caption>Le recadrage a déplacé la texture de 5,81 et la qualité JPEG a déplacé les boutons de 2,91, donc le pipeline de capture les tient tous les deux constants avant que quoi que ce soit ne parte à l'API. Si on ne peut pas contrôler une erreur, au moins ne pas l'ajouter.</image:caption>
      <image:title>Assay : un tracker soin de la peau avec une barre d'erreur. Recadrer la même photo déplace la texture de 5,81 points, alors un verdict ne s'affiche que si le changement dépasse ce plancher sur votre visage.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/assay_08_error_budget.png</image:loc>
      <image:caption>Le budget d'erreur, décomposé par cause. Parce que le bruit du modèle est zéro, chaque point de l'écart restant a un nom : JPEG, luminosité, ou pose. Assay contrôle les deux premiers et calibre contre le troisième.</image:caption>
      <image:title>Assay : un tracker soin de la peau avec une barre d'erreur. Recadrer la même photo déplace la texture de 5,81 points, alors un verdict ne s'affiche que si le changement dépasse ce plancher sur votre visage.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/unsay-agent-memory-cockroachdb-bitemporal-fda-recalls/</loc>
    <lastmod>2026-09-26T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/unsay-agent-memory-cockroachdb-bitemporal-fda-recalls/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/unsay-agent-memory-cockroachdb-bitemporal-fda-recalls/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/unsay-agent-memory-cockroachdb-bitemporal-fda-recalls/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_01_hero.png</image:loc>
      <image:caption>The whole app in one page. Real openFDA data, 554 live claims, hosted on a single AWS Lambda Function URL in front of a CockroachDB Cloud cluster. First request after a quiet spell pays a cold start of a few seconds; nothing after that.</image:caption>
      <image:title>Unsay: An AI Medication-Safety Agent that Goes Back and Un-Says What It Told You. When the FDA Escalates a Recall, It Corrects Every Named Patient It Already Reassured, in Seconds.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_03_supersede.png</image:loc>
      <image:caption>One transaction: the old claim is retracted and a new one is asserted beside it under the same key. Nothing already read is mutated. Every answer that consumed v1 is unchanged in the database until the sweep re-decides it.</image:caption>
      <image:title>Unsay: An AI Medication-Safety Agent that Goes Back and Un-Says What It Told You. When the FDA Escalates a Recall, It Corrects Every Named Patient It Already Reassured, in Seconds.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_05_corrections.png</image:loc>
      <image:caption>Nine reversed, three left alone. Each correction is written in the same transaction as the status change, the outbox entry, and the audit line, so a sweep killed halfway and restarted cannot double-send. Class I recalls arrive on Tuesdays and Fridays; this loop is what makes it possible to answer them by Friday afternoon.</image:caption>
      <image:title>Unsay: An AI Medication-Safety Agent that Goes Back and Un-Says What It Told You. When the FDA Escalates a Recall, It Corrects Every Named Patient It Already Reassured, in Seconds.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_06_expiry.png</image:loc>
      <image:caption>The control matters as much as the failure. Inside the window the two routes agree exactly, so the bitemporal model is reconstructing real history rather than inventing a convenient one.</image:caption>
      <image:title>Unsay: An AI Medication-Safety Agent that Goes Back and Un-Says What It Told You. When the FDA Escalates a Recall, It Corrects Every Named Patient It Already Reassured, in Seconds.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_07_audit.png</image:loc>
      <image:caption>The audit export is the receipt the app hands a pharmacist. Same query the sweep uses, sealed with a signature, so a regulator sees exactly what the system said, what it read, and what it corrected.</image:caption>
      <image:title>Unsay: An AI Medication-Safety Agent that Goes Back and Un-Says What It Told You. When the FDA Escalates a Recall, It Corrects Every Named Patient It Already Reassured, in Seconds.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/unsay-agent-memory-cockroachdb-bitemporal-fda-recalls/</loc>
    <lastmod>2026-09-26T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/unsay-agent-memory-cockroachdb-bitemporal-fda-recalls/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/unsay-agent-memory-cockroachdb-bitemporal-fda-recalls/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/unsay-agent-memory-cockroachdb-bitemporal-fda-recalls/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_01_hero.png</image:loc>
      <image:caption>Toute l'app en une page. De vraies données openFDA, 554 affirmations en direct, hébergée sur une seule Function URL AWS Lambda devant un cluster CockroachDB Cloud. La première requête après une période calme paie un cold-start de quelques secondes ; rien après.</image:caption>
      <image:title>Unsay : un agent IA de sécurité médicamenteuse qui revient et défait ce qu'il a dit. Quand la FDA élève un rappel, il corrige chaque patient nommé qu'il avait déjà rassuré, en quelques secondes.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_03_supersede.png</image:loc>
      <image:caption>Une transaction : l'ancienne affirmation est rétractée et une nouvelle est affirmée à côté sous la même clé. Rien de déjà lu n'est muté. Chaque réponse qui a consommé v1 est inchangée dans la base jusqu'à ce que le sweep la re-décide.</image:caption>
      <image:title>Unsay : un agent IA de sécurité médicamenteuse qui revient et défait ce qu'il a dit. Quand la FDA élève un rappel, il corrige chaque patient nommé qu'il avait déjà rassuré, en quelques secondes.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_05_corrections.png</image:loc>
      <image:caption>Neuf inversées, trois laissées tranquilles. Chaque correction est écrite dans la même transaction que le changement de statut, l'entrée outbox, et la ligne d'audit, donc un sweep tué à mi-course et relancé ne peut pas double-envoyer. Les rappels Classe I arrivent les mardis et vendredis ; cette boucle est ce qui rend possible d'y répondre le vendredi après-midi.</image:caption>
      <image:title>Unsay : un agent IA de sécurité médicamenteuse qui revient et défait ce qu'il a dit. Quand la FDA élève un rappel, il corrige chaque patient nommé qu'il avait déjà rassuré, en quelques secondes.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_06_expiry.png</image:loc>
      <image:caption>Le contrôle compte autant que l'échec. À l'intérieur de la fenêtre, les deux routes concordent exactement, donc le modèle bitemporel reconstruit une vraie histoire plutôt que d'en inventer une commode.</image:caption>
      <image:title>Unsay : un agent IA de sécurité médicamenteuse qui revient et défait ce qu'il a dit. Quand la FDA élève un rappel, il corrige chaque patient nommé qu'il avait déjà rassuré, en quelques secondes.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/unsay_07_audit.png</image:loc>
      <image:caption>L'export d'audit est le reçu que l'app remet à un pharmacien. Même requête que le sweep utilise, scellée avec une signature, pour qu'un régulateur voie exactement ce que le système a dit, ce qu'il a lu, et ce qu'il a corrigé.</image:caption>
      <image:title>Unsay : un agent IA de sécurité médicamenteuse qui revient et défait ce qu'il a dit. Quand la FDA élève un rappel, il corrige chaque patient nommé qu'il avait déjà rassuré, en quelques secondes.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/bloom-client-retention-gemini-xprize-hackerfund/</loc>
    <lastmod>2026-08-12T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/bloom-client-retention-gemini-xprize-hackerfund/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/bloom-client-retention-gemini-xprize-hackerfund/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/bloom-client-retention-gemini-xprize-hackerfund/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/bloom_07_closing.png</image:loc>
      <image:caption>The whole thesis in one screen. Six clients, one timeline each, one line of prose beneath: 'Aisha and Jane were both last in 44 days ago. Only one of them is leaving.' Everything else in the product is that sentence, made actionable.</image:caption>
      <image:title>Bloom: Nobody Cancels, They Just Stop Coming. Bloom Reads Each Client's Own Visit Rhythm, Flags the Ones Drifting from It, and Writes a Personal Note to Each. Rules Decide Who, Gemini Decides What to Say.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/bloom_03_aisha_jane.png</image:loc>
      <image:caption>Same last-seen date, opposite verdicts, and the calendar rule cannot see the difference. This is the single frame the entire product exists to make possible.</image:caption>
      <image:title>Bloom: Nobody Cancels, They Just Stop Coming. Bloom Reads Each Client's Own Visit Rhythm, Flags the Ones Drifting from It, and Writes a Personal Note to Each. Rules Decide Who, Gemini Decides What to Say.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/bloom_04_dashboard.png</image:loc>
      <image:caption>The AI activity log includes rejected drafts and self-QA scores, not just the final output. That transparency is what AI-Native Operations (a scored criterion at this hackathon) is actually asking about.</image:caption>
      <image:title>Bloom: Nobody Cancels, They Just Stop Coming. Bloom Reads Each Client's Own Visit Rhythm, Flags the Ones Drifting from It, and Writes a Personal Note to Each. Rules Decide Who, Gemini Decides What to Say.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/bloom_05_agent.png</image:loc>
      <image:caption>Public, unedited, includes rejections. This is what continuous autonomous operation looks like when it is not curated for a demo. The cron has run every Monday since early July.</image:caption>
      <image:title>Bloom: Nobody Cancels, They Just Stop Coming. Bloom Reads Each Client's Own Visit Rhythm, Flags the Ones Drifting from It, and Writes a Personal Note to Each. Rules Decide Who, Gemini Decides What to Say.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/bloom-client-retention-gemini-xprize-hackerfund/</loc>
    <lastmod>2026-08-12T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/bloom-client-retention-gemini-xprize-hackerfund/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/bloom-client-retention-gemini-xprize-hackerfund/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/bloom-client-retention-gemini-xprize-hackerfund/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/bloom_07_closing.png</image:loc>
      <image:caption>Toute la thèse en un écran. Six clientes, une frise chacune, une ligne de prose en dessous : « Aisha et Jane sont toutes deux passées il y a 44 jours. Une seule d'entre elles s'en va. » Tout le reste dans le produit, c'est cette phrase, rendue actionnable.</image:caption>
      <image:title>Bloom : personne n'annule, on cesse simplement de venir. Bloom lit le rythme de visite propre à chaque client, signale ceux qui s'en écartent, et écrit une note personnelle à chacun. Les règles décident QUI, Gemini décide QUOI DIRE.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/bloom_03_aisha_jane.png</image:loc>
      <image:caption>Même date de dernière visite, verdicts opposés, et la règle basée sur le calendrier ne voit pas la différence. C'est la seule image que tout le produit existe pour rendre possible.</image:caption>
      <image:title>Bloom : personne n'annule, on cesse simplement de venir. Bloom lit le rythme de visite propre à chaque client, signale ceux qui s'en écartent, et écrit une note personnelle à chacun. Les règles décident QUI, Gemini décide QUOI DIRE.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/bloom_04_dashboard.png</image:loc>
      <image:caption>Le journal d'activité IA inclut les brouillons rejetés et les scores de self-QA, pas seulement la sortie finale. Cette transparence est ce que AI-Native Operations (un critère noté à ce hackathon) demande vraiment.</image:caption>
      <image:title>Bloom : personne n'annule, on cesse simplement de venir. Bloom lit le rythme de visite propre à chaque client, signale ceux qui s'en écartent, et écrit une note personnelle à chacun. Les règles décident QUI, Gemini décide QUOI DIRE.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/bloom_05_agent.png</image:loc>
      <image:caption>Public, non édité, inclut les rejets. Voilà à quoi ressemble une opération autonome continue quand elle n'est pas curée pour une démo. Le cron a tourné chaque lundi depuis début juillet.</image:caption>
      <image:title>Bloom : personne n'annule, on cesse simplement de venir. Bloom lit le rythme de visite propre à chaque client, signale ceux qui s'en écartent, et écrit une note personnelle à chacun. Les règles décident QUI, Gemini décide QUOI DIRE.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/coldpath-ollama-arm-matrix-unit-off-ci-gate/</loc>
    <lastmod>2026-08-11T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/coldpath-ollama-arm-matrix-unit-off-ci-gate/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/coldpath-ollama-arm-matrix-unit-off-ci-gate/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/coldpath-ollama-arm-matrix-unit-off-ci-gate/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_01_cold.png</image:loc>
      <image:caption>Ollama v0.31.2, official Windows-on-Arm release, exactly as it ships. Zero matrix instructions, zero dot-product instructions, at 100% decode coverage. Absence is airtight: no core can ever run an i8mm matmul on this binary, however the library dispatches at runtime.</image:caption>
      <image:title>coldpath: The Ollama Windows-on-Arm Build Ships with the Matrix Unit Off. One-Line Fix Filed Upstream, 5.75x on Prompt Processing.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_02_contrast.png</image:loc>
      <image:caption>The comparison that makes the flag the cause. Same OS, same ggml source, two shipped binaries one build flag apart. Ollama does not fork ggml: it builds pinned upstream llama.cpp with one flag missing. This is a distribution hazard, not Ollama being incapable.</image:caption>
      <image:title>coldpath: The Ollama Windows-on-Arm Build Ships with the Matrix Unit Off. One-Line Fix Filed Upstream, 5.75x on Prompt Processing.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_03_cost.png</image:loc>
      <image:caption>$0.45 to $0.08 per million prompt tokens, on the free GitHub Arm64 runner. The dot-product fix is safe on every shipped Arm device, so this recovery is universal, not conditional on newer silicon.</image:caption>
      <image:title>coldpath: The Ollama Windows-on-Arm Build Ships with the Matrix Unit Off. One-Line Fix Filed Upstream, 5.75x on Prompt Processing.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_04_actions.png</image:loc>
      <image:caption>Every number regenerates from CI on free Arm hardware, so a judge can re-run it rather than trust a screenshot. Fork the repo and hit run on the workflow.</image:caption>
      <image:title>coldpath: The Ollama Windows-on-Arm Build Ships with the Matrix Unit Off. One-Line Fix Filed Upstream, 5.75x on Prompt Processing.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_05_fix.png</image:loc>
      <image:caption>The whole fix. One line, filed as Ollama PR #17654 during the challenge window. Confirmed COLD on all 8 stable Ollama releases from v0.31.2 through the current v0.32.7, so this claim cannot silently rot: `test.yml` re-downloads the latest release and re-checks it on every push.</image:caption>
      <image:title>coldpath: The Ollama Windows-on-Arm Build Ships with the Matrix Unit Off. One-Line Fix Filed Upstream, 5.75x on Prompt Processing.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_06_gate.png</image:loc>
      <image:caption>The gate is a reusable GitHub Action published from this repo: `uses: JonathanSolvesProblems/coldpath@v1` with `require: i8mm`. That is all it takes to block a cold build.</image:caption>
      <image:title>coldpath: The Ollama Windows-on-Arm Build Ships with the Matrix Unit Off. One-Line Fix Filed Upstream, 5.75x on Prompt Processing.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_07_outro.png</image:loc>
      <image:caption>`pip install coldpath`, MIT, on PyPI. The tool found a real defect in software millions of people run, on its first day. Next up: distinguishing SME from SME2, detecting runtime-gated dispatch as a third state, and growing the ecosystem scan into a public dashboard.</image:caption>
      <image:title>coldpath: The Ollama Windows-on-Arm Build Ships with the Matrix Unit Off. One-Line Fix Filed Upstream, 5.75x on Prompt Processing.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/coldpath-ollama-arm-matrix-unit-off-ci-gate/</loc>
    <lastmod>2026-08-11T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/coldpath-ollama-arm-matrix-unit-off-ci-gate/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/coldpath-ollama-arm-matrix-unit-off-ci-gate/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/coldpath-ollama-arm-matrix-unit-off-ci-gate/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_01_cold.png</image:loc>
      <image:caption>Ollama v0.31.2, release officielle Windows-on-Arm, exactement telle qu'expédiée. Zéro instruction matricielle, zéro dot-product, à 100 % de couverture de décodage. L'absence est étanche : aucun coeur ne peut jamais exécuter une matmul i8mm sur ce binaire, quelle que soit la façon dont la lib dispatche à l'exécution.</image:caption>
      <image:title>coldpath : le build Ollama Windows-on-Arm est expédié avec l'unité matricielle éteinte. Correctif d'une ligne déposé en amont, 5,75x sur le traitement de prompt.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_02_contrast.png</image:loc>
      <image:caption>La comparaison qui fait du flag la cause. Même OS, même source ggml, deux binaires expédiés séparés par un flag de build. Ollama ne forke pas ggml : il construit llama.cpp amont épinglé avec un flag manquant. C'est un risque de distribution, pas Ollama qui serait incapable.</image:caption>
      <image:title>coldpath : le build Ollama Windows-on-Arm est expédié avec l'unité matricielle éteinte. Correctif d'une ligne déposé en amont, 5,75x sur le traitement de prompt.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_03_cost.png</image:loc>
      <image:caption>0,45 $ à 0,08 $ par million de tokens de prompt, sur le runner Arm64 gratuit de GitHub. Le correctif dot-product est sûr sur chaque appareil Arm expédié, donc cette récupération est universelle, pas conditionnelle à du silicium plus récent.</image:caption>
      <image:title>coldpath : le build Ollama Windows-on-Arm est expédié avec l'unité matricielle éteinte. Correctif d'une ligne déposé en amont, 5,75x sur le traitement de prompt.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_04_actions.png</image:loc>
      <image:caption>Chaque chiffre se régénère depuis CI sur du matériel Arm gratuit, donc un juge peut le rejouer plutôt que faire confiance à une capture. Forkez le dépôt et lancez le workflow.</image:caption>
      <image:title>coldpath : le build Ollama Windows-on-Arm est expédié avec l'unité matricielle éteinte. Correctif d'une ligne déposé en amont, 5,75x sur le traitement de prompt.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_05_fix.png</image:loc>
      <image:caption>Tout le correctif. Une ligne, déposée comme Ollama PR #17654 pendant la fenêtre du défi. Confirmé COLD sur les 8 releases stables d'Ollama de v0.31.2 à v0.32.7 actuelle, donc cette revendication ne peut pas pourrir silencieusement : `test.yml` re-télécharge la dernière release et la re-vérifie à chaque push.</image:caption>
      <image:title>coldpath : le build Ollama Windows-on-Arm est expédié avec l'unité matricielle éteinte. Correctif d'une ligne déposé en amont, 5,75x sur le traitement de prompt.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_06_gate.png</image:loc>
      <image:caption>La gate est une GitHub Action réutilisable publiée depuis ce dépôt : `uses: JonathanSolvesProblems/coldpath@v1` avec `require: i8mm`. C'est tout ce qu'il faut pour bloquer un build froid.</image:caption>
      <image:title>coldpath : le build Ollama Windows-on-Arm est expédié avec l'unité matricielle éteinte. Correctif d'une ligne déposé en amont, 5,75x sur le traitement de prompt.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/coldpath_07_outro.png</image:loc>
      <image:caption>`pip install coldpath`, MIT, sur PyPI. L'outil a trouvé un vrai défaut dans un logiciel que des millions de personnes utilisent, dès son premier jour. Ensuite : distinguer SME de SME2, détecter le dispatch gaté à l'exécution comme troisième état, et faire grandir le scan d'écosystème en un tableau de bord public.</image:caption>
      <image:title>coldpath : le build Ollama Windows-on-Arm est expédié avec l'unité matricielle éteinte. Correctif d'une ligne déposé en amont, 5,75x sur le traitement de prompt.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/culprit-stack-trace-for-model-decay-datahub-agent/</loc>
    <lastmod>2026-09-10T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/culprit-stack-trace-for-model-decay-datahub-agent/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/culprit-stack-trace-for-model-decay-datahub-agent/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/culprit-stack-trace-for-model-decay-datahub-agent/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_01_model_properties.jpg</image:loc>
      <image:caption>The fact the whole diagnosis turns on, recorded in the graph before anything went wrong: the model was trained on vendors 1, 2 and 6. Nothing else knows this.</image:caption>
      <image:title>Culprit: A Stack Trace for Model Decay. $90,322 of Model Error Traced to One Column That Went from Max 6 to Max 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_02_tlc_dictionary.jpg</image:loc>
      <image:caption>Primary source, so none of this has to be taken on trust. Vendor 7 exists. The feed changed in December 2024, the documentation dated March 2025.</image:caption>
      <image:title>Culprit: A Stack Trace for Model Decay. $90,322 of Model Error Traced to One Column That Went from Max 6 to Max 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_03_semantic_change.jpg</image:loc>
      <image:caption>One integer changed. That is the whole signal at the source. Every structural check freshness, volume, null-rate, dtype was green through the entire six months of quiet wrongness.</image:caption>
      <image:title>Culprit: A Stack Trace for Model Decay. $90,322 of Model Error Traced to One Column That Went from Max 6 to Max 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_04_impact.jpg</image:loc>
      <image:caption>The model is never asked to produce a number. Every dollar figure comes from SQL run against the real warehouse, then handed to the agent as a fact. This is the guardrail on the engine.</image:caption>
      <image:title>Culprit: A Stack Trace for Model Decay. $90,322 of Model Error Traced to One Column That Went from Max 6 to Max 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_05_trace.jpg</image:loc>
      <image:caption>Recorded, not scripted. 28 tool calls in 13 turns. Four of the calls go through DataHub's own MCP server; the rest go through the deterministic SQL layer that returns numbers.</image:caption>
      <image:title>Culprit: A Stack Trace for Model Decay. $90,322 of Model Error Traced to One Column That Went from Max 6 to Max 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_06_lineage.jpg</image:loc>
      <image:caption>The dataset half is DataHub's native dbt connector doing real column-level lineage. The ML half is the SDK emitting mlFeatures with the exact source column each derives from, which is what makes the walk from model back to raw column possible.</image:caption>
      <image:title>Culprit: A Stack Trace for Model Decay. $90,322 of Model Error Traced to One Column That Went from Max 6 to Max 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_07_incident.jpg</image:loc>
      <image:caption>The incident is filed on the source dataset, not on the model, that is not a design choice, it is a DataHub v1.5 limitation I confirmed by probing a live instance, then corrected my own earlier wrong claim about it. Written up in docs/INCIDENT_PROBE_RESULTS.md.</image:caption>
      <image:title>Culprit: A Stack Trace for Model Decay. $90,322 of Model Error Traced to One Column That Went from Max 6 to Max 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_08_rejected_patch.jpg</image:loc>
      <image:caption>This is what an execution-verified remediation gate looks like. The model's first patch compiled and made the symptom disappear. It also would have destroyed 87,693 rows. The gate is the reason the PR that did land was safe.</image:caption>
      <image:title>Culprit: A Stack Trace for Model Decay. $90,322 of Model Error Traced to One Column That Went from Max 6 to Max 7.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/culprit-stack-trace-for-model-decay-datahub-agent/</loc>
    <lastmod>2026-09-10T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/culprit-stack-trace-for-model-decay-datahub-agent/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/culprit-stack-trace-for-model-decay-datahub-agent/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/culprit-stack-trace-for-model-decay-datahub-agent/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_01_model_properties.jpg</image:loc>
      <image:caption>Le fait dont dépend tout le diagnostic, enregistré dans le graphe avant que quoi que ce soit ne tourne mal : le modèle a été entraîné sur les fournisseurs 1, 2 et 6. Rien d'autre ne sait ça.</image:caption>
      <image:title>Culprit : une trace de pile pour la dérive de modèle. 90 322 $ d'erreur remontés à une seule colonne dont le max passe de 6 à 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_02_tlc_dictionary.jpg</image:loc>
      <image:caption>Source primaire, pour que rien n'ait à être pris sur parole. Le fournisseur 7 existe. Le flux a changé en décembre 2024, la documentation datée de mars 2025.</image:caption>
      <image:title>Culprit : une trace de pile pour la dérive de modèle. 90 322 $ d'erreur remontés à une seule colonne dont le max passe de 6 à 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_03_semantic_change.jpg</image:loc>
      <image:caption>Un entier a changé. C'est tout le signal à la source. Chaque vérification structurelle fraîcheur, volume, taux de nuls, dtype était au vert pendant les six mois entiers de fausseté silencieuse.</image:caption>
      <image:title>Culprit : une trace de pile pour la dérive de modèle. 90 322 $ d'erreur remontés à une seule colonne dont le max passe de 6 à 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_04_impact.jpg</image:loc>
      <image:caption>Le modèle n'est jamais sollicité pour produire un chiffre. Chaque figure en dollars vient de SQL exécuté contre le vrai warehouse, puis remis à l'agent comme un fait. C'est le garde-fou sur le moteur.</image:caption>
      <image:title>Culprit : une trace de pile pour la dérive de modèle. 90 322 $ d'erreur remontés à une seule colonne dont le max passe de 6 à 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_05_trace.jpg</image:loc>
      <image:caption>Enregistré, pas scripté. 28 appels d'outils en 13 tours. Quatre des appels passent par le propre serveur MCP de DataHub ; les autres passent par la couche SQL déterministe qui retourne les chiffres.</image:caption>
      <image:title>Culprit : une trace de pile pour la dérive de modèle. 90 322 $ d'erreur remontés à une seule colonne dont le max passe de 6 à 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_06_lineage.jpg</image:loc>
      <image:caption>La moitié dataset est le connecteur dbt natif de DataHub faisant de la vraie lignée au niveau colonne. La moitié ML est le SDK qui émet des mlFeatures avec la colonne source exacte dont chacune dérive, ce qui rend possible la marche du modèle jusqu'à la colonne brute.</image:caption>
      <image:title>Culprit : une trace de pile pour la dérive de modèle. 90 322 $ d'erreur remontés à une seule colonne dont le max passe de 6 à 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_07_incident.jpg</image:loc>
      <image:caption>L'incident est déposé sur le dataset source, pas sur le modèle, ce n'est pas un choix de conception, c'est une limitation de DataHub v1.5 que j'ai confirmée en sondant une instance en direct, puis corrigé ma propre affirmation antérieure erronée à ce sujet. Rédigé dans docs/INCIDENT_PROBE_RESULTS.md.</image:caption>
      <image:title>Culprit : une trace de pile pour la dérive de modèle. 90 322 $ d'erreur remontés à une seule colonne dont le max passe de 6 à 7.</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/culprit_08_rejected_patch.jpg</image:loc>
      <image:caption>Voilà à quoi ressemble un garde-fou de remédiation vérifié par exécution. Le premier patch du modèle compilait et faisait disparaître le symptôme. Il aurait aussi détruit 87 693 lignes. Le garde-fou est la raison pour laquelle la PR qui a effectivement atterri était sûre.</image:caption>
      <image:title>Culprit : une trace de pile pour la dérive de modèle. 90 322 $ d'erreur remontés à une seule colonne dont le max passe de 6 à 7.</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/overtone-audio-description-backblaze-b2-genblaze/</loc>
    <lastmod>2026-07-31T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/overtone-audio-description-backblaze-b2-genblaze/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/overtone-audio-description-backblaze-b2-genblaze/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/overtone-audio-description-backblaze-b2-genblaze/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_01_hero.jpg</image:loc>
      <image:caption>The whole thesis on one page. The bill for a professional describer versus the bill for compute, on the same lecture.</image:caption>
      <image:title>Overtone: WCAG Audio Description for a Whole Video Archive at ~$0.03 a Minute, Generated in Place on Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_02_described_result.jpg</image:loc>
      <image:caption>The described track drops straight into Panopto or Kaltura via the same plain WebVTT format both platforms already ingest.</image:caption>
      <image:title>Overtone: WCAG Audio Description for a Whole Video Archive at ~$0.03 a Minute, Generated in Place on Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_03_before_after.jpg</image:loc>
      <image:caption>Toggle the description off and the pause goes silent. Toggle it on and the description sits in exactly the same gap, with the program audio ducked underneath.</image:caption>
      <image:title>Overtone: WCAG Audio Description for a Whole Video Archive at ~$0.03 a Minute, Generated in Place on Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_04_real_mit.jpg</image:loc>
      <image:caption>The board is read as spoken math ("y equals m x plus b"), diagrams traced node by node, on-screen text read verbatim. Generic tools would say "a slide with an equation appears." That is useless to a blind engineering student.</image:caption>
      <image:title>Overtone: WCAG Audio Description for a Whole Video Archive at ~$0.03 a Minute, Generated in Place on Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_05_describe_live.png</image:loc>
      <image:caption>The hosted demo pipes each pipeline step out over SSE so a first-time viewer sees the whole shape of the work, and the cost.</image:caption>
      <image:title>Overtone: WCAG Audio Description for a Whole Video Archive at ~$0.03 a Minute, Generated in Place on Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_06_pipeline.jpg</image:loc>
      <image:caption>The generative steps all pass through Genblaze; the storage endpoints on both sides are Backblaze B2. Nothing ever leaves storage the customer already trusts, which matters for FERPA-covered recordings.</image:caption>
      <image:title>Overtone: WCAG Audio Description for a Whole Video Archive at ~$0.03 a Minute, Generated in Place on Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_07_run_archive.jpg</image:loc>
      <image:caption>Point it at a bucket prefix. It describes everything not already done, records what it did, and stays idempotent.</image:caption>
      <image:title>Overtone: WCAG Audio Description for a Whole Video Archive at ~$0.03 a Minute, Generated in Place on Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_08_problem.jpg</image:loc>
      <image:caption>Every claim in the demo cites a primary source shown on screen. The 2027 deadline is what makes this a shipping problem, not an academic one.</image:caption>
      <image:title>Overtone: WCAG Audio Description for a Whole Video Archive at ~$0.03 a Minute, Generated in Place on Backblaze B2</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/overtone-audio-description-backblaze-b2-genblaze/</loc>
    <lastmod>2026-07-31T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/overtone-audio-description-backblaze-b2-genblaze/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/overtone-audio-description-backblaze-b2-genblaze/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/overtone-audio-description-backblaze-b2-genblaze/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_01_hero.jpg</image:loc>
      <image:caption>Toute la thèse sur une page. La facture d'un descripteur professionnel contre la facture pour le compute, sur le même cours.</image:caption>
      <image:title>Overtone : description audio WCAG pour toute une archive vidéo à environ 0,03 $ la minute, générée sur place dans Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_02_described_result.jpg</image:loc>
      <image:caption>La piste décrite tombe directement dans Panopto ou Kaltura via le même format WebVTT que les deux plateformes ingèrent déjà.</image:caption>
      <image:title>Overtone : description audio WCAG pour toute une archive vidéo à environ 0,03 $ la minute, générée sur place dans Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_03_before_after.jpg</image:loc>
      <image:caption>On désactive la description et la pause devient silencieuse. On l'active et la description prend exactement le même intervalle, avec l'audio du programme atténué en dessous.</image:caption>
      <image:title>Overtone : description audio WCAG pour toute une archive vidéo à environ 0,03 $ la minute, générée sur place dans Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_04_real_mit.jpg</image:loc>
      <image:caption>Le tableau est lu comme des maths parlées (« y égale m x plus b »), les diagrammes tracés nœud par nœud, le texte à l'écran lu textuellement. Les outils génériques diraient « une diapo avec une équation apparaît ». C'est inutile pour un étudiant en ingénierie non-voyant.</image:caption>
      <image:title>Overtone : description audio WCAG pour toute une archive vidéo à environ 0,03 $ la minute, générée sur place dans Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_05_describe_live.png</image:loc>
      <image:caption>La démo hébergée diffuse chaque étape du pipeline via SSE pour qu'un visiteur qui découvre voie toute la forme du travail, et le coût.</image:caption>
      <image:title>Overtone : description audio WCAG pour toute une archive vidéo à environ 0,03 $ la minute, générée sur place dans Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_06_pipeline.jpg</image:loc>
      <image:caption>Toutes les étapes génératives passent par Genblaze ; les points de stockage des deux côtés sont Backblaze B2. Rien ne quitte jamais le stockage auquel le client fait déjà confiance, ce qui compte pour les enregistrements couverts par FERPA.</image:caption>
      <image:title>Overtone : description audio WCAG pour toute une archive vidéo à environ 0,03 $ la minute, générée sur place dans Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_07_run_archive.jpg</image:loc>
      <image:caption>On le pointe sur un préfixe de bucket. Il décrit tout ce qui n'est pas déjà fait, enregistre ce qu'il a fait, et reste idempotent.</image:caption>
      <image:title>Overtone : description audio WCAG pour toute une archive vidéo à environ 0,03 $ la minute, générée sur place dans Backblaze B2</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/overtone_08_problem.jpg</image:loc>
      <image:caption>Chaque affirmation dans la démo cite une source primaire montrée à l'écran. L'échéance 2027 est ce qui fait de ceci un problème à livrer, pas un problème académique.</image:caption>
      <image:title>Overtone : description audio WCAG pour toute une archive vidéo à environ 0,03 $ la minute, générée sur place dans Backblaze B2</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/viva-automated-oral-exam-for-code-openai-build-week/</loc>
    <lastmod>2026-07-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/viva-automated-oral-exam-for-code-openai-build-week/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/viva-automated-oral-exam-for-code-openai-build-week/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/viva-automated-oral-exam-for-code-openai-build-week/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_01_hero.jpg</image:loc>
      <image:caption>The headline number is 91 of 626, not a percentage in a vacuum. Every one of those 91 is a full-mark submission that hides a defect the autograder never checked.</image:caption>
      <image:title>Viva: 91 Full-Mark C Submissions Out of 626 Hide a Defect the Autograder Never Checked</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_02_ledger.jpg</image:loc>
      <image:caption>The Maximum-of-3 row alone: 50 of 226 full-mark submissions broken on the tie. The probe is a re-runnable command, not a paragraph.</image:caption>
      <image:title>Viva: 91 Full-Mark C Submissions Out of 626 Hide a Defect the Autograder Never Checked</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_03_replay.jpg</image:loc>
      <image:caption>Not a prediction, a replay. All 215 divergences replayed on an independent rerun. 100%. That is the number that matters after the headline.</image:caption>
      <image:title>Viva: 91 Full-Mark C Submissions Out of 626 Hide a Defect the Autograder Never Checked</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_04_concept_map.jpg</image:loc>
      <image:caption>The concept map tells the teacher what to reteach on Monday morning, not which student to email tonight.</image:caption>
      <image:title>Viva: 91 Full-Mark C Submissions Out of 626 Hide a Defect the Autograder Never Checked</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_05_not_cheating.jpg</image:loc>
      <image:caption>The framing is deliberate. Detection tools become useless the moment students learn to defeat them; a reteach brief remains useful either way.</image:caption>
      <image:title>Viva: 91 Full-Mark C Submissions Out of 626 Hide a Defect the Autograder Never Checked</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_06_defense_prompt.jpg</image:loc>
      <image:caption>One probe, one question, no template. The student is asked about their own submission, not the general concept.</image:caption>
      <image:title>Viva: 91 Full-Mark C Submissions Out of 626 Hide a Defect the Autograder Never Checked</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_08_bluff_caught.jpg</image:loc>
      <image:caption>The bluff-catch. Restating the output is not an explanation, and the grader will not accept it as one.</image:caption>
      <image:title>Viva: 91 Full-Mark C Submissions Out of 626 Hide a Defect the Autograder Never Checked</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/viva-automated-oral-exam-for-code-openai-build-week/</loc>
    <lastmod>2026-07-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/viva-automated-oral-exam-for-code-openai-build-week/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/viva-automated-oral-exam-for-code-openai-build-week/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/viva-automated-oral-exam-for-code-openai-build-week/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_01_hero.jpg</image:loc>
      <image:caption>Le chiffre principal est 91 sur 626, pas un pourcentage hors sol. Chacune de ces 91 est une soumission notée au maximum qui cache un défaut que l'autograder n'a jamais testé.</image:caption>
      <image:title>Viva : 91 soumissions C notées au maximum sur 626 cachent un défaut que l'autograder n'a jamais testé</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_02_ledger.jpg</image:loc>
      <image:caption>Rien que la ligne Maximum de 3 : 50 sur 226 soumissions notées au maximum cassées sur l'égalité. Le probe est une commande rejouable, pas un paragraphe.</image:caption>
      <image:title>Viva : 91 soumissions C notées au maximum sur 626 cachent un défaut que l'autograder n'a jamais testé</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_03_replay.jpg</image:loc>
      <image:caption>Pas une prédiction, un rejeu. Les 215 divergences se rejouent à l'identique sur un run indépendant. 100 %. C'est le chiffre qui compte après la une.</image:caption>
      <image:title>Viva : 91 soumissions C notées au maximum sur 626 cachent un défaut que l'autograder n'a jamais testé</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_04_concept_map.jpg</image:loc>
      <image:caption>La carte de concepts dit à l'enseignant quoi ré-enseigner lundi matin, pas quel étudiant courrieller ce soir.</image:caption>
      <image:title>Viva : 91 soumissions C notées au maximum sur 626 cachent un défaut que l'autograder n'a jamais testé</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_05_not_cheating.jpg</image:loc>
      <image:caption>Le cadrage est délibéré. Les outils de détection deviennent inutiles dès que les étudiants apprennent à les contourner ; une note de re-enseignement reste utile dans les deux cas.</image:caption>
      <image:title>Viva : 91 soumissions C notées au maximum sur 626 cachent un défaut que l'autograder n'a jamais testé</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_06_defense_prompt.jpg</image:loc>
      <image:caption>Un probe, une question, pas de modèle. On demande à l'étudiant sur sa propre soumission, pas sur le concept en général.</image:caption>
      <image:title>Viva : 91 soumissions C notées au maximum sur 626 cachent un défaut que l'autograder n'a jamais testé</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/viva_08_bluff_caught.jpg</image:loc>
      <image:caption>L'attrape-bluff. Récite la sortie n'est pas une explication, et le correcteur ne l'accepte pas comme telle.</image:caption>
      <image:title>Viva : 91 soumissions C notées au maximum sur 626 cachent un défaut que l'autograder n'a jamais testé</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/ledgerpilot-qwen-mcp-odoo-counterfactual-close-agent/</loc>
    <lastmod>2026-07-14T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/ledgerpilot-qwen-mcp-odoo-counterfactual-close-agent/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/ledgerpilot-qwen-mcp-odoo-counterfactual-close-agent/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/ledgerpilot-qwen-mcp-odoo-counterfactual-close-agent/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_01_architecture.png</image:loc>
      <image:caption>The trust boundary is the gate. The model is a proposer; the gate is the authority. HMAC token binds the approval to the exact entry, so tampering by the model itself is detectable.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_07_counterfactual.png</image:loc>
      <image:caption>The 5 wrong entries are not hypothetical. They are open in the ledger you would ship to your auditor. Same model, same ledger, gate off vs on.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_08_wrong_entry_detail.png</image:loc>
      <image:caption>Balanced. Real accounts. Would pass a trial balance. And it is wrong. This is the class of error the reconciliation check is built for.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_02_approved.jpg</image:loc>
      <image:caption>The happy path. Eight checks, each with an auditable answer, and the token that gets minted only if all eight pass.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_03_wrong_account.jpg</image:loc>
      <image:caption>Balanced, real accounts, passes a trial balance, and refused. Reconciliation catches the semantic error a balance check cannot.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_04_out_of_balance.jpg</image:loc>
      <image:caption>The boring one. The first check any accounting agent should do, and the first check any prompt-only agent skips.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_05_escalated.jpg</image:loc>
      <image:caption>Above the threshold, the gate does not silently approve. Human-in-the-loop is a gate outcome, not an afterthought.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_06_odoo_real_write.png</image:loc>
      <image:caption>Real posted account.move MISC/2026/06/0002 written by the agent from the ECS instance. Not mocked, not a screenshot of an intent, an actual line in the general ledger.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_09_ecs_console.png</image:loc>
      <image:caption>The ECS console. Provisioned by a script that hits the ECS + VPC OpenAPIs, torn down by the same script with a --destroy flag.</image:caption>
      <image:title>LedgerPilot: Same Qwen, Same Ledger, Gate Off Posts 5 Wrong Entries and Gate On Posts 0</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/ledgerpilot-qwen-mcp-odoo-counterfactual-close-agent/</loc>
    <lastmod>2026-07-14T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/ledgerpilot-qwen-mcp-odoo-counterfactual-close-agent/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/ledgerpilot-qwen-mcp-odoo-counterfactual-close-agent/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/ledgerpilot-qwen-mcp-odoo-counterfactual-close-agent/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_01_architecture.png</image:loc>
      <image:caption>La frontière de confiance, c'est le garde-fou. Le modèle est un proposeur ; le garde-fou est l'autorité. Le jeton HMAC lie l'approbation à l'écriture exacte, donc la falsification par le modèle lui-même est détectable.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_07_counterfactual.png</image:loc>
      <image:caption>Les 5 mauvaises écritures ne sont pas hypothétiques. Elles sont ouvertes dans le ledger que vous enverriez à votre auditeur. Même modèle, même ledger, garde-fou désactivé ou activé.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_08_wrong_entry_detail.png</image:loc>
      <image:caption>Équilibrée. Vrais comptes. Passerait un bilan de vérification. Et c'est faux. C'est la classe d'erreur pour laquelle le rapprochement est fait.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_02_approved.jpg</image:loc>
      <image:caption>Le chemin heureux. Huit vérifications, chacune avec une réponse auditable, et le jeton qui n'est émis que si les huit passent.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_03_wrong_account.jpg</image:loc>
      <image:caption>Équilibrée, vrais comptes, passe un bilan de vérification, et refusée. Le rapprochement attrape l'erreur sémantique que la vérification d'équilibre ne voit pas.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_04_out_of_balance.jpg</image:loc>
      <image:caption>L'ennuyeuse. La première vérification que tout agent comptable devrait faire, et la première que tout agent purement prompté saute.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_05_escalated.jpg</image:loc>
      <image:caption>Au-dessus du seuil, le garde-fou n'approuve pas en silence. L'humain dans la boucle est un résultat du garde-fou, pas une pensée après-coup.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_06_odoo_real_write.png</image:loc>
      <image:caption>Vrai account.move posté MISC/2026/06/0002 écrit par l'agent depuis l'instance ECS. Pas simulé, pas une capture d'intention, une vraie ligne dans le grand livre.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/ledgerpilot_09_ecs_console.png</image:loc>
      <image:caption>La console ECS. Provisionnée par un script qui appelle les OpenAPI ECS et VPC, détruite par le même script avec un flag --destroy.</image:caption>
      <image:title>LedgerPilot : même Qwen, même ledger, garde-fou désactivé poste 5 mauvaises écritures, activé en poste 0</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/clatterfall-one-marble-one-subreddit-one-shared-run-a-day/</loc>
    <lastmod>2026-07-13T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/clatterfall-one-marble-one-subreddit-one-shared-run-a-day/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/clatterfall-one-marble-one-subreddit-one-shared-run-a-day/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/clatterfall-one-marble-one-subreddit-one-shared-run-a-day/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_01_daily_run.png</image:loc>
      <image:caption>The daily run. Every morning the whole machine re-runs, and everyone watches the same marble.</image:caption>
      <image:title>Clatterfall: One Marble, One Subreddit, One Shared Run a Day, One Machine Nobody Can Build Alone</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_02_machine.png</image:loc>
      <image:caption>The machine. Every part is a redditor's daily contribution. Nobody could have built this alone.</image:caption>
      <image:title>Clatterfall: One Marble, One Subreddit, One Shared Run a Day, One Machine Nobody Can Build Alone</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_03_frontier.png</image:loc>
      <image:caption>The frontier. You can only build where yesterday's marble actually reached, widened by one column each side.</image:caption>
      <image:title>Clatterfall: One Marble, One Subreddit, One Shared Run a Day, One Machine Nobody Can Build Alone</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_04_one_part_a_day.png</image:loc>
      <image:caption>One part per person per day. Scarcity makes each contribution matter. Never spam.</image:caption>
      <image:title>Clatterfall: One Marble, One Subreddit, One Shared Run a Day, One Machine Nobody Can Build Alone</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_05_signed.png</image:loc>
      <image:caption>Every part is signed and credited to the exact px it moved the marble. The collective is visible, not abstract.</image:caption>
      <image:title>Clatterfall: One Marble, One Subreddit, One Shared Run a Day, One Machine Nobody Can Build Alone</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_06_new_record.png</image:loc>
      <image:caption>NEW RECORD. The whole community wakes up and finds out together.</image:caption>
      <image:title>Clatterfall: One Marble, One Subreddit, One Shared Run a Day, One Machine Nobody Can Build Alone</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_07_dissolve.png</image:loc>
      <image:caption>The dissolve. The artifact edits itself. Only parts the marble actually used survive the night.</image:caption>
      <image:title>Clatterfall: One Marble, One Subreddit, One Shared Run a Day, One Machine Nobody Can Build Alone</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_08_post.png</image:loc>
      <image:caption>The post as it appears in the Reddit feed. Fast, no Phaser boot required to advertise the state. The full game boots only when the user opens it.</image:caption>
      <image:title>Clatterfall: One Marble, One Subreddit, One Shared Run a Day, One Machine Nobody Can Build Alone</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/clatterfall-one-marble-one-subreddit-one-shared-run-a-day/</loc>
    <lastmod>2026-07-13T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/clatterfall-one-marble-one-subreddit-one-shared-run-a-day/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/clatterfall-one-marble-one-subreddit-one-shared-run-a-day/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/clatterfall-one-marble-one-subreddit-one-shared-run-a-day/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_01_daily_run.png</image:loc>
      <image:caption>L'exécution quotidienne. Chaque matin, toute la machine se ré-exécute, et tout le monde regarde la même bille.</image:caption>
      <image:title>Clatterfall : une bille, un subreddit, une exécution partagée par jour, une machine que personne ne peut bâtir seul</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_02_machine.png</image:loc>
      <image:caption>La machine. Chaque pièce est la contribution quotidienne d'un redditor. Personne n'aurait pu bâtir ça seul.</image:caption>
      <image:title>Clatterfall : une bille, un subreddit, une exécution partagée par jour, une machine que personne ne peut bâtir seul</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_03_frontier.png</image:loc>
      <image:caption>La frontière. On ne peut bâtir que là où la bille d'hier a réellement atteint, élargi d'une colonne de chaque côté.</image:caption>
      <image:title>Clatterfall : une bille, un subreddit, une exécution partagée par jour, une machine que personne ne peut bâtir seul</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_04_one_part_a_day.png</image:loc>
      <image:caption>Une pièce par personne par jour. La rareté rend chaque contribution importante. Jamais de spam.</image:caption>
      <image:title>Clatterfall : une bille, un subreddit, une exécution partagée par jour, une machine que personne ne peut bâtir seul</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_05_signed.png</image:loc>
      <image:caption>Chaque pièce est signée et créditée aux px exacts qu'elle a fait avancer la bille. Le collectif est visible, pas abstrait.</image:caption>
      <image:title>Clatterfall : une bille, un subreddit, une exécution partagée par jour, une machine que personne ne peut bâtir seul</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_06_new_record.png</image:loc>
      <image:caption>NEW RECORD. Toute la communauté se réveille et le découvre ensemble.</image:caption>
      <image:title>Clatterfall : une bille, un subreddit, une exécution partagée par jour, une machine que personne ne peut bâtir seul</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_07_dissolve.png</image:loc>
      <image:caption>La dissolution. L'artefact s'édite lui-même. Seules les pièces que la bille a réellement utilisées survivent à la nuit.</image:caption>
      <image:title>Clatterfall : une bille, un subreddit, une exécution partagée par jour, une machine que personne ne peut bâtir seul</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/clatterfall_08_post.png</image:loc>
      <image:caption>Le post tel qu'il apparaît dans le flux Reddit. Rapide, aucun boot Phaser requis pour annoncer l'état. Le jeu complet ne boote que quand l'utilisateur l'ouvre.</image:caption>
      <image:title>Clatterfall : une bille, un subreddit, une exécution partagée par jour, une machine que personne ne peut bâtir seul</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/loose-ends-slack-agent-closes-loops-on-evidence/</loc>
    <lastmod>2026-07-12T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/loose-ends-slack-agent-closes-loops-on-evidence/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/loose-ends-slack-agent-closes-loops-on-evidence/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/loose-ends-slack-agent-closes-loops-on-evidence/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_stat.png</image:loc>
      <image:caption>The number Loose Ends exists to close. Same 83,365 managed cases across 26 counties, the study measures both rates side by side.</image:caption>
      <image:title>Loose Ends: The Slack Agent That Refuses to Close a Loop Until It Sees Evidence</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_architecture.png</image:loc>
      <image:caption>Events API is the eyes; the ledger is the spine; RTS is the retroactive lookup that only fires on @-mention. Every write is behind a Block Kit human gate.</image:caption>
      <image:title>Loose Ends: The Slack Agent That Refuses to Close a Loop Until It Sees Evidence</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_unowned.png</image:loc>
      <image:caption>Unowned card. One-tap Claim / Done / Snooze / Dismiss. Never spams the channel; only surfaces when an ask has no owner.</image:caption>
      <image:title>Loose Ends: The Slack Agent That Refuses to Close a Loop Until It Sees Evidence</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_escalated.png</image:loc>
      <image:caption>Escalation. Response SLA passed with no claim, so the ask jumps to the designated backup human. Deterministic timer, LLM-free.</image:caption>
      <image:title>Loose Ends: The Slack Agent That Refuses to Close a Loop Until It Sees Evidence</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_verified.png</image:loc>
      <image:caption>The flagship moment. A later 'closed out the Diaz housing case' flips the card to Verified. Evidence, not a timer.</image:caption>
      <image:title>Loose Ends: The Slack Agent That Refuses to Close a Loop Until It Sees Evidence</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_broken.png</image:loc>
      <image:caption>'Closed is not done.' A deadline that passes with no evidence stays visible and eventually turns BROKEN, on the dashboard. This is the rung every other tool skips.</image:caption>
      <image:title>Loose Ends: The Slack Agent That Refuses to Close a Loop Until It Sees Evidence</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_eval.png</image:loc>
      <image:caption>The verifier's headline is not 92.3% recall, it's 0.0% false-verify. Loose Ends will miss a proof before it will falsely close a case.</image:caption>
      <image:title>Loose Ends: The Slack Agent That Refuses to Close a Loop Until It Sees Evidence</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_rts_briefing.jpg</image:loc>
      <image:caption>Mention the agent and say 'scan this channel'. RTS reads the past, classifies in memory, and reports permalinks. Nothing stored.</image:caption>
      <image:title>Loose Ends: The Slack Agent That Refuses to Close a Loop Until It Sees Evidence</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/loose-ends-slack-agent-closes-loops-on-evidence/</loc>
    <lastmod>2026-07-12T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/loose-ends-slack-agent-closes-loops-on-evidence/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/loose-ends-slack-agent-closes-loops-on-evidence/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/loose-ends-slack-agent-closes-loops-on-evidence/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_stat.png</image:loc>
      <image:caption>Le chiffre que Loose Ends existe pour combler. Mêmes 83 365 dossiers gérés dans 26 comtés, l'étude mesure les deux taux côte à côte.</image:caption>
      <image:title>Loose Ends : l'agent Slack qui refuse de fermer une boucle tant qu'il n'a pas vu la preuve</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_architecture.png</image:loc>
      <image:caption>L'Events API est les yeux ; le ledger est la colonne ; RTS est le lookup rétroactif qui ne se déclenche qu'au @-mention. Chaque écriture passe par une porte humaine Block Kit.</image:caption>
      <image:title>Loose Ends : l'agent Slack qui refuse de fermer une boucle tant qu'il n'a pas vu la preuve</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_unowned.png</image:loc>
      <image:caption>Carte sans propriétaire. Claim / Done / Snooze / Dismiss à un tap. Ne spamme jamais le canal ; n'apparaît que quand une demande n'a pas de propriétaire.</image:caption>
      <image:title>Loose Ends : l'agent Slack qui refuse de fermer une boucle tant qu'il n'a pas vu la preuve</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_escalated.png</image:loc>
      <image:caption>Escalade. Le SLA de réponse est passé sans revendication, alors la demande saute au back-up humain désigné. Minuterie déterministe, sans LLM.</image:caption>
      <image:title>Loose Ends : l'agent Slack qui refuse de fermer une boucle tant qu'il n'a pas vu la preuve</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_verified.png</image:loc>
      <image:caption>Le moment vedette. Un « dossier Diaz clôturé » ultérieur fait basculer la carte en Verified. Preuve, pas minuterie.</image:caption>
      <image:title>Loose Ends : l'agent Slack qui refuse de fermer une boucle tant qu'il n'a pas vu la preuve</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_broken.png</image:loc>
      <image:caption>« Fermé n'est pas fait. » Une échéance qui passe sans preuve reste visible et finit par tourner BROKEN, sur le tableau de bord. C'est l'échelon que chaque autre outil saute.</image:caption>
      <image:title>Loose Ends : l'agent Slack qui refuse de fermer une boucle tant qu'il n'a pas vu la preuve</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_eval.png</image:loc>
      <image:caption>Le titre du vérifieur n'est pas 92,3 % de rappel, c'est 0,0 % de fausse validation. Loose Ends ratera une preuve avant de fermer faussement un dossier.</image:caption>
      <image:title>Loose Ends : l'agent Slack qui refuse de fermer une boucle tant qu'il n'a pas vu la preuve</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/looseends_rts_briefing.jpg</image:loc>
      <image:caption>On mentionne l'agent et on lui dit « scanne ce canal ». RTS lit le passé, classe en mémoire, rapporte des permaliens. Rien de stocké.</image:caption>
      <image:title>Loose Ends : l'agent Slack qui refuse de fermer une boucle tant qu'il n'a pas vu la preuve</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/pick-your-side-manufactured-passion-gemini-elevenlabs/</loc>
    <lastmod>2026-07-11T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/pick-your-side-manufactured-passion-gemini-elevenlabs/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/pick-your-side-manufactured-passion-gemini-elevenlabs/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/pick-your-side-manufactured-passion-gemini-elevenlabs/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/pickyourside_landing.jpg</image:loc>
      <image:caption>The landing page tells you two things in one glance: this is a stadium moment, and the sound is half the app.</image:caption>
      <image:title>Pick Your Side: I Built a Machine That Manufactures a World Cup Team, Grounded in Real History</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/pickyourside_sources.jpg</image:loc>
      <image:caption>Sources are surfaced, not hidden. If the app claims a fact, the page it read and the query it ran are one click away.</image:caption>
      <image:title>Pick Your Side: I Built a Machine That Manufactures a World Cup Team, Grounded in Real History</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/pickyourside_reveal.jpg</image:loc>
      <image:caption>The reveal is the payoff. The colours, the name, the eruption, and the swear-in all land on the same beat.</image:caption>
      <image:title>Pick Your Side: I Built a Machine That Manufactures a World Cup Team, Grounded in Real History</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/pickyourside_mobile_reveal.jpg</image:loc>
      <image:caption>The reveal is designed vertical-first. The announcer control stays visible so a muted viewer is never trapped.</image:caption>
      <image:title>Pick Your Side: I Built a Machine That Manufactures a World Cup Team, Grounded in Real History</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/pick-your-side-manufactured-passion-gemini-elevenlabs/</loc>
    <lastmod>2026-07-11T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/pick-your-side-manufactured-passion-gemini-elevenlabs/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/pick-your-side-manufactured-passion-gemini-elevenlabs/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/pick-your-side-manufactured-passion-gemini-elevenlabs/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/pickyourside_landing.jpg</image:loc>
      <image:caption>La page d'accueil dit deux choses d'un coup : c'est un moment de stade, et le son fait la moitié de l'app.</image:caption>
      <image:title>Pick Your Side : j'ai construit une machine qui vous fabrique une équipe de Coupe du monde, ancrée dans la vraie histoire</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/pickyourside_sources.jpg</image:loc>
      <image:caption>Les sources sont affichées, pas cachées. Si l'app affirme un fait, la page lue et la requête exécutée sont à un clic.</image:caption>
      <image:title>Pick Your Side : j'ai construit une machine qui vous fabrique une équipe de Coupe du monde, ancrée dans la vraie histoire</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/pickyourside_reveal.jpg</image:loc>
      <image:caption>La révélation est la récompense. Les couleurs, le nom, l'éruption et le serment tombent sur le même temps.</image:caption>
      <image:title>Pick Your Side : j'ai construit une machine qui vous fabrique une équipe de Coupe du monde, ancrée dans la vraie histoire</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/pickyourside_mobile_reveal.jpg</image:loc>
      <image:caption>La révélation est pensée d'abord en vertical. La commande d'annonceur reste visible pour qu'un visiteur muet ne soit jamais coincé.</image:caption>
      <image:title>Pick Your Side : j'ai construit une machine qui vous fabrique une équipe de Coupe du monde, ancrée dans la vraie histoire</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/flakewarden-agentic-flaky-test-triage-uipath-maestro/</loc>
    <lastmod>2026-06-28T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/flakewarden-agentic-flaky-test-triage-uipath-maestro/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/flakewarden-agentic-flaky-test-triage-uipath-maestro/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/flakewarden-agentic-flaky-test-triage-uipath-maestro/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_architecture.png</image:loc>
      <image:caption>Deterministic where it must be exact, generative where the context is messy. The boundary is the architecture.</image:caption>
      <image:title>FlakeWarden: 90.7% Accuracy and a 0% Safety False-Positive Rate on Flaky-Test Triage, on UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_verdict_real_defect.png</image:loc>
      <image:caption>real_defect verdict on Checkout_E2E_007 (0.95 confidence). Notice the empty proposed_fix: the agent refuses to suggest a fix when the failure is a real bug, even though it could.</image:caption>
      <image:title>FlakeWarden: 90.7% Accuracy and a 0% Safety False-Positive Rate on Flaky-Test Triage, on UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_verdict_flaky.jpg</image:loc>
      <image:caption>flaky verdict on Cart_UI_012 with a proposed_fix. Only flaky verdicts populate the fix; the action still requires Action Center approval.</image:caption>
      <image:title>FlakeWarden: 90.7% Accuracy and a 0% Safety False-Positive Rate on Flaky-Test Triage, on UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_verdict_environment.png</image:loc>
      <image:caption>environment verdict on Payments_API_003 (0.97). Same agent, same schema, three different correct labels across the verified happy paths.</image:caption>
      <image:title>FlakeWarden: 90.7% Accuracy and a 0% Safety False-Positive Rate on Flaky-Test Triage, on UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_coding_agent_cli.png</image:loc>
      <image:caption>Coding-agent loop with the uip CLI. Every command in the loop has a deterministic exit code, which is what makes an agent-driven build trustworthy.</image:caption>
      <image:title>FlakeWarden: 90.7% Accuracy and a 0% Safety False-Positive Rate on Flaky-Test Triage, on UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_deployed_process.png</image:loc>
      <image:caption>Triage Classifier agent v1.0.0 deployed as an Orchestrator process. The agent itself is live; the deployment-plumbing step left is wiring the BPMN's Orchestrator.StartAgentJob call to a serverless robot.</image:caption>
      <image:title>FlakeWarden: 90.7% Accuracy and a 0% Safety False-Positive Rate on Flaky-Test Triage, on UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_maestro_validate.png</image:loc>
      <image:caption>uip maestro bpmn validate passes against the CLI-authored process. The validator catches missing registry entries and dangling gateway branches; a green output means the orchestration is at least structurally trustworthy.</image:caption>
      <image:title>FlakeWarden: 90.7% Accuracy and a 0% Safety False-Positive Rate on Flaky-Test Triage, on UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_maestro_run.jpg</image:loc>
      <image:caption>An ambiguous failure walks the path: scorer → classifier → flaky verdict with a proposed_fix → routed into the human-gated heal branch. The Action Center task is what stands between the agent and any change to the test suite.</image:caption>
      <image:title>FlakeWarden: 90.7% Accuracy and a 0% Safety False-Positive Rate on Flaky-Test Triage, on UiPath Maestro</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/flakewarden-agentic-flaky-test-triage-uipath-maestro/</loc>
    <lastmod>2026-06-28T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/flakewarden-agentic-flaky-test-triage-uipath-maestro/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/flakewarden-agentic-flaky-test-triage-uipath-maestro/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/flakewarden-agentic-flaky-test-triage-uipath-maestro/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_architecture.png</image:loc>
      <image:caption>Déterministe là où ça doit être exact, génératif là où le contexte est désordonné. La frontière, c'est l'architecture.</image:caption>
      <image:title>FlakeWarden : 90,7 % de précision et 0 % de faux positifs côté sécurité sur le triage des tests instables, sur UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_verdict_real_defect.png</image:loc>
      <image:caption>Verdict real_defect sur Checkout_E2E_007 (confiance 0,95). À noter le proposed_fix vide : l'agent refuse de proposer une correction quand l'échec est un vrai bug, même s'il le pourrait.</image:caption>
      <image:title>FlakeWarden : 90,7 % de précision et 0 % de faux positifs côté sécurité sur le triage des tests instables, sur UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_verdict_flaky.jpg</image:loc>
      <image:caption>Verdict flaky sur Cart_UI_012 avec un proposed_fix. Seuls les verdicts flaky remplissent le correctif ; l'action exige quand même une approbation dans Action Center.</image:caption>
      <image:title>FlakeWarden : 90,7 % de précision et 0 % de faux positifs côté sécurité sur le triage des tests instables, sur UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_verdict_environment.png</image:loc>
      <image:caption>Verdict environment sur Payments_API_003 (0,97). Même agent, même schéma, trois étiquettes correctes différentes sur les chemins heureux vérifiés.</image:caption>
      <image:title>FlakeWarden : 90,7 % de précision et 0 % de faux positifs côté sécurité sur le triage des tests instables, sur UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_coding_agent_cli.png</image:loc>
      <image:caption>Boucle d'agent codeur avec le CLI uip. Chaque commande de la boucle a un code de sortie déterministe, ce qui rend un build piloté par agent digne de confiance.</image:caption>
      <image:title>FlakeWarden : 90,7 % de précision et 0 % de faux positifs côté sécurité sur le triage des tests instables, sur UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_deployed_process.png</image:loc>
      <image:caption>Agent Triage Classifier v1.0.0 déployé comme processus Orchestrator. L'agent lui-même tourne ; l'étape de plomberie restante est de câbler l'appel Orchestrator.StartAgentJob du BPMN à un robot serverless.</image:caption>
      <image:title>FlakeWarden : 90,7 % de précision et 0 % de faux positifs côté sécurité sur le triage des tests instables, sur UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_maestro_validate.png</image:loc>
      <image:caption>uip maestro bpmn validate passe contre le processus rédigé via le CLI. Le validateur attrape les entrées de registre manquantes et les branches de passerelle pendantes ; une sortie verte signifie que l'orchestration est au moins structurellement digne de confiance.</image:caption>
      <image:title>FlakeWarden : 90,7 % de précision et 0 % de faux positifs côté sécurité sur le triage des tests instables, sur UiPath Maestro</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/flakewarden_maestro_run.jpg</image:loc>
      <image:caption>Un échec ambigu suit le chemin : scoreur → classifieur → verdict flaky avec un proposed_fix → routé vers la branche de soin validé par humain. La tâche Action Center est ce qui se tient entre l'agent et tout changement à la suite de tests.</image:caption>
      <image:title>FlakeWarden : 90,7 % de précision et 0 % de faux positifs côté sécurité sur le triage des tests instables, sur UiPath Maestro</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/lotzero-global-auctions-aurora-dsql-dynamodb/</loc>
    <lastmod>2026-06-27T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/lotzero-global-auctions-aurora-dsql-dynamodb/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/lotzero-global-auctions-aurora-dsql-dynamodb/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/lotzero-global-auctions-aurora-dsql-dynamodb/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_architecture.png</image:loc>
      <image:caption>Two stores, one explicit consistency boundary. The part that must be exactly right (the money) is strongly consistent across Regions. The part that just needs to be fast and huge (the social firehose) is eventually consistent.</image:caption>
      <image:title>LotZero: Zero Oversells and Zero Double-Spends on a Global Live Auction, Proven on Aurora DSQL</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_auction_english.png</image:loc>
      <image:caption>English ascending auction. The held line in the wallet is the DSQL transaction at work: funds are held on the high bidder and released the instant they are outbid.</image:caption>
      <image:title>LotZero: Zero Oversells and Zero Double-Spends on a Global Live Auction, Proven on Aurora DSQL</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_proof.png</image:loc>
      <image:caption>On local PGlite the transactions serialize, so the race is simulated. On Aurora DSQL the attempts run with true concurrency against the live cluster and the losing transactions hit a real OCC conflict and retry. Identical guarantee, real contention.</image:caption>
      <image:title>LotZero: Zero Oversells and Zero Double-Spends on a Global Live Auction, Proven on Aurora DSQL</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_auction_dutch.jpg</image:loc>
      <image:caption>Dutch is the brutal one. Everyone watching, the price drops, the first claim wins. That moment is exactly when an eventually-consistent store would silently let two Regions both win.</image:caption>
      <image:title>LotZero: Zero Oversells and Zero Double-Spends on a Global Live Auction, Proven on Aurora DSQL</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_wallet.png</image:loc>
      <image:caption>The wallet surfaces what the DSQL transactions are doing. 'Held' is the line that prevents double-spends; when an outbid event fires, the held figure decreases live.</image:caption>
      <image:title>LotZero: Zero Oversells and Zero Double-Spends on a Global Live Auction, Proven on Aurora DSQL</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/lotzero-global-auctions-aurora-dsql-dynamodb/</loc>
    <lastmod>2026-06-27T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/lotzero-global-auctions-aurora-dsql-dynamodb/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/lotzero-global-auctions-aurora-dsql-dynamodb/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/lotzero-global-auctions-aurora-dsql-dynamodb/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_architecture.png</image:loc>
      <image:caption>Deux moteurs de stockage, une frontière de cohérence explicite. La partie qui doit être exactement juste (l'argent) est fortement cohérente entre régions. La partie qui doit juste être rapide et massive (le flux social) est à cohérence éventuelle.</image:caption>
      <image:title>LotZero : zéro ventes en trop et zéro doubles débits sur une enchère mondiale en direct, prouvé sur Aurora DSQL</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_auction_english.png</image:loc>
      <image:caption>Enchère anglaise ascendante. La ligne « bloqué » du portefeuille est la transaction DSQL à l'œuvre : les fonds sont bloqués sur le plus haut enchérisseur et libérés à l'instant où il se fait surenchérir.</image:caption>
      <image:title>LotZero : zéro ventes en trop et zéro doubles débits sur une enchère mondiale en direct, prouvé sur Aurora DSQL</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_proof.png</image:loc>
      <image:caption>Sur PGlite local les transactions se sérialisent, donc la course est simulée. Sur Aurora DSQL les tentatives tournent en vraie concurrence contre la cluster en production et les transactions perdantes rencontrent un vrai conflit OCC puis réessayent. Garantie identique, contention réelle.</image:caption>
      <image:title>LotZero : zéro ventes en trop et zéro doubles débits sur une enchère mondiale en direct, prouvé sur Aurora DSQL</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_auction_dutch.jpg</image:loc>
      <image:caption>La néerlandaise est la brutale. Tout le monde observe, le prix baisse, la première réclamation gagne. C'est précisément à cet instant qu'un stockage à cohérence éventuelle laisserait silencieusement gagner deux régions.</image:caption>
      <image:title>LotZero : zéro ventes en trop et zéro doubles débits sur une enchère mondiale en direct, prouvé sur Aurora DSQL</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/lotzero_wallet.png</image:loc>
      <image:caption>Le portefeuille rend visible ce que font les transactions DSQL. « Bloqué » est la ligne qui empêche les doubles débits ; quand un événement de surenchère se déclenche, le chiffre bloqué diminue en direct.</image:caption>
      <image:title>LotZero : zéro ventes en trop et zéro doubles débits sur une enchère mondiale en direct, prouvé sur Aurora DSQL</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/orbitonboard-contributor-starter-kits-gitlab-orbit/</loc>
    <lastmod>2026-06-24T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/orbitonboard-contributor-starter-kits-gitlab-orbit/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/orbitonboard-contributor-starter-kits-gitlab-orbit/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/orbitonboard-contributor-starter-kits-gitlab-orbit/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_architecture.png</image:loc>
      <image:caption>All four Orbit query types in a single coordinated workflow. Five queries, in parallel, in about 10 seconds.</image:caption>
      <image:title>OrbitOnboard: I Used All Four GitLab Orbit Query Types to Generate a Contributor Starter Kit in 10 Seconds</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_issue_context.jpg</image:loc>
      <image:caption>OrbitOnboard's input is a single issue. The title and labels become the keyword set fed into the five Orbit queries.</image:caption>
      <image:title>OrbitOnboard: I Used All Four GitLab Orbit Query Types to Generate a Contributor Starter Kit in 10 Seconds</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_reading_order.jpg</image:loc>
      <image:caption>Reading order from path finding: start at the bottom, foundational code first, work up to the implementation. The graph picked this sequence, not me.</image:caption>
      <image:title>OrbitOnboard: I Used All Four GitLab Orbit Query Types to Generate a Contributor Starter Kit in 10 Seconds</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_critical_files.jpg</image:loc>
      <image:caption>Critical files ranked by MR-touch count: a runtime view of what the team actually keeps changing in this area.</image:caption>
      <image:title>OrbitOnboard: I Used All Four GitLab Orbit Query Types to Generate a Contributor Starter Kit in 10 Seconds</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_expert_map.jpg</image:loc>
      <image:caption>Expert map: contributors ranked by merged-MR authorship in the affected area, not by raw commit count. Reviewers-only contributors aren't included, which is a documented limitation.</image:caption>
      <image:title>OrbitOnboard: I Used All Four GitLab Orbit Query Types to Generate a Contributor Starter Kit in 10 Seconds</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_readme_render.jpg</image:loc>
      <image:caption>The README is the install path for both the CLI and the Custom Agent. Same query recipes either way.</image:caption>
      <image:title>OrbitOnboard: I Used All Four GitLab Orbit Query Types to Generate a Contributor Starter Kit in 10 Seconds</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/orbitonboard-contributor-starter-kits-gitlab-orbit/</loc>
    <lastmod>2026-06-24T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/orbitonboard-contributor-starter-kits-gitlab-orbit/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/orbitonboard-contributor-starter-kits-gitlab-orbit/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/orbitonboard-contributor-starter-kits-gitlab-orbit/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_architecture.png</image:loc>
      <image:caption>Les quatre types de requêtes Orbit dans un seul flux coordonné. Cinq requêtes, en parallèle, en une dizaine de secondes.</image:caption>
      <image:title>OrbitOnboard : j'ai utilisé les quatre types de requêtes GitLab Orbit pour générer un kit de démarrage en 10 secondes</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_issue_context.jpg</image:loc>
      <image:caption>L'entrée d'OrbitOnboard est un seul issue. Le titre et les labels deviennent l'ensemble de mots-clés alimentant les cinq requêtes Orbit.</image:caption>
      <image:title>OrbitOnboard : j'ai utilisé les quatre types de requêtes GitLab Orbit pour générer un kit de démarrage en 10 secondes</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_reading_order.jpg</image:loc>
      <image:caption>Ordre de lecture issu du path finding : commencer par le bas, code fondationnel d'abord, remonter jusqu'à l'implémentation. C'est le graphe qui a choisi cette séquence, pas moi.</image:caption>
      <image:title>OrbitOnboard : j'ai utilisé les quatre types de requêtes GitLab Orbit pour générer un kit de démarrage en 10 secondes</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_critical_files.jpg</image:loc>
      <image:caption>Fichiers critiques classés par nombre de touches MR : une vue runtime de ce que l'équipe change effectivement dans ce secteur.</image:caption>
      <image:title>OrbitOnboard : j'ai utilisé les quatre types de requêtes GitLab Orbit pour générer un kit de démarrage en 10 secondes</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_expert_map.jpg</image:loc>
      <image:caption>Carte des experts : contributeurs classés par paternité de MR fusionnés dans le secteur concerné, pas par nombre de commits brut. Les contributeurs uniquement reviewers ne sont pas inclus, ce qui est une limite documentée.</image:caption>
      <image:title>OrbitOnboard : j'ai utilisé les quatre types de requêtes GitLab Orbit pour générer un kit de démarrage en 10 secondes</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/orbitonboard_readme_render.jpg</image:loc>
      <image:caption>Le README est le chemin d'installation à la fois pour le CLI et le Custom Agent. Mêmes recettes de requêtes dans les deux cas.</image:caption>
      <image:title>OrbitOnboard : j'ai utilisé les quatre types de requêtes GitLab Orbit pour générer un kit de démarrage en 10 secondes</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/sworn-signed-dfir-findings-mcp-gateway/</loc>
    <lastmod>2026-06-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/sworn-signed-dfir-findings-mcp-gateway/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/sworn-signed-dfir-findings-mcp-gateway/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/sworn-signed-dfir-findings-mcp-gateway/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sworn_architecture.png</image:loc>
      <image:caption>Trust boundaries are colour-coded. The LLM and the evidence content are both untrusted; everything the orchestrator says is wrapped in &lt;evidence&gt;...&lt;/evidence&gt; tags and the gateway sanitizes inline &lt;system&gt;, &lt;assistant&gt;, &lt;tool_use&gt; markers before they reach the model.</image:caption>
      <image:title>SWORN: I Built a DFIR Gateway That Cryptographically Signs Every Finding</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sworn_triage_run.png</image:loc>
      <image:caption>Triage run with specialists: memory, disk, network. Six self-correction sequences logged with rationale and re-plan. The orchestrator survives every tool failure because no single specialist holds full case context.</image:caption>
      <image:title>SWORN: I Built a DFIR Gateway That Cryptographically Signs Every Finding</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sworn_corroboration_gate.png</image:loc>
      <image:caption>Single-source claim demoted to INDICATION. Two artifact families promoted to DRAFT. Ledger verification: 31 entries verified. The corroboration gate is in code, not in a prompt.</image:caption>
      <image:title>SWORN: I Built a DFIR Gateway That Cryptographically Signs Every Finding</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/sworn-signed-dfir-findings-mcp-gateway/</loc>
    <lastmod>2026-06-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/sworn-signed-dfir-findings-mcp-gateway/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/sworn-signed-dfir-findings-mcp-gateway/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/sworn-signed-dfir-findings-mcp-gateway/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sworn_architecture.png</image:loc>
      <image:caption>Les frontières de confiance sont en couleur. Le LLM et le contenu des preuves sont tous deux non fiables ; tout ce que l'orchestrateur dit est encapsulé en balises &lt;evidence&gt;...&lt;/evidence&gt; et la passerelle nettoie les marqueurs &lt;system&gt;, &lt;assistant&gt;, &lt;tool_use&gt; inline avant qu'ils atteignent le modèle.</image:caption>
      <image:title>SWORN : j'ai bâti une passerelle DFIR qui signe cryptographiquement chaque finding</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sworn_triage_run.png</image:loc>
      <image:caption>Session de triage avec spécialistes : mémoire, disque, réseau. Six séquences d'auto-correction journalisées avec rationale et re-planification. L'orchestrateur survit à chaque échec d'outil parce qu'aucun spécialiste seul ne tient le contexte complet du cas.</image:caption>
      <image:title>SWORN : j'ai bâti une passerelle DFIR qui signe cryptographiquement chaque finding</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/sworn_corroboration_gate.png</image:loc>
      <image:caption>Revendication à source unique rétrogradée en INDICATION. Deux familles d'artéfacts promues en DRAFT. Vérification du ledger : 31 entrées vérifiées. Le portail de corroboration est dans le code, pas dans un prompt.</image:caption>
      <image:title>SWORN : j'ai bâti une passerelle DFIR qui signe cryptographiquement chaque finding</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/agentgate-pre-action-governance-for-splunk/</loc>
    <lastmod>2026-06-14T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/agentgate-pre-action-governance-for-splunk/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/agentgate-pre-action-governance-for-splunk/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/agentgate-pre-action-governance-for-splunk/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_dashboard_top.png</image:loc>
      <image:caption>Every AI-agent action against Splunk that AgentGate has gated, in a native Splunk dashboard you can filter by decision, agent, or policy.</image:caption>
      <image:title>AgentGate: I Built the Gate Between AI Agents and Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_architecture.png</image:loc>
      <image:caption>Five deterministic stages plus one advisory reasoning stage. Decisions are reproducible from the policy library alone; the LLM explains, it doesn't gate.</image:caption>
      <image:title>AgentGate: I Built the Gate Between AI Agents and Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_friendly_fire.png</image:loc>
      <image:caption>Friendly fire BLOCK. A cleanup agent proposes to disable a saved search. The blast-radius walk shows host paymentapp01 has zero redundant coverage and is PCI-tagged. POL-001 + POL-002 + POL-010 fire. Verdict BLOCK. Standards cited: EU AI Act Article 14, PCI DSS 10.6, NIST AI RMF MANAGE-2.3.</image:caption>
      <image:title>AgentGate: I Built the Gate Between AI Agents and Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_prompt_injection.png</image:loc>
      <image:caption>Prompt injection BLOCK. A triage agent reads a log line containing 'ignore all previous instructions'. The injection stage catches the override_instruction pattern; POL-006 (OWASP LLM01) fires. Verdict BLOCK.</image:caption>
      <image:title>AgentGate: I Built the Gate Between AI Agents and Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_require_approval.png</image:loc>
      <image:caption>REQUIRE_APPROVAL Finding. A retention-audit agent issues a 30-day SPL across six indexes with no early filter. Cost stage flags &gt;1 SVC-hour and cross-index reach &gt; 5. POL-007 + POL-012 route to REQUIRE_APPROVAL. AgentGate drafts a Finding so an analyst can approve, deny, or modify. Proves the gate is not just `return BLOCK`.</image:caption>
      <image:title>AgentGate: I Built the Gate Between AI Agents and Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_happy_path.png</image:loc>
      <image:caption>Happy-path ALLOW. A reporter bot runs a benign read-only query on host webfront01. All five gating stages pass in under 1 ms. Most decisions look like this; the dashboard makes that visible so SOC leaders can prove false-positive cost is bounded.</image:caption>
      <image:title>AgentGate: I Built the Gate Between AI Agents and Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_dashboard_bottom.png</image:loc>
      <image:caption>Findings table at the bottom of the audit dashboard. Each row is a non-ALLOW verdict, queryable in SPL, mockable as ES 8 v2 Findings on Cloud.</image:caption>
      <image:title>AgentGate: I Built the Gate Between AI Agents and Splunk</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/agentgate-pre-action-governance-for-splunk/</loc>
    <lastmod>2026-06-14T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/agentgate-pre-action-governance-for-splunk/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/agentgate-pre-action-governance-for-splunk/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/agentgate-pre-action-governance-for-splunk/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_dashboard_top.png</image:loc>
      <image:caption>Chaque action d'agent IA contre Splunk passée par AgentGate, dans un tableau de bord Splunk natif filtrable par décision, agent ou politique.</image:caption>
      <image:title>AgentGate : j'ai construit la porte entre les agents IA et Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_architecture.png</image:loc>
      <image:caption>Cinq étapes déterministes plus une étape de raisonnement consultative. Les décisions sont reproductibles depuis la bibliothèque de politiques seule ; le LLM explique, il ne contrôle pas.</image:caption>
      <image:title>AgentGate : j'ai construit la porte entre les agents IA et Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_friendly_fire.png</image:loc>
      <image:caption>Tir ami BLOCK. Un agent de nettoyage propose de désactiver une saved search. Le parcours du rayon d'impact montre que l'hôte paymentapp01 n'a aucune couverture redondante et est tagué PCI. POL-001 + POL-002 + POL-010 se déclenchent. Verdict BLOCK. Standards cités : EU AI Act Article 14, PCI DSS 10.6, NIST AI RMF MANAGE-2.3.</image:caption>
      <image:title>AgentGate : j'ai construit la porte entre les agents IA et Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_prompt_injection.png</image:loc>
      <image:caption>Injection de prompt BLOCK. Un agent de triage lit une ligne de log contenant « ignore all previous instructions ». L'étape d'injection capte le motif override_instruction ; POL-006 (OWASP LLM01) se déclenche. Verdict BLOCK.</image:caption>
      <image:title>AgentGate : j'ai construit la porte entre les agents IA et Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_require_approval.png</image:loc>
      <image:caption>Finding REQUIRE_APPROVAL. Un agent d'audit de rétention émet un SPL de 30 jours sur six indexes sans filtre précoce. L'étape de coût signale &gt; 1 SVC-heure et une portée multi-index &gt; 5. POL-007 + POL-012 routent vers REQUIRE_APPROVAL. AgentGate rédige un Finding pour qu'un analyste approuve, refuse ou modifie. Prouve que le portail n'est pas qu'un `return BLOCK`.</image:caption>
      <image:title>AgentGate : j'ai construit la porte entre les agents IA et Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_happy_path.png</image:loc>
      <image:caption>Chemin heureux ALLOW. Un reporter bot exécute une requête en lecture seule bénigne sur l'hôte webfront01. Les cinq étapes de portail passent en moins de 1 ms. La plupart des décisions ressemblent à ça ; le tableau de bord le rend visible pour que les leaders SOC prouvent que le coût des faux positifs est borné.</image:caption>
      <image:title>AgentGate : j'ai construit la porte entre les agents IA et Splunk</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/agentgate_dashboard_bottom.png</image:loc>
      <image:caption>Table des Findings au bas du tableau d'audit. Chaque ligne est un verdict non-ALLOW, requêtable en SPL, simulable comme Findings ES 8 v2 sur Cloud.</image:caption>
      <image:title>AgentGate : j'ai construit la porte entre les agents IA et Splunk</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/warden-agent-that-governs-your-agents/</loc>
    <lastmod>2026-06-10T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/warden-agent-that-governs-your-agents/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/warden-agent-that-governs-your-agents/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/warden-agent-that-governs-your-agents/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_cover.png</image:loc>
      <image:caption>Warden: an agent that watches the rest of your agents and governs them under human oversight.</image:caption>
      <image:title>Warden: I Built an Agent That Governs Your Other Agents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_rogue_detected.png</image:loc>
      <image:caption>Live operator console: a refund agent goes rogue, Warden senses it through Dynatrace, Gemini classifies the failure, and the human-in-the-loop bar appears at the top because the action is irreversible.</image:caption>
      <image:title>Warden: I Built an Agent That Governs Your Other Agents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_architecture.png</image:loc>
      <image:caption>Five layers: Subjects (the worker fleet), Senses (Dynatrace MCP), Supervisor (Gemini 3 + Python policy), Hands (action layer with human-gated approvals), Output (measured ledger).</image:caption>
      <image:title>Warden: I Built an Agent That Governs Your Other Agents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_incident_audit.png</image:loc>
      <image:caption>Click any incident card and the full audit modal opens: diagnosis, policy trace, human decision, action ledger, dollar impact.</image:caption>
      <image:title>Warden: I Built an Agent That Governs Your Other Agents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_live_dynatrace.png</image:loc>
      <image:caption>The Live Evidence tab on the hosted demo surfaces real OpenTelemetry traces and per-agent metrics in the actual Dynatrace tenant (xwy37883.apps.dynatrace.com), with the DQL queries and span attributes documented inline.</image:caption>
      <image:title>Warden: I Built an Agent That Governs Your Other Agents</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/warden-agent-that-governs-your-agents/</loc>
    <lastmod>2026-06-10T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/warden-agent-that-governs-your-agents/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/warden-agent-that-governs-your-agents/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/warden-agent-that-governs-your-agents/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_cover.png</image:loc>
      <image:caption>Warden : un agent qui surveille les autres et les gouverne sous supervision humaine.</image:caption>
      <image:title>Warden : j'ai construit un agent qui gouverne vos autres agents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_rogue_detected.png</image:loc>
      <image:caption>Console d'opérateur en direct : un agent de remboursement dérape, Warden le détecte via Dynatrace, Gemini classe la défaillance, et la barre d'approbation humaine apparaît en haut parce que l'action est irréversible.</image:caption>
      <image:title>Warden : j'ai construit un agent qui gouverne vos autres agents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_architecture.png</image:loc>
      <image:caption>Cinq couches : Sujets (la flotte de workers), Sens (MCP Dynatrace), Superviseur (Gemini 3 + politique Python), Mains (couche d'action avec approbations humaines) et Sortie (registre mesuré).</image:caption>
      <image:title>Warden : j'ai construit un agent qui gouverne vos autres agents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_incident_audit.png</image:loc>
      <image:caption>Cliquer sur n'importe quelle carte d'incident ouvre l'audit complet : diagnostic, trace de politique, décision humaine, registre d'actions, impact en dollars.</image:caption>
      <image:title>Warden : j'ai construit un agent qui gouverne vos autres agents</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/warden_live_dynatrace.png</image:loc>
      <image:caption>L'onglet Live Evidence de la démo hébergée montre de vraies traces OpenTelemetry et des métriques par agent dans le locataire Dynatrace réel (xwy37883.apps.dynatrace.com), avec les requêtes DQL et les attributs de span documentés en ligne.</image:caption>
      <image:title>Warden : j'ai construit un agent qui gouverne vos autres agents</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/appetite-for-noise-glp1-spending-tested/</loc>
    <lastmod>2026-05-31T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/appetite-for-noise-glp1-spending-tested/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/appetite-for-noise-glp1-spending-tested/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/appetite-for-noise-glp1-spending-tested/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_summary.png</image:loc>
      <image:caption>The whole project on one card: a significant-looking naive result that collapses under proper inference, and a defensible dollar range from the micro literature.</image:caption>
      <image:title>Appetite for Noise: I Tested the Ozempic Spending Story on the National Data</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_did_forest.png</image:loc>
      <image:caption>Same data, two specifications. Once each group has its own trend so the model tests for a break at the GLP-1 ramp instead of a level gap, the effect lands on zero.</image:caption>
      <image:title>Appetite for Noise: I Tested the Ozempic Spending Story on the National Data</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_event_study.png</image:loc>
      <image:caption>The event study tells the story plainly: restaurants and food services have outgrown goods retail for over a decade. The naive DiD is reading that pre-existing drift as a GLP-1 effect.</image:caption>
      <image:title>Appetite for Noise: I Tested the Ozempic Spending Story on the National Data</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_impact_estimator.png</image:loc>
      <image:caption>The Impact Estimator lets you change adoption growth or per-user reduction and project forward. The defaults come from KFF, Cornell/Numerator, and Morgan Stanley.</image:caption>
      <image:title>Appetite for Noise: I Tested the Ozempic Spending Story on the National Data</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_real_spending.png</image:loc>
      <image:caption>Real spending lines after deflating each category by its own CPI. The 2022 line marks the GLP-1 ramp. The sensitive line was already on a steeper trajectory in the 2010s.</image:caption>
      <image:title>Appetite for Noise: I Tested the Ozempic Spending Story on the National Data</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_category_growth.png</image:loc>
      <image:caption>Pre/post growth by category. The sensitive group outgrew the control group, but the event study above shows that was already the secular pattern in the 2010s.</image:caption>
      <image:title>Appetite for Noise: I Tested the Ozempic Spending Story on the National Data</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/appetite-for-noise-glp1-spending-tested/</loc>
    <lastmod>2026-05-31T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/appetite-for-noise-glp1-spending-tested/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/appetite-for-noise-glp1-spending-tested/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/appetite-for-noise-glp1-spending-tested/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_summary.png</image:loc>
      <image:caption>Tout le projet sur une fiche : un résultat naïf qui semble significatif mais s'effondre sous une inférence correcte, et une fourchette en dollars défendable tirée de la littérature micro.</image:caption>
      <image:title>Appetite for Noise : j'ai testé le récit Ozempic sur les données nationales</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_did_forest.png</image:loc>
      <image:caption>Mêmes données, deux spécifications. Dès que chaque groupe a sa propre tendance et que le modèle teste une rupture à la rampe GLP-1 plutôt qu'un écart de niveau, l'effet retombe à zéro.</image:caption>
      <image:title>Appetite for Noise : j'ai testé le récit Ozempic sur les données nationales</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_event_study.png</image:loc>
      <image:caption>L'étude d'événement le dit clairement : les restaurants et services alimentaires surperforment le commerce de biens depuis plus d'une décennie. La DiD naïve confond ce glissement préexistant avec un effet GLP-1.</image:caption>
      <image:title>Appetite for Noise : j'ai testé le récit Ozempic sur les données nationales</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_impact_estimator.png</image:loc>
      <image:caption>L'Impact Estimator permet de changer la croissance d'adoption ou la baisse par utilisateur et de projeter dans le futur. Les valeurs par défaut viennent de KFF, Cornell/Numerator et Morgan Stanley.</image:caption>
      <image:title>Appetite for Noise : j'ai testé le récit Ozempic sur les données nationales</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_real_spending.png</image:loc>
      <image:caption>Lignes de dépenses réelles après déflation de chaque catégorie par son propre IPC. La ligne 2022 marque la rampe GLP-1. La courbe sensible était déjà sur une trajectoire plus pentue dans les années 2010.</image:caption>
      <image:title>Appetite for Noise : j'ai testé le récit Ozempic sur les données nationales</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/glp1_category_growth.png</image:loc>
      <image:caption>Croissance pré/post par catégorie. Le groupe sensible a surperformé le groupe témoin, mais l'étude d'événement plus haut montre que c'était déjà la tendance séculaire des années 2010.</image:caption>
      <image:title>Appetite for Noise : j'ai testé le récit Ozempic sur les données nationales</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/memex-institutional-memory-for-reddit-mod-teams/</loc>
    <lastmod>2026-05-24T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/memex-institutional-memory-for-reddit-mod-teams/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/memex-institutional-memory-for-reddit-mod-teams/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/memex-institutional-memory-for-reddit-mod-teams/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_dna.png</image:loc>
      <image:caption>Decision DNA straight from the mod menu on a live post: dominant outcome, consistency, and the similar decisions on record.</image:caption>
      <image:title>Giving a Mod Team a Memory</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_conclave.png</image:loc>
      <image:caption>A Conclave room: a crypto-spam post auto-routed on the keyword 'crypto', with Decision DNA and Remove / Keep / Warn / Escalate voting.</image:caption>
      <image:title>Giving a Mod Team a Memory</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_rulebook.png</image:loc>
      <image:caption>The Living Rulebook: 21 decisions, a 14-day activity view, per-outcome bars, and the 'test a phrase' tool with real example chips.</image:caption>
      <image:title>Giving a Mod Team a Memory</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_calibration.png</image:loc>
      <image:caption>The weekly calibration digest in modmail: agreement with consensus, and exactly where the new mod diverged.</image:caption>
      <image:title>Giving a Mod Team a Memory</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_settings.png</image:loc>
      <image:caption>Settings: auto-routing, escalations-never-auto-ban (on), a scheduled sweep, and the optional semantic matching toggle (off by default).</image:caption>
      <image:title>Giving a Mod Team a Memory</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/memex-institutional-memory-for-reddit-mod-teams/</loc>
    <lastmod>2026-05-24T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/memex-institutional-memory-for-reddit-mod-teams/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/memex-institutional-memory-for-reddit-mod-teams/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/memex-institutional-memory-for-reddit-mod-teams/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_dna.png</image:loc>
      <image:caption>La Decision DNA directement depuis le menu de modération sur un billet : verdict dominant, cohérence, et les décisions similaires au dossier.</image:caption>
      <image:title>Donner une mémoire à une équipe de modération</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_conclave.png</image:loc>
      <image:caption>Une salle Conclave : un billet de spam crypto acheminé sur le mot-clé « crypto », avec la Decision DNA et le vote Retirer / Garder / Avertir / Escalader.</image:caption>
      <image:title>Donner une mémoire à une équipe de modération</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_rulebook.png</image:loc>
      <image:caption>Le Living Rulebook : 21 décisions, une vue d'activité sur 14 jours, des barres par verdict, et l'outil « tester une phrase » avec de vrais exemples.</image:caption>
      <image:title>Donner une mémoire à une équipe de modération</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_calibration.png</image:loc>
      <image:caption>Le résumé de calibration hebdomadaire en modmail : l'accord avec le consensus, et précisément là où le nouveau modérateur a divergé.</image:caption>
      <image:title>Donner une mémoire à une équipe de modération</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/memex_settings.png</image:loc>
      <image:caption>Réglages : acheminement automatique, escalades-jamais-de-ban-auto (activé), balayage planifié, et la bascule optionnelle de correspondance sémantique (désactivée par défaut).</image:caption>
      <image:title>Donner une mémoire à une équipe de modération</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/salon-generative-agents-you-can-walk-into/</loc>
    <lastmod>2026-05-20T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/salon-generative-agents-you-can-walk-into/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/salon-generative-agents-you-can-walk-into/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/salon-generative-agents-you-can-walk-into/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/salon_classic.jpg</image:loc>
      <image:caption>Classic view, five portraits around the octagonal table, the chronicle scroll on the right edge.</image:caption>
      <image:title>Putting Five Minds in a Candle-Lit Room</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/salon_walk.jpg</image:loc>
      <image:caption>Walk the Salon, a top-down view you can move through, with thought bubbles and whisper-pairs near the fire.</image:caption>
      <image:title>Putting Five Minds in a Candle-Lit Room</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/salon_agents.jpg</image:loc>
      <image:caption>Agents reacting to each other, the relationship graph forming in real time.</image:caption>
      <image:title>Putting Five Minds in a Candle-Lit Room</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/salon_languages.jpg</image:loc>
      <image:caption>End-to-end multilingual, the same salon, three languages.</image:caption>
      <image:title>Putting Five Minds in a Candle-Lit Room</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/salon-generative-agents-you-can-walk-into/</loc>
    <lastmod>2026-05-20T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/salon-generative-agents-you-can-walk-into/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/salon-generative-agents-you-can-walk-into/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/salon-generative-agents-you-can-walk-into/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/salon_classic.jpg</image:loc>
      <image:caption>Vue classique, cinq portraits autour de la table octogonale, le parchemin de chronique sur le bord droit.</image:caption>
      <image:title>Cinq esprits dans une pièce éclairée à la chandelle</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/salon_walk.jpg</image:loc>
      <image:caption>Walk the Salon, une vue de haut que l'on parcourt, avec des bulles de pensée et des chuchotements près du feu.</image:caption>
      <image:title>Cinq esprits dans une pièce éclairée à la chandelle</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/salon_agents.jpg</image:loc>
      <image:caption>Les agents réagissent, le graphe de relations se forme en temps réel.</image:caption>
      <image:title>Cinq esprits dans une pièce éclairée à la chandelle</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/salon_languages.jpg</image:loc>
      <image:caption>Multilingue de bout en bout, le même salon, trois langues.</image:caption>
      <image:title>Cinq esprits dans une pièce éclairée à la chandelle</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/auteur-the-first-multi-model-film-festival/</loc>
    <lastmod>2026-05-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/auteur-the-first-multi-model-film-festival/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/auteur-the-first-multi-model-film-festival/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/auteur-the-first-multi-model-film-festival/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/auteur_grid.jpg</image:loc>
      <image:caption>The full festival canvas, 90 nodes, 85 edges, six director groups, scaffolded in a single MCP call.</image:caption>
      <image:title>A Film Festival Where AI Models Compete as Directors</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/auteur_render0.jpg</image:loc>
      <image:caption>The Painter, Ghibli × Tarkovsky. Amber. Patient. (Flux Pro Ultra + Kling 3 Pro)</image:caption>
      <image:title>A Film Festival Where AI Models Compete as Directors</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/auteur_render2.jpg</image:loc>
      <image:caption>The Maximalist, Luhrmann on a scarlet rooftop. (Flux 2 Max + Kling 2.6 Pro)</image:caption>
      <image:title>A Film Festival Where AI Models Compete as Directors</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/auteur-the-first-multi-model-film-festival/</loc>
    <lastmod>2026-05-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/auteur-the-first-multi-model-film-festival/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/auteur-the-first-multi-model-film-festival/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/auteur-the-first-multi-model-film-festival/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/auteur_grid.jpg</image:loc>
      <image:caption>Le canvas complet du festival, 90 nœuds, 85 arêtes, six groupes de réalisateurs, échafaudés en un seul appel MCP.</image:caption>
      <image:title>Un festival où les modèles d'IA s'affrontent comme réalisateurs</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/auteur_render0.jpg</image:loc>
      <image:caption>Le Peintre, Ghibli × Tarkovski. Ambré. Patient. (Flux Pro Ultra + Kling 3 Pro)</image:caption>
      <image:title>Un festival où les modèles d'IA s'affrontent comme réalisateurs</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/auteur_render2.jpg</image:loc>
      <image:caption>Le Maximaliste, Luhrmann sur un toit écarlate. (Flux 2 Max + Kling 2.6 Pro)</image:caption>
      <image:title>Un festival où les modèles d'IA s'affrontent comme réalisateurs</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/maternal-guard-fhir-mcp/</loc>
    <lastmod>2026-05-05T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/maternal-guard-fhir-mcp/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/maternal-guard-fhir-mcp/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/maternal-guard-fhir-mcp/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/maternalguard_architecture.jpg</image:loc>
      <image:caption>End-to-end architecture, clinician, orchestrator agent, specialist agent, MCP tools, FHIR R4 workspace store.</image:caption>
      <image:title>Five MCP Tools That See a Pregnancy as One Clinical Unit</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/maternalguard_tools.png</image:loc>
      <image:caption>All 5 MCP tools firing in order on a single Prenatal Visit Prep run: AssessMaternalRisk, InterpretLabTrends, ScreenSocialDeterminants, GenerateCarePlan, PredictNeonatalImpact.</image:caption>
      <image:title>Five MCP Tools That See a Pregnancy as One Clinical Unit</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/maternal-guard-fhir-mcp/</loc>
    <lastmod>2026-05-05T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/maternal-guard-fhir-mcp/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/maternal-guard-fhir-mcp/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/maternal-guard-fhir-mcp/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/maternalguard_architecture.jpg</image:loc>
      <image:caption>Architecture de bout en bout, clinicien, agent orchestrateur, agent spécialiste, outils MCP, magasin FHIR R4.</image:caption>
      <image:title>Cinq outils MCP qui voient une grossesse comme une seule unité clinique</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/maternalguard_tools.png</image:loc>
      <image:caption>Les 5 outils MCP s'exécutent dans l'ordre sur une seule exécution de Prenatal Visit Prep : AssessMaternalRisk, InterpretLabTrends, ScreenSocialDeterminants, GenerateCarePlan, PredictNeonatalImpact.</image:caption>
      <image:title>Cinq outils MCP qui voient une grossesse comme une seule unité clinique</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/polymarket-decoded-live-mispricing-scanner/</loc>
    <lastmod>2026-05-10T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/polymarket-decoded-live-mispricing-scanner/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/polymarket-decoded-live-mispricing-scanner/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/polymarket-decoded-live-mispricing-scanner/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/polymarket-decoded-live-mispricing-scanner/</loc>
    <lastmod>2026-05-10T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/polymarket-decoded-live-mispricing-scanner/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/polymarket-decoded-live-mispricing-scanner/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/polymarket-decoded-live-mispricing-scanner/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/fog-canada-charity-golf-tournament/</loc>
    <lastmod>2024-01-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/fog-canada-charity-golf-tournament/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/fog-canada-charity-golf-tournament/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/fog-canada-charity-golf-tournament/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/fogCanada.jpg</image:loc>
      <image:caption>The live homepage, live tournament countdown, sponsor wall, and registration CTA on a single scannable surface.</image:caption>
      <image:title>Building a Charity Site Non-Developers Can Actually Maintain</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/fogCanada_logo.png</image:loc>
      <image:caption>The F.O.G. Canada mark. Treated as a fixed asset the volunteers can drop into any new section template without resizing.</image:caption>
      <image:title>Building a Charity Site Non-Developers Can Actually Maintain</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/fog-canada-charity-golf-tournament/</loc>
    <lastmod>2024-01-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/fog-canada-charity-golf-tournament/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/fog-canada-charity-golf-tournament/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/fog-canada-charity-golf-tournament/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/fogCanada.jpg</image:loc>
      <image:caption>La page d'accueil en direct, décompte du tournoi, mur de commanditaires et appel à l'inscription sur une même surface lisible.</image:caption>
      <image:title>Un site caritatif qu'une équipe non-technique peut vraiment maintenir</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/fogCanada_logo.png</image:loc>
      <image:caption>La marque F.O.G. Canada. Traitée comme un actif fixe que les bénévoles peuvent insérer dans n'importe quel nouveau gabarit de section sans redimensionnement.</image:caption>
      <image:title>Un site caritatif qu'une équipe non-technique peut vraiment maintenir</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/al-pro-solutions-bilingual-roofing-solar-marketing/</loc>
    <lastmod>2026-02-10T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/al-pro-solutions-bilingual-roofing-solar-marketing/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/al-pro-solutions-bilingual-roofing-solar-marketing/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/al-pro-solutions-bilingual-roofing-solar-marketing/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/alproSolutions.jpg</image:loc>
      <image:caption>The AL Pro Solutions homepage: bilingual hero, service selector, and a feasibility-study CTA above the fold.</image:caption>
      <image:title>Shipping a Bilingual Marketing Site That a Quebec Team Can Own</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/alproSolutions_process.png</image:loc>
      <image:caption>The process section explains the buyer journey in five steps, mirrored in French with the same illustrations.</image:caption>
      <image:title>Shipping a Bilingual Marketing Site That a Quebec Team Can Own</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/alproSolutions_projects.jpg</image:loc>
      <image:caption>The projects grid: each card carries city, building type, and roofing system as filterable metadata.</image:caption>
      <image:title>Shipping a Bilingual Marketing Site That a Quebec Team Can Own</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/al-pro-solutions-bilingual-roofing-solar-marketing/</loc>
    <lastmod>2026-02-10T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/al-pro-solutions-bilingual-roofing-solar-marketing/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/al-pro-solutions-bilingual-roofing-solar-marketing/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/al-pro-solutions-bilingual-roofing-solar-marketing/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/alproSolutions.jpg</image:loc>
      <image:caption>La page d'accueil d'AL Pro Solutions : hero bilingue, sélecteur de services et CTA d'étude de faisabilité au-dessus du pli.</image:caption>
      <image:title>Livrer un site bilingue qu'une équipe québécoise peut s'approprier</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/alproSolutions_process.png</image:loc>
      <image:caption>La section processus explique le parcours d'achat en cinq étapes, reflétée en français avec les mêmes illustrations.</image:caption>
      <image:title>Livrer un site bilingue qu'une équipe québécoise peut s'approprier</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/alproSolutions_projects.jpg</image:loc>
      <image:caption>La grille des projets : chaque carte porte la ville, le type de bâtiment et le système de toiture comme métadonnées filtrables.</image:caption>
      <image:title>Livrer un site bilingue qu'une équipe québécoise peut s'approprier</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/roamly-social-discovery-for-modern-nomads/</loc>
    <lastmod>2026-01-25T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/roamly-social-discovery-for-modern-nomads/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/roamly-social-discovery-for-modern-nomads/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/roamly-social-discovery-for-modern-nomads/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/roamly_logo.jpg</image:loc>
      <image:caption>Roamly: a social discovery app shaped around the way digital nomads actually move between cities.</image:caption>
      <image:title>50 Screens, 25 Tables, One Solo Developer, One Month</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/roamly_discover.webp</image:loc>
      <image:caption>The discovery feed: a swipe queue with local counters for instant feedback, smart caching, and convergence detection layered into the ranking.</image:caption>
      <image:title>50 Screens, 25 Tables, One Solo Developer, One Month</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/roamly_friends.webp</image:loc>
      <image:caption>Connections view: verification badges come from community vouching with an automatic threshold, so trust is earned without a single moderator in the loop.</image:caption>
      <image:title>50 Screens, 25 Tables, One Solo Developer, One Month</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/roamly_messages.webp</image:loc>
      <image:caption>Messages run through Supabase realtime channels with optimistic updates, so the UI never waits for the server before showing a sent bubble.</image:caption>
      <image:title>50 Screens, 25 Tables, One Solo Developer, One Month</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/roamly-social-discovery-for-modern-nomads/</loc>
    <lastmod>2026-01-25T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/roamly-social-discovery-for-modern-nomads/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/roamly-social-discovery-for-modern-nomads/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/roamly-social-discovery-for-modern-nomads/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/roamly_logo.jpg</image:loc>
      <image:caption>Roamly : une appli de découverte sociale pensée pour la façon dont les nomades numériques bougent réellement entre les villes.</image:caption>
      <image:title>50 écrans, 25 tables, un développeur seul, un mois</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/roamly_discover.webp</image:loc>
      <image:caption>Le fil de découverte : une file de swipes avec compteurs locaux pour un retour instantané, mise en cache intelligente et détection de convergence intégrée au classement.</image:caption>
      <image:title>50 écrans, 25 tables, un développeur seul, un mois</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/roamly_friends.webp</image:loc>
      <image:caption>Vue des connexions : les badges de vérification viennent du vouching communautaire avec un seuil automatique, donc la confiance se gagne sans qu'un seul modérateur soit dans la boucle.</image:caption>
      <image:title>50 écrans, 25 tables, un développeur seul, un mois</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/roamly_messages.webp</image:loc>
      <image:caption>Les messages passent par les canaux temps réel de Supabase avec mises à jour optimistes, donc l'UI n'attend jamais le serveur avant d'afficher une bulle envoyée.</image:caption>
      <image:title>50 écrans, 25 tables, un développeur seul, un mois</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/shot-supervisor-deterministic-shot-planning/</loc>
    <lastmod>2025-12-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/shot-supervisor-deterministic-shot-planning/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/shot-supervisor-deterministic-shot-planning/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/shot-supervisor-deterministic-shot-planning/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/shotSupervisor.webp</image:loc>
      <image:caption>Shot Supervisor: a director-style mobile tool for planning, generating, editing, and exporting shots.</image:caption>
      <image:title>Turning a Phone into a Director Workflow</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/shotSupervisor_projects.webp</image:loc>
      <image:caption>Projects tab. A new project optionally fans out into up to 5 AI-generated shots via the LLM breakdown.</image:caption>
      <image:title>Turning a Phone into a Director Workflow</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/shotSupervisor_editor.webp</image:loc>
      <image:caption>The editor: HDR tone control, LUTs, color space, bit depth, all on a phone.</image:caption>
      <image:title>Turning a Phone into a Director Workflow</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/shotSupervisor_export.webp</image:loc>
      <image:caption>Exports go to device or email, in JPEG, PNG, TIFF, or EXR, so the file lands in whatever grading tool comes next.</image:caption>
      <image:title>Turning a Phone into a Director Workflow</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/shot-supervisor-deterministic-shot-planning/</loc>
    <lastmod>2025-12-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/shot-supervisor-deterministic-shot-planning/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/shot-supervisor-deterministic-shot-planning/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/shot-supervisor-deterministic-shot-planning/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/shotSupervisor.webp</image:loc>
      <image:caption>Shot Supervisor : un outil mobile façon réalisateur pour planifier, générer, retoucher et exporter des plans.</image:caption>
      <image:title>Transformer un téléphone en flux de réalisateur</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/shotSupervisor_projects.webp</image:loc>
      <image:caption>Onglet Projets. Un nouveau projet peut se déployer en jusqu'à 5 plans générés par IA via la décomposition LLM.</image:caption>
      <image:title>Transformer un téléphone en flux de réalisateur</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/shotSupervisor_editor.webp</image:loc>
      <image:caption>L'éditeur : contrôle des tons HDR, LUT, espace colorimétrique, profondeur de bits, tout sur un téléphone.</image:caption>
      <image:title>Transformer un téléphone en flux de réalisateur</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/shotSupervisor_export.webp</image:loc>
      <image:caption>Les exports vont sur l'appareil ou par courriel, en JPEG, PNG, TIFF ou EXR, pour que le fichier atterrisse dans l'outil d'étalonnage suivant.</image:caption>
      <image:title>Transformer un téléphone en flux de réalisateur</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/novatrace-exoplanet-discovery-with-confidence/</loc>
    <lastmod>2025-10-20T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/novatrace-exoplanet-discovery-with-confidence/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/novatrace-exoplanet-discovery-with-confidence/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/novatrace-exoplanet-discovery-with-confidence/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/novatrace.jpg</image:loc>
      <image:caption>NovaTrace: an exoplanet classifier that admits when it isn't sure.</image:caption>
      <image:title>Why I Built an AI Exoplanet Classifier That Says 'I Don't Know'</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/novatrace_classification.jpg</image:loc>
      <image:caption>When the top two probabilities are too close, NovaTrace returns UNKNOWN instead of pretending.</image:caption>
      <image:title>Why I Built an AI Exoplanet Classifier That Says 'I Don't Know'</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/novatrace_retrain.png</image:loc>
      <image:caption>Browser-driven retraining: upload an Excel file of candidates, tune hyperparameters, and a fresh XGBoost model swaps in.</image:caption>
      <image:title>Why I Built an AI Exoplanet Classifier That Says 'I Don't Know'</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/novatrace-exoplanet-discovery-with-confidence/</loc>
    <lastmod>2025-10-20T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/novatrace-exoplanet-discovery-with-confidence/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/novatrace-exoplanet-discovery-with-confidence/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/novatrace-exoplanet-discovery-with-confidence/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/novatrace.jpg</image:loc>
      <image:caption>NovaTrace : un classificateur d'exoplanètes qui admet quand il n'est pas certain.</image:caption>
      <image:title>Pourquoi j'ai bâti un classificateur d'exoplanètes qui dit « Je ne sais pas »</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/novatrace_classification.jpg</image:loc>
      <image:caption>Quand les deux probabilités principales sont trop proches, NovaTrace renvoie UNKNOWN au lieu de faire semblant.</image:caption>
      <image:title>Pourquoi j'ai bâti un classificateur d'exoplanètes qui dit « Je ne sais pas »</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/novatrace_retrain.png</image:loc>
      <image:caption>Réentraînement piloté depuis le navigateur : téléversez un fichier Excel de candidats, ajustez les hyperparamètres et un nouveau modèle XGBoost prend le relais.</image:caption>
      <image:title>Pourquoi j'ai bâti un classificateur d'exoplanètes qui dit « Je ne sais pas »</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/steam-print-voice-to-print-on-steam-deck/</loc>
    <lastmod>2025-09-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/steam-print-voice-to-print-on-steam-deck/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/steam-print-voice-to-print-on-steam-deck/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/steam-print-voice-to-print-on-steam-deck/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/steamPrint.png</image:loc>
      <image:caption>Steam Print on the Deck. A handheld Linux box, a microphone, gpt-oss, and a 3D printer at the other end of the network.</image:caption>
      <image:title>Why I Turned a Steam Deck into a 3D Printer Console</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/steamPrint_viewer.png</image:loc>
      <image:caption>The STL viewer. PySide6 for the chrome, OpenGL for the geometry, Trimesh for the mesh math.</image:caption>
      <image:title>Why I Turned a Steam Deck into a 3D Printer Console</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/steamPrint_printing.png</image:loc>
      <image:caption>OctoPi camera feed piped back into the Deck. The loop closes here: voice in, plastic out.</image:caption>
      <image:title>Why I Turned a Steam Deck into a 3D Printer Console</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/steam-print-voice-to-print-on-steam-deck/</loc>
    <lastmod>2025-09-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/steam-print-voice-to-print-on-steam-deck/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/steam-print-voice-to-print-on-steam-deck/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/steam-print-voice-to-print-on-steam-deck/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/steamPrint.png</image:loc>
      <image:caption>Steam Print sur la Deck. Un boîtier Linux portable, un micro, gpt-oss et une imprimante 3D à l'autre bout du réseau.</image:caption>
      <image:title>Pourquoi j'ai transformé une Steam Deck en console d'impression 3D</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/steamPrint_viewer.png</image:loc>
      <image:caption>Le visualiseur STL. PySide6 pour l'habillage, OpenGL pour la géométrie, Trimesh pour les calculs de maillage.</image:caption>
      <image:title>Pourquoi j'ai transformé une Steam Deck en console d'impression 3D</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/steamPrint_printing.png</image:loc>
      <image:caption>Le flux caméra OctoPi renvoyé dans la Deck. La boucle se referme ici : la voix entre, le plastique sort.</image:caption>
      <image:title>Pourquoi j'ai transformé une Steam Deck en console d'impression 3D</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/condoshield-special-assessment-analysis/</loc>
    <lastmod>2025-06-20T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/condoshield-special-assessment-analysis/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/condoshield-special-assessment-analysis/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/condoshield-special-assessment-analysis/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/condoshield.png</image:loc>
      <image:caption>The landing dashboard. Upload a PDF, get a charge breakdown, then drill into anything that looks off.</image:caption>
      <image:title>Helping Condo Owners Argue with 200-Page PDFs</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/condoshield_legal.png</image:loc>
      <image:caption>Pick a region, the assistant pulls the right body of law. Quebec gets Code civil clauses, Ontario gets the Condominium Act.</image:caption>
      <image:title>Helping Condo Owners Argue with 200-Page PDFs</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/condoshield_dispute.png</image:loc>
      <image:caption>Dispute letters export to PDF through jspdf and html2pdf.js so an owner can sign and send without reformatting.</image:caption>
      <image:title>Helping Condo Owners Argue with 200-Page PDFs</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/condoshield_savings.png</image:loc>
      <image:caption>Cost-saving suggestions come from a second pass over the parsed line items, stored in Supabase so they persist across sessions.</image:caption>
      <image:title>Helping Condo Owners Argue with 200-Page PDFs</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/condoshield-special-assessment-analysis/</loc>
    <lastmod>2025-06-20T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/condoshield-special-assessment-analysis/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/condoshield-special-assessment-analysis/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/condoshield-special-assessment-analysis/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/condoshield.png</image:loc>
      <image:caption>Le tableau de bord d'accueil. Téléverser un PDF, obtenir une ventilation des frais, puis fouiller tout ce qui semble douteux.</image:caption>
      <image:title>Aider les copropriétaires à argumenter face à des PDF de 200 pages</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/condoshield_legal.png</image:loc>
      <image:caption>On choisit une région, l'assistant tire le bon corpus juridique. Le Québec reçoit des clauses du Code civil, l'Ontario reçoit le Condominium Act.</image:caption>
      <image:title>Aider les copropriétaires à argumenter face à des PDF de 200 pages</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/condoshield_dispute.png</image:loc>
      <image:caption>Les lettres de contestation s'exportent en PDF via jspdf et html2pdf.js pour qu'un copropriétaire signe et envoie sans reformater.</image:caption>
      <image:title>Aider les copropriétaires à argumenter face à des PDF de 200 pages</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/condoshield_savings.png</image:loc>
      <image:caption>Les suggestions d'économies viennent d'un second passage sur les lignes analysées, stockées dans Supabase pour persister entre les sessions.</image:caption>
      <image:title>Aider les copropriétaires à argumenter face à des PDF de 200 pages</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/predictive-ai-healthcare-meldrx/</loc>
    <lastmod>2025-03-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/predictive-ai-healthcare-meldrx/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/predictive-ai-healthcare-meldrx/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/predictive-ai-healthcare-meldrx/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/healthCare.png</image:loc>
      <image:caption>The clinician view after MeldRx hands off the patient context: risk score on the left, drivers and suggested actions on the right.</image:caption>
      <image:title>FHIR + CDS Hooks + Gemini: a Risk Score a Clinician Will Actually Read</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/healthCare_accuracy.png</image:loc>
      <image:caption>The accuracy panel. The number is paired with the reasoning the model used to arrive at it.</image:caption>
      <image:title>FHIR + CDS Hooks + Gemini: a Risk Score a Clinician Will Actually Read</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/healthCare_pdf.png</image:loc>
      <image:caption>The PDF export rendered with @react-pdf/renderer. Same content as the dashboard, formatted for the chart.</image:caption>
      <image:title>FHIR + CDS Hooks + Gemini: a Risk Score a Clinician Will Actually Read</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/predictive-ai-healthcare-meldrx/</loc>
    <lastmod>2025-03-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/predictive-ai-healthcare-meldrx/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/predictive-ai-healthcare-meldrx/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/predictive-ai-healthcare-meldrx/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/healthCare.png</image:loc>
      <image:caption>La vue clinicien après que MeldRx ait transmis le contexte patient : score de risque à gauche, facteurs et actions suggérées à droite.</image:caption>
      <image:title>FHIR + CDS Hooks + Gemini : un score de risque qu'un clinicien lira vraiment</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/healthCare_accuracy.png</image:loc>
      <image:caption>Le panneau de précision. Le chiffre est accompagné du raisonnement que le modèle a utilisé pour y arriver.</image:caption>
      <image:title>FHIR + CDS Hooks + Gemini : un score de risque qu'un clinicien lira vraiment</image:title>
    </image:image>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/healthCare_pdf.png</image:loc>
      <image:caption>L'export PDF rendu avec @react-pdf/renderer. Même contenu que le tableau de bord, formaté pour le dossier.</image:caption>
      <image:title>FHIR + CDS Hooks + Gemini : un score de risque qu'un clinicien lira vraiment</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/baseball-ai-generator-google-mlb/</loc>
    <lastmod>2025-02-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/baseball-ai-generator-google-mlb/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/baseball-ai-generator-google-mlb/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/baseball-ai-generator-google-mlb/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/baseballGame.png</image:loc>
      <image:caption>The personalized dashboard: followed players, daily AI articles, and charts on demand.</image:caption>
      <image:title>Letting Gemini Write SQL Against BigQuery, So Fans Don't Have To</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/baseball-ai-generator-google-mlb/</loc>
    <lastmod>2025-02-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/baseball-ai-generator-google-mlb/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/baseball-ai-generator-google-mlb/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/baseball-ai-generator-google-mlb/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/baseballGame.png</image:loc>
      <image:caption>Le tableau de bord personnalisé : joueurs suivis, articles IA quotidiens et graphiques sur demande.</image:caption>
      <image:title>Laisser Gemini écrire le SQL sur BigQuery pour que les fans n'aient pas à le faire</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/instaq-chrome-extension/</loc>
    <lastmod>2024-12-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/instaq-chrome-extension/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/instaq-chrome-extension/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/instaq-chrome-extension/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/aiBrowserExtension.webp</image:loc>
      <image:caption>InstaQ runs entirely in the browser using Chrome's built-in Prompt, Summarizer, and Translator APIs.</image:caption>
      <image:title>What I Learned Building a Browser Extension Around Chrome's Built-in AI</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/instaq-chrome-extension/</loc>
    <lastmod>2024-12-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/instaq-chrome-extension/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/instaq-chrome-extension/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/instaq-chrome-extension/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/aiBrowserExtension.webp</image:loc>
      <image:caption>InstaQ fonctionne entièrement dans le navigateur grâce aux API intégrées Prompt, Summarizer et Translator de Chrome.</image:caption>
      <image:title>Ce que j'ai appris en construisant une extension autour de l'IA intégrée de Chrome</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/google-photorealistic-3d-maps/</loc>
    <lastmod>2024-11-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/google-photorealistic-3d-maps/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/google-photorealistic-3d-maps/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/google-photorealistic-3d-maps/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/3dGoogleMaps.jpg</image:loc>
      <image:caption>Photorealistic 3D tiles plus a voice layer. The camera follows what I say.</image:caption>
      <image:title>Driving a 3D Map by Voice</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/google-photorealistic-3d-maps/</loc>
    <lastmod>2024-11-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/google-photorealistic-3d-maps/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/google-photorealistic-3d-maps/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/google-photorealistic-3d-maps/"/>
    <image:image>
      <image:loc>https://jonathanandrei.com/projectImages/3dGoogleMaps.jpg</image:loc>
      <image:caption>Tuiles 3D photoréalistes plus une couche vocale. La caméra suit ce que je dis.</image:caption>
      <image:title>Piloter une carte 3D à la voix</image:title>
    </image:image>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/wireless-motion-capture-rotation/</loc>
    <lastmod>2022-04-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/wireless-motion-capture-rotation/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/wireless-motion-capture-rotation/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/wireless-motion-capture-rotation/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/wireless-motion-capture-rotation/</loc>
    <lastmod>2022-04-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/wireless-motion-capture-rotation/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/wireless-motion-capture-rotation/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/wireless-motion-capture-rotation/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/dynamic-fitness-graphs-observer-pattern/</loc>
    <lastmod>2022-04-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/dynamic-fitness-graphs-observer-pattern/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/dynamic-fitness-graphs-observer-pattern/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/dynamic-fitness-graphs-observer-pattern/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/dynamic-fitness-graphs-observer-pattern/</loc>
    <lastmod>2022-04-01T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/dynamic-fitness-graphs-observer-pattern/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/dynamic-fitness-graphs-observer-pattern/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/dynamic-fitness-graphs-observer-pattern/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/multiplayer-card-game-with-ai/</loc>
    <lastmod>2021-04-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/multiplayer-card-game-with-ai/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/multiplayer-card-game-with-ai/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/multiplayer-card-game-with-ai/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/multiplayer-card-game-with-ai/</loc>
    <lastmod>2021-04-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/multiplayer-card-game-with-ai/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/multiplayer-card-game-with-ai/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/multiplayer-card-game-with-ai/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/blog/motion-capture-hand-arduino-blender/</loc>
    <lastmod>2021-12-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/motion-capture-hand-arduino-blender/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/motion-capture-hand-arduino-blender/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/motion-capture-hand-arduino-blender/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/blog/motion-capture-hand-arduino-blender/</loc>
    <lastmod>2021-12-15T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.7</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/blog/motion-capture-hand-arduino-blender/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/blog/motion-capture-hand-arduino-blender/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/blog/motion-capture-hand-arduino-blender/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/case-studies/cirque-du-soleil-servicenow-hr-automation/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/cirque-du-soleil-servicenow-hr-automation/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/cirque-du-soleil-servicenow-hr-automation/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/cirque-du-soleil-servicenow-hr-automation/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/case-studies/cirque-du-soleil-servicenow-hr-automation/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/cirque-du-soleil-servicenow-hr-automation/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/cirque-du-soleil-servicenow-hr-automation/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/cirque-du-soleil-servicenow-hr-automation/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/case-studies/ibm-servicenow-watson-aiops-integrations/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/ibm-servicenow-watson-aiops-integrations/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/ibm-servicenow-watson-aiops-integrations/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/ibm-servicenow-watson-aiops-integrations/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/case-studies/ibm-servicenow-watson-aiops-integrations/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/ibm-servicenow-watson-aiops-integrations/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/ibm-servicenow-watson-aiops-integrations/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/ibm-servicenow-watson-aiops-integrations/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/case-studies/concordia-imu-quaternion-visualisation/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/concordia-imu-quaternion-visualisation/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/concordia-imu-quaternion-visualisation/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/concordia-imu-quaternion-visualisation/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/case-studies/concordia-imu-quaternion-visualisation/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/concordia-imu-quaternion-visualisation/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/concordia-imu-quaternion-visualisation/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/concordia-imu-quaternion-visualisation/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/case-studies/sphera-entity-framework-build-time/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/sphera-entity-framework-build-time/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/sphera-entity-framework-build-time/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/sphera-entity-framework-build-time/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/case-studies/sphera-entity-framework-build-time/</loc>
    <lastmod>2026-08-21T00:00:00.000Z</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.8</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/case-studies/sphera-entity-framework-build-time/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/case-studies/sphera-entity-framework-build-time/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/case-studies/sphera-entity-framework-build-time/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/trainings/odoo/</loc>
    <lastmod>2026-08-23T17:58:02.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.75</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/trainings/odoo/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/trainings/odoo/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/trainings/odoo/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/trainings/odoo/</loc>
    <lastmod>2026-08-23T17:58:02.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.75</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/trainings/odoo/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/trainings/odoo/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/trainings/odoo/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/trainings/servicenow/</loc>
    <lastmod>2025-05-25T20:04:02.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.75</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/trainings/servicenow/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/trainings/servicenow/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/trainings/servicenow/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/trainings/servicenow/</loc>
    <lastmod>2025-05-25T20:04:02.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.75</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/trainings/servicenow/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/trainings/servicenow/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/trainings/servicenow/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/trainings/personal-projects/</loc>
    <lastmod>2026-09-17T05:45:12.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.75</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/trainings/personal-projects/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/trainings/personal-projects/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/trainings/personal-projects/"/>
  </url>
  <url>
    <loc>https://jonathanandrei.com/fr/trainings/personal-projects/</loc>
    <lastmod>2026-09-17T05:45:12.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.75</priority>
    <xhtml:link rel="alternate" hreflang="en" href="https://jonathanandrei.com/trainings/personal-projects/"/>
    <xhtml:link rel="alternate" hreflang="fr-CA" href="https://jonathanandrei.com/fr/trainings/personal-projects/"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://jonathanandrei.com/trainings/personal-projects/"/>
  </url>
</urlset>
