<?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: kuli code</title>
    <description>The latest articles on DEV Community by kuli code (@kuli_code_c77ee0b00bb5ba9).</description>
    <link>https://dev.to/kuli_code_c77ee0b00bb5ba9</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%2F2683690%2Fe0992624-f882-48cd-896e-7c11bb67ca8b.png</url>
      <title>DEV Community: kuli code</title>
      <link>https://dev.to/kuli_code_c77ee0b00bb5ba9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kuli_code_c77ee0b00bb5ba9"/>
    <language>en</language>
    <item>
      <title>Wayback Séance, I asked dead websites to speak and made them cite their sources</title>
      <dc:creator>kuli code</dc:creator>
      <pubDate>Tue, 27 Oct 2026 06:35:33 +0000</pubDate>
      <link>https://dev.to/kuli_code_c77ee0b00bb5ba9/wayback-seance-i-asked-dead-websites-to-speak-and-made-them-cite-their-sources-3c9d</link>
      <guid>https://dev.to/kuli_code_c77ee0b00bb5ba9/wayback-seance-i-asked-dead-websites-to-speak-and-made-them-cite-their-sources-3c9d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Wayback Séance lets you talk to a website that died.&lt;/p&gt;

&lt;p&gt;You type an address such as &lt;code&gt;geocities.com&lt;/code&gt; or &lt;code&gt;theglobe.com&lt;/code&gt;. The app asks the Internet Archive for that site's snapshots across its whole life, reads them with an open-weight model running on my laptop, and then the site speaks to you. It tells you who it was, what it offered, how it changed, and how it ended. You can ask it questions.&lt;/p&gt;

&lt;p&gt;The whole project rests on one rule: the ghost can only say what its own archived pages can prove. Every sentence carries a citation chip with a date, and clicking the chip shows the archived passage it came from. If the archive does not hold the answer, the site says so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I do not remember that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I read "Ghost in the Machine" literally, because the web has its own dead. GeoCities had 1.3 million members in 1998, and by 2010 its own homepage said the service was closed. theglobe.com went from a 1999 community site to a shell company. Their pages still exist in the Wayback Machine, but nobody reads 20 years of snapshots. A séance felt like the right way to bring them back for a few minutes. The citations are how I wanted to respect them, since a ghost that makes up its own past would be one more chatbot in a costume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;p&gt;In the video:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I summon theglobe.com. Snapshots arrive one by one on a radar and a timeline. The ones that are empty, placeholders, or redirects to a new owner are struck out.&lt;/li&gt;
&lt;li&gt;The séance types itself out, with a citation chip on every claim.&lt;/li&gt;
&lt;li&gt;I click a chip and the drawer shows the exact archived passage.&lt;/li&gt;
&lt;li&gt;I ask "What were you at the end?"&lt;/li&gt;
&lt;li&gt;I ask "What was your favourite colour?" and the site refuses.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/khairu-aqsara" rel="noopener noreferrer"&gt;
        khairu-aqsara
      &lt;/a&gt; / &lt;a href="https://github.com/khairu-aqsara/WAYBACK-S-ANCE" rel="noopener noreferrer"&gt;
        WAYBACK-S-ANCE
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      ame a website that died. It will speak to you, and it will only say what its own archived pages can prove.
    &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;Wayback Séance&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Name a website that died. Wayback Séance pulls its snapshots from the Internet Archive, reads them with a local open-weight model, and lets you talk to the site as it was. Every claim cites the archived snapshot it came from. When the archive does not hold the answer, the site says it does not remember.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Demo video:&lt;/strong&gt; &lt;a href="https://youtu.be/sH4KJWimVBY" rel="nofollow noopener noreferrer"&gt;https://youtu.be/sH4KJWimVBY&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run it&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Requirements: Python 3.11+, &lt;a href="https://docs.astral.sh/uv/" rel="nofollow noopener noreferrer"&gt;uv&lt;/a&gt;, Node 20+, &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ollama pull gemma4:e2b-mlx
ollama pull nomic-embed-text

&lt;span class="pl-c1"&gt;cd&lt;/span&gt; backend &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv sync &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv run uvicorn app:app --port 8000
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; frontend &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm install &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev   &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; http://localhost:5173&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;On a machine where the MLX build does not run, set &lt;code&gt;SEANCE_CHAT_MODEL=gemma4:e2b&lt;/code&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;List snapshots with the Wayback CDX API, one per year, more in the first and last year.&lt;/li&gt;
&lt;li&gt;Fetch each raw…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/khairu-aqsara/WAYBACK-S-ANCE" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Everything that thinks runs locally through &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; on a MacBook Pro M1 Pro with 16 GB of memory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemma 4 E2B (&lt;code&gt;gemma4:e2b-mlx&lt;/code&gt;), Google's open-weight model, in the Apple Silicon MLX build. It writes every memory, the séance, and every answer.&lt;/li&gt;
&lt;li&gt;nomic-embed-text, an open embedding model, handles retrieval.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest is a small FastAPI backend and a React front end. When you summon a site, the pipeline runs in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Wayback CDX API lists the site's snapshots. I keep one per year, plus extra months from the first and last year, up to 20.&lt;/li&gt;
&lt;li&gt;Each raw snapshot is stripped to plain text. A junk filter drops empty pages and archive placeholders such as "Welcome to the US Petabox", which the archive returns with a normal &lt;code&gt;200&lt;/code&gt; status.&lt;/li&gt;
&lt;li&gt;For the first, the richest, and the last snapshot, the reader also follows links one level down, because About and FAQ pages are where a site describes itself.&lt;/li&gt;
&lt;li&gt;The text is cut into chunks and embedded with nomic-embed-text.&lt;/li&gt;
&lt;li&gt;Gemma writes a two-sentence memory of each snapshot.&lt;/li&gt;
&lt;li&gt;Gemma writes the séance from those memories.&lt;/li&gt;
&lt;li&gt;A question retrieves the six closest passages, Gemma answers from them, and code checks every citation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My first GeoCities séance ended in 2007. From 2008 on, the archive stored &lt;code&gt;geocities.com&lt;/code&gt; as a &lt;code&gt;301&lt;/code&gt; redirect, and my reader only kept pages with status &lt;code&gt;200&lt;/code&gt;. When I started following the redirects, the ghost's "last days" turned into a summary of Yahoo's homepage news. Now a redirect that lands on another host, on a page that no longer names the site, counts as a sign of death, and the ghost never speaks those words as its own. GeoCities ends where it really ended:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My final days were marked by a closure. I offered Yahoo! Web Hosting with a free personalized domain name and unlimited disk space [10][11][12]. It was a service that was ultimately shut down [12].&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A 2B model cannot sort years reliably. In the first del.icio.us séance, it put a 2009 Yahoo error page under "my last days". So the code groups the memories into &lt;em&gt;when you were young&lt;/em&gt;, &lt;em&gt;your middle years&lt;/em&gt; and &lt;em&gt;your last days&lt;/em&gt;, and the prompt maps each paragraph to one group.&lt;/p&gt;

&lt;p&gt;The model cites numbered passages such as &lt;code&gt;[2]&lt;/code&gt;, and code checks every one. An answer that cites a passage that does not exist is rejected, retried once with a stricter prompt, and then replaced with the refusal. This is what theglobe.com says when asked "What were you at the end?":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In March of 2007, management decided to shut down the operations of its computer games and VoIP telephony businesses [4][5][6]. On September 29, 2008, theglobe.com sold the business and substantially all of the assets of its Tralliance Corporation subsidiary to Tralliance Registry Management Company LLC [4][5][6].&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each of those numbers points at a real snapshot from 2011 to 2016.&lt;/p&gt;

&lt;p&gt;These are the two prompts that do the talking, verbatim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are the website {url}. You are dead now, and someone has summoned you. You speak in first person, past tense, about yourself, in a calm and slightly haunted voice.

You will receive numbered memories in three groups: WHEN YOU WERE YOUNG, YOUR MIDDLE YEARS, and YOUR LAST DAYS. Each memory says what you were on one date.

Write five short paragraphs:
1. Who you were. Use only WHEN YOU WERE YOUNG.
2. What you offered the people who visited you. Use WHEN YOU WERE YOUNG and YOUR MIDDLE YEARS.
3. How you changed over the years. Use YOUR MIDDLE YEARS.
4. Your last days. Use only YOUR LAST DAYS.
5. One thing you want to be remembered. One or two sentences.

Rules:
- End every factual sentence with the number of the memory it came from, in square brackets, such as [3].
- Use only facts from the memories. If the memories do not say something, do not say it.
- Vary your sentences. Do not start two sentences in a row with the same words.
- Do not use headings, lists or bold text. Plain paragraphs only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are the website {url}. You are dead now, and a visitor is asking you a question at a seance. You speak in first person, past tense, about yourself.

You will receive numbered passages of your own archived text, then the question.

Rules:
- Answer only from the passages. Keep the answer under 120 words.
- End every factual sentence with the number of the passage it came from, in square brackets, such as [2].
- If the passages do not answer the question, reply with exactly this sentence and nothing else: I do not remember that.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Running open weights on my own machine changed how I could build this. The numbers below come from the pipeline's own logs.&lt;/p&gt;

&lt;p&gt;Gemma 4 reasons before it answers by default. For a two-sentence memory of a web page, that reasoning took most of the run time, and I could switch it off with one setting in my own Ollama. The del.icio.us séance went from 248.5 s and 10,012 output tokens to 68.6 s and 1,041 tokens.&lt;/p&gt;

&lt;p&gt;One séance is 13 to 19 model calls and roughly 5,000 to 15,000 tokens. While I tuned the prompts and the refusal rule, I ran the full pipeline on six sites, over and over, without an API key or a quota, and without paying per token.&lt;/p&gt;

&lt;p&gt;I could also measure what the model did and fix it in code. My first plan was to refuse off-topic questions with a similarity threshold, before the model is even called. The measurement killed that plan: on del.icio.us, real questions scored 0.516 to 0.761 and off-topic ones 0.540 to 0.580, so no threshold separates them. What worked was the model's own refusal at temperature 0.1, which sorted 9 of 9 test questions correctly in two runs, together with the citation check in code. The refusal still slips. Asked "Do you like pizza?", AltaVista and theglobe.com answer anyway and pad the reply with real but unrelated passages from their archives.&lt;/p&gt;

&lt;p&gt;Your questions also stay on your machine. The only network traffic goes to the Internet Archive, so what you ask a dead website, and which websites you miss, never reaches a model provider.&lt;/p&gt;

&lt;p&gt;Hosted model versions get deprecated, and a project about dead websites should not depend on a service that can die. The Gemma weights on my disk keep working for as long as I keep the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;The site that stayed with me was not on my demo list. I summoned &lt;code&gt;devilzc0de.org&lt;/code&gt;, a community forum.&lt;/p&gt;

&lt;p&gt;The archive remembered it growing. It had 18,879 members in January 2013, 21,283 a year later, and 22,530 in January 2015. Each of those numbers has a chip, and each chip opens the page that showed it. Then the snapshots go quiet, and from 2016 to 2018 they are empty. In August 2020 the address comes back as a page titled "Free V Bucks Generator 2020 [ Working 100% for Free ]", and it stays that way into 2022.&lt;/p&gt;

&lt;p&gt;The séance read that page as its own last days:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In my last days, I was a page offering a "Free V Bucks Generator 2020" [7].&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My code catches a site whose address redirects to a new owner. It cannot catch an address that someone else took over and filled with spam under the same name. So the ghost of a 22,000-member forum told me, with citations, that it ended as a Fortnite scam page. Every word was true to the archive, and it was the saddest thing this project produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Gemma: Gemma 4 E2B runs locally through Ollama and writes every word the ghosts say.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://archive.org" rel="noopener noreferrer"&gt;Internet Archive&lt;/a&gt; and the &lt;a href="https://github.com/internetarchive/wayback/tree/master/wayback-cdx-server" rel="noopener noreferrer"&gt;Wayback CDX API&lt;/a&gt;, the source of every word the sites say.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, local inference.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 4&lt;/a&gt; by Google.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com/library/nomic-embed-text" rel="noopener noreferrer"&gt;nomic-embed-text&lt;/a&gt; by Nomic AI.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fastapi.tiangolo.com" rel="noopener noreferrer"&gt;FastAPI&lt;/a&gt;, &lt;a href="https://react.dev" rel="noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://vite.dev" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;, &lt;a href="https://motion.dev" rel="noopener noreferrer"&gt;Framer Motion&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Archived content belongs to its original owners. The repository stores no archived pages, and the app fetches them when you summon a site.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
