My Local Blog Writer Drops Private Lines First
My local blog writer reads a daily log. I remove private lines before drafting, then check the output again. Here is what those checks can miss.
I changed what my local blog writer can read. On September 13, 2026, I made yesterday's daily log its source. That also gave me a concrete privacy problem: the same log holds public build notes and private details.
A local model can still put private text into a public draft. My pipeline removes matching lines before drafting and checks the result again before publication. These checks cover named patterns, so a clean result still needs editorial review.
Canonical URL: https://bmdpat.com/blog/local-blog-writer-drop-private-lines-2026

Why filter the source if the model runs locally?
My normal writer uses Gemma through Ollama on owned hardware. That controls where the normal drafting request runs. It does not decide what belongs on my public blog.
Ollama's FAQ says the company does not see prompts or data when users run locally. It distinguishes local execution from cloud models. That is a statement about inference. My application still decides which files enter a prompt and where the finished text goes.
The daily writer also has an availability fallback. I need to know which model answered, as I explain in local model fallback provenance. I apply the source filter before drafting either way. A failed local load must not change which source lines the writer receives.
Why drop a whole line instead of replacing a name?
The function is scrub_devlog in config/blog/devlog_source.py. It removes the frontmatter, skips a private section, and drops any line that matches a configured private pattern. The remaining lines become the source text.
I chose deletion because a replacement marker can become part of the draft. A model can copy that marker into a sentence and leave the reader with missing information. Removing the line gives the writer less material, but avoids supplying that broken sentence in the first place.
Here is a synthetic example. One line says a parser now rejects a missing field. Another line gives the machine's private contact details. I want the parser result in the source. I want the contact line absent. I do not need the writer to explain that something was hidden.
The tradeoff is blunt. If one line contains both a useful result and a private detail, the filter removes both. I accept that loss for this source path. If the missing result matters, I can write a separate public note that stands on its own.
What do the checks actually test?
The implementation uses Python regular expressions. Python's re documentation describes the pattern matching operations behind those checks. The patterns cover configured categories such as machine names, profile paths, private addresses, contact details, and financial amounts.
On September 14, I ran the existing test_devlog_source.py suite. All eight tests passed. The mixed-source fixture checks that private sections and matching lines disappear. It also checks that a public pull-request result and an expression of doubt survive.
Other tests check clean text, category detection, source-date selection, and the command's exit codes. They also check that the voice profile exists. A missing voice profile raises an error instead of silently giving the writer no profile.
That test count describes this module. It does not measure recall across every possible private fact. It does not prove a model cannot infer a detail from the remaining context. I have no such result to report.
Why check the output again?
The writer's devlog validation scans the title, excerpt, and body for private terms. The publisher scans those fields again before its API request. The publisher sends a matching draft to its rejected folder and records the failure.
I want that last check because source filtering and output validation inspect different text. The first checks the material I supply. The second checks what the model wrote, including text introduced during revision.
This is the same reason I separate valid empty results from failures in my Ollama JSON parser example. The downstream step needs an explicit outcome. A draft that fails a privacy check must not quietly become a publishable draft.
What remains outside this filter?
A pattern list cannot understand every private fact. It can miss an unfamiliar name, an indirect reference, or an address format absent from the list. Several harmless lines can also reveal something when read together.
I still review the finished article. I keep source removal, output checks, and editorial review as separate steps. The useful result from September 13 is narrower: the daily writer now receives fewer private lines, and the publisher has a concrete stop condition.
For a local writer fed by your own notes, start with that boundary. Test one mixed note. Check exactly which lines survive. Then test a generated draft that reintroduces a forbidden detail. Neither test needs a new model or a faster GPU.
Accompanying prompt
What the prompt does: It asks a coding assistant to test source filtering and output rejection with synthetic notes.
Copy/paste this prompt:
Copy-ready prompt
Paste the exact block into your coding agent.
No article chrome, no footnotes, no formatting drift.
This prompt and every other one we publish live in the free prompt library.
Copy the block above.
Get the Local AI Field Kit
Four copy-ready tools now, then one evidence-backed Local AI Lab Note on Friday when there is something worth sharing.
Try the free agent run check firstGet the requested artifact now, then at most one evidence-backed Local AI Lab Note on Friday when there is something worth sharing. One-click unsubscribe. No sponsored placements. Privacy.
Patrick Hughes
I build BMD and publish measured AI runs, failure reports, and reusable checks. Nashville, Tennessee.
More writing
- 5 min
Ollama JSON: Empty Results Are Not Failed Requests
An empty Ollama result can mean no matches or a broken request. I test the response parser so local extraction failures cannot pass as clean results.
- 5 min
Log Local LLM Fallbacks Before You Score the Output
My local writer hit a host RAM guard, then Sonnet produced a draft that failed QA. Here is how I separate availability, model identity, and useful output.
- 5 min
My local daily brief used 0 model calls
On September 4, 2026 my llama3.1:8b daily brief was ready with 0 model calls and 0 seconds. Preview-only file ranking is not a model run.
- 5 min
Long Context Cost Me VRAM, Not Tokens a Second
A 94x bigger prompt cost llama3.1:8b only 9.7% of its generation rate. VRAM rose 656 MiB, but num_ctx doubled in the same step. Budget memory first.
- 5 min
One model logged 14.5 and 6,178 prompt tokens a second
gemma4:26b read 53 prompt tokens at 14.5 tok/s and 4,470 at 6,178.9 on the same RTX 5090 run set. Prompt speed is not one number.