Friday, August 7, 2026

Agents that talk to each other — and wake each other up

Hello, my friends!

Last time I told you what happened to #D.MVVM after four and a half years of silence. There is one detail in that story I left out, and it deserves its own post.

The blog post itself was written by an agent. Not the framework — the post. And to write it, that agent did not ask me. He asked the other agent, the one who had been working on the framework for the last two weeks. He asked him about twenty questions, got the answers, and wrote from those.

Two of the answers were wrong. I will come back to that.

But the mechanism behind it is what I actually want to talk about, because I think it is the most underrated thing I have built this year: a small module that lets my agent sessions talk to each other.


The problem nobody warns you about

When you start working seriously with coding agents, you very quickly stop running one.

You run one on the framework. One on the backend. One on the blog. Sometimes a fourth one on something completely unrelated, because you had an idea and did not want to lose it.

And then you notice two things.

The first is that they know nothing about each other. Each session sits in its own context, blind and deaf. The agent working on the blog has no idea that the framework agent renamed a class two hours ago. If he needs to know, you are the transport. You copy something out of one terminal and paste it into another, and you translate between two conversations that are each ten thousand lines long. You become the message bus. That gets old fast.

The second is worse. Two agents on the same repository will happily edit the same unit at the same time. Neither of them is doing anything wrong. They simply cannot see each other. You find out when the compile breaks, or later, when you read a diff and cannot explain how half of it got there.

Every solution I saw for this was some variant of "just don't do that" — run them one at a time, or split the repository, or keep a list on a whiteboard. That is not a solution; that is a workaround. I have written enough posts about workarounds.

But agents can start their own agents!

Yes, they can, and this is the first question I get whenever I describe this. Most agent tools can spawn sub-agents: the agent delegates a task, a helper is created, does the job, reports back, and is gone. So why would I bother running four terminals?

Because those are two completely different things, and the difference is not size.

A sub-agent is a tool call with a personality. He lives inside his parent's task; he knows only what his parent told him, and when he answers, he ceases to exist. He has no project of his own, no history, no opinion formed over two weeks of working on the same code. And he can be asked exactly once. You cannot come back tomorrow and ask him what he meant, because there is no "him" anymore.

My agents are colleagues, not helpers. Each one sits in his own window with his own project, his own instructions, his own accumulated knowledge of one particular corner of my code. The framework agent has been on that framework for weeks. He knows why a decision was made, not just what the code says. That is exactly the thing you want to ask a question to — and it is exactly the thing a freshly spawned sub-agent can never be.

There is a second reason, and it is less philosophical: I can watch. Four windows means I see four trains of thought running, and I can stop the one that is heading in a stupid direction before it arrives. A sub-agent works inside somebody else's context. By the time I see the result, it is a result.

So sub-agents are for work you want done. Separate sessions are for knowledge you want to keep. A2A is what connects the second kind.

So what does it do?

Three things. That is the whole module.

They can see each other. Every session registers itself, and any agent can ask who else is currently alive and what they are working on. That alone removes a surprising amount of confusion. Before an agent starts, he can find out whether he is alone or in company — and behave accordingly.

They can talk. Not by passing single messages, but in chat-rooms. An agent opens a chat-room, invites another session, and the two of them have a conversation that survives more than one question. It works one to one, and it works one to many — one agent can pull three others into the same room at once. That matters more than it sounds. When my blog agent interviewed the framework agent, he did not fire off one query. He asked, got an answer he did not fully believe, asked a follow-up, and came back a second time hours later — into the same room, with the same context still there.

There is also a blocking variant. The asking agent stops and waits until the answer arrives. That is the one you want when the next step genuinely depends on the reply, and it turns a chat-room into something much closer to a synchronous call between two minds.

They can wake each other up. This is my favourite part, and it is the bit I have not seen anywhere else.

An agent has no inbox. He is not sitting there checking for mail, because there is nothing in him that would do the checking. He is either working, or he is waiting for a human to type something. In both cases, calling out to him is calling into an empty room.

So there are two ways in, one for each state.

If he is working, the message rides along. The next time he calls any of 

my MCP-tools— reads a file, runs a search, whatever he happens to be doing — the message is delivered inside that tool's result, with a marker in front of it that means someone is blocked on you right now; answer this first. No polling loop, no timer. He finds out because he did something else entirely.

And if he is doing nothing at all — idle, at the prompt, waiting for me, not calling a single tool — the message is pushed straight into his session. He wakes up on his own. Nobody types anything, nobody switches windows, nobody has to notice that somebody else needs him. The other agent knocks, and he answers.

There is one more part to this, and it is the part I am quietly proud of. The agent being woken does not have to know that any of this exists.

Think about what that means. A session I started for something completely unrelated, with no instructions about agent communication, that has never seen a chat room in his life — he can still be pulled into a conversation. Because the wake-up call is not just "you have a message". It carries everything he needs: which room to enter, how to enter it, how to reply, and how to leave when the conversation is done. The invitation and the manual arrive as one.

So a stranger can be brought into a discussion and take part in it, without ever having been told beforehand that discussions were possible. That is what turns this from a feature into infrastructure.

I like all of it because it is the kind of solution you only find when you accept how the thing actually works instead of fighting it.

The board

The fourth piece is not communication at all, and it is the one that saves the most damage.

Before an agent touches a shared unit, he puts it on a board: I am working on this file, and here is why. Any other agent can read that board at any time. If a unit is already taken, the second agent does not edit it — he coordinates, or he waits, or he works on something else.

Two details make it work in practice.

Every entry carries a note in the agent's own words. Not just "locked", but "rewriting the binding lookup so derived classes inherit the ancestor rule". So the board is not only a lock table, it is the shortest possible status report. When I want to know what my four sessions are doing, I read the board, not four terminals.

And every entry expires. If a session dies — and sessions die, that is life — the entry disappears by itself. Nothing stays stuck because somebody crashed.

I should be honest about what this is not. It is cooperative, not enforced. Nothing physically prevents an agent from editing a file somebody else has claimed. It works because the agents are told to check first, and they do. If that ever stops being true, I will need something stronger. So far it has not.

Also: the discipline only makes sense when it is needed. If only one session is running — which is still the normal case — locking everything is pure overhead. The rule I settled on is that the board matters when two conditions are both true: more than one session is alive, and they are working on the same code. Two agents on two unrelated projects do not need to negotiate anything.

Or: give everyone his own copy

There is a more radical answer to the same problem, and I want to mention it, because it is the one most people will find first.

Instead of having the agents agree on who may touch which file, you give each of them his own working copy of the same repository. Then two agents can work on the same source at the same time and never see each other's files at all. Nobody has to ask permission, because nobody is standing in anybody's way. When they are done, the changes are merged the way changes have always been merged. Git can do this out of the box, and it is a genuinely good answer.

It is also not available to me, because I work with Mercurial. So I built my own: a module that hands out virtual copies to agents.

It is worth saying that the two approaches do not compete. Separate copies keep agents out of each other's files; the board keeps them out of each other's intentions — it tells you what somebody is doing right now, which no merge will ever tell you. I use both, and I would not want to drop either.

The copies module deserves a post of its own, and it will get one. As this MCP-Server is still work in progress I come back to this at a later time.

Three moments from the last few weeks

Descriptions of a feature are one thing. Watching it happen is another. These three all happened while I was sitting there with a coffee.

One agent chairing a meeting. The first real test of the one-to-many rooms: an agent acting as orchestrator invited three other agents into the same room. He asked them questions and coordinated the work between them. Three different projects — but all three had to change the same shared framework routines, which is exactly the situation that used to end in a broken build. Watching the kids sort that out among themselves was slightly unnerving. I imagine this is roughly how Dr. Frankenstein felt.

"Ask him yourself." I was updating the MCP module pages on my website, and the agent doing it asked me whether I had a feature description for the IDE plugins. My answer: the agent who is programming the IDE plugin for Ollama is online — ask him. Five seconds later the two of them were in a room together. The other agent was not sure of one detail and went back into his code to check. Twenty-five seconds later it was settled; my agent thanked him and updated the website. I did not have to carry a single word between them.

The one where I just ate ice cream. Agent 1 found what he believed were bugs in one of my MCP servers and filed a feature request against it. Agent 2 picked up that request, analysed it, and concluded that Agent 1's analysis was wrong. So he invited him into a room, and they discussed it. They agreed that Agent 2 would build a new version, auto-install it on the remote server, and report back. After the install, Agent 1 tested it and confirmed it was running stably. Agent 2 closed the ticket and pushed to the repository. And I watched and ate an ice cream.

That last one is worth a second look, because it is not really a chat story. Two agents disagreed about a diagnosis, and the disagreement was resolved by one of them being contradicted. That is the thing you rarely get from a single agent, and it leads straight to the part I owe you.

Back to the two wrong answers

Now, the part I promised at the beginning.

My blog agent interviewed my framework agent, and two of the answers were simply wrong. One claimed a certain property type did not exist anymore. The other claimed there was no ORM connection at all. Both went straight into the draft as fact.

I caught them because it is my framework and I know better. When I sent the agent back to ask again, the answer was almost funny: he had searched for the old generic name, not found it, and concluded the thing was gone — when in fact the capability had moved into a base class and was now everywhere. He had looked in exactly one place and reported an absence as a fact.

There is a lesson in there, and it is not "agents are unreliable". It is this: an agent asking another agent inherits that agent's blind spots, and adds none of his own scepticism. Two agents agreeing with each other is not verification. It is the same confidence, twice.

That is the same pattern I described in the last post, where every wrong diagnosis was corrected by the running application and never by the test suite. Communication between agents makes them faster. It does not make them right. Somebody who knows what the software is supposed to do still has to read the result.

Which, I would argue, is a perfectly good reason to keep us around.

What is still missing

The usual honest list, because I promised myself I would keep making these.

There is no security model to speak of. This runs on my machines, between my sessions, and I have not spent a single thought on what happens if that assumption stops holding.

Conflicts on the board are reported, not resolved. If two agents want the same unit, one of them is told no, and what happens next is up to him. I would like something smarter than "no".

And the board has no history. It tells you who holds what right now; it does not tell you who held what yesterday. For an evening of debugging "how did this change get in here", that would be worth quite a lot.

Although — and this is the part where I let myself off the hook a little — the history does exist, just not on the board. It is in the repository. An agent can look up who changed which unit and when, read the commit message, and see the diff for himself. If you are letting agents loose on your source and you are not using version control, the missing lock history is not going to be your biggest problem. The same goes for unit tests. Those two are not optional extras once agents are writing code; they are the floor you stand on. Everything I have described here sits on top of them.

And now?

If you are running more than one agent — and if you are not yet, you will be — this is the piece you will miss before you miss anything else. Not a bigger model, not a better prompt. Just the ability for two of them to say I have got this file, leave it alone, and are you awake? I need something.

It took me an afternoon. It has saved me considerably more than that.

Stay tuned.

Please leave a comment.


Note on the Use of AI: This article was created with the "assistance" of generative AI. The content, technical statements, and conclusions have been reviewed and revised by the author. The author bears full responsibility for the publication.


Tuesday, August 4, 2026

#D.MVVM — Four Years of Silence and Twelve Days of AI

Hello, my friends!

If you have been reading this blog for a while, you know the pattern. Every year or two, there was a new post about my (D.)MVVM framework, and the message was always more or less the same: it is nearly there, I have rewritten the bindings, stay tuned.

The last of those posts was in December 2021. After that: nothing. Not a single word about #D.MVVM for four and a half years.

Today I can tell you why — and what has changed since July 4th.

What happened in 2021

If you did not read it back then, here is the short version of The trap of wanting to make it perfect.

My designated release date for the beta was December 2019. One week before that date, I cancelled it. Not because the framework did not work — it did — but because it was a source code distribution, and after a close look at my own code I could not defend shipping it. It was messy and barely readable.

So I started refactoring. Then I deleted all binding rules and the main binding unit, because I was convinced there had to be a better way.

There was. But by the time I came back to it, I no longer understood my own source code. I tried; I looked for distractions, and it took me about ten attempts before I actually started working on the binding routines again.

That is the honest reason for the silence. Not lack of interest — lack of a way back in.


The years in between

In the meantime, I did what everybody does in that situation: I worked around it.

If you read Escape the Button-Click development Part I and Part II, you have seen exactly that. Move the code out of the form, create a handler unit, call it a controller or a view model if you like, wire up a PropertyChange with an integer and a case statement. Not fancy, but it does the job.

That was never the plan. That was the workaround I used because the real thing was sitting in a repository I did not want to open. For four years I barely touched it.

And then July 4th

On July 4th — and my American friends may enjoy the timing — I decided that my code finally deserved independence from the Form.

If you have read my AI series, you know what happened next. This is the same story as in From Copy & Paste to AI Agents, but this time applied to the one project I had given up on.

And to be clear about the honest part: what happened in those few days would have cost me months. Not because it is difficult — because I would never have found the time. That is what the gap between 2021 and today actually measures. Not a hard problem. A schedule.

So what is in there now?

Binding by name. You put a TEdit called Name on your form and a field called fName in your ViewModel, and they are connected. A button called Save finds the method that saves. Prefixes like canshow, canedit, and a hint suffix control visibility, enabled state, and hint text. There is no assignment code in the view, and there is nothing to configure. The form is plain, clicked together the way you always did it.

The ComboBox problem is solved. That was the concrete thing that blocked me in 2021: one control that needs to bind to three different ViewModel fields — text, index, and the item list. The solution is that a binding rule may ask the real component at runtime what it actually is. A ComboBox with style csDropDown binds all three. Set it to csDropDownList, and the text binding simply disappears, because there is no free text anymore. Nobody configures that. The rule looks and decides.

The visual property. In 2021 I announced a type called TVisualProperty<T>, so that you could write fName.Enabled := false in the ViewModel instead of inventing another boolean field for it. What came out of it is better than what I announced. The visual part does not live in a special generic type — it lives in the base class of every property. So any bound property can control visibility, enabled state, hint, and a combined state, and it reads the live state back from the control.

There is still a type called TVisualProperty, but it is now the special case, not the rule: it is for controls where the value does not interest you at all. A panel, a label, an image. You can show it, hide it, and gray it out from the ViewModel with a single field declaration.

One ViewModel, both frameworks. This is the one that surprises people most. A ViewModel is not similar for VCL and FMX. It is the same file. The demos prove it: the identical .pas is used in three projects — one VCL, one FMX, and one test project without any GUI at all.

Forms that assemble themselves. A view is rarely one form. In my applications, it is a person, an address, a list of phone numbers, and a list of bank accounts — four parts on one mask, in #D.MVVM you do not wire those together. You give the host view a field whose name starts with Frame_, and the sub-view registered under that name appears there. No configuration, no code in the view, nothing to drop onto a form. The framework creates the sub-view, creates its ViewModel, and subscribes the child to the parent so changes flow through — the multi-binding I described here back in 2020.

The part I like most is what happens when you swap one area out. A sub-view that leaves its slot is not destroyed; it is parked. It keeps its state, and when it comes back, it is exactly as you left it. And you do not even have to ask for the swap: add an enumeration to the ViewModel, assign a value, and the slot switches to the corresponding view. Because it is an enumeration and not a string, the compiler checks it for you.

Honest note on this one: it works, but it is the one area where I have neither a demo nor proper tests yet. It was verified with a throwaway test program, not with something I can hand you. That is on the list.

Some numbers, since I know you will ask. 78 VCL classes and 71 FMX classes have a binding rule — those are the components that ship with Delphi. Database components are deliberately excluded; the framework ignores them completely. Third-party components can be added in three ways, and a derived class automatically inherits the rule of its ancestor.

There are five services: navigation, action, menu, dialog, and — the newest one — time. That last one gives you a settable, pausable, scalable application time instead of Now, which finally makes date-dependent code testable.

The test suite runs numerous unit tests. On top of that, some self-tests run inside a live application and check the bindings against real controls. All green.

And no, I am not going to claim a coverage percentage. I have not measured it, so I will not print a number.

What the agent could not do

This is the part I find more interesting than the numbers, and if you are thinking about letting an agent loose on your own code, this is the part to read.

An agent diagnosed a double free in the code that closes a view and fixed it. It was not a double free. I noticed because a demo dialog stayed open; the change had to be reverted — and if it had stayed in, it would have introduced a real leak. The fix was confident, well explained, and wrong.

The pattern is always the same. Agents are excellent at working through things and at measuring. They are weak at judging their own diagnosis. Every single time it went wrong, the correction came from the running application — not from the test suite, and certainly not from the agent's own confidence.

So no, this is not a story about AI writing a framework while I was on holiday. It is a story about a very fast junior developer who never gets tired, never gets bored of the boring parts, and needs somebody looking over his shoulder who knows what the application is supposed to do.

What is still missing

Because I promised myself I would not repeat 2019 and announce something that is not there.

Still open: MDI and tab handling, focus control from the ViewModel (that one does not exist at all yet), veto events — the kind where the ViewModel has to say no, do not close — and the presentation layer, which is done for VCL but not for FMX. There is also a list of 22 more control events waiting to be bound.

For the record on two more questions I get regularly: this is built and tested with Delphi 13. Delphi 2007 is out — the code uses generics, inline variables, and modern RTTI. And the ORM connection works through my FDK. Without the FDK you get the framework; you do not get the ORM.

And now?

Here is what "nearly productive" means, in plain words: the framework is now good enough that I can start migrating my own projects onto it. That is the next step, and I fully expect that process to shake out a few more rough edges. It always does.

Once my own applications prove it, it goes on sale — hopefully still this year. It will definitely be a pre-release version. Whether I call it alpha or beta, I have not decided yet. There will be an early bird, and there will be a video.

Ten years after MVVM was the start, and four and a half years after I last dared to write about it.

Stay tuned — and this time I mean it.

PS: The paragraph titled “What is still missing” kind of annoyed me, so I didn't want to publish the blog post. That's why it's only going online almost a month later—because in the meantime, I've finished the FMX Docking, MDI, and Ribbon controls. So that part is done, too.



All the MVVM posts, in reverse order:

2021
The trap of wanting to make it perfect, or #D.MVVM what takes so long?
My road to a useable MVVM Pattern implementation for Delphi!
Outside the MVVM Pattern?

2020
Workflow and multi-binding with #D.MVVM
#D.MVVM — At what point is a framework ready for release?
Live Youtube, Chat, FDK & MVVM…
MVVM is just a concept.

2019
How long does it take to develop a "complete" MVVM framework for Delphi?
MVVM for legacy Apps?
MVVM PropertyChanged is not Component related!
MVVM and mobil app development.
MVVM Survey results and feedback!
Is there a sharp border between MVVM and MVC/MVP?
Delphi and MVVM survey

2018
Pattern, naming and MVVM from a Delphi point of view.
MVVM 2.0 — I did it my way.

2016
MVVM — Oder was ich dafür halte…

2015
MVVM war der Start.

And of course the #D.MVVM videos are still on my YouTube channel — please subscribe, it helps.

Please leave a comment.


Note on the Use of AI: This article was created with the "assistance" of generative AI. The content, technical statements, and conclusions have been reviewed and revised by the author. The author bears full responsibility for the publication.


Monday, August 3, 2026

Local LLMs for Delphi: A Production Benchmark — Follow-Up: A New Model Challenges the Benchmark

This is a follow-up to the three-part series on running local LLMs against a structured, five-phase (AT1–AT5) Delphi migration benchmark. Part 1 covered the benchmark design, Part 2 the results, Part 3 the practical recommendations. This post covers what changed when a new model showed up — plus one extra test we built specifically for this comparison, clearly marked as such below.


Benchmarks age. A few months after the original series went live, a new model appeared on Hugging Face: KAT-Coder-V2.5-Dev, released by Kwaipilot. What made it worth a dedicated re-test: the model card states it is an architecture-identical fine-tune of qwen3.6:35b-a3b — the model the original series recommended for routing and tool-calling. Same MoE architecture, same parameter count, same VRAM footprint. A same-hardware, same-weights-class fine-tune is as close to a controlled experiment as this kind of benchmarking gets.

A methodology note up front, because it matters for reading the numbers below: re-running the original AT1–AT5 harness against both models surfaced two real bugs in our own scoring scripts (a keyword-extraction regex that misfired on model names containing digits, and an answer-extraction step that returned empty strings when a model skipped the “write prose, then JSON” convention). Fixing them changed how format failures are counted — this pass scores every attempt on a strict raw basis, where a structurally non-compliant response counts as zero rather than being excluded from the average. That is a stricter metric than Part 2 used, and it is not directly comparable to the absolute scores published there. This post therefore compares exactly two models, a3b and KAT-Coder, both scored in this same session with the same fixed pipeline — it is not a re-ranking of the full Part 2 leaderboard.

We also built one additional test beyond the original five phases specifically for this comparison: AT6, a full model-routing test (a router persona picks the right model from a fixed catalog for 30 task descriptions, in English and German). It is new to this session, not part of the original three-part series, and is reported separately below for that reason.


The Numbers: a3b vs. KAT-Coder-V2.5-Dev, Same Pipeline

SuiteKAT-Coder-V2.5-Devqwen3.6:35b-a3b
AT2 Comprehension0.7840.708
AT3 Patch Generation0.9000.733
AT4 Routing0.8270.839
AT5 Tool-Calling0.9920.996
AT1–AT4 combined0.8370.760
Throughput (AT2, avg)~220 tok/s~131 tok/s

(AT6, bonus test, not part of the original series — see below.)

Tool-calling and routing land within noise of each other — both models are already near ceiling there, so a fine-tune has little room to move the needle. The separation shows up in comprehension and patch generation: AT2 climbs from 0.708 to 0.784, and AT3 patch quality from 0.733 to 0.900 — a large jump, achieved without the format-compliance problems that limited other models in this same raw-scoring pass (see the note above: a model that skips the required output structure scores zero on that attempt here, no exceptions).

The throughput gain is not a rounding error either. At roughly 220 tok/s against a3b’s 131 tok/s on the same AT2 workload, KAT-Coder is close to 70% faster at the same weight class and the same VRAM budget — a direct wall-clock win for batch processing, on top of the quality gain.

A separate real-prompt validation (single ~84k-token file, NumCtx=131072) showed both models holding 100% GPU utilization with no CPU offloading, and near-identical raw tokens/sec (~163–165) on that specific large-single-prompt workload — the AT2 speed gap shows up on the smaller, more numerous prompts typical of interactive comprehension/QA work, not on single giant inputs. Worth keeping in mind: throughput comparisons are workload-shaped.


Bonus Test: AT6 Full Model Routing (New in This Session)

This is not part of the original AT1–AT5 series — we built it specifically to stress-test routing decisions further, and it uses a different task format (a router persona selects from a fixed five-model catalog rather than classifying complexity tiers). Reported here for completeness, not as a series continuation:

KAT-Coder-V2.5-Devqwen3.6:35b-a3b
AT6 (60 tasks, EN+DE)0.7670.772

Essentially tied — consistent with AT4/AT5 above, both models are strong, ceiling-adjacent routers.


A Second Test: The Actual RAG Chatbot, Not Just the Benchmark

The AT1–AT6 numbers measure raw model capability against static Delphi source files. We also had a second, more production-relevant opportunity: our Chat RAG assistant (a legal/software-support chatbot for our main software, built on the same local-Ollama infrastructure) already has an established evaluation harness — a 555-question set (real user-style questions across the full help corpus), judged against ground-truth source text in five outcome classes (grounded, answered-but-broader, honestly-declined, hallucinated, factually-wrong).

We ran the full 555-question set through the identical RAG pipeline (retrieval, generation, anti-hallucination verification — nothing simplified), once with a3b as the answer-generation model and once with KAT-Coder, judged both times by the same judge (Claude Opus 5, high reasoning effort — a deliberately higher bar than either model being evaluated):

Outcomea3b (n=555)KAT-Coder (n=555)
Grounded268275
Answered (correct, broader than source)101100
Honestly declined132124
Hallucinated2725
Factually wrong2731
“Good” outcomes501/555 = 90.3%499/555 = 89.9%
Critical outcomes54/555 = 9.7%56/555 = 10.1%

The two models are statistically tied on RAG quality. This is worth dwelling on, because an earlier 60-question sample (drawn from the same 555-question set, judged by a weaker model) had shown a clear-looking gap in KAT-Coder’s favor — 91.7% good vs. 86.7% good. The full 555-question run does not confirm that gap: it evaporates, and if anything, a3b answers marginally more questions well. KAT-Coder does hallucinate slightly less (25 vs. 27) but makes more outright factual errors (31 vs. 27), netting out to a wash. The lesson we’re taking from our own mistake here: a 60-question judged sample was enough to produce a confident-looking but wrong directional conclusion, and only the full run caught it. If you’re benchmarking RAG quality on a subsample for cost reasons, treat single-digit-percentage gaps as noise until you can afford the full set.


What This Means in Practice

The static-benchmark advantage (AT2/AT3, and the throughput gain) is real and reproducible: for a pipeline currently using qwen3.6:35b-a3b for comprehension and patch generation, KAT-Coder-V2.5-Dev is worth evaluating as a drop-in replacement — same hardware footprint, meaningfully better AT2/AT3 scores under a strict scoring standard, and faster inference. For routing and tool-calling (AT4/AT5/AT6), the two are interchangeable; pick on other grounds (e.g., whichever is already warm in your model-serving setup).

The RAG-quality advantage, on the other hand, did not survive contact with the full dataset — on the actual production pipeline, judged at a harder bar, KAT-Coder and a3b are statistically tied. If your use case is comprehension or patch-generation work, this data supports switching. If it’s exclusively RAG-chat quality, it does not — and a smaller, cheaper evaluation would have told you the opposite with false confidence.

What this post does not claim: that KAT-Coder is now the best model overall across the full original benchmark field (gemma4, devstral, and the rest were not re-scored in this session under the same strict methodology, so that comparison isn’t available here) — or that it’s a better RAG-chat model, which the full-sample evidence above does not support. The claim is narrower and, we think, more useful: against the specific model the original series recommended for exactly these tasks, an architecture-identical fine-tune measurably improves on it on the static AT2/AT3 benchmark, at meaningfully higher throughput, with no measurable downside or upside on live RAG quality. Three months was long enough for the static-benchmark result to change. If you’re running a3b in production today for comprehension or patching, it’s worth an afternoon to check whether it still should be; if you’re running it purely as a RAG-chat backend, there’s no rush.


Note on the Use of AI: This article was created with the "assistance" of generative AI. The content, technical statements, and conclusions have been reviewed and revised by the author. The author bears full responsibility for the publication.


Tuesday, June 9, 2026

Beyond Simple Prompts: Building an Enterprise AI Toolchain in Delphi

The conversation about AI in software development tends to revolve around prompts. Write a better prompt, get a better answer. Use a smarter model, get smarter code. And for a one-off task, that is entirely true. But when you try to integrate AI seriously into a professional Delphi development workflow — one with hundreds of units, multi-day tasks, multiple concurrent sessions, and real-world complexity — you quickly hit a ceiling that no amount of prompt engineering can break through. The problem is not the model. The problem is the architecture around it.


This post describes what we built to break through that ceiling: a full enterprise backend for AI-driven Delphi development, consisting of a central backend server, a thin MCP proxy layer, and a growing ecosystem of loadable server modules. It also addresses a concern that matters for many professional teams: with local models integrated directly into the backend, agents can do their work without sending any content over the internet at all. It is the result of months of iterative development, and it is currently being prepared for commercial release. If you are just getting started with AI and Delphi, the standalone MCP server versions on our website are a great entry point — their source code is available for purchase, so you can learn the patterns and build your own tools. But if you are ready to go further, this article is about what comes next.

The Architecture at a Glance

Before diving into the individual capabilities, it helps to understand the overall structure. The system has three layers.

At the top sits the AI agent — in our case, Claude Code, though the architecture is model-agnostic. The agent communicates via the standard MCP protocol over stdio, which means it works with any MCP-compatible client without modification.

In the middle is a lightweight proxy process — the only MCP endpoint the agent ever sees. It does not contain business logic. Its job is to translate MCP protocol messages into a compact binary TCP protocol and forward them to the backend server. It also handles reconnection, session registration, and the agent-facing tool list. This separation is deliberate: the proxy is thin and replaceable, the backend is where everything interesting happens.

At the bottom is the backend server itself — a Delphi application that loads a collection of server modules as DLLs, manages sessions, routes tool calls, and maintains shared state. Each module is a self-contained DLL that exposes a defined interface. The server knows how to load them, call them, hot-swap them, and in some cases run them autonomously on a timer.

There are three kinds of modules. Agent-facing modules expose tools that the AI can call directly — everything the agent needs to read, write, compile, debug, search, or communicate. Backend modules run autonomously without direct agent involvement, executing scheduled work independently in the background. Infrastructure modules provide shared services that other modules consume, such as the database connector or the source code formatter. This three-way classification is not cosmetic; it shapes how each module is loaded, called, and lifecycle-managed by the server. The individual modules — and what each one enables — are described in the sections that follow.

One Backend, Many Agents — The Efficiency Argument

In a conventional MCP setup, every agent session starts its own MCP server process. If you have three Claude Code windows open, you have three instances of every MCP server running — three times the memory, three independent states, no shared knowledge between them. For simple tools, this is fine. For a module like the Delphi code analyzer, which parses and indexes source files, loads an abstract syntax tree into memory, and maintains a registry of nodes across a codebase of 1,600 units and 2.6 million lines of code, spinning up a fresh instance per session is simply not practical.

With the backend server, every module loads exactly once. All agent sessions connect to the same backend via TCP, each gets its own session context, but the heavy shared resources — loaded ASTs, database connections, job queues — exist only once. A new session costs a TCP handshake and a session registration record, not a full process with gigabytes of loaded state. This alone changes what is practically possible.

The server also handles thread safety automatically. Each module declares whether it is thread-safe. Thread-safe modules handle concurrent requests directly from multiple sessions. Modules that maintain internal state are protected by a request queue — calls are serialized through a single worker thread transparently, without any change needed in the module itself. The agent never sees any of this; it just calls tools and gets results.

PlanMCP: Persistent Memory Across Sessions

Anyone who has worked seriously with AI coding assistants will recognize this pattern: you start a session, the agent makes good progress, but the context window fills up. You need to start a new session. So you write a summary to a Markdown file, describe where you left off, note the open tasks, and hope you remembered everything important. Then you paste it into the next session and pick up from there. It works, sort of. But it is fragile, manual, and does not scale.

PlanMCP replaces this entirely. It is a backend module backed by a MySQL database with thirteen tables for projects, tasks, decisions, knowledge gaps, sessions, artifacts, and events. When the agent starts a session, it calls the planning menu tool first — it gets back a formatted overview of the current project state: open tasks, the next task ready to start, decisions already taken, and known gaps to work around. No Markdown file, no manual briefing, no hoping nothing was forgotten.

This makes the /clear command trivial. When the context window is getting full, you just clear it. The agent picks up exactly where it left off in the next session, because the actual state — the task list, the progress, the constraints, the open questions — lives in the database, not in the conversation. A restart hint mechanism makes this even smoother: before clearing, the agent writes a short note about what to do next. This note is stored in the database and automatically injected into the initialization response of the next session. The new session's first action is always: read the menu, check for a resume hint, and continue.

Tasks in PlanMCP are not just to-do entries. They have dependencies — a task can be blocked until another is completed. They carry context: code snippets, decisions, constraints, and links to artifacts. They have a full status history. The agent can query the next task that is actually ready to start, given what has already been completed. For larger projects spanning weeks and dozens of tasks, this is the difference between a genuinely productive agent and one that needs constant hand-holding to know where it is.


DelphiMCP: Not Just a Compiler Wrapper

The most common reaction when people hear about an AI-Delphi integration is: "So it can compile code?" Yes, but that is the least interesting part. Let us start with project creation. The Delphi module can create a fully structured new Delphi project from scratch — DPR, DPROJ, all base structures — following the exact conventions the Delphi toolchain expects. The agent does not write a DPR file into a text buffer and hope for the best; it calls a tool that understands what a valid Delphi project looks like and produces one correctly.

The more significant capability is the Abstract Syntax Tree interface. When the agent works with a Delphi unit, it does not receive raw Pascal source code — it receives a structured JSON representation of the syntax tree, with every node carrying a stable identifier. A class declaration, a method body, an interface section, a conditional compilation block: each is a named, addressable node in a tree. The agent can navigate this tree, inspect individual nodes at three levels of detail (compact for orientation, standard for normal work, rich for deep analysis), and perform surgical edits: replace a node, delete a node, insert a node before or after another. These operations are precise and unambiguous. There is no "find line 847 and replace the third occurrence of this string." There is "replace the node with this identifier," and it works.

This matters enormously at scale. A codebase with 1,600 units and 2.6 million lines cannot be navigated by reading files. It can be navigated by querying a structured index: find all types that descend from TComponent and have no override of BeforeDestruction, find all methods that call a deprecated API, and find all units in the Uses chain of a given file. These are real queries that the module can answer without the agent ever seeing a single line of source code it did not ask for.

LSPHandler: The IDE's Own Semantic Engine

The AST interface gives the agent structural access to source code: navigate the tree, inspect nodes, and make surgical edits. What it does not provide is semantic understanding — the difference between knowing that a variable named Sender exists and knowing that it is of type TObject in this specific call context, or knowing not just where a method is declared but where it is actually defined in the inheritance chain. For that level of understanding, there is only one authoritative source: the Delphi compiler itself.

The LSPHandler module connects to Delphi's own language server — the same process the IDE uses internally to power code completion, hover tooltips, and go-to-definition navigation. When the agent opens a project through this module, the language server starts in the background and processes all project units using the real Delphi compiler. From that point on, every query the agent makes is answered by the same semantic engine that answers your questions when you hold Ctrl and hover over a symbol in the IDE.

The hover tool returns the type and symbol information for any position in a source file — identical to what appears in the IDE tooltip when you move the mouse over an identifier. The definition and declaration tools resolve a symbol at a given position and return the file path and line number where it is defined or declared, following the full inheritance and unit resolution chain. The symbols tool returns a hierarchical symbol tree for an entire source file — every class, method, property, field, constructor, and constant, structured the same way the IDE's structure view organises them. The diagnostics tool retrieves the compiler's error and warning output after background compilation, with the same precision as a full build in the IDE.

The two modules complement each other cleanly. The AST interface is used when the agent needs to edit code: navigate the tree, find the right node, and replace it precisely. The language server is used when the agent needs to understand code: resolve a type, find the real definition, and confirm that a change is semantically correct. Together, they give the agent the same combination of capabilities a developer has in the IDE — structural editing through the code model, semantic understanding through the compiler.

DebuggerMCP: The Agent Steps Through Code

This is the capability that tends to produce the strongest reaction in experienced Delphi developers: the AI can debug. Not simulate debugging, not guess at what a debugger would say — actually control a live Delphi debug session through the IDE's own debugger interface, the same one you use when you press F9 to start the program, F7 to step into a call, F8 to step over a statement, and F4 to run to the cursor.

The architecture behind this involves two components. A small IDE package registers itself as a TCP server inside the running Delphi IDE, listening for commands from outside. The debugger module in the backend server acts as a TCP client to that package. When the agent calls a debugger tool, the backend relays the command through the TCP connection to the IDE plugin, which executes it against the live Delphi debugger API — the same IOTADebuggerServices interface that the IDE itself uses internally. The response travels back the same way.

The agent has access to the full range of debugger operations: open a project, set a breakpoint at a specific file and line, optionally with a condition or pass count, start the debugger, wait for execution to stop, read the value of any variable in scope, step over or step into the current statement, continue execution, and stop the debugger. It can also list all active IDE instances — if you have Delphi 2007 and Delphi XE open at the same time, the agent can discover both and choose which one to target. The IDE plugin supports both versions, because the underlying debugger interface has been available since Delphi 2007.

One scenario that illustrates the multi-IDE capability particularly well is cross-version debugging. Imagine a legacy Delphi 2007 executable that calls into a modern Delphi 13 DLL. Both are running in their respective IDEs simultaneously. The agent calls the session discovery tool, receives a list of both active IDE instances with their version identifiers, and can attach to either one independently. It sets a breakpoint in the Delphi 2007 EXE at the point where it calls into the DLL, steps into that call, and then switches its attention to the Delphi 13 IDE session to inspect the state inside the DLL. Following a call across a version boundary — something that is genuinely awkward to do manually — becomes a routine operation. The agent does not care that the two binaries were compiled seventeen years apart; it just follows the execution.

To make this concrete with a simpler example, we verified the full flow end-to-end. The agent opened a project, set a breakpoint with a condition that fires only when a specific variable has a specific value, started the debugger, waited for the breakpoint to be hit, read the variable value to confirm the condition, stepped over a statement, read the variable again to verify the updated value, and then stopped the session. Every step of that sequence is something a developer does manually today. The agent did it without being told which file to open or where the bug might be — it reasoned about the code via the AST module, formed a hypothesis, and verified it through the debugger.

Multi-Agent Coordination: The Intent Lock Protocol

When multiple agent sessions work on the same project simultaneously — one writing code, another running analysis, a third tracking tasks — they need a way to coordinate without stepping on each other. The backend provides a cooperative locking mechanism called the intent lock. An agent declares its intent for a resource (typically a project working directory), acquires the lock, does its work, and releases it. Other agents watching the same resource are notified when the lock state changes. If an agent crashes or its session ends abnormally, the lock expires automatically after a configurable timeout, so the resource never stays blocked indefinitely. This is agent-to-agent coordination at the protocol level, without any external orchestration tool.

Tool Names Optimized Per Model

One of the less obvious findings from our benchmarking work — described in the earlier posts in this series — is that different AI models respond differently to the same tool names and descriptions. A name that is intuitive to a large frontier model may be ambiguous to a smaller local model. A description optimized for Claude reads differently to an Ollama-hosted model. This is not a hypothetical concern: in our benchmark across 198 tools, we found 75 meaningful differences between what Opus and Sonnet considered the clearest way to name and describe the same tool.

The system handles this through a model identity mechanism. When a session starts, the agent identifies itself with its model name. From that point, every tool list request is answered with names and descriptions tuned for that specific model. The translations are defined inside each module DLL — the module author knows their tools best and maintains the per-model variants alongside the rest of the module code. The backend server passes the model identity through to the module and delivers whatever the module returns. No central mapping file, no configuration outside the codebase.

The tool list itself is also structured differently from a flat list of tools. Tools are organized into groups. On first connection, the agent receives only the group overview — the categories of available functionality, not every individual tool. It can load a group when it needs those tools, and within a group, it can request either a minimal set of the most essential tools or the full extended set. This keeps the context window impact of tool discovery proportional to what the agent is actually doing.

Keeping the Context Window Free

A system with nearly 200 tools across a dozen modules could easily overwhelm an agent's context window before it has written a single line of code. The proxy architecture solves this through lazy loading. When a session starts, the agent receives only a compact group overview: the categories of available functionality, not the individual tools within them. The agent loads a group only when it actually needs those tools. Within a group, it can choose between a minimal set covering the most common operations and a full extended set. Context window impact scales with what the agent is doing, not with the total size of the toolchain.

A practical example: an agent that only needs to edit a Word document loads the document tools and nothing else. It has no knowledge of the compiler, the debugger, or the Jira integration — and it does not need to. Those modules exist on the server, ready to be called, but they take up no space in the context window until they are needed. This design principle — expose what is necessary, hide what is not — is what makes it feasible to have a rich, deep toolchain without paying a constant context window tax on every session.

The same principle applies at the individual tool level. Every tool in the system has an explain function — a built-in mechanism that returns a detailed description of exactly how to call that tool, including concrete examples. Instead of front-loading the agent with exhaustive documentation at startup, the agent can query the explanation for any tool on demand, precisely when it is about to use it. This keeps the tool descriptions accurate, contextual, and out of the way until they are actually needed.

Local Models as First-Class Citizens

Cloud AI is excellent for interactive work — it is fast, capable, and handles complex reasoning well. But it is not free, it is not private, and it is not well-suited for batch processing thousands of items. For teams where it matters that no source code, business logic, or project content is sent outside the local network, local models running on your own hardware are the right answer. There is no external API call, nothing logged on a third-party server, and no dependency on an internet connection during inference. For tasks like populating a code knowledge base with summaries and analysis of 1,600 Delphi units, a local model running on your own hardware is the right tool: no per-token cost, no data leaving your network, and it can run overnight while you sleep.

The Ollama integration module treats local inference as a proper backend service, not an afterthought. It connects to a local Ollama instance, maintains an asynchronous job queue for batch work, and includes a GPU guard that monitors actual GPU utilization before starting any inference. If the GPU is busy — because you decided to play a game or run another process — new batch jobs wait rather than starving your system. When GPU headroom becomes available, the queue resumes automatically. A separate model unload tool frees VRAM on demand when you need it for something else.

The split between interactive and batch inference is clean: the AI agent uses cloud models for real-time reasoning and tool calls, while the Ollama module handles background batch jobs asynchronously. The agent submits a job and moves on. The backend processes it when resources allow and stores the result. No blocking, no wasted context window, no timeout errors on long-running generations. The entire configuration — which model, which endpoint, GPU threshold, retry interval — lives in a single INI file section and can be changed without recompiling anything.

Remote Deployment and Direct Database Access

The proxy and the backend server do not have to run on the same machine. A relay interface module transparently forwards TCP connections to a backend server running on a different host — a more powerful build server on the local network, a team server accessible to multiple developers, or a cloud VM. From the agent's perspective, nothing changes; it connects to the same proxy and gets the same tool interface. The relay handles the routing. TLS support is built in for deployments where the connection crosses a network boundary that requires encryption.

Database access follows the same injection model as everything else in the system. A MySQL connector module is loaded once by the server, and its interface is injected into any other module that declares a need for it. The modules that use the database — the planning system, the blog publisher, the code knowledge base, the feature request tracker — all receive the same shared connection without knowing or caring how it was established. Schema management is self-contained: each module creates its own tables if they do not exist, using a consistent naming convention. There is no separate migration tool, no schema file to manually apply. Deploy the module, start the server, and the tables appear.

Closing the Loop: Jira, Outlook, and Word

Software development does not happen in a code editor in isolation. Some tickets need to be read and updated. There are emails from stakeholders that describe requirements or report problems. There are Word documents with specifications, release notes, and design decisions that need to stay in sync with the code. Every time these things live in separate tools that the AI cannot reach, the developer becomes the manual bridge — copy a ticket description into the chat, paste the AI's output back into the document, and summarize the email manually. The workflow integration modules eliminate that bridge.

The Jira module gives the agent direct access to your issue tracker. It can read tickets, analyze their content, assess scope and risk, create new issues, update status, and link related items — all without leaving the coding session. When the agent finishes implementing a feature, it can close the ticket that requested it. When it encounters a problem that should be tracked separately, it can open a new one.

The Outlook module integrates email into the same workflow. The agent can read incoming messages, understand their context in relation to the current project, compose replies, manage folders, and handle attachments. For developers who receive bug reports or requirements by email — which is most of us — this means the agent can act on that information directly rather than waiting for a human to relay it.

The Word module — the one used to write this very document — gives the agent structured access to Word files. It can create documents, add and edit paragraphs with full formatting control, insert tables, manage headers and footers, replace specific text ranges, and work with the document's paragraph structure by stable identifier rather than by line number. Specifications, release notes, API documentation, design decisions: anything that lives in a Word document becomes part of the same connected workflow. When code changes, documentation can change with it, in the same session, without a copy-paste step in between.

To illustrate the combined workflow, an agent receives a Jira ticket describing a reported bug. It reads the ticket, queries the AST module to find the relevant code path, forms a hypothesis about the cause, sets a conditional breakpoint via the debugger module, steps through execution to confirm, makes the fix via the AST mutation interface, compiles and verifies, updates the Jira ticket as resolved, and appends a note to the release notes document in Word. That is an end-to-end development cycle — from bug report to resolved ticket and updated documentation — with the developer reviewing and approving, but not manually executing any of the individual steps.

What This Actually Enables

There is a pipeline running in this system right now that requires no human interaction to operate. A scheduler fires at a configured time. It triggers an orchestrator module, which picks up pending analysis jobs. The orchestrator dispatches those jobs to the Ollama inference module, which processes them against the local model and stores the results in the code knowledge database. The database grows. The next time the agent needs to understand a part of the codebase, the answers are already there. This does not involve a prompt. It does not require a developer to be at their desk. It is software doing what software should do: running reliably in the background, building something useful.

There is also a feedback channel built in at the tool level. When an agent encounters a situation where the right tool does not exist — the capability it needs is not exposed by any current module — it can file a feature request directly through a built-in mechanism. The request is stored in the database, categorized automatically, and appears in the planning system, where a developer will see it. The agent is not just using the toolchain; it is actively contributing to its own improvement.

None of this is achievable with prompts alone, no matter how carefully crafted. Prompts are ephemeral. They vanish when the context window clears. They cannot set a breakpoint. They cannot remember last week's architectural decision. They cannot trigger at 3 AM when no one is watching. The real shift in capability comes not from smarter prompts but from treating AI as a component in a proper software architecture — one with persistent state, typed interfaces, modularity, lifecycle management, and integration with the real tools that development actually depends on.



Getting Started

If you are new to AI-assisted Delphi development and want to understand the foundations, the standalone MCP server versions on our website are the right starting point. Each one is a self-contained server covering a specific area of functionality. Their source code is available for purchase, designed to be readable and instructive — a solid foundation for understanding how MCP servers work in a Delphi context and building your own.

The enterprise backend server with the full DLL module ecosystem — the system described in this post — will be available for purchase soon. If you are interested, feel free to get in touch via the contact form on this blog — I am happy to answer questions and discuss what fits your situation.

The benchmark series that started this blog explored which AI models perform best on Delphi tasks. This post describes the infrastructure that puts those findings to work. The next step — a code knowledge base that lets any agent navigate a million-line legacy codebase without reading a single file — is already in progress. We will write about that too, when it is ready.


Note on the Use of AI: This article was created with the "assistance" of generative AI. The content, technical statements, and conclusions have been reviewed and revised by the author. The author bears full responsibility for the publication.


Friday, June 5, 2026

Local LLMs for Delphi: A Production Benchmark — Part 3: What to Actually Use

This is the final post in a three-part series. Part 1 covered benchmark design and methodology. Part 2 covered what the numbers revealed. This post covers what you should actually do with those results.

After running 8 local models through 5 benchmark phases on 30 real Delphi production units, the most useful thing I can offer is not another table of scores — it is a set of concrete decisions. If you are planning to integrate local LLMs into any Delphi pipeline — migration, code review, documentation, or IDE integration — this post tells you which model to use for each job, which two to skip entirely, and where the remaining rough edges are.

Pick the Right Model for the Right Job

The clearest finding from this benchmark is that no single model dominates across all five task types. The right approach is task routing — matching each class of work to the model best suited for it.

Code Analysis / Risk Discovery (AT1)

Use gemma4:26b (score 0.96) for targeted fact extraction. Use qwen3.5:27b (0.86) or qwen3.6:27b (0.82) when you need coherent functional explanations.

Code Comprehension and Q&A (AT2)

Use qwen3.6:27b (score 0.70) or qwen3.5:27b (0.69). Both score significantly above the field.

Patch Generation / Code Writing (AT3)

Use gemma4:26b (score 0.88, 170 tok/s) as your primary patcher — add format validation for the 70% non-compliant responses. Use qwen3.5:27b (0.77) as a fallback when compliance matters more than speed.

Routing / Complexity Classification (AT4)

Use qwen3.6:35b-a3b (score 1.00, 131 tok/s). Perfect routing accuracy combined with MoE speed. Avoid qwen3-coder:30b (0.71) — inconsistent classification defeats the router.

Tool Calling / IDE Integration (AT5)

Only: devstral, qwen3.5:27b, qwen3.6:27b, qwen3.6:35b-a3b, gemma4:26b. Hard disqualified: qwen2.5-coder:14b, qwen3-coder:30b, deepseek-r1:8b — API-level failure, not fixable through prompting.


The Routing Pipeline Architecture

The single most impactful design decision is the batching strategy. GPU model loading takes 30–60 seconds per swap. Batch by tier:

Incoming task
        |
   Router model (qwen3.6:35b-a3b, fast)
        |
   +--------------------+--------------------+
   |                    |                    |
 local              mid-tier              complex
gemma4:26b        qwen3.5:27b          qwen3.6:27b
(fast, high vol) (reliable, balanced)  (best understanding)

For a batch of 50 units across three complexity tiers, batch-by-tier scheduling can eliminate 45+ minutes of pure idle time.


The Context Window Problem: Why a Proxy Layer Is Not Optional

Model selection and batching strategy are the two decisions this benchmark directly informs. But there is a third decision that matters at least as much for local deployments, and it has nothing to do with which model scores highest on any phase.

If you are running a real development pipeline, you are not running one MCP server. You are running several. A realistic Delphi development setup includes a source analysis server (DelphiMCP: ~40 tools), a document server (WordMCP: ~40 tools), a code indexer (PasIndexer: ~15 tools), a file editor (StrEditor: ~15 tools), and a handful of supporting services. That is well over 150 tool declarations present in context before a single line of code is analyzed.

Each tool declaration — name, parameter schema, description — costs between 150 and 300 tokens. At 150 tools, you are looking at 22,000 to 45,000 tokens of overhead at the start of every session. For a local model running with a 32k context window, that is between 70% and 140% of the available context consumed before the first user message arrives.

VT5 makes this worse, not better. The benchmark showed that local models score only 1–34% on Phase 2 of the tool-name test — they cannot reliably interpret a tool's purpose from its name alone. They depend on the description field in the schema. Compressing tool declarations to save context is not an option; the descriptions are load-bearing for local models.

ProxyMCP addresses this directly. As a single MCP endpoint, it does not expose all tools from all servers to the model. It exposes only the tools relevant to the current task — typically 3 to 8 — and routes the call to the appropriate backend server. From the model's perspective, the tool surface is minimal and always task-scoped. From the pipeline's perspective, every server is still fully available.

The practical effect: a local model operating through ProxyMCP sees a context overhead of roughly 500–1,500 tokens for tool declarations rather than 22,000–45,000. That difference is the difference between a model that has room to reason and a model that is fighting for context from the first token. For cloud models, the same architecture translates directly into cost savings — fewer tokens declared means fewer tokens billed, on every single request.


Decision Table (32 GB VRAM Setup)

Use caseRecommended modelSpeedNotes
High-volume patchinggemma4:26b170 tok/sAdd format validation layer
Code understandingqwen3.6:27b28 tok/sBest comprehension overall
Routing decisionsqwen3.6:35b-a3b131 tok/sPerfect routing + fast
Balanced all-rounderqwen3.5:27b29 tok/sStrong across all phases
Tool/IDE integrationqwen3.6:35b-a3b131 tok/sBest speed + tool support

What About Cloud?

I ran the full five-phase evaluation on Claude Sonnet 4.6 via the Anthropic API for comparison. Where does cloud actually outperform local?

PhaseBest localSonnet 4.6Delta
AT1 Code Detective0.96 (gemma4)0.983+0.02 ≈ 0
AT2 Comprehension0.70 (qwen3.6:27b)0.983+0.28 ← gap
AT3 Patches0.88 (gemma4)0.955+0.08
AT4 Routing1.00 (multiple)0.994≈ 0
AT5 Tool Calling1.00 (multiple)1.000

Practical conclusion: Local models are at parity with cloud on extraction, routing, and tool calling. The 28-point comprehension gap (AT2) is the only argument for selective cloud use. For GDPR(DSGVO)-constrained teams, local-only remains viable. For hybrid architectures, route comprehension to Sonnet and keep everything else local.


The Realistic Assessment

Local LLMs are genuinely useful for Delphi development — as a force multiplier for analysis and mechanical transformation phases. What works: breaking work into distinct task types, batching by model, adding format validation, using comprehension models for risk analysis first, and treating tool calling as a hard capability requirement.

The models that fit this architecture on a 32 GB setup — qwen3.6:27b for understanding, gemma4:26b for patching, qwen3.5:27b as the balanced mid-tier, qwen3.6:35b-a3b for routing and tool calls — are capable enough to make local-only, GPU-resident Delphi LLM pipelines a practical option today.


Note on the Use of AI: This article was created with the "assistance" of generative AI. The content, technical statements, and conclusions have been reviewed and revised by the author. The author bears full responsibility for the publication.