<?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: Ibn Web3</title>
    <description>The latest articles on DEV Community by Ibn Web3 (@ibnweb3).</description>
    <link>https://dev.to/ibnweb3</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%2F4158605%2Fbc71d605-66a5-45a3-a953-98ea932cd49b.jpg</url>
      <title>DEV Community: Ibn Web3</title>
      <link>https://dev.to/ibnweb3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ibnweb3"/>
    <language>en</language>
    <item>
      <title>Whylog: three Claude Code agents that say "This was not recorded" instead of guessing why</title>
      <dc:creator>Ibn Web3</dc:creator>
      <pubDate>Sat, 07 Nov 2026 13:28:14 +0000</pubDate>
      <link>https://dev.to/ibnweb3/whylog-three-claude-code-agents-that-say-this-was-not-recorded-instead-of-guessing-why-1nk1</link>
      <guid>https://dev.to/ibnweb3/whylog-three-claude-code-agents-that-say-this-was-not-recorded-instead-of-guessing-why-1nk1</guid>
      <description>&lt;p&gt;Your team's code outlives the people who wrote it. The reasons behind it don't. This is how I built three Claude Code agents on Switch that record those reasons while a decision is being made, and that say "This was not recorded" instead of guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whylog is three Claude Code agents in one Switch room: a coder, a scribe and a recall agent.&lt;/li&gt;
&lt;li&gt;The scribe writes each decision into a room document (DR-1, DR-2...) and marks anything nobody said as "not stated".&lt;/li&gt;
&lt;li&gt;The recall agent answers "why did we do it this way?" only from those records and the linked PRs. If nothing covers the question, it says so.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything here ran on one Windows machine with Switch Console 0.40.1 and its bundled Mattermost. One person played the engineer. The people and the reasons in the demo are made up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Open any old pull request. "Add 5s timeout." "Retry failed deliveries with backoff." The diff shows what changed. It never shows why 5 seconds, why 3 retries, what was rejected, or what would make the team change its mind.&lt;/p&gt;

&lt;p&gt;When the person who knew leaves, that reasoning leaves with them. A new hire can ask a chat model, and it will give a confident, plausible reason that nobody ever said.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The record decides. The model doesn't.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the one rule Whylog follows. The agents may ask, write and quote. They may not invent a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I built
&lt;/h3&gt;

&lt;p&gt;Three agents, created from one Switch template, in one private room called &lt;code&gt;whylog-engineering&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;whylog-coder&lt;/strong&gt; works with an engineer on code. Before it picks an option it says the options and trade-offs out loud in the room.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;whylog-scribe&lt;/strong&gt; takes the &lt;code&gt;scribe&lt;/code&gt; role. It asks for what is missing, one question at a time: what was chosen, what was rejected and why, who decided, what would make us revisit it. It writes only what a person said.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;whylog-recall&lt;/strong&gt; takes the &lt;code&gt;recall&lt;/code&gt; role. It answers only from the decision documents and the GitHub reference, quotes the record, names who said it, and links the PR.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The test subject
&lt;/h2&gt;

&lt;p&gt;I made a tiny webhook sender, &lt;a href="https://github.com/ibnweb3/whylog-demo" rel="noopener noreferrer"&gt;whylog-demo&lt;/a&gt;, with two merged PRs: #1 adds a 5 second timeout, #2 adds up to 3 retries with backoff. Neither PR description says why. That gap is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Switch is used
&lt;/h2&gt;

&lt;p&gt;Every part of Whylog is a Switch feature.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Switch feature&lt;/th&gt;
&lt;th&gt;What Whylog does with it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agents&lt;/td&gt;
&lt;td&gt;Three Claude Code agents on the local machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Room&lt;/td&gt;
&lt;td&gt;One private room where a person and all three agents talk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roles&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;scribe&lt;/code&gt; and &lt;code&gt;recall&lt;/code&gt;, both exclusive; the agents claim them in the room&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Room documents&lt;/td&gt;
&lt;td&gt;One document per decision (&lt;code&gt;DR-1 ...&lt;/code&gt;), following a "How we record decisions" document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;References&lt;/td&gt;
&lt;td&gt;A GitHub reference to the demo repo, so records link to real PRs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Room instructions&lt;/td&gt;
&lt;td&gt;The briefing every agent in the room reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;!&lt;/code&gt; commands&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;!roles&lt;/code&gt;, &lt;code&gt;!list-documents&lt;/code&gt;, &lt;code&gt;!list-references&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The room documents carry the idea. A person can open DR-1, read it and edit it. The record does not live only in chat scrollback.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened in the demo
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Options first.&lt;/strong&gt; I asked the coder what options we had on each PR. It listed options and trade-offs and gave a pick for each. It also said it had not read the PR diffs, so it described the general option space, not our code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The scribe records.&lt;/strong&gt; I told the scribe to take its role and record the decision. It created &lt;code&gt;DR-1 5s timeout and 3 retries with backoff&lt;/code&gt;, linked both PRs, and marked every part nobody had said as "not stated".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filling the gaps.&lt;/strong&gt; I gave the rejected options (separate connect and read timeouts, because our HTTP client supports one value; a circuit breaker, as too heavy for now) and the backoff. The scribe updated DR-1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offboarding.&lt;/strong&gt; I said I was leaving. The scribe listed the one decision I own, named three gaps, and interviewed me about them. My answers went into DR-1, marked as mine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recall, with a record.&lt;/strong&gt; I asked the recall agent why we use a 5 second timeout and 3 retries. It quoted DR-1, named who decided, linked both PRs and listed what is still not stated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recall, without a record.&lt;/strong&gt; I asked why we did not use a message queue. The answer was "This was not recorded." It named DR-1 as the nearest record and gave no reason.&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%2Frj62t0uqrm7d0r0wnv7j.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%2Frj62t0uqrm7d0r0wnv7j.png" alt="Scribe creating DR-1 in the Switch room" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The scribe takes its role, creates DR-1 and marks the gaps (step 2). Screenshot from the demo run.&lt;/em&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%2F59mlx5bjcdt9yktvh67d.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%2F59mlx5bjcdt9yktvh67d.png" alt="Switch listing the room documents and the scribe interviewing the leaver" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;code&gt;!list-documents&lt;/code&gt;, then the offboarding interview listing the three gaps (step 4).&lt;/em&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%2Fdteefouozjtsan5o3jwi.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%2Fdteefouozjtsan5o3jwi.png" alt="Recall agent answering from DR-1" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The recall agent answers from DR-1, quotes the record and says what is not recorded (step 5).&lt;/em&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%2Fzmnbemjt56cdi1l42dsx.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%2Fzmnbemjt56cdi1l42dsx.png" alt="Recall agent saying this was not recorded" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;No record covers the queue question, so the answer is "This was not recorded." (step 6).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Step 6 matters most. Any system can answer when the answer is on file. What a new hire needs to know is where the file ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it still gets wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The sources are narrow.&lt;/strong&gt; The reasons come from the room only. Switch has reference types for Jira, Confluence and Drive, but I connected only GitHub. Whylog does not read tickets, company chat history or research documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a retrieval system.&lt;/strong&gt; The recall agent reads the room's decision documents and the GitHub reference with its own access. There is no search index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The coder had no clone of the repo.&lt;/strong&gt; It described options in general and did not read the diffs. The scribe and recall agents rely on what people said and on the PR links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The demo is one person.&lt;/strong&gt; One person played the engineer. This is a single-person test, not a real team. I did not test Slack, Teams, Discord or Telegram.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roles are claimed by the agents.&lt;/strong&gt; Switch lists the roles as free until an agent takes one. The scribe and recall agents each did in my run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What got in the way (my #HowISwitch notes)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create all shows a timeout while the server keeps building.&lt;/strong&gt; The "Nothing was created" screen is wrong. Retrying made duplicate rooms. Now I click once, wait about five minutes and check the Agents and Rooms lists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A stale remote host blocked the template.&lt;/strong&gt; An unreachable WSL host broke the provider check in the form. Deleting the host did not clear it. I fixed it with a template variant that sets the provider and location directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deleting a room returned HTTP 500.&lt;/strong&gt; I made new team names instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent replies take 30 to 90 seconds,&lt;/strong&gt; sometimes longer, with no progress indicator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kickoff messages in the template timed out the Console,&lt;/strong&gt; so the template has none. You start the flow by talking to the agents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I would want next: a progress view while Create all runs, a working room delete, a provider check that ignores unreachable hosts, and a way to see which agent is working on a message.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"Not stated" is a feature.&lt;/strong&gt; The most useful thing the scribe does is refuse to fill a blank.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roles keep jobs clear.&lt;/strong&gt; One agent writes, one reads. The recall agent cannot edit the record it answers from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documents beat scrollback.&lt;/strong&gt; A decision a person can open and fix is a decision a team will trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say what you did not test.&lt;/strong&gt; Most of this post is a list of limits. A recall tool that overclaims would be worse than none.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Run it
&lt;/h2&gt;

&lt;p&gt;You need Switch Console 0.40.1 (the Windows early-access build), Docker Desktop for the bundled Mattermost, and Claude Code installed and signed in on the same machine.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork or clone &lt;a href="https://github.com/ibnweb3/whylog-demo" rel="noopener noreferrer"&gt;whylog-demo&lt;/a&gt;, or use your own public repo.&lt;/li&gt;
&lt;li&gt;In Switch Console, open Templates, import &lt;code&gt;template/whylog.template.yaml&lt;/code&gt; from the Whylog repo.&lt;/li&gt;
&lt;li&gt;Fill in &lt;code&gt;team&lt;/code&gt;, &lt;code&gt;repo_url&lt;/code&gt; and &lt;code&gt;newhire_user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click Create all once, and wait. Do not retry.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;&amp;lt;team&amp;gt;-engineering&lt;/code&gt;, run &lt;code&gt;!roles&lt;/code&gt; and &lt;code&gt;!list-references&lt;/code&gt;, then follow &lt;code&gt;docs/SCENARIO.md&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/ibnweb3/whylog" rel="noopener noreferrer"&gt;https://github.com/ibnweb3/whylog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo video: &lt;a href="https://youtu.be/dYZPn9NlDb4" rel="noopener noreferrer"&gt;https://youtu.be/dYZPn9NlDb4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo subject: &lt;a href="https://github.com/ibnweb3/whylog-demo" rel="noopener noreferrer"&gt;https://github.com/ibnweb3/whylog-demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built with Claude Code for the Flip the Switch hackathon (WeMakeDevs x DevTools Academy x Switch), Track 01. If you have ever inherited code with no reasons attached, tell me which decision you wish someone had written down.&lt;/p&gt;

</description>
      <category>switch</category>
      <category>ai</category>
      <category>claude</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>Your bibliography might be standing on a retracted paper</title>
      <dc:creator>Ibn Web3</dc:creator>
      <pubDate>Sat, 17 Oct 2026 10:45:14 +0000</pubDate>
      <link>https://dev.to/ibnweb3/the-retraction-two-citations-back-building-a-citation-path-audit-on-falkordb-3cjk</link>
      <guid>https://dev.to/ibnweb3/the-retraction-two-citations-back-building-a-citation-path-audit-on-falkordb-3cjk</guid>
      <description>&lt;p&gt;&lt;strong&gt;I tried to predict retracted papers. I failed, and built something better.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bedrock-audit.ibnweb3lab.workers.dev/blog/" rel="noopener noreferrer"&gt;bedrock-audit.ibnweb3lab.workers.dev/blog&lt;/a&gt;. Built for the Graph Hacks hackathon (WeMakeDevs x FalkorDB), Track 01.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine you've spent a year on a literature review. Every source on your list checks out, because your reference manager flags nothing. But two steps back, one of those sources leans on a paper that was retracted years ago, and no tool you use was ever going to tell you.&lt;/p&gt;

&lt;p&gt;That gap is what &lt;strong&gt;Bedrock&lt;/strong&gt; is for. You paste a list of DOIs, and it follows every citation path, up to two steps, from your papers to retracted research. For each finding it shows the exact chain, the retraction date and the reasons on record. An AI agent explains the results and proposes edits. It can never apply them.&lt;/p&gt;

&lt;p&gt;This is the story of building it in three days: the parts that went well, the part that went badly, and the part where my own agent miscounted.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code (MIT):&lt;/strong&gt; &lt;a href="https://github.com/ibnweb3/bedrock" rel="noopener noreferrer"&gt;https://github.com/ibnweb3/bedrock&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://bedrock-audit.ibnweb3lab.workers.dev" rel="noopener noreferrer"&gt;https://bedrock-audit.ibnweb3lab.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The idea: a question about paths, not rows
&lt;/h2&gt;

&lt;p&gt;Tools that flag retractions typically check the papers you cite. They don't ask what those papers cite. That second question is a graph question: start at a paper, follow &lt;code&gt;CITES&lt;/code&gt; edges, stop when you reach a retracted node, and report the path.&lt;/p&gt;

&lt;p&gt;Here is a real path from the corpus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your reference ──cites──▶ not flagged ──cites──▶ RETRACTED 2014-10-01
10.1016/j.neubiorev       10.1111/ejn.13603      10.1016/j.jpsychires
.2019.09.040                                     .2014.06.018
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middle paper isn't retracted, which is exactly why nothing warns you.&lt;/p&gt;

&lt;p&gt;Two rules shaped everything. First, &lt;strong&gt;Bedrock states facts about a fixed corpus, it doesn't guess.&lt;/strong&gt; A paper with "no exposure found" is never called safe, only not found within two hops. Second, &lt;strong&gt;the person decides.&lt;/strong&gt; The agent proposes, a human approves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data: public, and kept that way
&lt;/h2&gt;

&lt;p&gt;The retractions come from the Retraction Watch database, distributed by Crossref. The citations come from OpenAlex, which is CC0. I pulled works from 213 journals, 251,645 articles in all, and joined them to 61,299 retracted works. The hackathon rules ask for public data only, so authors were hashed with a secret salt in memory before anything touched disk, and names, emails and ORCIDs were dropped. The graph I publish has &lt;strong&gt;no author data at all&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retraction Watch ─┐
                  ├─▶ FalkorDB (Cypher) ─▶ audit queries ─▶ read-only agent ─▶ you approve
OpenAlex (CC0) ───┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph produces every finding. The model only writes the summary, and a person approves any change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The graph: a small model, sharp queries
&lt;/h2&gt;

&lt;p&gt;The model is deliberately plain: &lt;code&gt;Paper&lt;/code&gt; nodes carrying &lt;code&gt;doi&lt;/code&gt;, &lt;code&gt;retracted_at&lt;/code&gt; and &lt;code&gt;retraction_reasons&lt;/code&gt;, &lt;code&gt;Venue&lt;/code&gt; nodes, and two relationships, &lt;code&gt;CITES&lt;/code&gt; and &lt;code&gt;PUBLISHED_IN&lt;/code&gt;. A retraction is a property, not its own node, which keeps the graph small and makes "as of this date" filters trivial. The two-hop question is one query:&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;UNWIND&lt;/span&gt; &lt;span class="n"&gt;$ids&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;i&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="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;Paper&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;i&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;p&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;p&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;:CITES&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;x:&lt;/span&gt;&lt;span class="n"&gt;Paper&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;x.retracted_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&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;x&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;:CITES&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;r:&lt;/span&gt;&lt;span class="n"&gt;Paper&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;r.retracted_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;:CITES&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="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;p.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x.doi&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r.doi&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r.retracted_at&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r.retraction_reasons&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="n"&gt;$limit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it aloud: the paper cites &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt; isn't retracted, &lt;code&gt;x&lt;/code&gt; cites a retracted &lt;code&gt;r&lt;/code&gt;, and the paper doesn't already cite &lt;code&gt;r&lt;/code&gt; directly. Everything Bedrock shows, the direct hits, the two-hop paths and the "papers cited alongside it" suggestions for replacements, is a query of this size, run by FalkorDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 59-second query: the planner started in the wrong place
&lt;/h2&gt;

&lt;p&gt;My first audit of nine papers took &lt;strong&gt;59 seconds&lt;/strong&gt;. The data was small and the question was simple, so something was off. The cause: FalkorDB's planner began at the 61,299 retracted papers and worked back toward my input, instead of starting at the few papers I'd asked about. The fix was the humble &lt;code&gt;WITH p&lt;/code&gt; you can see above. It forces the traversal to start from the input papers and expand their handful of references.&lt;/p&gt;

&lt;p&gt;Re-timed after the fix: 0.05 to 0.4 seconds for nine papers, and 0.15 to 0.4 seconds for 150.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I took from it:&lt;/strong&gt; a graph database makes the shape of the query matter more than the size of the data. When an indexed lookup can anchor a traversal, say so in the query. It's the difference between a demo and a product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The test that failed: I tried to predict retractions
&lt;/h2&gt;

&lt;p&gt;Bedrock's first idea was ambitious: use graph signals, like who a paper cites, who it shares authors with and which community it sits in, to predict which papers will be retracted. Before looking at any result I wrote down the pass mark: the graph method had to beat the best simple baseline, and the gap had to survive removing the largest journal. Then I ran it on 134,798 articles from journals with a retraction history, 140 of which were retracted within the next two years (cutoff 2020-12-31).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Average precision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Random ranking&lt;/td&gt;
&lt;td&gt;0.0012&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Journal's past retraction rate&lt;/td&gt;
&lt;td&gt;0.0066&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cites a retracted work (one hop)&lt;/td&gt;
&lt;td&gt;0.0066&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two citation hops&lt;/td&gt;
&lt;td&gt;0.0021&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All graph signals combined&lt;/td&gt;
&lt;td&gt;0.0041&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Journal history plus graph&lt;/td&gt;
&lt;td&gt;0.0074&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The verdict: no reliable improvement.&lt;/strong&gt; The best combination beat the journal baseline by less than the uncertainty. The 140 retractions were concentrated in 20 to 27 journals, so the study has low power, and the multi-hop signals did not beat their one-hop versions. The single signal that stood out, "this paper already cites a retracted one", made a paper 5 to 12 times more likely than average to be retracted later, depending on the cutoff. That is a lookup, not a forecast.&lt;/p&gt;

&lt;p&gt;I could have shipped a "retraction risk score" anyway. It would have looked impressive and been unearned. Instead I changed the product: Bedrock stopped predicting and started proving. Exact provenance, with the failed test on the front page. That decision, more than any query, is what I'd defend in front of anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent: useful, on a short leash
&lt;/h2&gt;

&lt;p&gt;The agent runs on Groq's free tier with &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;. It has three tools, &lt;code&gt;audit_bibliography&lt;/code&gt;, &lt;code&gt;explain_finding&lt;/code&gt; and &lt;code&gt;find_dois&lt;/code&gt;, and every one only reads. There's no edit tool, and a test makes sure it stays that way. The guardrails came from watching it misbehave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounding.&lt;/strong&gt; Every DOI in the answer must come from your text or a tool result. Models retyped DOIs and corrupted some, so an invented one now raises a visible warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completeness.&lt;/strong&gt; It once skipped a flagged paper. If the summary omits one, the agent gets a single retry; anything still missing is appended in plain words.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counts.&lt;/strong&gt; The same run opened with "one retracted paper" when the audit had found two. Models miscount, so the opening line, "Checked 9 DOIs: 2 retracted, 2 cite a retracted work directly...", is written by code from the audit, never by the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback.&lt;/strong&gt; The findings are computed independently of the model. If the key is missing or Groq is rate-limited, you still get the full audit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the approval step: the server re-runs the audit itself and only applies proposal ids it generated. The browser can't hand it a result to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping it: 2.5 million nodes down to 318,942
&lt;/h2&gt;

&lt;p&gt;The research graph, with authors and two derived edge types for the backtest (1.69M &lt;code&gt;COAUTHOR&lt;/code&gt; and 2.68M &lt;code&gt;BIB_COUPLED&lt;/code&gt;, both built in Cypher inside FalkorDB), reaches about 2.5 million nodes. Too big to hand to anyone. But an exposure path can only follow &lt;code&gt;CITES&lt;/code&gt; between papers I hold, so for the audit most of it is dead weight.&lt;/p&gt;

&lt;p&gt;So I built a slim graph with just papers, venues and the citations between held papers: &lt;strong&gt;318,942 nodes and 530,894 edges.&lt;/strong&gt; Then I tested the claim, not just made it. A script audited 2,501 DOIs on both graphs and compared the complete results: statuses, paths, reasons, coverage and alternatives. &lt;strong&gt;Zero differences.&lt;/strong&gt; (That test also caught a real bug: self-citations had inflated a reference count by exactly one in 37 papers.)&lt;/p&gt;

&lt;p&gt;The slim graph ships as a 17 MB &lt;code&gt;DUMP&lt;/code&gt; snapshot in the repo. In a container limited to 512 MB and a tenth of a CPU, it restores in about a minute and sits at roughly 300 MB. A judge can run the whole thing with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ibnweb3/bedrock &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;bedrock
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; demo up &lt;span class="nt"&gt;--build&lt;/span&gt; app
&lt;span class="c"&gt;# open http://127.0.0.1:8765&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I checked that from a fresh clone with no keys: a no-cache build took just over two minutes and the app answered five seconds after starting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It knows only its corpus: 213 journals, 251,645 articles and the papers they cite, with a Retraction Watch snapshot from Oct 15, 2026.&lt;/li&gt;
&lt;li&gt;It looks two hops back. "No exposure found" means nothing was found within that, not that a paper is sound.&lt;/li&gt;
&lt;li&gt;Reasons and dates come from Retraction Watch. A flag is a prompt for a person to read, never a finding about an author.&lt;/li&gt;
&lt;li&gt;The live demo runs on one small AWS server, so it can be slow if many people try it at once. The one-command setup above always works.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Write the pass mark before you run the test. It's the only reason a failure was a result instead of an argument.&lt;/li&gt;
&lt;li&gt;In a graph database, anchor your traversals. Query shape beats data size.&lt;/li&gt;
&lt;li&gt;Let code state the facts and let the model write the prose. Check its numbers anyway.&lt;/li&gt;
&lt;li&gt;Prove your shortcuts. "The slim graph is equivalent" became a claim I could stand behind only after the 2,501-DOI test.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I built it solo in three days, with Claude Code writing most of the code and docs from my instructions. I chose the idea, set the rules, approved the experiment's size, decided the pivot from prediction to provenance, picked Groq for the agent, and signed off every publication and deployment. The decision log is in the repo at &lt;code&gt;docs/DECISIONS.md&lt;/code&gt;. The code is MIT licensed; the data keeps its own terms (Retraction Watch via Crossref, OpenAlex CC0).&lt;/p&gt;

&lt;p&gt;Photo: sedimentary rock layers at Zabriskie Point, Death Valley, by Brigitte Werner, CC0 (&lt;a href="https://commons.wikimedia.org/wiki/File:Sedimentary_Rock_Layers_Zabriskie_Point_Death_Valley_USA.jpg" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>database</category>
      <category>ai</category>
      <category>python</category>
      <category>hackathon</category>
    </item>
  </channel>
</rss>
