This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Never Leaves drafts real work documents on your own laptop, from your own rough notes, using a local open-weight model, with the network switched off at the kernel.
It produces two things a tradesperson or a field worker actually has to send: a quote and a site report.
Here is the part that matters for this week's theme. The people who need this are already outside. They spent the day under a house, on a roof, in a paddock, in a ceiling cavity with a torch in their teeth. They are not on the screen and they do not want to be. What drags them back to a desk at nine at night is not the work. It is the paperwork about the work.
So the tool is built to make the screen the shortest part of the day. You dump a messy note onto the laptop on site, the way you would tell it to an apprentice. Then you get a draft quote back while the dust is still on your boots. No signal required. No account. No upload. Nothing to wait for.
And it is built for the sites where the alternative does not exist. Pasting a client's name, address and phone number into a company you cannot audit, over a connection that is not there, is not an option for a lot of real work. Those people get nothing from a cloud tool. They get something from this.
Demo
The network is off before your notes are read. The tool shows you
The first thing a drafting run prints is a proof of its own isolation. This is the real first screen from the run recorded below:
==============================================================
NEVER LEAVES - proof of isolation
==============================================================
isolated yes
mechanism linux network namespace via unshare -rn
interfaces lo
interfaces up none
routes 0
open IP sockets 0
checked at 2026-10-08T03:38:00+00:00
==============================================================
Only the loopback interface exists and it is not up. The route table is empty and no IP sockets are open, so outbound traffic was impossible rather than merely unused.
==============================================================
Only loopback exists and it is down. The route table is empty. No IP sockets are open. Outbound traffic was impossible rather than merely unused.
The socket count is not decoration. A namespace with no interfaces cannot stop a process that inherited an already open socket from its parent. Counting open IP sockets is how that is ruled out. It is part of the pass condition.
never-leaves check proves the same thing and exits immediately, so it can be used as a gate in a script.
A 4B model drafts the quote from a scribbled note
Gemma 3 4B, 2.5 GB as a single Q4_K_M file, loaded in-process through llama-cpp-python. The note was a scribbled site visit:
- replace 6 downlights in kitchen ceiling, existing 90mm cutouts
- customer wants warm white, not cool white
- also wants a price to add 3 double GPOs in the shed
- shed has no existing circuit at all, would need to run cable back to the board, approx 18m
- access under the house is tight, about 400mm, will slow the cable run down
It drafted a 143 word quote. The scope and the price table, verbatim:
## Scope of work
Replace six downlights in kitchen ceiling. Install two new warm white downlights.
Add three double GPOs in the shed. Run cable from switchboard to shed.
## Price
| Item | Rate | Total |
|---|---|---|
| Downlights (6) | [RATE] | [TOTAL] |
| Downlights (2 - Warm White) | [RATE] | [TOTAL] |
| GPOs (3) | [RATE] | [TOTAL] |
| Cable Run (18m) | [RATE] | [TOTAL] |
| Labour | [RATE] | [TOTAL] |
Every figure in that table traces back to the notes: 6, 2, 3 and 18m. No rate was guessed. The tool then listed everything a human still has to fill in. That was the entire list:
words 143
ledger /home/vault/.local/share/never-leaves/runs.jsonl
BEFORE YOU SEND IT
- FILL THESE IN. [AMOUNT], [DEPOSIT], [EXCLUSIONS], [NUMBER], [RATE], [TIMEFRAME], [TOTAL]
isolation re-checked after drafting: still isolated, still no route
Where it refused and what it got wrong
On a second run the note said the customer wanted a ballpark figure that day. The tool still produced [RATE] and [TOTAL] and no number at all. It does not price work, on any run, however the notes are worded. Missing figures become placeholders and every placeholder is listed at the end.
The check exists because a small local model can state a number it made up. Earlier in development, on a smaller default model, these same notes produced this:
Access under house is about 40 short 400mm.
There is no 40 anywhere in the notes. The check reported it before the draft could reach a client:
BEFORE YOU SEND IT
- FIGURE TO CHECK. These appear in the draft and not in your notes: 40
That catch is written to the local ledger with the digest of the notes and the digest of the draft, so the claim is checkable rather than something I am asking you to believe.
What I will not claim. Gemma 3 4B kept every figure straight on both runs recorded in this post, so nothing was flagged. The failure that did show up, twice, is the opposite one: the 400mm access note and the 90mm cutouts never reached the quote. The check catches what a draft adds. It cannot yet see what a draft leaves out. That is the next thing this needs. Until it exists a human reads the draft. That is why the output is stamped as one.
Demo recording
The recording is unedited output from a single real run. The pause while the model drafts is compressed. Nothing else is.
Code
github.com/narko4u/never-leaves MIT licensed.
never_leaves/
isolation.py kernel-enforced no-network guarantee and its proof
model.py any local GGUF, loaded in-process. No server, no port
templates.py document shapes and the rules the model must follow
claims.py checks the draft against your notes, money first
document.py renders the draft, the flags and the run record
ledger.py local one-line-per-run record
cli.py the command
tests/ 41 tests, two of which assert isolation from inside it
docs/VERIFY.md how to check every claim in this post yourself
docs/demo.sh the exact script used to record the demo above
.github/workflows/ci.yml the suite on Python 3.10 and 3.12, plus a secret scan
docs/VERIFY.md is the honest part of the repository. It does not ask you to believe the tool's own summary. It tells you to read /proc/net/dev, /proc/net/route and /proc/net/tcp from inside the namespace yourself, to prove that a connection attempt fails and to try to make it phone home and watch the suite refuse. It also lists what the guarantee does not cover.
Continuous integration
Every push runs this suite on Python 3.10 and 3.12. It also runs a secret scan across the entire commit history. The workflow token can only read the repository and both actions are pinned by commit SHA rather than a moving tag.
The two isolation tests are the reason this was worth wiring up instead of leaving to habit. They assert that egress is impossible from inside a network namespace. GitHub's hosted runners are Ubuntu 24.04, which refuses unprivileged user namespaces by default. The first run went red with unshare: write failed /proc/self/uid_map: Operation not permitted. The skip condition was asking whether the unshare binary exists, which is the wrong question, because the binary was there and the kernel was saying no. A test that skips silently is worse than one that fails, since the guarantee stops being checked without anyone noticing. It now probes the capability itself and carries the kernel's own message into the skip reason. The workflow also lifts the restriction before the run, so the guarantee is exercised on every push rather than asserted in a README.
How I Built It
Isolation. The tool re-executes itself through unshare -rn, which creates a user namespace and a network namespace together. Inside that namespace the loopback device exists and is down, the route table is empty and there is no path to anything. The check is deliberately suspicious of its own environment variable: a marker records "we believe we were launched isolated" and the kernel tables decide whether that is true. Only the second one counts. It reads the proc files directly, so it needs no extra binaries. It refuses to run without the guarantee. --no-isolate exists for debugging and says loudly that privacy was lost.
Inference. A GGUF file loaded in-process through llama-cpp-python. No server, no socket, no port to leave open. This mattered more than the model choice. A local model behind a local HTTP server would have been easier and would have handed back a listening port on a machine that may be on a cafe network. There is nothing to connect to because there is nothing listening.
The check. Every figure in the draft is looked up in the notes you actually wrote. Money is checked first, because that is the error that costs money. Anything untraceable is reported at the top of the output where you cannot miss it.
The refusal to guess. No invented prices, no subtotal computed from invented rates, no claiming to be a finished document. The draft banner cannot be removed by configuration.
Three defects the demo run found, all fixed. Building the demo is what surfaced them, which is the argument for building the demo.
- Model discovery only looked inside the home directory. On a normal Linux install ollama keeps its weights under
/usr/share/ollama, so the search found nothing there and fell through to a branch that took whatever model it found next. The tool would have drafted on an unrelated model while naming something you had no reason to check. It now searches every location ollama uses and announces any substitution out loud. - ollama's own Gemma 3 export does not load on this build of
llama-cpp-python, because it omitsgemma3.attention.layer_norm_rms_epsilon, a hyperparameter the loader requires. The same model converted by the llama.cpp toolchain carries the key and loads. The tool now looks for the GGUF first and, if it is handed an unloadable one, explains the cause in plain language instead of printing a traceback out of a C library. -
never-leaves modelsprinted the isolation banner while running outside any namespace, so a listing command reported "isolated NO" and looked broken. It no longer makes a privacy claim it is not making.
Why Does Open Innovation Matter?
A closed API would do this job better. That is the honest version of the argument. It would write more fluent prose. It would not need a 2.5 GB download.
It would also mean that to draft a quote for a house in Birkdale, a client's name, address and phone number has to leave the machine and go to a company whose terms I cannot audit, over a connection that does not exist on site. The moment the data has to go out, the product cannot exist in the shape it needs to exist. Not "should not". Cannot.
Open weights remove the choice between a good writer and a private one. Because the model runs here, the guarantee is possible at all. Three consequences fall out of that:
It costs nothing to run. No API key, no account, no meter, no bill and no rate limit that decides whether you can quote a job tonight. It works on the laptop that is already in the ute.
Nobody can change it under you. The weights are a file on your disk. No vendor deprecates the model your workflow depends on, no silent upgrade rewrites your document templates and nothing you typed becomes training data.
You can leave. The tool takes any GGUF. Swap the model when a better one lands, run a smaller one on a weaker machine or keep the old one forever because it works and it is yours. That is only a real freedom when the weights are open.
The last one is the reason the project is called Never Leaves. It is not a slogan about the software. It is a property of the system that the kernel enforces and that you can check yourself in about four commands.
Prize Categories
Entering the overall prize, Best Use of Gemma and Best Use of GitHub Copilot.
Gemma 3 4B is the default model. It runs locally from a single GGUF file on disk, with no server and no network. It drafted every output shown in this post. The tool takes any GGUF, so the model is a parameter rather than a dependency. The default can be swapped without touching the code.
For Best Use of GitHub Copilot, the route taken is the GitHub Actions one that category lists. Every push runs the suite and a secret scan. The workflow token can only read the repository and both actions are pinned by commit SHA rather than a moving tag. Copilot's agent features are not used and the entry is claimed on the Actions automation alone, stated here so it can be judged on what it actually is.
Written by Empire Labs Pty Ltd. www.empirelabs.com.au

Top comments (0)