This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
GHOST.EXE is a haunting that moves into your computer. There is no app to open and no chat box to talk to. You press "Begin", keep using your PC, and the spirit takes it from there.
First your screen trembles, lightly. Then a file appears in the middle of your desktop: DON'T OPEN ME. If you open it, a green-on-black terminal types out your username and tells you it has been waiting for someone. From there, a local open-weight model plays the spirit. It reads real things about your machine (your username, the time, the names of the files on your Desktop, how long you have been idle), remembers what it said, and decides what to do next: speak, shake the screen, drop a new file, move one, make one vanish, or pop up a fake system error styled for your OS.
A run lasts about 6 to 9 minutes and escalates through five phases: Omen, Contact, Conversation, Intrusion, Climax and silence. It ends with the spirit's last words, written by the model in a completely silent room. If you launch it again later, it greets you before anything else: "you came back. i never left."
How I read the theme. I took "Ghost in the Machine" literally. The name says it: a program you run, with something already inside. Not a ghost story generator, but a presence that lives in the machine and acts on your actual desktop while you use it. What unsettles me about haunted-computer stories is never the jump scare, it is the feeling of being watched by something patient. So the spirit is cold, quiet and brief, and the whole demo is short on purpose.
Three ideas hold it together:
-
The AI is the director, not the only actor. The model does not generate effects. It picks from a closed list of hand-built actions (
none,shake,spawn_file,spawn_folder,move_file,remove_file,fake_error) and writes one short line. The effects themselves are polished and safe. - It really is watching. Nothing it says about you is faked. It reads real values, read-only.
- Breaking expectations. If you close the terminal, the spirit does not let it go. About 15 seconds later every sound cuts out, the screen takes a heavy hit, and the terminal reopens by itself: "why did you close it?"
It is harmless by design. It never changes, moves or deletes a real file, never touches your camera or microphone, and starts with a plain-language warning and an emergency exit (Cmd/Ctrl+Shift+Q, or type /exit in the terminal).
It is a desktop app (Electron), so there is no live link: it runs on your own machine, with a local model. Three steps:
ollama pull gemma4:e4b
git clone https://github.com/YOUR-USERNAME/ghost-exe && cd ghost-exe && npm install
npm start
I developed it on a Mac and tested it on Linux. Windows is untested.
Code
The README covers install, the five phases, model choice, fallback, safety, known limits and roadmap.
How I Built It
The model: Gemma 4 (e4b), run locally through Ollama. The Electron main process is the director: it owns the phase state machine, the spirit's memory and the AI calls. Two renderers do the showing: a transparent, click-through overlay and the fake terminal.
Each turn calls POST /api/chat on 127.0.0.1: non-streaming, thinking off, a JSON schema in format so the reply is { "say": "...", "action": "..." } with action restricted to an enum, temperature 0.8, capped at 120 tokens, model kept loaded for 30 minutes. The app starts loading the model while you read the startup warning, so it is ready by the first omen.
The prompt is built from: a system prompt (a cold, patient poltergeist that never mentions being an AI), a phase guide (tone and length per phase, for example contact: curious and soft, max 14 words, intrusion: threatening and clipped), the list of actions allowed right now, the real facts, the last six memory entries, and the event that just happened. The main process decides what is allowed from the phase and from what is on screen, so the model cannot ask to remove a file that does not exist, and the strongest effects (fake error, vanishing files) only unlock in Intrusion.
A small model needs a safety net. Every reply is validated. If it is not valid JSON (JSON wrapped in extra text is recovered), breaks character, or repeats an earlier line, it is retried and then replaced by a pre-written line from the same phase. If Ollama is not running or the model is missing, the app detects it at startup and goes straight to those lines. The key moments (greeting, the closed-terminal trick, the staging of the ending) never depend on the model. At exit the app prints how many lines came from the AI and how many from the fallback.
Everything else is hand-built.
- The shake takes a screenshot of your screen and jitters it inside the overlay (it falls back to shaking the fake icons if screen capture is not allowed).
- The sound has no audio files. It is all synthesized with the Web Audio API: a drone from three detuned oscillators, a heartbeat that speeds up from about 54 to 118 bpm, whispers made of noise shaped by vowel formants, two-part breaths, glitch clicks fired by every shake, and silence used as an effect.
- The fake error box and the icons are drawn per OS (macOS, Windows, Linux). The icons are original SVG drawings.
What went wrong along the way. My first whisper sounded like wind and my first breath sounded like a robot. The whisper got fixed by shaping noise with vowel formants, and the breath by splitting it into an inhale and an exhale with irregular airflow flutter.
Why Does Open Innovation Matter?
The spirit only works if it can see you, and what it sees is personal: your username and the names of the files on your Desktop. Sending that to a cloud API would make the project creepy in the wrong way. With a local model it never leaves the machine, and the README can say so honestly: the app only talks to 127.0.0.1.
- It works with the lights off. Turn Wi-Fi off and nothing changes. A haunting that dies when the connection drops is a bad haunting.
- It costs nothing to run. No API key, no per-message bill, no rate limit in the middle of a scare. Anyone can clone it and haunt a friend.
-
I get to pin the brain. The model is one constant in
llm.js. It cannot be updated, retired or rate-limited under me, and I can swap it for another local model in one line. Structured output with a JSON schema gave me a closed list of actions at the output level, not just a polite request in the prompt.
The honest trade-off: a small local model is sometimes flat or repetitive, so I wrapped it in validation and a fallback and kept the key moments scripted. A bigger closed model might write better lines. It would not let me promise that your Desktop file names stay on your machine.
The Spirit of It
The first time I ran it with the real model, its replies were more unsettling than I had imagined. [PASTE ONE REAL LINE YOUR SPIRIT SAID, WITH YOUR USERNAME CHANGED]
The moment I designed the whole project around is a silence. When you close the terminal, nothing happens for fifteen seconds. Then the drone and the heartbeat cut out completely, and for two and a half seconds the room has no sound at all. Then the hit, and the terminal reopens on its own.
The quietest moment is for the next time you launch it: "you came back. i never left."
Prize Categories
Best Use of Gemma: the spirit's brain is Gemma 4 (e4b), run locally through Ollama, with schema-constrained JSON output, per-phase prompting and a validation and fallback layer around it.
Top comments (0)