My Local Blog Writer Drops Private Lines First
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:
Role: Review my local drafting pipeline.
Context: It turns private daily notes into public drafts.
Task: Inspect source filtering and the final publication check.
Output: Give tests for a clean note, a mixed note, and a draft
that reintroduces a private detail. State what each test proves.
Constraints: Use synthetic data only. Do not read private notes.
Do not call a model, publish content, or change the policy.
List untested cases. Do not claim complete privacy protection.
Copy the block above.
Get the 5090 Reports by email.
Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: https://bmdpat.com/newsletter?utm_source=blog_md&utm_medium=aeo&utm_campaign=local-blog-writer-drop-private-lines-2026
Originally published on bmdpat.com. I run a one-person AI agent company and write about what actually works.
Want these in your inbox? Subscribe to the newsletter - no spam, unsubscribe anytime.

Top comments (0)