cd /news/artificial-intelligence/seance-i-let-a-gemma-ghost-haunt-my-… · home › topics › artificial-intelligence › article
[ARTICLE · art-159200] src=dev.to ↗ pub= topic=artificial-intelligence verified=true sentiment=↑ positive

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.

by read7 min views3 publishedOct 28, 2026

This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 4

👻 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 s 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

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=<anything Ollama can run>. No vendor decides that a fun but

pointless app is no longer worth serving.

Because things you depend on should outlive the company that made them. That one fits the theme a

little too well. An ofrenda is about remembering what's gone. I didn't want to build one that would join

it the day an API was deprecated. Gemma's weights are on my disk. As long as Ollama, or llama.cpp, or

whatever comes after them can load a GGUF, Ezequiel stays.

Where it was worse than a closed frontier model: latency and polish. A big hosted model would

whisper faster and repeat itself less. But a faster ghost that phones home with your shell history

isn't the ghost I wanted.

Best Use of Gemma: Gemma 3 (gemma3:4b by default, gemma3:1b optional) run locally through Ollama

is the ghost. It writes every whisper and every ofrenda remembrance, grounded by deterministic omens

and benchmarked on CPU-only hardware.

Thanks for reading. If you install it, run seance ofrenda on Nov 1 or 2. The altar notices the date. 🕯️🌼

— xt

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @gemma 3 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/seance-i-let-a-gemma…] indexed:0 read:7min 2026-10-28 · —