Eight models, three AI services, and a README that refused to be written
How I moved the spec-driven development of Sentinelle di Casa from the cloud to my LAN — and what I learned when the agent said it was writing but wasn't.
![]()
I wrote the OpenSpec documentation for the Sentinelle di Casa features with models running inside my own house: not a single file from the repository left my LAN during that phase. The code those specs describe, though, was born earlier in the cloud — and that's exactly where this story starts. Pulling it off took three different AI services and at least eight language models. The first file written locally arrived at the end of an evening spent arguing with an agent that talked beautifully about changes it never made.
This is the story of that evening.
Where I'm coming from
A recap for anyone who missed the previous post: on the night of 25 October 2026 a blackout took down three nodes on my home network, and none of them came back up on its own. Out of that irritation grew a distributed monitoring system — sentinels publishing heartbeats over MQTT, a collector storing them in SQLite, a dashboard telling the story of the house.
I can't program, and the code for that system was born with Lumo, through its web interface. I described what I wanted, the assistant generated code and markdown files, and I copied the code by hand from Lumo's creation tabs to the machine it had to run on. It worked. But the human step was mandatory, and everything went through the web.
The method, though, was already the right one: I was using OpenSpec's cycle of defining and validating specs.
The circular loop
That evening's experiment was an attempt to close the circle inside the LAN. The result is a flow I genuinely enjoy, because it's circular and has no single master:
- Lumo (with models accessed through the web interface) generated the foundation: the Sentinels' code and markdown.
- Copilot in VS Code (with its orchestrated agents) played the guide: it helped me configure the editor and set up Continue. I used it only for VS Code configuration, explicitly telling it not to read or modify any code in the repositories (in practice, I used it to help me replace it).
- Continue (version 2.0.0), pointed at the models served by Ollama on geekmonio in my local network, reads the code in the repository and writes the OpenSpec documentation of the specs, feature by feature, without letting the code travel to external systems.
The local models on my inference machine are five: GPT-OSS 20B, Qwen 2.5 Coder 7B, Mistral Nemo 12B, Llama 3.2 3B and Gemma 3 12B. Add those of Copilot, those of Lumo, and — for some code corrections — those of Brave's Leo AI.
Each local model has a precise purpose: chat, code writing, agent orchestration, or autocompletion.
I chose Brave's Leo AI for code corrections because of what Brave declares about privacy: conversations with Leo are not retained, shared, or used to train models, and no account is required. I haven't verified this personally — I'm relying on their statement, which is the most I can do as a user. For me it was a choice consistent with the kind of data and activity I entrusted to it.
Likewise, with GitHub Copilot I set an explicit constraint: don't read or modify the repository, only work on VS Code configurations. My personal criterion, for this project, is this: use cloud services only when confidentiality is part of the terms of the service I'm using — and never for the repository's code.
Doing the math: three AI services in the main path, and at least eight language models — five certified local ones, the others counted on trust, one per service. Leo AI sits outside the main path: it only comes in for reviews.
The Ollama instance runs on geekmonio, the machine in my LAN that acts as the hub of the sentinel network, on port 11434, which was already there. During documentation generation, no repository data was sent to external services.
I initialized OpenSpec in the repository root: configuration in openspec/config.yaml, spec-driven schema, and six ready-made prompts in .continue/prompts/ — /opsx-explore, /opsx-propose, /opsx-apply, /opsx-update, /opsx-sync, /opsx-archive. openspec validate --all passed on the first try. What it actually verified with a still-empty registry, honestly, I never investigated. The CLI lives in ~/.local/bin/openspec and doesn't need Node.js.
So much for theory. Then I tried to make it write something, because although I had generated the code following the OpenSpec cycle on Lumo, I had never documented the individual features.
Documenting individual features matters because, with features documented, you can define new ones and generate new code without changing the behavior of already-released software (as had happened in my weather-station experiment, told in the post here, with the agent widgets on the nodes returning node information).
The agent who talked a great game
The stated goal: a minimal edit to the repository's README. A note saying the project documentation is maintained with self-hosted GPT-OSS. Thirty seconds' work, in theory.
Agent mode, Geekmonio GPT-OSS 20B model, fresh chat. I ask for the change.
The agent replies. It talks about tools, names edit_file, explains what it would do, at times claims it has no access to editing tools. The README, meanwhile, remains exactly as it was.
The detail that cost me half an hour: none of those replies was an event. They were chat text. The model was narrating tool use, not performing it. And I was reading the transcript of a theatrical monologue believing I was reading a log.
![]()
Temperature wasn't enough of a culprit
First hypothesis, the most convenient one: the model is small, the temperature is too high and the model invents things when it doesn't know, GPT-OSS 20B isn't made for agents. Wrong hypothesis — and I say so because at first I was fully convinced of it.
Looking closer at the session, the problems were three, very mundane ones, and none involved sampling parameters:
- The workspace was the wrong folder. Continue was attached to the repository's parent folder, not the root. Ambiguous paths, ambiguous context.
- The tools being called didn't exist. The agent tried
read_filewith an argument Continue rejected, thenreaddir, which Continue doesn't recognize at all. Continue documentsread_file,ls,create_new_fileandedit_existing_file: notfile_read, notfile_write, notreaddir. - Phantom tool calls. The model described the action instead of performing it. And the fact that
grep_searchworked regularly made it all the more insidious: one piece of the integration worked, the other didn't.
Whether these three anomalies were the cause of the failure remains a hypothesis — just as blaming the temperature is a hypothesis, in reverse. But at least these three were visible in the logs.
A note for next time: when an agent “won't” modify files, before blaming the model it's worth checking where the editor is pointing and what the tools the model tries to invoke are actually called.
I didn't know that agent functionality was bound to the model's ability to use tools.
Who can use tools, and who merely says so
To understand whether the problem was the model not knowing how to use agents or the integration, I queried the Ollama API directly, outside Continue. Results:
- GPT-OSS 20B returned a real
tool_call. The model, in short, can do it. - Qwen 2.5 Coder 7B returned JSON as plain text, not a structured call.
- Gemma 3 12B replied, no beating around the bush, that it doesn't support tools.
Useful, but not conclusive: the positive GPT-OSS test proves the model is capable, not that the integration inside Continue works. Two different things.
Trust no one, especially agents
This is the point I'm taking home, and it holds well beyond that evening.
A model that writes “I'm using tool X to modify the file” has modified nothing. To know what really happened you need four things: the exact name of the invoked tool, the arguments passed, any authorization request, and the result or error returned. Without those, you're interpreting prose.
Let me add a methodological detail: I wanted exactly this behavior. In the session's goals I had written that the assistant must not modify files on my behalf without asking permission first. Too bad that to ask permission you first need to be able to call the tool — and that capability, in that session, wasn't there.
The light
Same evening, a little later: I open VS Code on the correct repository root, verify with pwd that I'm in the right place, check that the README is visible in the Explorer. Fresh Continue chat, same model, same Agent mode.
And after a few @agent Try Again, the note appears in the README.
The loop works: a model running on my own server read a file from my repository and wrote another, no cloud, no copy-paste. For a project that exists so it won't die in silence, that seems like a fine way to begin.
The part I don't know
Here I have to be honest, because that's the point of this blog.
Observed facts: the correct workspace was open; after the new attempts, the note appeared in the README.
Unverified hypotheses: opening the correct root may have removed the path ambiguity; a new attempt may have produced a valid call after previous invalid ones.
I don't have a detailed transcript of the successful attempt. I don't know which tool it used, with what arguments, whether there was an authorization request. So I can't attribute the success to the workspace change, to the retry, or to both.
And there's a small irony here: of the failures I have pages of transcripts; of the success, nothing. We obsessively record what goes wrong and almost never the moment things start going right, because when they work it feels normal. Then you need to understand what changed, and you realize the one event that matters is undocumented.
Oh well.
What I learned
That the method holds, but only if the robot actually lays the rails I designed. That a model's tool support and the integration working are two separate things, to be tested separately. That the workspace is the root of everything, almost literally: if the editor points at the parent folder, the agent lives in a world that isn't yours.
Postscript: the hallucination happened here too
I wrote and revised this post with Lumo, the same assistant the Sentinels' code was born with. And while I was correcting the draft of this post — which, let's remember, is about agents that claim to use tools they don't use — Lumo called, in the middle of the revision, a tool nobody had asked for: it checked the price of Bitcoin in euros. The tool answered €71,665.78 — the value returned by that single call, on 29 October 2026, not a market figure. It had nothing to do with the draft, with the Sentinels, with anything.
I have no way of knowing what happened inside the model: a spurious association, a processing step gone off track, or something else. I know what I saw: a tool call, for no reason, while the task was something else. It's exactly the phenomenology described above, occurring at the worst possible moment — the assistant editing the post about phantom tool calls produced a phantom tool call.
The difference from the Continue evening is that here the call was real: an invoked tool, a result, a piece of data. The data just had nothing to do with anything. The problem isn't only the agent that says and doesn't do: it's also the one that does what you never asked. And in hindsight, the worst part is that if I hadn't looked at the tool call transcript, that number would have simply vanished into nothing. Quiet, silent, undocumented — like my successful README.
Don't trust, verify, read the logs. It goes for local models and it goes for cloud ones. Especially for the cloud ones.
“Free technology isn't just code — it's the freedom to look under the hood.”
👤 Lorenzo | ItsMeLarry 🧪 AI & Open Source Experiments | 🕹️ Retro Gaming & Graphic Novels 🎸 Rock, Folk, Jazz, Punk | 🔒 Privacy First 📍 Tuscany / Digital