<?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: Shailendra</title>
    <description>The latest articles on DEV Community by Shailendra (@shailendra_kumar_n).</description>
    <link>https://dev.to/shailendra_kumar_n</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%2F4232583%2F14334a19-3ad6-4041-918a-efc1a387ce16.png</url>
      <title>DEV Community: Shailendra</title>
      <link>https://dev.to/shailendra_kumar_n</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shailendra_kumar_n"/>
    <language>en</language>
    <item>
      <title>Calling Hindsight From FastAPI Without Breaking the Event Loop</title>
      <dc:creator>Shailendra</dc:creator>
      <pubDate>Sat, 07 Nov 2026 18:09:27 +0000</pubDate>
      <link>https://dev.to/shailendra_kumar_n/calling-hindsight-from-fastapi-without-breaking-the-event-loop-1h20</link>
      <guid>https://dev.to/shailendra_kumar_n/calling-hindsight-from-fastapi-without-breaking-the-event-loop-1h20</guid>
      <description>&lt;p&gt;Calling Hindsight From FastAPI Without Breaking the Event Loop&lt;/p&gt;

&lt;p&gt;One of the more annoying bugs we hit while building IncidentIQ passed every isolated test and then failed intermittently once the real server was running. The code path was identical. The Hindsight bank was the same. The only thing that changed was which thread made the SDK call.&lt;/p&gt;

&lt;p&gt;IncidentIQ is an incident investigation assistant. An engineer submits a production incident, a Groq model proposes root-cause hypotheses, and an engineer later verifies what actually happened. Only that verified outcome is retained in &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt;, so the next related incident can recall it. This article is about the backend that makes that loop work, and about the event-loop problem that nearly derailed it. Everything here uses synthetic incidents, and the system is a prototype running locally, not something we have exposed to real production traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the backend
&lt;/h2&gt;

&lt;p&gt;The backend is FastAPI with SQLAlchemy on PostgreSQL, and Alembic handles migrations. Routes stay thin: &lt;code&gt;api/routes.py&lt;/code&gt; validates input and translates errors. The real work lives in three services. &lt;code&gt;InvestigationService&lt;/code&gt; coordinates the flow, &lt;code&gt;HindsightMemoryService&lt;/code&gt; owns every memory operation, and &lt;code&gt;GroqAIService&lt;/code&gt; owns the model call. PostgreSQL stores incidents, logs, deployments, investigation runs, findings, verified resolutions and an audit table of memory events. Hindsight holds only the experience we want an agent to recall later.&lt;/p&gt;

&lt;p&gt;The SDK client and both services are created once, in the FastAPI lifespan handler, and attached to &lt;code&gt;app.state&lt;/code&gt;:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hindsight_api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require_hindsight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hindsight_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Hindsight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hindsight_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HindsightMemoryService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hindsight_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bank_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hindsight_bank_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the key is missing, the service still exists with a &lt;code&gt;None&lt;/code&gt; client and every operation reports &lt;code&gt;client_unavailable&lt;/code&gt; instead of crashing. That made local development far less brittle. The app boots, the dashboard loads, and the health endpoint tells you which dependency is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  One endpoint, two modes
&lt;/h2&gt;

&lt;p&gt;The investigation endpoint takes a &lt;code&gt;memory_mode&lt;/code&gt; of &lt;code&gt;MEMORY_OFF&lt;/code&gt; or &lt;code&gt;MEMORY_ON&lt;/code&gt;. The mode is stored on every &lt;code&gt;InvestigationRun&lt;/code&gt;, so you can always see afterwards whether a run had historical context. The core of &lt;code&gt;run_investigation&lt;/code&gt; is short:&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;historical_memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;MemoryMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEMORY_ON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;recall_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context_without_memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recall_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# run is marked failed, audit event written
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;InvestigationServiceError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hindsight_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;historical_memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recall_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ai_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context_without_memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;historical_memories&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;historical_memories&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;analysis&lt;/span&gt; &lt;span class="o"&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;_ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze_incident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building the context this way means &lt;code&gt;MEMORY_OFF&lt;/code&gt; and &lt;code&gt;MEMORY_ON&lt;/code&gt; share one code path. The only difference is whether &lt;code&gt;historical_memories&lt;/code&gt; is empty. That keeps the comparison honest, because when we show two runs side by side, nothing else in the pipeline differs.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;analyze_incident&lt;/code&gt;, current evidence, recalled memories and engineer-verified findings are sent as separate lists, each item with a reference like &lt;code&gt;historical:0&lt;/code&gt;. The system prompt tells the model that historical similarity is not proof of a current cause, and that it may only cite references it was given. The call asks Groq for a JSON object at a low temperature, and the result is validated against a Pydantic schema. A malformed answer becomes a categorised error instead of a half-parsed result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug: threads and event loops
&lt;/h2&gt;

&lt;p&gt;The Hindsight Python client has sync methods and async ones. At one point our routes were ordinary &lt;code&gt;def&lt;/code&gt; handlers calling the sync &lt;code&gt;recall&lt;/code&gt;. FastAPI runs those in a thread pool, while the client had been created on the main event loop during startup. Our QA notes traced the intermittent 503s to that mismatch: the SDK's internal async machinery was being driven from a different thread than the one that owned the loop. In a standalone script or a pytest run there is only one loop, so nothing ever complained.&lt;/p&gt;

&lt;p&gt;The symptoms were maddening. Memory Explorer queries and &lt;code&gt;MEMORY_ON&lt;/code&gt; runs failed with a generic "dependency unavailable", and the audit table recorded &lt;code&gt;sdk_error&lt;/code&gt;, which told us nothing.&lt;/p&gt;

&lt;p&gt;Two changes followed. The routes that touch Hindsight became &lt;code&gt;async def&lt;/code&gt;, and the service now uses the client's async methods, &lt;code&gt;arecall&lt;/code&gt; and &lt;code&gt;aretain&lt;/code&gt;, so everything runs on the loop that created the client. The retain path also takes an &lt;code&gt;asyncio.Lock&lt;/code&gt;, so two concurrent verifications of the same incident cannot race to write the same memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making failures legible
&lt;/h2&gt;

&lt;p&gt;That &lt;code&gt;sdk_error&lt;/code&gt; result pointed at a second bug, this one in our own code. Our error classifier looked for &lt;code&gt;status_code&lt;/code&gt; on the exception, but the generated Hindsight API client exposes &lt;code&gt;status&lt;/code&gt;. Every HTTP failure fell through to the default category. The fix is one line:&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;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With it, a 401 becomes &lt;code&gt;authentication_error&lt;/code&gt;, a 429 becomes &lt;code&gt;rate_limited&lt;/code&gt;, and a 5xx becomes &lt;code&gt;provider_error&lt;/code&gt;. The API returns only these stable categories to the browser, inside a &lt;code&gt;{"detail": {"code": ..., "message": ...}}&lt;/code&gt; envelope, because raw provider messages can leak details you do not want in a UI:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hindsight_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;groq_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEPENDENCY_UNAVAILABLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An investigation dependency is unavailable.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser gets something deliberately vague and the audit log gets something precise. That split is what we wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the behaviour looks like
&lt;/h2&gt;

&lt;p&gt;Take a synthetic incident: payment-api returns HTTP 503 at checkout while database connection utilisation sits near its limit. With &lt;code&gt;MEMORY_OFF&lt;/code&gt;, the model reasons from the logs, metrics and deployment note alone. Later an engineer verifies the real cause. In our seeded scenario, a worker-concurrency increase exhausted the PostgreSQL connection pool. That resolution is retained with the tags &lt;code&gt;incident&lt;/code&gt; and &lt;code&gt;verified-resolution&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A follow-up incident on the same service can be investigated with MEMORY_ON. In this mode, IncidentIQ requests relevant historical context from Hindsight and, when memories are successfully returned, passes them to the model as historical information rather than confirmed evidence. This distinction is important because a previous resolution can guide an investigation, but the current incident must still be evaluated using its own logs and metrics.&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%2Flgp43zj07qc3ry6hl6r2.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%2Flgp43zj07qc3ry6hl6r2.png" alt=" " width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have not measured accuracy or time to resolution, and we are not claiming any improvement beyond what the recorded runs show.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create SDK clients on the loop that will use them.&lt;/strong&gt; If a library is async underneath, a thread-pool handler is the wrong place to call its sync wrapper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test through the running server.&lt;/strong&gt; Our unit tests passed because they never crossed the thread boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classify errors from real exception objects.&lt;/strong&gt; Check what the exception actually carries before writing a mapper for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep memory operations behind one service.&lt;/strong&gt; With a single place for retain and recall, the audit trail and the locking were easy to add.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give modes a single code path.&lt;/strong&gt; &lt;code&gt;MEMORY_OFF&lt;/code&gt; and &lt;code&gt;MEMORY_ON&lt;/code&gt; differ by one list, which is what makes comparing them meaningful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits and next steps
&lt;/h2&gt;

&lt;p&gt;Today a failed recall fails the whole &lt;code&gt;MEMORY_ON&lt;/code&gt; run instead of falling back to a clearly labelled baseline. The Groq call is still synchronous inside an async function, so it should move to a worker thread. The health endpoint performs real provider checks and should cache them. There is no authentication yet, so the typed engineer name is an audit label, not an identity.&lt;/p&gt;

&lt;p&gt;If you are building something similar, the &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight documentation&lt;/a&gt; covers the async client, and &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;Vectorize's explanation of agent memory&lt;/a&gt; is a good primer on why recalled context should sit beside the model rather than inside it.&lt;/p&gt;

&lt;p&gt;The code is on &lt;a href="https://github.com/1dt24cs248/hackwithbanglore" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  ai
&lt;/h1&gt;

&lt;h1&gt;
  
  
  python
&lt;/h1&gt;

&lt;h1&gt;
  
  
  fastapi
&lt;/h1&gt;

&lt;h1&gt;
  
  
  hindsight
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>fastapi</category>
      <category>python</category>
    </item>
  </channel>
</rss>
