<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nirmal Joseph Ukken</title>
    <description>The latest articles on DEV Community by Nirmal Joseph Ukken (@nekku).</description>
    <link>https://dev.to/nekku</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4140381%2Fb95878a6-b12d-4b7d-b11f-13eabff5e543.png</url>
      <title>DEV Community: Nirmal Joseph Ukken</title>
      <link>https://dev.to/nekku</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nekku"/>
    <language>en</language>
    <item>
      <title>700,000 people watched a VLC redesign. I built the player.</title>
      <dc:creator>Nirmal Joseph Ukken</dc:creator>
      <pubDate>Wed, 14 Oct 2026 06:07:21 +0000</pubDate>
      <link>https://dev.to/nekku/700000-people-watched-a-vlc-redesign-i-built-the-player-1aio</link>
      <guid>https://dev.to/nekku/700000-people-watched-a-vlc-redesign-i-built-the-player-1aio</guid>
      <description>&lt;p&gt;A VLC redesign video went up on YouTube, and I caught it the day after it was released. It already had about half a million views and around 2,000 comments, and the comments kept asking the same thing: &lt;em&gt;Is this real? Can I actually use it? Will VLC ever look like this?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's now past 700,000 views, and the answer to those comments is still no. It's a design concept, not an app.&lt;/p&gt;

&lt;p&gt;Seeing that much demand, I thought: why not try building it myself? Within two days I had a working media player on my PC, set as my default app, with installers for Windows, Linux and macOS.&lt;/p&gt;

&lt;p&gt;This is the story of &lt;strong&gt;Pylon&lt;/strong&gt;: where the idea came from, how it's built, what broke along the way, and what I learned shipping a desktop app.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nirmaljosephukken" rel="noopener noreferrer"&gt;
        nirmaljosephukken
      &lt;/a&gt; / &lt;a href="https://github.com/nirmaljosephukken/pylon-player" rel="noopener noreferrer"&gt;
        pylon-player
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A calmer, redesigned interface for the VLC engine on Windows. Hover-only controls, drag-to-sync subtitle timeline, per-show presets, library and queue. Based on Juxtopposed's VLC redesign.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Pylon&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A calmer, redesigned interface for the VLC engine, for Windows, Linux and (experimentally) macOS.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Download:&lt;/strong&gt; &lt;a href="https://github.com/nirmaljosephukken/pylon-player/releases/latest/download/Pylon-Setup-1.1.1.exe" rel="noopener noreferrer"&gt;Windows&lt;/a&gt; · &lt;a href="https://github.com/nirmaljosephukken/pylon-player/releases/latest/download/Pylon-1.1.1-x86_64.AppImage" rel="noopener noreferrer"&gt;Linux&lt;/a&gt; · &lt;a href="https://github.com/nirmaljosephukken/pylon-player/releases/latest/download/Pylon-1.1.1-macOS.zip" rel="noopener noreferrer"&gt;macOS (experimental)&lt;/a&gt; · &lt;a href="https://github.com/nirmaljosephukken/pylon-player/releases" rel="noopener noreferrer"&gt;All releases&lt;/a&gt;&lt;br&gt;
You also need &lt;a href="https://www.videolan.org/vlc/" rel="nofollow noopener noreferrer"&gt;VLC&lt;/a&gt; installed. See &lt;a href="https://github.com/nirmaljosephukken/pylon-player#install" rel="noopener noreferrer"&gt;Install&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pylon puts a new interface on top of libVLC, the engine inside VLC media player. VLC still does all the playback, decoding, subtitles and streaming, so Pylon plays everything VLC plays. The interface is built around the way people actually watch: quiet bars that get out of the way, a thick seek bar, subtitle timing you can see, per-show presets, and a library and queue you can reach without leaving the video.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Beta.&lt;/strong&gt; Tested on Windows 10/11 and Ubuntu; the macOS build hasn't been tried on a real Mac yet. Bug reports are very welcome.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nirmaljosephukken/pylon-player/docs/player.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnirmaljosephukken%2Fpylon-player%2FHEAD%2Fdocs%2Fplayer.png" alt="Pylon playing a video"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nirmaljosephukken/pylon-player/docs/panels.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnirmaljosephukken%2Fpylon-player%2FHEAD%2Fdocs%2Fpanels.png" alt="Library, queue and settings sidebar"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Credit&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The interface follows the &lt;strong&gt;VLC redesign concept by &lt;a href="https://www.youtube.com/@juxtopposed" rel="nofollow noopener noreferrer"&gt;Juxtopposed&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Video: &lt;a href="https://www.youtube.com/watch?v=ERFeOgqk324" rel="nofollow noopener noreferrer"&gt;the VLC redesign walkthrough&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Design file: &lt;a href="https://www.figma.com/community/file/1691465999134370980/vlc-redesign" rel="nofollow noopener noreferrer"&gt;VLC redesign on Figma&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nirmaljosephukken/pylon-player" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  It started with a YouTube video
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@juxtopposed" rel="noopener noreferrer"&gt;Juxtopposed&lt;/a&gt; published a redesign concept for VLC media player. VLC is the player everyone has installed and nobody calls beautiful. The redesign kept everything that makes VLC great and rethought how it feels to watch something: quieter controls, a seek bar you can actually hit, subtitle timing you can see, and settings that don't throw a dialog over your video.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ERFeOgqk324" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The design was shared on Figma Community under a Creative Commons licence. But it was a concept, not an app, and judging by the comments, I wasn't the only one who wanted to &lt;em&gt;use&lt;/em&gt; it, every day, on their own files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skin or wrapper?
&lt;/h2&gt;

&lt;p&gt;My first thought was a VLC skin. VLC does support skins, but they can only rearrange and restyle VLC's existing controls. The ideas I liked most (a subtitle timeline, a settings sidebar, a real queue, a home page with "continue watching") need new behaviour, not just new pixels.&lt;/p&gt;

&lt;p&gt;So I went the other way: &lt;strong&gt;a new app with VLC's engine inside it&lt;/strong&gt;. VLC ships its playback engine as a library, libVLC. Pylon is a new interface on top of that library. VLC still does all the decoding, subtitles and streaming, so Pylon plays everything VLC plays.&lt;/p&gt;

&lt;p&gt;The stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; for the app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qt for Python (PySide6)&lt;/strong&gt; for the interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;python-vlc&lt;/strong&gt; to talk to libVLC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VLC 3.0&lt;/strong&gt;, installed separately, for the actual playback&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/vCV3B_blLU0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzoz2bkg2cnax4nihhaas.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzoz2bkg2cnax4nihhaas.png" alt="Pylon playing a video, with quiet bars along the top and bottom" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The features I built from the concept, plus a few of my own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quiet bars.&lt;/strong&gt; The top and bottom bars float over the video, appear when you move the mouse, and disappear when you stop. They work in fullscreen too. &lt;code&gt;H&lt;/code&gt; hides everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A thick seek bar&lt;/strong&gt; with hover time, bookmark ticks and chapter marks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cone volume.&lt;/strong&gt; A volume control shaped like a traffic cone lying on its side. Click, drag or scroll, up to 125%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A settings sidebar&lt;/strong&gt; (&lt;code&gt;Ctrl+T&lt;/code&gt;) with Subtitles, Audio, Video, Playback and Bookmarks tabs, so nothing covers the video.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A subtitle timeline.&lt;/strong&gt; You can see where subtitle lines fall around the playhead and drag them until they line up with the speech.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-show presets.&lt;/strong&gt; Save subtitle timing, look, tracks and speed once, and every episode in that folder uses them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subtitles in bulk.&lt;/strong&gt; Add subtitles to a whole season at once. Files are matched by episode number (S01E02, 1x02, E02).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A real queue and library.&lt;/strong&gt; Playing an episode queues the rest of the folder, so the next one just plays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-screen hints.&lt;/strong&gt; Every key you press shows what it did, and every shortcut can be changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzeje0p445gh2zlc4jgn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzeje0p445gh2zlc4jgn1.png" alt="Library, queue and settings sidebar open next to the video" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Drawing on top of a video you don't own
&lt;/h3&gt;

&lt;p&gt;libVLC draws video straight into a native window, using the window handle you give it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;media_player_new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_hwnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;winId&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;      &lt;span class="c1"&gt;# Windows
# player.set_xwindow(...) on Linux, set_nsobject(...) on macOS
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's fast, but it means Qt can't paint over that area. Any button you put "on top" of the video simply isn't visible. The fix was to make the top and bottom bars their own small frameless, translucent windows that float above the video and follow the main window around. It sounds hacky, but it's how you get controls that fade over the picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The player froze, and then it didn't play at all
&lt;/h3&gt;

&lt;p&gt;libVLC reports events (playing, paused, ended, length changed) from its own thread. My first version called back into VLC from inside those event handlers, and every so often playback froze. The rule I ended up with: &lt;strong&gt;event handlers only emit a Qt signal, nothing else&lt;/strong&gt;. The GUI thread then asks the player for the real values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# runs on a VLC thread: never touch the player here
&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;event_attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vlc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MediaPlayerPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state_changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;playing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# and connect it so Qt delivers it on the GUI thread
&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state_changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Qt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueuedConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Qt.QueuedConnection&lt;/code&gt; turned out to matter. &lt;code&gt;stop()&lt;/code&gt; fires its "stopped" event on the &lt;em&gt;calling&lt;/em&gt; thread, so a direct connection could re-enter the player while it was still stopping, and the app locked up.&lt;/p&gt;

&lt;p&gt;Then, on my actual Windows PC, nothing played at all. The clock sat at 0:00. A small diagnostic script showed &lt;code&gt;play()&lt;/code&gt; returning -1. The cause was a single character: Qt gives you paths with forward slashes (&lt;code&gt;D:/Movies/file.mkv&lt;/code&gt;), and VLC on Windows wanted backslashes. One &lt;code&gt;os.path.normpath()&lt;/code&gt; later, it played.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Making it the default player on Windows
&lt;/h3&gt;

&lt;p&gt;I wanted to double-click a video and have it open in Pylon. Windows lists apps in &lt;strong&gt;Default apps&lt;/strong&gt; by their &lt;code&gt;.exe&lt;/code&gt;, and Pylon was a Python script, so it showed up as "Python" or not at all.&lt;/p&gt;

&lt;p&gt;The fix was a tiny launcher, &lt;code&gt;Pylon.exe&lt;/code&gt;, written in C. It finds the bundled Python next to it and starts the app with the file you clicked. It's about 90 KB and has Pylon's own icon, so Windows treats Pylon like any other app. The installer then registers Pylon under "Open with" for 34 video and audio types. Windows doesn't let apps make themselves the default, so the last click is still yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. One-click installers
&lt;/h3&gt;

&lt;p&gt;Asking people to install Python, then pip packages, then run a script, loses most of them. So each download now carries its own Python and Qt:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Download&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows 10/11&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Pylon-Setup.exe&lt;/code&gt;, a per-user installer (no admin needed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;an AppImage: &lt;code&gt;chmod +x&lt;/code&gt;, then run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS (experimental)&lt;/td&gt;
&lt;td&gt;a zipped &lt;code&gt;.app&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Qt is big, so I trimmed it to the modules Pylon actually imports (Core, Gui, Widgets, Svg, Network) by reading each library's import table. That keeps the Windows installer around 22 MB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VLC is never bundled.&lt;/strong&gt; You install VLC yourself and Pylon finds it. That keeps the licensing simple and means you get VLC's own updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The bug my first real user found
&lt;/h3&gt;

&lt;p&gt;Version 1.1.0 worked on my test setups, then didn't find VLC on my own PC. The reason: when you pick a folder with the installer's &lt;strong&gt;Browse&lt;/strong&gt; button, the installer adds &lt;code&gt;\Pylon&lt;/code&gt; to the end. My VLC was at &lt;code&gt;E:\Softwares\VLC&lt;/code&gt;, Pylon ended up in &lt;code&gt;E:\Softwares\Pylon-1.1\Pylon&lt;/code&gt;, and Pylon only looked one folder up.&lt;/p&gt;

&lt;p&gt;Version 1.1.1 now looks in a few folders above itself as well as the usual places, setup saves the VLC location it finds, and if you point Pylon at VLC by hand it starts using it immediately instead of asking for a restart. Small fix, but it's exactly the kind of thing you only find by installing your own app like a stranger would.&lt;/p&gt;

&lt;h2&gt;
  
  
  The name
&lt;/h2&gt;

&lt;p&gt;It started as "Cone Player", after VLC's traffic-cone logo. That was too close to VLC's trademark, so I renamed it &lt;strong&gt;Pylon&lt;/strong&gt;: another word for a traffic cone, and a nod to where it came from without borrowing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it properly: credit and licences
&lt;/h2&gt;

&lt;p&gt;Because this builds on other people's work, I tried to get the boring parts right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The design&lt;/strong&gt; is credited to Juxtopposed in the README and in the app's About box. The Figma file is under CC BY 4.0, which allows adaptations with credit. No files or artwork from it are included; the interface is rebuilt in code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VLC and VideoLAN&lt;/strong&gt;: Pylon says clearly that it isn't affiliated with or endorsed by VideoLAN, and it doesn't use the VLC name or cone logo as its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The code&lt;/strong&gt; is MIT licensed. The bundled Python, Qt for Python and python-vlc keep their own licences (PSF, LGPLv3, LGPL 2.1+), and those licence texts ship with every download.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;I built Pylon with &lt;strong&gt;Claude&lt;/strong&gt; (Anthropic's AI) as my pair programmer. My part was the product side: deciding what to build from the concept, testing every version on my own Windows PC, finding what broke, and deciding what "good" looked like. Claude wrote most of the code, the installers and the documentation with me, one problem at a time.&lt;/p&gt;

&lt;p&gt;What surprised me most is how much of shipping software isn't the main feature. Getting a video on screen was the easy part. The rest was paths, threads, file associations, installers, licences and an installer that adds &lt;code&gt;\Pylon&lt;/code&gt; to your folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Not built yet from the original concept: thumbnails while seeking, a waveform seek bar, music sorted by artist and album, and casting. The macOS build also still needs someone with a real Mac to try it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code and downloads: &lt;strong&gt;&lt;a href="https://github.com/nirmaljosephukken/pylon-player" rel="noopener noreferrer"&gt;github.com/nirmaljosephukken/pylon-player&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You'll need &lt;a href="https://www.videolan.org/vlc/" rel="noopener noreferrer"&gt;VLC&lt;/a&gt; installed (Pylon uses its engine).&lt;/li&gt;
&lt;li&gt;It's a beta. If something breaks, please &lt;a href="https://github.com/nirmaljosephukken/pylon-player/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;. Bug reports are the most useful thing you can send me.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To everyone in those comments asking "can I actually use this?": now you can. I'd love to hear what you think, especially which feature you'd want next. And if you haven't seen Juxtopposed's video, go watch it. None of this exists without it.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>python</category>
      <category>opensource</category>
      <category>design</category>
    </item>
    <item>
      <title>When Does a Question Need an Agent? RAG vs GraphRAG vs Agentic GraphRAG on TigerGraph</title>
      <dc:creator>Nirmal Joseph Ukken</dc:creator>
      <pubDate>Wed, 07 Oct 2026 11:04:12 +0000</pubDate>
      <link>https://dev.to/nekku/-when-does-a-question-need-an-agent-rag-vs-graphrag-vs-agentic-graphrag-on-tigergraph-2fgb</link>
      <guid>https://dev.to/nekku/-when-does-a-question-need-an-agent-rag-vs-graphrag-vs-agentic-graphrag-on-tigergraph-2fgb</guid>
      <description>&lt;p&gt;Everyone is building agents. Fewer people are asking when an agent is actually worth it. An agent plans, calls tools, checks its work and tries again, and every one of those steps costs tokens and time. If a single retrieval answers the question just as well, the agent is overhead.&lt;/p&gt;

&lt;p&gt;For the TigerGraph Agentic GraphRAG Hackathon I tried to answer that question with numbers. I built three question-answering pipelines on the same TigerGraph graph, gave them the same LLM and the same questions, and measured where each one succeeds and fails. I called the project OlympicRAG, because most of the corpus is about Olympic events.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The hackathon provides 2,951 Wikipedia articles (Olympic events from 1988 to 2022, plus films and people), 100 public questions with answers and 50 hidden ones. I added three small sets of my own to stress specific behaviours: 36 harder multi-step questions, 18 questions about facts that changed over time, and 12 questions about films. Every gold answer in my sets is computed or quoted by code from the corpus, never written by an LLM.&lt;/p&gt;

&lt;p&gt;All three pipelines use Gemini 3.1 Flash-Lite and one TigerGraph Savanna graph that holds both the structure and the vectors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;RAG&lt;/strong&gt;: vector search over 22,554 text chunks, then one LLM call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GraphRAG&lt;/strong&gt;: link the entities in the question to the graph, fetch the matching events and their neighbours, search vectors only inside those articles, then one LLM call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic GraphRAG&lt;/strong&gt;: an orchestrator LLM plans the investigation one step at a time. It chooses one of 13 specialised agents (entity linking, graph traversal, multi-hop reasoning, GSQL aggregation, similarity search, document retrieval, fact history and others), reads what came back, and decides the next step. An evidence evaluator written in code decides whether it may stop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fztrqid4amnou81c0gmbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fztrqid4amnou81c0gmbb.png" alt="Architecture" width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the graph without an LLM
&lt;/h2&gt;

&lt;p&gt;Most Olympic articles start with an infobox: venue, date, number of competitors, nations, and the gold, silver and bronze medallists. I parse these directly into Event, Games, Sport, Venue, Athlete and Nation vertices, and link each event to the same event at the previous and next Games. No tokens are spent on extraction, and no relationship is invented.&lt;/p&gt;

&lt;p&gt;One rule mattered more than I expected: team names are split into individual athletes only when the split is certain. If an infobox lists a relay team without separators, no Athlete vertices are created at all. A guessed split creates people who never existed.&lt;/p&gt;

&lt;p&gt;The agents reach the graph through the official TigerGraph MCP server. Every step in a trace records whether MCP served it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I cared about most: never release an unsupported answer
&lt;/h2&gt;

&lt;p&gt;In a previous TigerGraph hackathon, the main feedback on our entry was that the LLM made up details and merged several items into one answer. So this time the safeguards live in code, not in the prompt. Before the agent may answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the whole answer must be supported by &lt;strong&gt;one&lt;/strong&gt; cited evidence item;&lt;/li&gt;
&lt;li&gt;every name and number in the answer must appear in that evidence (a count must equal the GSQL aggregate, not a number the LLM computed);&lt;/li&gt;
&lt;li&gt;a silver medallist can never be released as the gold winner;&lt;/li&gt;
&lt;li&gt;an answer that combines facts from two events is rejected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A rejected answer goes back to the orchestrator with the reason. After two rejections the result is "unknown", never the rejected text. When several events match a question equally, the agent answers for one of them and the code attaches a note naming all the candidates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;On the 100 public questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;RAG&lt;/th&gt;
&lt;th&gt;GraphRAG&lt;/th&gt;
&lt;th&gt;Agentic GraphRAG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Accuracy&lt;/td&gt;
&lt;td&gt;47%&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens per question&lt;/td&gt;
&lt;td&gt;3,116&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,364&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,095&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens per correct answer&lt;/td&gt;
&lt;td&gt;6,629&lt;/td&gt;
&lt;td&gt;3,638&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3,126&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answers fully supported by one cited source&lt;/td&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GraphRAG is the cheapest per question. The agent is the cheapest per &lt;strong&gt;correct&lt;/strong&gt; answer, and it is the only pipeline whose every answer is backed by its evidence.&lt;/p&gt;

&lt;p&gt;The more interesting view is by question type, because it shows where the agent earns its cost and where it does not.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question type&lt;/th&gt;
&lt;th&gt;RAG&lt;/th&gt;
&lt;th&gt;GraphRAG&lt;/th&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Counting (21)&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;29%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;Agent needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Held immediately before" (22)&lt;/td&gt;
&lt;td&gt;64%&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;Agent needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single lookup (19)&lt;/td&gt;
&lt;td&gt;63%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;GraphRAG is enough, and cheaper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Venue and date (28)&lt;/td&gt;
&lt;td&gt;64%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;td&gt;GraphRAG is enough, and cheaper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Superlatives (10)&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;GraphRAG is enough, and cheaper&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Counting is where LLM pipelines break.&lt;/strong&gt; In 14 of GraphRAG's 15 misses on counting questions, every relevant event and its competitor count was already in its context. The LLM simply miscounted. The agent does not count in the LLM at all: it calls a GSQL aggregation and cites the result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hops through time need structure.&lt;/strong&gt; For "who won the event at the Games held immediately before 2016", GraphRAG fetches the events of 2016, because that is the year named in the question, and the answer never reaches its context. The agent's multi-hop agent finds the 2016 event, follows the edge to the previous edition and answers from there, with evidence at each hop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the agent is overkill
&lt;/h2&gt;

&lt;p&gt;To test the other side, I wrote 12 questions about films. The corpus contains 546 film articles, but the graph only models Olympic events, and each question describes a film without naming it ("the 1995 film directed by Bryan Singer and starring Stephen Baldwin").&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Films (12)&lt;/th&gt;
&lt;th&gt;RAG&lt;/th&gt;
&lt;th&gt;GraphRAG&lt;/th&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Accuracy&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;58%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens per question&lt;/td&gt;
&lt;td&gt;2,684&lt;/td&gt;
&lt;td&gt;3,218&lt;/td&gt;
&lt;td&gt;7,002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plain RAG is as accurate as the agent here, at under 40% of its tokens. When the answer sits in one article that a description can find, an agent is not worth it.&lt;/p&gt;

&lt;p&gt;GraphRAG's 58% follows an exact pattern. It failed every question set in an Olympic year (1992, 2002, 2004) and answered every other one correctly. For "the 2002 film directed by Kunal Kohli", the only entity the graph recognises is the year, so it returns 40 events of the 2002 Winter Olympics and searches only those articles. The film can never be reached. In a non-Olympic year the graph finds nothing, GraphRAG falls back to searching everything, and it gets the answer. That is GraphRAG's blind spot: it trusts the graph's view of the question, which helps when the answer is in the graph and hurts when it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Facts that change
&lt;/h2&gt;

&lt;p&gt;The hackathon's stretch goal is reasoning over evolving and conflicting facts. Wikipedia has plenty: a weightlifter "originally won" and was later disqualified, or the infobox says 48 competitors while the article text says 47.&lt;/p&gt;

&lt;p&gt;I extract each version of a fact with code and store it as a Claim vertex in TigerGraph, with a SUPERSEDES edge from a dated decision to the original result. The source policy is explicit: a dated change supersedes the original, the infobox is the current official record, and a disagreeing statement is reported, never dropped or averaged. On 18 such questions RAG scored 78%, GraphRAG 94% and the agent 100%. The agent reported both versions in all 5 count conflicts and flagged the change in all 13 questions where a result had changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running everything twice
&lt;/h2&gt;

&lt;p&gt;Before submitting, I ran every pipeline on every question a second time, independently. Out of 216 questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Same answer in both runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;206&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphRAG&lt;/td&gt;
&lt;td&gt;191&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentic GraphRAG&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;216&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most of GraphRAG's changes were counts: 8 in one run, 7 in the other, for the same question. The LLM counts differently each time. The agent's counts come from the graph, so they do not move. One question shows the conflict problem directly: for the 2016 men's triple jump, RAG answered 48 in one run and 47 in the other, GraphRAG did the opposite, and the agent answered 48, the official record, both times and reported the conflict.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The answer to "do I need an agent?" is per question type.&lt;/strong&gt; Counting, hops through time and multi-step chains need one. A single lookup or a single article found by description does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put your guarantees in code.&lt;/strong&gt; A prompt that says "do not merge events" is a hope. A check that rejects any answer citing two events is a guarantee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let the database do the arithmetic.&lt;/strong&gt; Most of the accuracy gap on counting questions and most of the run-to-run instability came from the LLM counting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure where your system is overkill, not just where it wins.&lt;/strong&gt; The film questions were the most useful 12 questions in the project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It still has limitations. The provided questions are templated and Olympic-heavy, my extra sets come from the same corpus, and the agent costs more per question than GraphRAG on simple lookups. The one question the agent missed (out of 100) is ambiguous in the corpus itself: three finals share the same venue and date. The agent answered for one of them and the code flagged all three, which I prefer to blending them into one answer.&lt;/p&gt;

&lt;p&gt;The code, every trace, the hidden-set outputs and a dashboard with every question are in the repository.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to TigerGraph for the hackathon, the Savanna credits and the MCP server.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for the TigerGraph Agentic GraphRAG Hackathon. &lt;br&gt;
Code, results and traces: &lt;a href="https://github.com/nirmaljosephukken/olympicrag-agentic-graphrag" rel="noopener noreferrer"&gt;github.com/nirmaljosephukken/olympicrag-agentic-graphrag&lt;/a&gt; · &lt;br&gt;
Live dashboard: &lt;a href="https://nirmaljosephukken.github.io/olympicrag-agentic-graphrag/dashboard/" rel="noopener noreferrer"&gt;nirmaljosephukken.github.io/olympicrag-agentic-graphrag/dashboard&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tigergraph</category>
      <category>agenticai</category>
      <category>rag</category>
      <category>llm</category>
    </item>
    <item>
      <title>The Agent That Knows When to Stop: Agentic Fraud Investigation on TigerGraph</title>
      <dc:creator>Nirmal Joseph Ukken</dc:creator>
      <pubDate>Thu, 24 Sep 2026 09:22:08 +0000</pubDate>
      <link>https://dev.to/nekku/the-agent-that-knows-when-to-stop-agentic-fraud-investigation-on-tigergraph-5d18</link>
      <guid>https://dev.to/nekku/the-agent-that-knows-when-to-stop-agentic-fraud-investigation-on-tigergraph-5d18</guid>
      <description>&lt;p&gt;&lt;em&gt;Task #4 · TigerGraph problem statement · Hacker House Goa 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A risk score pings. Is it fraud, a holiday, or a new phone?&lt;/p&gt;

&lt;p&gt;We built &lt;strong&gt;SentinelGraph&lt;/strong&gt;, an AI agent that works the alert like a sharp analyst: it digs through the graph, admits what it doesn't know, asks for the one piece of evidence that would settle it, and recommends the next move with the right person signing off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;590,742&lt;/strong&gt; transactions in the graph · &lt;strong&gt;5,565&lt;/strong&gt; closed cases as memory · &lt;strong&gt;16&lt;/strong&gt; GSQL queries via MCP · &lt;strong&gt;0.914&lt;/strong&gt; memory-model AUC (bank score: 0.866) · &lt;strong&gt;28&lt;/strong&gt; cards in the biggest ring we found&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/nirmaljosephukken/HH_GOA_Task_4" rel="noopener noreferrer"&gt;https://github.com/nirmaljosephukken/HH_GOA_Task_4&lt;/a&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  1. What we built
&lt;/h2&gt;

&lt;p&gt;Fraud teams are never short of alerts. They're short of time to turn each one into a call they can defend. For every ping an analyst pulls the card history, checks the device, hunts for other victims, rereads the policy, decides whether to phone the customer, and writes it all up. Usually after the money has moved.&lt;/p&gt;

&lt;p&gt;SentinelGraph runs that loop end to end:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Triggers:&lt;/strong&gt; a bank risk score, a customer report, or an analyst request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Investigates&lt;/strong&gt; the card, the latent cardholder, devices, emails, regions and &lt;em&gt;other cards&lt;/em&gt; through TigerGraph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remembers&lt;/strong&gt; 5,565 closed investigations and every case it opened before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measures its own uncertainty&lt;/strong&gt; and stops only when two independent lines of evidence agree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asks for more evidence&lt;/strong&gt; through policy-approved actions (customer validation, step-up authentication, analyst review) when it isn't sure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recommends the next best action&lt;/strong&gt; with its approval route (auto / L1 team lead / L2 fraud manager), drafts a SAR when policy requires one, and writes the whole case back into the graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SentinelGraph console&lt;/strong&gt; (Streamlit) shows the alert, the live investigation, what it found, how certain it is, what happens next, the evidence graph, and why it stopped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We ran it on the 20 benchmark alerts: &lt;strong&gt;10 legitimate, 9 fraud, 1 uncertain, 6 SARs&lt;/strong&gt;. Re-running all 20 gives the same decisions every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the room first
&lt;/h3&gt;

&lt;p&gt;The dataset is the IEEE-CIS card data with the fraud label taken away. In its place: a bank risk score, 5,565 closed investigations (July to October), a fraud policy, five documented patterns, and 20 alerts from November and December. Three findings shaped everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A "customer" isn't a person.&lt;/strong&gt; Customer IDs come from an issuer code, so one card can hold 2,788 transactions across dozens of billing regions. We added a &lt;code&gt;Client&lt;/code&gt; vertex: a latent cardholder built from card + billing region + account-open day. In closed cases, 85% of a fraud client's transactions are fraud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bank's memory is lopsided, and that's useful.&lt;/strong&gt; All 4,665 confirmed frauds began with a cardholder report. All 900 cleared cases began with a model score the cardholder explained (travel 716, new phone 158, intended purchase 26). &lt;em&gt;A risk score is a reason to look. Never a verdict.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Card IDs come back exactly.&lt;/strong&gt; Ranking each customer's &lt;code&gt;card6&lt;/code&gt; values reproduces every card ID in the closed cases and the case pack.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. The architecture
&lt;/h2&gt;

&lt;p&gt;Every layer has one job. Graph queries find facts. A deterministic evidence layer weighs them. A policy engine picks the actions. The LLM probes open questions and writes the story, and it never sets a probability or an action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trigger (score / customer / analyst)
   │
   ▼
Case orchestrator ── state machine + audit trail ──────────────┐
   │  MCP: tigergraph__run_installed_query   GraphRAG: vectorSearch
   ▼                                                            │
TigerGraph Savanna (FraudGraph)                                 │
   │                                                            │
   ▼                                                            │
Evidence layer ── detectors → likelihood ratios by family       │
   ▼                                                            │
Uncertainty engine ── log-odds fusion, confidence, open questions
   ▼                                                            │
Policy engine ── R1–R10, case vs report, stop rule, routes, guardrails
   │                       │                                    │
   ▼                       ▼                                    │
ask for evidence     next best action (auto runs, L1/L2 wait)   │
(verify / step-up)         │                                    │
   └──► re-assess          ▼                                    │
                     FraudCase + CaseEvents written back ◄──────┘

LLM (Gemini): bounded extra tool calls + narrative/SAR writer, output validated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One alert, six moves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open the case&lt;/strong&gt; with an audit trail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dig the graph:&lt;/strong&gt; 16 installed GSQL queries through the TigerGraph MCP server, about 0.1 s each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remember:&lt;/strong&gt; GraphRAG vector search over closed cases, earlier agent cases and policy clauses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weigh it:&lt;/strong&gt; each finding gets a likelihood ratio and an evidence family (ml, trigger, sequence, network, device, behaviour, history, customer). Within a family only the strongest counts, so correlated signals can't pile up. Families multiply in log-odds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop or ask:&lt;/strong&gt; two independent families agree past 85% or 15%? Stop. Otherwise request the evidence that would settle it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act and write back:&lt;/strong&gt; auto actions execute, L1/L2 actions wait for a human, the case goes back into the graph as memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. How TigerGraph is used
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One graph, three layers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evidence graph:&lt;/strong&gt; &lt;code&gt;Customer–Card–Client–Txn&lt;/code&gt;, with &lt;code&gt;Txn&lt;/code&gt; linked to &lt;code&gt;DeviceProfile&lt;/code&gt;, &lt;code&gt;EmailDomain&lt;/code&gt; (purchaser and recipient) and &lt;code&gt;BillingRegion&lt;/code&gt;, plus a &lt;code&gt;NEXT_TXN&lt;/code&gt; chain per card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case memory:&lt;/strong&gt; 5,565 &lt;code&gt;ClosedCase&lt;/code&gt; vertices linked to their transactions, cards, connected cards and pattern. Every &lt;code&gt;FraudCase&lt;/code&gt; the agent opens is stored too, with edges to the transactions, cards, devices and closed cases it used, and a &lt;code&gt;CaseEvent&lt;/code&gt; chain as its audit trail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge:&lt;/strong&gt; &lt;code&gt;PolicyChunk&lt;/code&gt; vertices, one per policy rule, plus fraud typologies, a FinCEN/FATF/FFIEC digest and lessons mined from closed cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;ClosedCase&lt;/code&gt;, &lt;code&gt;FraudCase&lt;/code&gt; and &lt;code&gt;PolicyChunk&lt;/code&gt; carry TigerGraph's &lt;strong&gt;native 256-dimension vector attribute&lt;/strong&gt;, so memory lookups are a &lt;code&gt;vectorSearch()&lt;/code&gt; inside an installed query, right next to the graph hops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sixteen installed GSQL queries&lt;/strong&gt;, each exposed to the agent as a tool. Highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;peer_txns&lt;/code&gt;: two hops, flagged transaction → its device or email → purchases on &lt;em&gt;other&lt;/em&gt; cards at a similar amount within 48 hours. This is the "what happened on other cards?" question.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;card_profile&lt;/code&gt;: a behavioural baseline in one pass with &lt;code&gt;MapAccum&lt;/code&gt;s over products, regions, devices and emails.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ring_components&lt;/code&gt;: &lt;strong&gt;weakly connected components&lt;/strong&gt; (min-label propagation adapted from &lt;code&gt;tg_wcc&lt;/code&gt;) over the client↔device co-usage graph. It isolates the 28-card ring as one component.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;device_ring_scan&lt;/code&gt; and &lt;code&gt;amount_band_scan&lt;/code&gt;: sweep the whole exam period, so the agent raises alerts nobody reported (&lt;code&gt;monitor.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prior_cases&lt;/code&gt; and &lt;code&gt;device_cases&lt;/code&gt;: memory by adjacency. Which closed cases touched this device?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;TigerGraph MCP.&lt;/strong&gt; The agent launches the official &lt;a href="https://github.com/tigergraph/tigergraph-mcp" rel="noopener noreferrer"&gt;&lt;code&gt;tigergraph-mcp&lt;/code&gt;&lt;/a&gt; server over stdio, discovers its tools, and calls &lt;code&gt;tigergraph__run_installed_query&lt;/code&gt; for every read, vector searches included. Argument names come from the tool schema at runtime. If MCP is unavailable it falls back to RESTPP, and every call in the trace records which transport served it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loading.&lt;/strong&gt; Streamed parallel RESTPP upserts loaded 590k transactions and about 3M edges into Savanna in under two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The agentic capabilities
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Explicit uncertainty.&lt;/strong&gt; Every detector returns a claim, the query it came from, the entity IDs it rests on, a likelihood ratio and a family. The case records the prior, the posterior, each family's contribution, which families argue each way, and the open questions ("device ownership not established", "evidence conflicts").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A calibrated prior learned from case memory.&lt;/strong&gt; The closed cases cover about 3.4% of July to October transactions, close to the true fraud rate, so we trained a gradient-boosted model on them. Trained on July to September and tested on October: &lt;strong&gt;AUC 0.914 vs 0.866 for the bank's score, with nearly double the average precision.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowing when to stop.&lt;/strong&gt; Policy section 6, as code: stop at ≥ 0.85 or ≤ 0.15 with two independent families agreeing. Otherwise the agent picks the evidence request that best resolves the case: step-up authentication for an online score alert, customer verification for a dispute, R7 verification for a disputed charge that matches the customer's own recurring pattern. Replies aren't provided in this round, so a simulator assumes the reply the evidence supports, records the assumption and its basis, and the agent re-assesses. The next best action is recorded &lt;strong&gt;before&lt;/strong&gt; and &lt;strong&gt;after&lt;/strong&gt; the evidence, with what changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy as code, with permissions.&lt;/strong&gt; R1–R10, the case-vs-report rule, exposure-based routing, action ordering, and a guard that refuses breaches (blocking on a single weak signal, &lt;code&gt;BLOCK_ALL_CARDS&lt;/code&gt; without two compromised cards, blocking a recurring dispute). Only &lt;code&gt;auto&lt;/code&gt; actions execute. L1 and L2 actions wait in the console's approval queue, and every approval is written to the case's audit trail in TigerGraph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory that changes the answer.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HHG-014:&lt;/strong&gt; &lt;code&gt;device_cases&lt;/code&gt; returns closed cases analysts labelled &lt;em&gt;undocumented&lt;/em&gt;, so the agent labels the new ring undocumented too and escalates under R9.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HHG-006:&lt;/strong&gt; vector retrieval surfaces five undocumented September structuring cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legitimate alerts:&lt;/strong&gt; cleared cases on the same card ("confirmed travel") are cited as precedent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The LLM where language is the product.&lt;/strong&gt; Gemini gets a bounded budget of extra tool calls to probe open questions, then writes the summary, pattern description and SAR narrative. Any text that cites an ID not present in the evidence is rejected. Its notes carry zero weight in the probability, which is why decisions are reproducible.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the graph caught
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HHG-006 · Threshold structuring.&lt;/strong&gt; Four online purchases in 30 minutes, each just under $500 ($1,906.07 total). Same trick on 11 other cards; matches five undocumented September cases. SAR, R9 escalation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HHG-014 · A 28-card device ring.&lt;/strong&gt; One Samsung profile behind an anonymous proxy, marked &lt;em&gt;New&lt;/em&gt; on every account. The November wave of the August ring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HHG-019, HHG-011, HHG-016 · Rings you can't see from one card.&lt;/strong&gt; Each purchase looks ordinary on its own card. On the graph, the same rare device made near-identical purchases on 3–5 other cards within days. R6: report and monitor connected cards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HHG-018 · Disputed, but it recurs.&lt;/strong&gt; A $39.08 charge recurring at the same amount, region and product since August. R7: verify and remind, no block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HHG-003 · Honest about doubt.&lt;/strong&gt; A denied $49 purchase in a region the card uses all the time. The card is blocked (R2), but the outcome stays &lt;em&gt;uncertain&lt;/em&gt; and goes to an analyst (R8).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scoreboard: all 20 alerts
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;P(fraud)&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Exposure&lt;/th&gt;
&lt;th&gt;Linked cards&lt;/th&gt;
&lt;th&gt;SAR&lt;/th&gt;
&lt;th&gt;Evidence asked&lt;/th&gt;
&lt;th&gt;Final next best action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HHG-001&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;GENERATE_REPORT&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-002&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;step up auth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-003&lt;/td&gt;
&lt;td&gt;uncertain&lt;/td&gt;
&lt;td&gt;0.63&lt;/td&gt;
&lt;td&gt;out of region use&lt;/td&gt;
&lt;td&gt;$49.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;customer validation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;ESCALATE_TO_ANALYST&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-004&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;0.73&lt;/td&gt;
&lt;td&gt;card not present new device&lt;/td&gt;
&lt;td&gt;$128.33&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;customer validation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-005&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;step up auth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-006&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;undocumented&lt;/td&gt;
&lt;td&gt;$1,906.07&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;FILE_REPORT&lt;/code&gt; → &lt;code&gt;MONITOR_CONNECTED_CARDS&lt;/code&gt; → &lt;code&gt;ESCALATE_TO_ANALYST&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-007&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;GENERATE_REPORT&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-008&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;card not present fraud&lt;/td&gt;
&lt;td&gt;$166.97&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;FILE_REPORT&lt;/code&gt; → &lt;code&gt;MONITOR_CONNECTED_CARDS&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-009&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;0.98&lt;/td&gt;
&lt;td&gt;card not present fraud&lt;/td&gt;
&lt;td&gt;$30.02&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-010&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;step up auth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-011&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;card not present new device&lt;/td&gt;
&lt;td&gt;$131.30&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;FILE_REPORT&lt;/code&gt; → &lt;code&gt;MONITOR_CONNECTED_CARDS&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-012&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;GENERATE_REPORT&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-013&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;step up auth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-014&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;0.89&lt;/td&gt;
&lt;td&gt;undocumented&lt;/td&gt;
&lt;td&gt;$439.61&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;FILE_REPORT&lt;/code&gt; → &lt;code&gt;MONITOR_CONNECTED_CARDS&lt;/code&gt; → &lt;code&gt;ESCALATE_TO_ANALYST&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-015&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;step up auth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-016&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;card not present new device&lt;/td&gt;
&lt;td&gt;$59.67&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;FILE_REPORT&lt;/code&gt; → &lt;code&gt;MONITOR_CONNECTED_CARDS&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-017&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;GENERATE_REPORT&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-018&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.02&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;customer validation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;WARN_CUSTOMER&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-019&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;card not present new device&lt;/td&gt;
&lt;td&gt;$99.92&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BLOCK_CARD&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;FILE_REPORT&lt;/code&gt; → &lt;code&gt;MONITOR_CONNECTED_CARDS&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-020&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;step up auth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ALLOW_TRANSACTION&lt;/code&gt; → &lt;code&gt;CREATE_CASE&lt;/code&gt; → &lt;code&gt;CLOSE_NO_FRAUD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  5. What we learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema design is investigation design.&lt;/strong&gt; The &lt;code&gt;Client&lt;/code&gt; vertex and the device fan-out query did more for accuracy than any model tuning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the LLM where language is the product.&lt;/strong&gt; When it computed probabilities, runs weren't repeatable. As a bounded prober and a validated writer, it helps without drifting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Uncertain" is an honest answer.&lt;/strong&gt; When evidence disagrees, protect the customer now and let a human decide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph memory beats flat memory.&lt;/strong&gt; Retrieving past cases by &lt;em&gt;adjacency&lt;/em&gt; (same device, same card) and by &lt;em&gt;similarity&lt;/em&gt; (vectors) together caught things neither did alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. What we'd improve with more time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real reply channels (SMS, app push) instead of simulated replies, feeding outcomes back as training signal.&lt;/li&gt;
&lt;li&gt;Streaming ingestion into TigerGraph with the ring and structuring scans running continuously.&lt;/li&gt;
&lt;li&gt;Likelihood ratios learned from resolved agent cases instead of set by hand.&lt;/li&gt;
&lt;li&gt;Louvain community detection over client↔device↔email to catch rings that share more than one element.&lt;/li&gt;
&lt;li&gt;External enrichment (BIN/issuer, IP reputation) as additional evidence families.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; TigerGraph Savanna · GSQL · TigerGraph MCP · native vector search · Python · Gemini · Streamlit&lt;/p&gt;

&lt;p&gt;Built for Hacker House Goa 2026 with @TigerGraphDB. Code, the 20 answer files and full investigation traces: &lt;a href="https://github.com/nirmaljosephukken/HH_GOA_Task_4" rel="noopener noreferrer"&gt;https://github.com/nirmaljosephukken/HH_GOA_Task_4&lt;/a&gt;&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/Rw8Wb1xVlPg" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>tigergraph</category>
      <category>ai</category>
      <category>graphrag</category>
      <category>python</category>
    </item>
  </channel>
</rss>
