<?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: Sandeep Kumar</title>
    <description>The latest articles on DEV Community by Sandeep Kumar (@sandeepkr24).</description>
    <link>https://dev.to/sandeepkr24</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%2F4192326%2F6375e7c9-b8ac-4295-a6b5-7ff893c68d72.jpg</url>
      <title>DEV Community: Sandeep Kumar</title>
      <link>https://dev.to/sandeepkr24</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sandeepkr24"/>
    <language>en</language>
    <item>
      <title>PitGraph: building an F1 strategist whose memory is a graph</title>
      <dc:creator>Sandeep Kumar</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:30:48 +0000</pubDate>
      <link>https://dev.to/sandeepkr24/pitgraph-building-an-f1-strategist-whose-memory-is-a-graph-567d</link>
      <guid>https://dev.to/sandeepkr24/pitgraph-building-an-f1-strategist-whose-memory-is-a-graph-567d</guid>
      <description>&lt;p&gt;&lt;em&gt;How I built a multi-agent race-strategy assistant on FalkorDB in four days for Graph Hacks, and what the graph made easy (and hard).&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Ask a Formula 1 strategist whether a one-stop is viable at Monza if there's an early safety car, and they won't give you a number pulled from thin air. They'll think about what won there before, what the tyres did, what happened the last time the safety car came out, and which other tracks behave like Monza. Then they'll remember the answer for next time.&lt;/p&gt;

&lt;p&gt;That's the whole idea behind &lt;strong&gt;PitGraph&lt;/strong&gt;: a team of AI agents that answers F1 strategy questions by reasoning over a graph of real race data, shows the exact evidence behind every answer, and keeps a memory that grows with each conversation. I built it for &lt;strong&gt;Graph Hacks&lt;/strong&gt; by FalkorDB and entered it in two tracks: &lt;em&gt;Agents That Act on Connected Data&lt;/em&gt; and &lt;em&gt;Agent Memory and Coordination&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://wmdfalkor-pitgraph.onrender.com/" rel="noopener noreferrer"&gt;https://wmdfalkor-pitgraph.onrender.com/&lt;/a&gt; (free tier, so give it up to a minute to wake up)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/SandeepKr24/wmdfalkor_pitgraph" rel="noopener noreferrer"&gt;https://github.com/SandeepKr24/wmdfalkor_pitgraph&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Why a graph?
&lt;/h2&gt;

&lt;p&gt;Race strategy is relational by nature. A driver &lt;em&gt;drove&lt;/em&gt; an entry, which &lt;em&gt;was in&lt;/em&gt; a race, &lt;em&gt;held at&lt;/em&gt; a circuit. That entry &lt;em&gt;had&lt;/em&gt; a stint, which was &lt;em&gt;followed by&lt;/em&gt; another stint, each &lt;em&gt;on&lt;/em&gt; a tyre compound. A safety car &lt;em&gt;happened during&lt;/em&gt; a race, and some drivers &lt;em&gt;pitted under&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;In a table, a tyre strategy is a string you have to parse. In a graph, &lt;strong&gt;a strategy is a path&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;Entry&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;HAD_STINT&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Stint&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;NEXT&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Stint&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;NEXT&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Stint&lt;/span&gt;
                     &lt;span class="err"&gt;|&lt;/span&gt;             &lt;span class="err"&gt;|&lt;/span&gt;             &lt;span class="err"&gt;|&lt;/span&gt;
                &lt;span class="nv"&gt;ON_COMPOUND&lt;/span&gt;   &lt;span class="nv"&gt;ON_COMPOUND&lt;/span&gt;   &lt;span class="nv"&gt;ON_COMPOUND&lt;/span&gt;
                     &lt;span class="nv"&gt;v&lt;/span&gt;             &lt;span class="nv"&gt;v&lt;/span&gt;             &lt;span class="nv"&gt;v&lt;/span&gt;
                  &lt;span class="nv"&gt;MEDIUM&lt;/span&gt;         &lt;span class="nv"&gt;HARD&lt;/span&gt;          &lt;span class="nv"&gt;HARD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So "what did the top five run at Monza?" becomes a short traversal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;c:&lt;/span&gt;&lt;span class="n"&gt;Circuit&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;circuit_id:&lt;/span&gt; &lt;span class="n"&gt;$circuit&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:HELD_AT&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;r:&lt;/span&gt;&lt;span class="n"&gt;Race&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:IN_RACE&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;e:&lt;/span&gt;&lt;span class="n"&gt;Entry&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;e.finish_pos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;$top_n&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:HAD_STINT&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;s:&lt;/span&gt;&lt;span class="n"&gt;Stint&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:ON_COMPOUND&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;k:&lt;/span&gt;&lt;span class="n"&gt;Compound&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s.stint_no&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;r.race_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;race_id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e.entry_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e.finish_pos&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;finish_pos&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
       &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k.name&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e.stops&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;stops&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;race_id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finish_pos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I loaded three seasons (2023–2025) of public data from &lt;a href="https://github.com/jolpica/jolpica-f1" rel="noopener noreferrer"&gt;Jolpica-F1&lt;/a&gt; and &lt;a href="https://openf1.org" rel="noopener noreferrer"&gt;OpenF1&lt;/a&gt;: 70 races, ~1,400 driver entries, ~4,000 tyre stints, ~2,400 pit stops and 90 safety car / VSC / red flag periods. That's small enough to fit comfortably in FalkorDB Cloud's free tier, and rich enough to argue about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agents
&lt;/h2&gt;

&lt;p&gt;A question goes through five agents, wired together with LangGraph:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Planner&lt;/strong&gt;: works out the circuits and the kind of question (strategy choice, safety-car impact, circuit comparison, memory recall), recalls what this user already knows, and picks a sequence of tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retriever&lt;/strong&gt;: calls those tools against the graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analyst&lt;/strong&gt;: writes a verdict, a confidence score, and the evidence it relied on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critic&lt;/strong&gt;: approves the answer or sends it back once for revision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MemoryWriter&lt;/strong&gt;: saves the answer, the evidence and anything worth remembering.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One rule I set early: &lt;strong&gt;the LLM never writes Cypher.&lt;/strong&gt; The tools are exposed over &lt;strong&gt;MCP&lt;/strong&gt; (Model Context Protocol), ten of them, and the model only chooses which tool to call and with what arguments. Every value goes in as a query parameter. That made the system predictable, safe from query injection, and, importantly for a hackathon, something I could explain line by line. Every query is documented in &lt;a href="https://github.com/SandeepKr24/wmdfalkor_pitgraph/blob/main/QUERIES.md" rel="noopener noreferrer"&gt;QUERIES.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The analysis also leans on FalkorDB's built-in graph algorithms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Label propagation&lt;/strong&gt; (community detection) groups circuits that win with the same strategy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PageRank&lt;/strong&gt; finds which past races carry the most weight as evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shortest path&lt;/strong&gt; shows how two circuits are connected through similarity links.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When the data runs out
&lt;/h2&gt;

&lt;p&gt;Here's my favourite part. Monza has &lt;strong&gt;no safety cars&lt;/strong&gt; in the 2023–2025 data. So what should the agent say about an early safety car there?&lt;/p&gt;

&lt;p&gt;A plain LLM would happily make something up. PitGraph's Retriever notices when a circuit's own data is thin (fewer than three rows) and switches to a recovery path. It follows &lt;code&gt;SIMILAR_TO&lt;/code&gt; edges to the circuits most like Monza, and pulls in the strategy cluster Monza belongs to. Each &lt;code&gt;SIMILAR_TO&lt;/code&gt; edge stores &lt;em&gt;why&lt;/em&gt; two circuits match (&lt;code&gt;basis: "street,low_sc,one_stop"&lt;/code&gt;), so the answer can say: "Monza has no safety-car history, so this is based on circuits with similar characteristics." That honesty is something the graph structure gives you almost for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every answer shows its working
&lt;/h2&gt;

&lt;p&gt;Each answer stores the race nodes it cited, and the Analyst can't cite anything that wasn't actually retrieved (made-up IDs are dropped). The &lt;strong&gt;Why?&lt;/strong&gt; panel in the UI draws that evidence as a subgraph, together with the full agent handoff chain, every tool call (including the failed ones), and the Critic's review.&lt;/p&gt;

&lt;p&gt;The handoffs themselves are stored in the graph as &lt;code&gt;Task&lt;/code&gt; nodes linked by &lt;code&gt;HANDED_OFF_TO&lt;/code&gt;. Because the trace reads that chain as a variable-length path, a "Critic sent it back" round needed no special handling. It's just one more hop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory as a graph
&lt;/h2&gt;

&lt;p&gt;This was the part that made FalkorDB feel like the right tool rather than just a capable one. One FalkorDB instance holds several graphs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;f1&lt;/code&gt;: the race data, read-only at runtime&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mem_&amp;lt;user&amp;gt;&lt;/code&gt;: one isolated memory graph per user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mem_global&lt;/code&gt;: shared "what works" knowledge, with no user data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside each user's graph live three kinds of memory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Episodic&lt;/strong&gt;: sessions, messages, questions and answers. Follow-up questions use the conversation's &lt;code&gt;NEXT&lt;/code&gt; chain, so "And what if there's an early safety car there?" becomes a full standalone question about Monza.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic&lt;/strong&gt;: insights about circuits. When a new answer agrees with an old insight, it &lt;strong&gt;confirms&lt;/strong&gt; it (raising its count and confidence). When it disagrees, it &lt;strong&gt;contradicts&lt;/strong&gt; it, and both are kept and the conflict is shown. When it replaces it, it &lt;strong&gt;supersedes&lt;/strong&gt; it: the old insight leaves recall but stays as history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Procedural&lt;/strong&gt;: which tool sequences tend to get approved for each type of question, ranked by a smoothed success rate. This is shared across users, so a brand-new user's very first question already benefits from plans that worked for others, without seeing anyone else's memories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping users in separate graphs means isolation is structural, not a &lt;code&gt;WHERE user_id = ...&lt;/code&gt; I might forget. The cost is that Cypher can't traverse between graphs, so memory points into the race data through small stub nodes (&lt;code&gt;Evidence&lt;/code&gt;, &lt;code&gt;EntityRef&lt;/code&gt;) holding a label and an ID, which the trace resolves with a second query. That turned out to be a fair trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that surprised me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FalkorDB's algorithms run on stored edges only.&lt;/strong&gt; I couldn't cluster circuits on a computed relationship; I had to write &lt;code&gt;FAVOURS&lt;/code&gt; edges (circuit → its dominant strategy) into the graph first. My first attempt linked every strategy a circuit had ever seen, and label propagation put every circuit in one giant cluster. Linking only the &lt;em&gt;dominant&lt;/em&gt; dry-race strategy fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cypher's &lt;code&gt;shortestPath()&lt;/code&gt; is directed-only in FalkorDB&lt;/strong&gt;, so it couldn't follow similarity links both ways. &lt;code&gt;algo.SPpaths&lt;/code&gt; with &lt;code&gt;relDirection: 'both'&lt;/code&gt; did the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raw PageRank scores are tiny&lt;/strong&gt; when they're spread over a whole graph, so I rescaled them (1.0 = most central circuit) to make them readable for both the model and humans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measuring "success" is harder than it looks.&lt;/strong&gt; I first counted a tool plan as successful only if the Critic approved the answer on the first try. But the Critic asks for one revision on most answers, so every plan scored zero. "Approved after at most one revision" turned out to be the useful signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers are honest.&lt;/strong&gt; Everything worked locally, then every tool call failed in Docker. The MCP SDK starts the tool-server subprocess with a minimal environment, and without a &lt;code&gt;.env&lt;/code&gt; file on disk the database credentials never arrived. The fix was to forward them explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free tiers shape design.&lt;/strong&gt; Oracle Cloud had no free capacity, so the app runs on Render's free tier (512 MB RAM). I simulated those limits locally before deploying, and capped concurrent questions to stay safely within memory.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I used Claude Code as a coding assistant throughout, and every AI-assisted step is disclosed in &lt;a href="https://github.com/SandeepKr24/wmdfalkor_pitgraph/blob/main/AI_USAGE.md" rel="noopener noreferrer"&gt;AI_USAGE.md&lt;/a&gt;. The design decisions, such as the graph model, which algorithms to use where, the hosting choice, and what counted as "done" each day, were mine. I kept the code simple enough to explain every query in it.&lt;/p&gt;

&lt;p&gt;The stack: Python 3.11, FalkorDB Cloud, an MCP server for the graph tools, LangGraph for the agent flow, Claude as the model, FastAPI, and a single-page UI with vis-network for the evidence graph. There are 38 integration tests, run against the live database.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real authentication.&lt;/strong&gt; Right now the client chooses its user ID, which is fine for a demo but not beyond it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lap-level telemetry&lt;/strong&gt;, so the agents can reason about tyre degradation, not just the sequence of compounds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More seasons and sessions&lt;/strong&gt;, such as qualifying and sprints.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Open the &lt;a href="https://wmdfalkor-pitgraph.onrender.com/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; and ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Best strategy at Monza for a top-5 finish?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Is a one-stop viable at Monza if there's an early safety car?&lt;/em&gt; (watch the recovery path kick in)&lt;/li&gt;
&lt;li&gt;Then open a new session and ask: &lt;em&gt;What do you know about Monza?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Why?&lt;/strong&gt; on any answer to see the graph behind it.&lt;/p&gt;

&lt;p&gt;The thing I'll take away from this hackathon is that a graph is about more than storage. It's a format an agent can &lt;em&gt;explain itself&lt;/em&gt; in. Every answer PitGraph gives is a path you can follow back to the races it came from.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>falkordb</category>
      <category>agents</category>
      <category>python</category>
    </item>
  </channel>
</rss>
