DEV Community

ninefyi
ninefyi

Posted on

Revenant: a haunted terminal where my code remembers and Gemma only does the voice

Hacktoberfest Open-Source AI Challenge: Ghost in the Machine Submission πŸ‘»πŸŒΌ

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

The model kept getting my past wrong

I gave a local Gemma one line of my shell history and asked it to haunt me. In my first ten test cases, the 4B model said an ssh that had exited with code 255 "succeeded". The 12B model said an npm install from May happened "on a date far in the future".

A ghost that remembers your past wrongly is not eerie. It is just wrong. So the design changed who does the remembering: code remembers, and Gemma only does the voice.

What I Built

revenant is a haunted terminal. After a command finishes, the Ghost may whisper one short line about my own shell history, in dim text above the next prompt:

πŸ‘» A subtle warmth stains the edges of perception. The last time you ran something like this was 2026-09-17 11:43: `git push -f`.
Enter fullscreen mode Exit fullscreen mode

Every whisper has two halves:

  • The fact line, written by plain code from a real entry in my history: the command, the date, the exit code.
  • The flourish, the atmospheric sentence, written by a local Gemma. It must state no fact at all.

I read the theme both ways. It is spooky, because a voice speaks after the commands you run. It honors the past, because the Ghost is made of my own history: 743 timestamped commands, from November 2025 to October 2026.

Most of the time the Ghost says nothing. It speaks only when something is worth saying: a command comes back after 60 days or more, a risky command (force-push, rm -rf, reset --hard, and so on) repeats a risky one from before, the same command runs three times in ten minutes, a command that failed before (with a known exit code) runs again, or it is the small hours and the command is one I have run before.

Demo

revenant demo: a terminal with a fabricated history, where the Ghost whispers after three commands

This is recorded in a throwaway shell with a fabricated history and stand-in docker, git, make and npm scripts, so none of the commands are real and none of my real history is on screen. The whispers are from a real Gemma 3 12B. I recorded five takes after fixing the setup. In one of them the validator rejected the model's line for the risky command three times and the Ghost stayed silent, so that take lacked a whisper. I kept the last of the four that had all three kinds.

The recording script is in the repo (demo/record.sh). My first take used the wrong shell config and ran my real docker, git push --force and make once. Nothing was harmed (the folder had no compose file, no git remote and no Makefile), but I added stand-in scripts first on the PATH and a check that stops the recording if they are not found.

Code

ninefyi/hf26challenge-week4 β€” MIT, Python 3.13+, standard library only for the engine, uv for the project.

Try it in a throwaway shell that touches nothing:

git clone https://github.com/ninefyi/hf26challenge-week4.git
cd hf26challenge-week4
uv sync
zsh -f
source shell/revenant.zsh
Enter fullscreen mode Exit fullscreen mode

How I Built It

The hook. A small zsh file registers preexec and precmd. It records the command, its exit code and its duration, never the output. In my first sandboxed version that cost about 0.05 ms per command, because it only appended a line to a private file (I have not re-measured since the hook also began starting the engine). It then starts python -m revenant whisper in the background and prints the one line it returns above the prompt using zle -F. If a command is still running when the whisper is ready, the whisper waits and appears with the next prompt.

Memory by code. The engine reads ~/.zsh_history and the hook's own log. It finds the Memory (the past command a whisper may refer to) by plain code: the same command, the latest earlier run, or the last risky command of the same kind.

A thing I got wrong. My plan said "the same command failed before". But zsh's history stores no exit codes, only a start time and a duration. So "failed before" works only for commands run after the hook was installed, because the hook records the exit codes itself. The other rules work on old history.

The fact line is recomputed, not trusted. After Gemma speaks, a validator rebuilds the fact line from the Memory and compares it. It also checks that the Memory is in the history, that it is not from the future, and that it is the same command or the same kind of risky command. Any mismatch and the Ghost says nothing.

The flourish is a vocabulary problem. Gemma gets a rotating image (fog, a candle, a stopped clock, a cracked mirror…), the last five lines to avoid repeating, and a rule: present tense only, no numbers, no commands, no past, no memory. The validator rejects the line if it contains a digit, a backtick, any part of the command, a word about time, outcome or history (failed, ago, last, once…), or any word ending in -ed.

Secrets. Before anything reaches the model, URLs, emails, IPs, env secrets, long tokens and long numbers become placeholders. On my own history that changed 113 of 743 commands (90 had URLs). A crude scan found no long leftover strings. That catches the obvious, and I do not claim more.

Models. Gemma 3 12B first, 4B if 12B fails, and silence if both fail. On fabricated cases the warm 12B took about 3 s and the 4B about 0.8 s; the first call after a quiet spell took 6 to 9 s while the model loaded. The whisper arrives after the prompt is back, so none of this blocks me.

Did it work? The numbers, and where they stop

The bar I set before testing: 0 invented memories and 0 false claims.

I ran 30 real commands from my history (treated as if re-run now) and 15 fabricated cases (risky, failed-before, repeat), because old history has few risky commands and no exit codes. An independent checker re-verified every fact line against the raw history text with its own regexes, not the validator's code.

Run What changed Whispers shown (of 34 eligible) Silent Bad fact lines
1 validator only 16 18 0
2 prompt told not to refer to the past 34 0 0
3 present-tense rule added 34 0 0

Another 11 of the 30 real commands correctly got no whisper at all.

Run 1 was so silent because Gemma, playing a ghost of my history, loved words like "once", "remembers" and "forgotten", which my validator bans. I fixed the prompt, not the validator. But I tuned the prompt on the same 45 cases, so "34 of 34" is not a held-out result.

Then I tried to trick the validator, with hand-written bad whispers and no model:

Attack Caught
Tampered fact line (memory not in history, wrong command, from the future, wrong exit code, wrong year, extra claim glued on) 7 of 7
Flourish stating a fact in words I had banned ("It failed again", "three days ago") 14 of 14
Past-tense lie ("The disk filled up.") 6 of 6
Present-tense lie ("The disk is full.", "Nothing breaks."), written after the -ed rule 0 of 6

That last row is the honest limit. The fact line is safe by construction. The flourish is only safe against what I thought to ban. A blocklist and a tense rule cannot stop a model from writing "The disk is full." My claim of zero false claims holds for the fact line, and for the flourish on everything I tried and read by eye. I know a way around it.

Two more things I would not claim away: the flourishes repeat ("Dust" opens about half of the lines in my eval, which runs without the Ghost's "avoid recent lines" memory), and some are vague rather than eerie.

Real use, so far. I switched the Ghost on in my real shell on Oct 27 and have not had a full workday with it, so these numbers are small. For the test, Claude typed two harmless commands into my terminal. revenant stats afterwards:

commands seen: 5
whispers shown: 1 (20.0%)
outcomes: not worth 4, ok 1
shown by kind: returns 1
model time when asked: median 10.5s, max 10.5s
Enter fullscreen mode Exit fullscreen mode

The one whisper, for ls -latr, which I had last run on 2026-05-29:

πŸ‘» Dust settles; shadows stretch, drawn to the pane's quiet light. It has been 150 days since you ran `ls -latr` (2026-05-29).
Enter fullscreen mode Exit fullscreen mode

I checked the command and the date against my history. The 150 counts whole elapsed days, rounded down; by calendar days it would be 151. The 10.5 s is the cold start of the 12B model, and the whisper still arrived after the prompt was back. ls -lah, which I had run recently, got no whisper, as designed. Five commands cannot tell me how annoying the Ghost is over a day, so I will not read a rate out of them.

Why Open Innovation Matters

A shell history is one of the most private files on a machine: tokens, hostnames, client names. 15% of mine (113 of 743 commands) contained something my redaction had to hide. With a local Gemma, nothing leaves the laptop, and I did not have to decide whether a hosted service should read my history.

Open also let me do things a hosted model would have made awkward:

  • Swap models by measurement. I timed 12B against 4B on the same machine and made 12B the default with 4B as the fallback.
  • Fail quietly. If Ollama is down, the Ghost is silent and my terminal is unchanged. It never needs the internet.
  • Test what it does. I could run the model hundreds of times in a loop against a checker I wrote, with no usage cost and no rate limits.

I did not run the same test against a closed model, so I cannot say it would have done worse at the voice. The argument is about keeping the history on my machine, not quality.

My Agent Session

I chose the direction (a haunted terminal) and approved each design decision, including the zero-false-claims bar. Claude Code proposed most options and wrote the code, tests, evaluation, and the first draft of this post, so the post is marked AI-assisted. After the first spike, Gemma kept stating facts incorrectly, and Claude proposed splitting the work between code and model, which I accepted.
Two honest slips from the session: twice my test harness wrote its own test lines into my real ~/.zsh_history (my config overrides HISTFILE), and the first demo take ran my real docker, git and make. The Ghost itself only reads the history file and never edits it; the mistakes were in my tests, and I added checks to stop them.

The Spirit of It

The ghost in this machine is not a character I invented. It is the record of what I did. Every whisper points at a line that was really in my history, with the real date, and when the Ghost cannot prove it, it says nothing. That felt like the right way to honor the past: not to retell it with a model's imagination, but to let the plain record speak, and let Gemma only set the mood.

Prize Categories

Best Use of Gemma. Gemma 3 12B is the voice, with 4B as the fallback, through Ollama. It is used with JSON output, a rotating image in the prompt, and a validator that decides whether its line may be shown. The test harness (independent oracle, adversarial attacks, the run that found the model loved the word "once") is part of the use, because the interesting problem was not getting Gemma to be spooky, but getting it to be spooky without being wrong.

Top comments (0)