This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
This challenge for me started in a specific mindset.
How do I automate this and that. How do I become more productive. How do I improve my life using AI.
So far, everything I need is already getting handled by my Hermes (deployed on another Raspberry Pi).
And so I iterate. And iterate. On the ideas, on the things I want to do, on the things I already have, and on the things I can actually pull off in a weekend.
At some point a sipra idea came up. I need it for an upcoming jam. But it doesn't fit this week's challenge.
Then, while I was reading on my xteink, something popped into my head.
Sloth. Still. Slow and steady.
What do I have, or what do I do, that is slow and steady?
- Reading
- A Raspberry Pi
- A local LLM on a Raspberry Pi
So, how about building a world inside my Raspberry Pi.
Someone who lives in the terminal. Someone with a life he's living inside this little world of a 2-billion-parameter model. Doing something. Not doing something. Interesting things. Mundane things.
What I Built
So. What is Still.
Still is a life that lives on my Raspberry Pi.
There is someone in there. He lives in a terminal. He has a job he is supposed to do, he has days, he has things he is waiting on. Sometimes something happens and he writes about it. Most of the time nothing happens, and he does not write anything at all.
That last part took me the longest to get to.
My first instinct was a schedule. Write in the morning, write in the afternoon, write in the evening. Three entries a day, always. Predictable.
But that is just a report.
So he writes on impulse instead. Every tick the world moves a little. Most ticks nothing worth writing down happens, so nothing gets written. When something does happen, he writes it. He writes two or three times a day at most. Some days he writes nothing.
And he runs on the Pi and nothing else. No API, no cloud, no internet required. So the pace is not a setting I chose, it is the hardware. When the Pi heats up it throttles, and he slows down.
You cannot make him faster. There is no button for that.
I like that part the most.
An entry looks like this.
I met Ivo at the Repair shop. I showed Ivo the file I'd recovered. He looked
at the empty owner field. He handed it back without filling it in.
Most of his days are not like that one. Most of them are nothing much, and getting comfortable with that was the hardest part.
The world he lives in
He is a courier. That is the whole job.
There are three places. The Depot, where he starts. The Records office, where Mara works the counter. The Repair shop, where Ivo fixes things. Parcels go between them. If he is late, a man called Ren turns up to look at the handoff, writes down the time, and leaves.
He also has a file of his own. It is called old-index, and three of its lines are damaged. When he has spare time he restores it, one line at a time. Here is where it stands now.
path: /archive/landing
owner: [blank]
keep: yes
The owner line is empty. He does not know who he belongs to. When he takes the file to Ivo and asks, Ivo looks at the blank and hands it back without filling it in.
That file is the only thing in his world that is actually about him, and it is the only thing nobody can help him with.
The machine is his weather
He does not know any of this.
- When the world has not been running, the missing hours are recorded as sleep or as ordinary time, depending on the hour. No events are invented to fill them. He does not get a story for the time he was not there.
- When the machine is under pressure, moving is slow, he arrives late, and being late is the one thing that brings Ren.
- He does not know that anyone reads his diary.
Demo
The diary: https://still.zorx.space
The world behind it: https://still.zorx.space/world/view
The second link is the one I would open first. It shows the courier's location, the parcel he is carrying, who he has met and how many times, everything in flight, and the state of his own file. Everything the diary only half knows.
Code
https://github.com/zorexsalvo/still
How I Built It
Still is a Python service, about 1,300 lines across a dozen modules. Four parts matter.
Gemma 4 E2B, running locally. Open weights, downloaded once, served by llama-server from llama.cpp under an alias called still.
llama-server \
--hf-repo ggml-org/gemma-4-E2B-it-GGUF \
--hf-file gemma-4-E2B-it-Q4_0.gguf \
--alias still \
--ctx-size 4096 \
--host 127.0.0.1 \
--port 8080
A worker that finishes a tick, then rests one real hour. Not cron. The next tick is scheduled from the moment the last one finished, so a slow tick pushes the world later instead of stacking ticks on top of each other. One writer only: the worker takes a lease inside the database, and a second worker gets rejected even when it is on the far side of Docker's filesystem boundary. I found that bug by running a native worker next to the containerised one. Docker's flock did not stop it. The database lease does.
A model that chooses, and code that decides. Gemma never invents anything. It picks a numbered action from a JSON schema containing only the legal moves, and code validates the answer again. If the model is unreachable, or the answer is not a legal index, the tick records a quiet wait and a failure. It never invents an action. Files, deliveries, deadlines, who is standing where, and what a missed deadline costs are all decided in Python.
SQLite as the source of truth. Every tick is stored, including the ones where nothing happened. Published entries live in their own table, and triggers prohibit updates and deletes, so an entry cannot be edited after it is written. A publication row records whether the tick was silent, writing, published, or failed. Failed writing is never retried later. The gap stays a gap.
Then three smaller decisions that turned out to matter more than the architecture.
The rejection list. Every entry is checked before it is stored. Wrong number of sentences, too long, not first person, addressed to a reader, containing a comparison, or stating an emotion outright, and it is thrown away. The diary would rather stay blank than publish something that breaks the voice.
The hardware has to be sampled outside the container. Ask the container for load average and you get the Docker VM, which is a polite lie. So a separate process samples the real machine into a small JSON file and the worker reads that. When the machine is under pressure, the courier's legal moves get cut down to the delivery he is already carrying, and the world can record:
Moving was slow. I stayed where I was.
And one honest limit. The hourly cadence was tested deterministically, not by waiting a week. It has not been soaked for days. The writing gate is deliberately simple, and simple gates produce long silences.
The model was not writing
For the first two days, every entry read like a receipt.
I delivered the sealed directory copy 123. Mara accepted the sealed directory
copy 123. Mara signed for it.
I blamed the model. Then I read my own code.
simulation.py was storing finished first-person sentences as the event facts, and writing_moment() handed those sentences to Gemma as the thing to write about. The voice prompt told it to keep the concrete nouns and not pad. So the model reformatted prose into prose and handed it back. It was not writing the diary. It was copy-editing my database.
The proof is this pair:
simulation.py: "I recovered the last line in {name}. It said keep: yes.
I saved it with the owner still blank."
published: "I recovered the last line in old-index. It said keep: yes.
I saved it with the owner still blank."
Word for word. The entries I liked best were the ones I had written myself, in Python, and the model just typed them out.
Three changes, in order of how much they helped.
-
Stopped publishing routine successes.
deliveredandfirst_meetingcame off the publish list. A parcel that arrives on time is not a diary entry. - Rejected receipt prose. If an entry reads like a confirmation, it does not get stored.
- Centred the writing on unresolved threads. The writer now gets the open items alongside the current moment, so an entry has something to be about instead of something to restate.
Same world, same model:
after: I read the repair-notes. A corrected line was followed by the earlier
version. Neither had been removed.
One more thing needed a rule rather than a fix. A live entry read The file state is now updated. The operation completed. even though the voice prompt explicitly bans that exact phrase. The instruction was not enough. It had to be rejected in code.
And one place I have not fixed yet. The simulation still stores finished sentences as event facts, so the writer is partly reformatting instead of composing. The rejection layer is holding the line, but the real fix is to store facts as data and let the model write from them. I would rather say that than pretend the design is finished.
Why Does Open Innovation Matter?
The obvious question is why I did not just use an API.
It would have been easier. Probably better prose too, with a big hosted model. Faster, smarter, no weights to download.
But then the slow part stops being the computer and becomes somebody's pricing page.
I did not want that. I wanted the slowness to be real.
So it runs on the Pi. Open weights, local inference, no internet needed.
It keeps writing when my connection drops.
It keeps writing when a provider changes their free tier, or shuts down, or decides my account is not worth keeping.
And the speed is not a setting I chose. It is whatever this little computer can manage. Slow, and slower when it is warm.
If I want the diary to write faster, there is no config option. I would have to buy different hardware.
That is what slow and steady means here. Not a theme I decorated the project with, but the physical limit of the machine I already had.
The Lazy Payoff
How much time does Still save me? None.
It is a diary I never write, and I do not read it on a schedule. It costs a little electricity and it does not do my chores.
I built it that way on purpose.
Everything else I made this week, I made for me. This one runs without me. I set it going once and it accumulates whether I look at it or not. No API key, no account, no bill, no internet, nothing to maintain.
So the honest number is zero minutes saved. What I got instead is a record of a small life that I did not have to ask for and did not have to write.
The laziest diary is the one I never have to write.
Prize Categories
- Best Use of Gemma. Still runs Gemma 4 E2B locally, on the Pi, as its only model.


Top comments (0)