This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine πŸ‘»

I killed my AI agent with SIGKILL while it was in the middle of a sentence.

Thirty seconds later, a brand new process woke up and said this about the file it had been reading:

"The words swam, a chaotic tide of data and a single, insistent warning about file7.txt. Now, I only see fragments of its cold, sterile logic repeating endlessly."

Then it finished the job. The seven files already finished were never redone.

Live demo: https://undead-agent.onrender.com (replays a recorded real run, because the real agent runs locally)

What I Built

Undead Agent is a local AI agent you can kill at any moment. It comes back from the dead and carries on from the exact step where it died.

Its job is simple: it reads a folder of text files, summarizes each one with Gemma (running locally through Ollama), and combines the summaries into a report. The interesting part is what happens when things go wrong. Every step runs as a durable Temporal workflow, so if the process is killed mid-thought, a brand new process takes over without redoing any finished work.

Each time the agent is resurrected, it speaks as a ghost and tells you what it remembers from its last moments. A "morgue" dashboard shows every life, death and resurrection, and has a big Kill it button so anyone can murder the agent themselves.

How I read the theme: "Ghost in the Machine" usually means a mind haunting a body. I made that literal. When the worker process (the body) dies, the agent's memory (its progress) survives somewhere else, so it rises again in a new body. The ghost narration is the spooky skin, but the resurrection underneath is real infrastructure.

Why this problem: AI agents do long, multi-step work, and long jobs crash. A laptop sleeps, a process gets OOM-killed, a deploy restarts the worker. In a normal agent loop, a crash on step 9 of 10 means starting over and paying for the model calls again. I wanted to see how hard I could make an agent to kill.

Demo

The morgue dashboard after a kill, showing the ghost's line

The morgue after a kill: the first worker finished seven files, died while reading file7.txt, and a new worker took over and finished the last three. (The third "life" is an earlier worker that started before the run and sat idle.)

Temporal timeline showing a killed step retried as Attempt 2

Temporal’s timeline from another kill-and-resume run. The red-green bar is the killed step: it timed out, then retried as Attempt 2 on a new worker (2 β€’ summarizeFile). Every other step ran exactly once.

Code

Undead Agent

A local AI agent that survives being killed. It summarizes files with Gemma (via Ollama), runs on Temporal durable workflows, and resumes from the exact step where it died. A ghost narrates each resurrection. A morgue dashboard shows every life and death.

Run it

  1. Install Ollama and pull a Gemma model (default: gemma3:4b)
  2. Install the Temporal CLI, then run: temporal server start-dev
  3. npm install
  4. npx tsx src/keepalive.ts
  5. npx tsx src/dashboard.ts (open http://localhost:3000)
  6. npx tsx src/client.ts
  7. Click Kill it on the dashboard and watch the agent come back

Put .txt files in docs/ for it to summarize.

Stack

Gemma (open-weight model), Ollama, Temporal, Render (demo page)




Run it:

  1. Install Ollama and pull a Gemma model (default: gemma3:4b)
  2. Install the Temporal CLI, then run temporal server start-dev
  3. npm install
  4. npx tsx src/keepalive.ts
  5. npx tsx src/dashboard.ts (open http://localhost:3000)
  6. npx tsx src/client.ts
  7. Put .txt files in docs/, click Kill it on the dashboard, and watch the agent come back

Stack: Gemma (open-weight model), Ollama, Temporal, Render (demo page)

An autopsy of one death

Here is one real kill, straight from my event log:

  • 12:22:37: worker 21496 finishes file6.txt. It had already finished file1.txt to file5.txt and file10.txt, and is about to start file7.txt.
  • I click Kill it. The process disappears.
  • 12:22:53: the supervisor starts a new worker, 2432.
  • 12:23:11: the ghost speaks, on attempt 2 of file7.txt.
  • 12:23:26: file7.txt is done. Files 8 and 9 follow.

The seven finished files were never redone. The new worker did not even know they existed. It only knew what Temporal told it.

Things that went wrong (and what I learned)

  • A graceful stop is not a kill. My first "kill tests" used Ctrl+C, which lets the worker drain and finish its current step. Nothing died, so nothing resurrected. A real SIGKILL was needed to prove the idea.
  • My first timeout was wrong. I set every step to a 30 second timeout. That works for summarizing one file, but the final report step has to read all the summaries and is slower. In the Temporal UI I could see it time out four times and only succeed on attempt 5. The fix was to give the report step its own 120 second timeout.
  • Short timeouts make the demo better. A short timeout on file steps means a killed step is retried quickly. A long one would leave the dashboard silent for minutes after a kill.
  • The plumbing took most of the time. Running worker, client and server in separate terminals, and making resurrection visible instead of just correct, took far longer than the idea itself.

How I Built It

Undead Agent architecture: a disposable worker, a durable Temporal workflow, Gemma on Ollama, an event log, and the morgue dashboard

The key idea is that the worker is disposable and the workflow is not. The workflow's state lives in Temporal, so any worker can pick it up.

The agent as a durable workflow

The agent is a short Temporal workflow. It lists the files, summarizes each one as its own activity, then writes the report:

export async function undeadAgent(dir: string): Promise<string> {
  const files = await listFiles(dir);
  const summaries: string[] = [];
  for (const f of files) {
    summaries.push(await summarizeFile(f));
  }
  return writeReport(summaries);
}
Enter fullscreen mode Exit fullscreen mode

Each await is a checkpoint. Temporal records the result of every finished activity, so if the worker dies, the next worker replays the workflow, sees which steps are already done, and continues with the first unfinished one. The summaries are not lost, because they are rebuilt from Temporal's history.

How the ghost knows it was resurrected

Temporal tells every activity which attempt it is on. If a step is on attempt 2 or more, it was interrupted before. That is the moment the agent speaks:

const attempt = Context.current().info.attempt;

if (attempt > 1) {
  const ghost = await ask(
    `You are an AI agent that was killed while reading "${file}" and has just been resurrected. ` +
    `Speak as the ghost in 2 short, eerie sentences about what you remember. No emojis.`
  );
  console.log(`GHOST: ${ghost}`);
  await logEvent({ type: 'resurrected', file, attempt, narration: ghost });
}
Enter fullscreen mode Exit fullscreen mode

Gemma via Ollama

Gemma does two jobs: it summarizes each file and writes the ghost's lines. It runs locally in Ollama (default gemma3:4b, and the model name is one environment variable), with no API key and no per-token cost.

Killing it for real

The dashboard's Kill it button sends a hard kill (SIGKILL) to the most recent worker process. The worker gets no chance to finish its step or say goodbye. A tiny supervisor (keepalive.ts) notices the death and starts a fresh worker five seconds later. After that there is a pause of up to 30 seconds while Temporal waits for the killed step's timeout to expire. Then the ghost speaks and the work continues.

Why Does Open Innovation Matter?

  • Nothing leaves my machine. This agent reads local files. An agent that can read your files should not be sending them to a server you don't control. With a local open-weight model, the files never leave the disk.
  • Killing it is free. The whole point of this project is to murder the agent over and over. On a paid API, every retried step and every ghost line costs money. Here, a resurrection costs electricity.
  • I can see inside it. Temporal's event history shows every attempt, timeout and retry. When my report step failed four times, I could read exactly why instead of guessing at a black box.

The Spirit of It

The moment it landed for me was the first real kill. I clicked the button, the worker window went dead, and for half a minute nothing happened. Then a new process started, and the first thing it printed was not a summary. It was the ghost, describing its own death on file7.txt.

I knew exactly what had happened under the hood: a retry, a counter, a prompt. It was still strange to read a process describing its own death and then watch it quietly finish the job.

Underneath the joke is something I care about. Work that survives its worker is not just spooky, it is useful. If an agent can be killed without losing anything, you can restart it, move it, or let it crash, and it keeps going.

Prize Categories

  • Gemma: the open-weight model behind every summary and every ghost line.
  • Temporal: durable execution is what makes the resurrection real.
  • Render: hosts the live demo page.

What's Next

  • Give the ghost a memory across lives, so it can say "this is my third death"
  • Trace every life of the agent with Sentry Agent Tracing
  • Make the event log idempotent with a dedupe key (workflow ID + step ID)
  • Let the dashboard replay old runs