{"slug": "seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted", "title": "séance: I let a Gemma ghost haunt my shell (and built it an ofrenda for my deleted files)", "summary": "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.", "body_md": "*This is a submission for the [Hacktoberfest Open-Source AI Challenge: Week 4](https://dev.to/challenges/hacktoberfest-week4-2026-10-26)*\n\n👻 **ezequiel ›** make test again, you foolish mortal? Exit code two; a familiar echo of my own demise.\n\nThat line was written by a 4-billion-parameter model running on the CPU of a small Linux VM, about three\n\nand a half seconds after my test suite failed for the second time in a row. No network request left\n\nthe machine to produce it. That's the whole project, really: a ghost that lives *inside* the machine,\n\nin the most literal way I could manage.\n\n**séance** is a haunted terminal. You add one line to your `.bashrc` (or `.zshrc`), and from then on\n\nyour shell is shared with **Ezequiel**, a sysadmin who died in 1999 halfway through an `fsck` and\n\nnever left the server room. After each command he whispers, in one or two sentences, what he thinks\n\nof what you just did.\n\nYes, the \"haunted terminal\" is the first idea on the challenge's own list. I couldn't resist it, so I\n\ntried to make it *mean* something:\n\n`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\ndeleted, and the It's a pipx-installable Python package with zero dependencies, it works fully offline, it has no API\n\nkeys, and it's MIT-licensed.\n\nThe GIF above is a real recording: real bash, the real hook, and gemma3:4b answering live. The keystrokes\n\nare typed by a small script (`scripts/demo.py`), so anyone can reproduce the recording. The only\n\nediting is that pauses longer than 1.5 s are shortened. The latency under each whisper is what séance\n\nmeasured at the time.\n\n**Possession:** the second failing `make test` makes the terminal glitch while the ghost stirs.\n\n**Omens and apparitions:** `rm -rf` raises a tombstone, and a SIGKILLed training script gets a calavera.\n\n**The ofrenda:** remembrances for a deleted hotfix branch, a SIGKILLed process, two deleted files,\n\nand an abandoned `experiment/rewrite-in-rust` branch, 117 days old.\n\nA few lines from that recorded session, verbatim:\n\n👻 **ezequiel ›** gti status. The command is a phantom, like my existence – it simply doesn't exist.\n\n🕯️ **experiment/rewrite-in-rust** *[alma en pena · abandoned branch · 117 days ago]*\n\nPoor experiment/rewrite-in-rust. It wrestled with grand ambitions, a tangled promise of rust, then simply vanished into the depths of the repository.\n\n🕯️ **~/crypt/build** *[archivo · file]*\n\nThe build folder, a tireless worker, finally rests. May marigold petals guide its code back to Mictlan, a gentle farewell to its frantic efforts.\n\nThe typo one got me. I typed `gti` on purpose for the demo, and I still laughed.\n\n**Code:** [https://github.com/Tx13758627780/seance-sh](https://github.com/Tx13758627780/seance-sh)\n\n```\nollama pull gemma3:4b\npipx install git+https://github.com/Tx13758627780/seance-sh\necho 'eval \"$(seance init bash)\"' >> ~/.bashrc\nbash/zsh hook ──(command, exit code, start time, cwd)──▶ seance whisper\n   omens.py   deterministic facts: exit meanings, streaks, repeats, deaths, which apparition\n   memory.py  last N commands this session (JSONL, secrets redacted) + a graveyard of deaths\n   ghost.py   persona + memory + omens → POST /api/chat (streaming) → Ollama → Gemma 3\n   effects.py \"the ghost stirs\" glitch while waiting → whisper streams in, word-wrapped\n```\n\nThe bash hook uses a `DEBUG` trap to catch the command and its start time, plus `PROMPT_COMMAND` to\n\ncall `seance whisper` with the exit code once the command finishes. zsh uses `preexec`/` precmd`. The hook\n\nkeeps your `$?` intact, skips tab completion, and doesn't re-whisper the previous command when you just\n\npress Enter. That last bug took me longer than any of the AI parts.\n\nI benchmarked both small Gemma 3 models through Ollama with séance's *real* prompt (persona, memory,\n\nomens), 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.\n\n| model | cold start (load + first reply) | first word, warm (median) | full whisper, warm (median) | RAM ( `ollama ps` ) | \n|---|---|---|---|---|\n| gemma3:1b | 3.08 s | 0.90 s (0.70–1.13) | 1.78 s (1.41–2.34) | 1.2 GB | \n| gemma3:4b | 6.79 s | 1.90 s (1.38–2.75) | 3.58 s (2.54–5.90) | 2.9 GB | \n\nThe 1B model is twice as fast, but in my testing its whispers were generic. They were spooky in the\n\nabstract, but they often ignored the actual command. The 4B model reliably names the\n\nfile, the branch or the exit code, and that's what makes it feel like someone is *watching*. So\n\n**gemma3:4b is the default**, and `SEANCE_MODEL=gemma3:1b` is one environment variable away if your\n\nmachine is slower or you want a ghost that talks faster. In the recorded demo session, the 7 whispers\n\nhad a median of **1.80 s to the first word** and **3.15 s for the full whisper**.\n\nThe benchmark script is in the repo (`scripts/bench.py`), so you can measure your own machine instead\n\nof trusting mine.\n\nThree seconds of nothing after every command would feel broken. So séance streams the response and, until the\n\nfirst token arrives, plays a glitching `░▒▓ the ghost stirs ▓▒░` line that dissolves into the whisper.\n\nThe whisper is then typed out as it streams, word-wrapped with a hanging indent. The roughly\n\n1.9 s wait for the first word stops feeling like lag and starts feeling like the ghost gathering itself.\n\nFor when you actually need to work, `SEANCE_RATE=0.3` keeps him quiet on calm, successful commands\n\n(failures and destructive commands always get a whisper), and `SEANCE_HUSH=1` silences him entirely.\n\nSome things I learned, in roughly the order they bit me:\n\n`clean()` step strips that and caps the whisper at two\nsentences. An earlier version of that step also stripped the underscores out of\n`notes_from_last_halloween.txt`, which was its own small horror.\nIt isn't perfect. Ezequiel brings up his own death *a lot*, and sometimes he invents history (\"the\n\nlast time you checked for disks\"). For a ghost, I've decided that's in character.\n\nShell history is about as private as data gets: hostnames, paths, typo'd passwords. Before anything is\n\nstored, séance redacts `*TOKEN*/*SECRET*/*PASSWORD*` variables, `--password` flags, `Authorization:`\n\nheaders, `user:pass@` URLs, and GitHub, OpenAI, Hugging Face and AWS-style keys. There are tests for\n\nthis. And then it only ever sends that redacted text to `127.0.0.1`.\n\n**Because the ghost reads your shell history.** I would never pipe every command I type, with its\n\npaths, hostnames and the odd password pasted in the wrong window, to someone else's API to get a joke\n\nback. With an open-weight model it never has to leave the box. séance talks only to\n\n`http://127.0.0.1:11434`. You can unplug the network cable and the haunting carries on. I measured\n\nevery number in this post on a machine with no GPU.\n\n**Because it costs nothing to be haunted.** An always-on shell companion makes a model call after\n\n*every* command. With a hosted API, that's a meter running all day. Locally it's 2.9 GB of RAM and a few\n\nseconds of CPU per whisper (about 3.6 s median for gemma3:4b, 1.8 s for gemma3:1b on my 8-vCPU VM).\n\nThere's no key to leak and no bill.\n\n**Because the model is swappable.** The 1B-vs-4B trade-off above was a decision *I* got to make,\n\nwith my own benchmarks, on my own hardware. Next year it'll be a different model, and switching is one\n\nenvironment variable: `SEANCE_MODEL=<anything Ollama can run>`. No vendor decides that a fun but\n\npointless app is no longer worth serving.\n\n**Because things you depend on should outlive the company that made them.** That one fits the theme a\n\nlittle too well. An ofrenda is about remembering what's gone. I didn't want to build one that would join\n\nit the day an API was deprecated. Gemma's weights are on my disk. As long as Ollama, or llama.cpp, or\n\nwhatever comes after them can load a GGUF, Ezequiel stays.\n\nWhere it was *worse* than a closed frontier model: latency and polish. A big hosted model would\n\nwhisper faster and repeat itself less. But a faster ghost that phones home with your shell history\n\nisn't the ghost I wanted.\n\n**Best Use of Gemma:** Gemma 3 (gemma3:4b by default, gemma3:1b optional) run locally through Ollama\n\nis the ghost. It writes every whisper and every ofrenda remembrance, grounded by deterministic omens\n\nand benchmarked on CPU-only hardware.\n\nThanks for reading. If you install it, run `seance ofrenda` on Nov 1 or 2. The altar notices the date. 🕯️🌼\n\n*— xt*", "url": "https://wpnews.pro/news/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted", "canonical_source": "https://dev.to/t364170654/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted-files-2dii", "published_at": "2026-10-28 03:06:11+00:00", "updated_at": "2026-10-28 03:11:13.154754+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-tools", "developer-tools", "generative-ai"], "entities": ["Gemma 3", "Ollama", "séance", "Google", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted", "markdown": "https://wpnews.pro/news/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted.md", "text": "https://wpnews.pro/news/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted.txt", "jsonld": "https://wpnews.pro/news/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted.jsonld"}}