séance: I let a Gemma ghost haunt my shell (and built it an ofrenda for my deleted files) A developer built séance, an open-source terminal hook that runs Google's Gemma 3 4B locally via Ollama to generate one- or two-sentence in-character commentary after each shell command. The tool combines deterministic facts (exit codes, repeated failures, deleted files and branches) with a short session memory and a ghost persona, then streams a whisper back into bash or zsh with no network calls or API keys. It ships as a zero-dependency, MIT-licensed Python package installable via pipx, and the developer benchmarked both small Gemma 3 models through Ollama with the project's real prompt across 14 runs each. This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 4 https://dev.to/challenges/hacktoberfest-week4-2026-10-26 👻 ezequiel › make test again, you foolish mortal? Exit code two; a familiar echo of my own demise. That line was written by a 4-billion-parameter model running on the CPU of a small Linux VM, about three and a half seconds after my test suite failed for the second time in a row. No network request left the machine to produce it. That's the whole project, really: a ghost that lives inside the machine, in the most literal way I could manage. séance is a haunted terminal. You add one line to your .bashrc or .zshrc , and from then on your shell is shared with Ezequiel , a sysadmin who died in 1999 halfway through an fsck and never left the server room. After each command he whispers, in one or two sentences, what he thinks of what you just did. Yes, the "haunted terminal" is the first idea on the challenge's own list. I couldn't resist it, so I tried to make it mean something: make test , and he'll bring up the exit code 2 from three commands ago. sudo . Gemma gets those facts; it only has to be eerie. git push --force gets a ghost. seance ofrenda rm 'd, processes you kill ed or that got SIGKILLed, git branches you deleted, and the It's a pipx-installable Python package with zero dependencies, it works fully offline, it has no API keys, and it's MIT-licensed. The GIF above is a real recording: real bash, the real hook, and gemma3:4b answering live. The keystrokes are typed by a small script scripts/demo.py , so anyone can reproduce the recording. The only editing is that pauses longer than 1.5 s are shortened. The latency under each whisper is what séance measured at the time. Possession: the second failing make test makes the terminal glitch while the ghost stirs. Omens and apparitions: rm -rf raises a tombstone, and a SIGKILLed training script gets a calavera. The ofrenda: remembrances for a deleted hotfix branch, a SIGKILLed process, two deleted files, and an abandoned experiment/rewrite-in-rust branch, 117 days old. A few lines from that recorded session, verbatim: 👻 ezequiel › gti status. The command is a phantom, like my existence – it simply doesn't exist. 🕯️ experiment/rewrite-in-rust alma en pena · abandoned branch · 117 days ago Poor experiment/rewrite-in-rust. It wrestled with grand ambitions, a tangled promise of rust, then simply vanished into the depths of the repository. 🕯️ ~/crypt/build archivo · file The build folder, a tireless worker, finally rests. May marigold petals guide its code back to Mictlan, a gentle farewell to its frantic efforts. The typo one got me. I typed gti on purpose for the demo, and I still laughed. Code: https://github.com/Tx13758627780/seance-sh https://github.com/Tx13758627780/seance-sh ollama pull gemma3:4b pipx install git+https://github.com/Tx13758627780/seance-sh echo 'eval "$ seance init bash "' ~/.bashrc bash/zsh hook ── command, exit code, start time, cwd ──▶ seance whisper omens.py deterministic facts: exit meanings, streaks, repeats, deaths, which apparition memory.py last N commands this session JSONL, secrets redacted + a graveyard of deaths ghost.py persona + memory + omens → POST /api/chat streaming → Ollama → Gemma 3 effects.py "the ghost stirs" glitch while waiting → whisper streams in, word-wrapped The bash hook uses a DEBUG trap to catch the command and its start time, plus PROMPT COMMAND to call seance whisper with the exit code once the command finishes. zsh uses preexec / precmd . The hook keeps your $? intact, skips tab completion, and doesn't re-whisper the previous command when you just press Enter. That last bug took me longer than any of the AI parts. I benchmarked both small Gemma 3 models through Ollama with séance's real prompt persona, memory, omens , 14 runs each, on the only hardware I had for this: a Linux VM with 8 vCPUs Intel Xeon , 16 GB of RAM and no GPU. Ollama confirms it's running on CPU. | model | cold start load + first reply | first word, warm median | full whisper, warm median | RAM ollama ps | |---|---|---|---|---| | gemma3:1b | 3.08 s | 0.90 s 0.70–1.13 | 1.78 s 1.41–2.34 | 1.2 GB | | gemma3:4b | 6.79 s | 1.90 s 1.38–2.75 | 3.58 s 2.54–5.90 | 2.9 GB | The 1B model is twice as fast, but in my testing its whispers were generic. They were spooky in the abstract, but they often ignored the actual command. The 4B model reliably names the file, the branch or the exit code, and that's what makes it feel like someone is watching . So gemma3:4b is the default , and SEANCE MODEL=gemma3:1b is one environment variable away if your machine is slower or you want a ghost that talks faster. In the recorded demo session, the 7 whispers had a median of 1.80 s to the first word and 3.15 s for the full whisper . The benchmark script is in the repo scripts/bench.py , so you can measure your own machine instead of trusting mine. Three seconds of nothing after every command would feel broken. So séance streams the response and, until the first token arrives, plays a glitching ░▒▓ the ghost stirs ▓▒░ line that dissolves into the whisper. The whisper is then typed out as it streams, word-wrapped with a hanging indent. The roughly 1.9 s wait for the first word stops feeling like lag and starts feeling like the ghost gathering itself. For when you actually need to work, SEANCE RATE=0.3 keeps him quiet on calm, successful commands failures and destructive commands always get a whisper , and SEANCE HUSH=1 silences him entirely. Some things I learned, in roughly the order they bit me: clean step strips that and caps the whisper at two sentences. An earlier version of that step also stripped the underscores out of notes from last halloween.txt , which was its own small horror. It isn't perfect. Ezequiel brings up his own death a lot , and sometimes he invents history "the last time you checked for disks" . For a ghost, I've decided that's in character. Shell history is about as private as data gets: hostnames, paths, typo'd passwords. Before anything is stored, séance redacts TOKEN / SECRET / PASSWORD variables, --password flags, Authorization: headers, user:pass@ URLs, and GitHub, OpenAI, Hugging Face and AWS-style keys. There are tests for this. And then it only ever sends that redacted text to 127.0.0.1 . Because the ghost reads your shell history. I would never pipe every command I type, with its paths, hostnames and the odd password pasted in the wrong window, to someone else's API to get a joke back. With an open-weight model it never has to leave the box. séance talks only to http://127.0.0.1:11434 . You can unplug the network cable and the haunting carries on. I measured every number in this post on a machine with no GPU. Because it costs nothing to be haunted. An always-on shell companion makes a model call after every command. With a hosted API, that's a meter running all day. Locally it's 2.9 GB of RAM and a few seconds of CPU per whisper about 3.6 s median for gemma3:4b, 1.8 s for gemma3:1b on my 8-vCPU VM . There's no key to leak and no bill. Because the model is swappable. The 1B-vs-4B trade-off above was a decision I got to make, with my own benchmarks, on my own hardware. Next year it'll be a different model, and switching is one environment variable: SEANCE MODEL=