Skip to content

nostdahl.com

A technology journal
Article archive
← All articles
AI

Agentic Coding on a Twenty-Year-Old Delphi Codebase

Bjørn T. Nøstdahl

~6 min read

When the BELA codebase arrived in 2022, it came as source files on a NAS. No version control, no backlog, no tests, and a product that customers across Europe depended on every day. The first two years went into the unglamorous work of fixing that: Git, Azure DevOps, a structured backlog, CI builds, unit and integration tests, and Playwright running end-to-end against the React frontend. I wrote about the habits in DevSecOps for Delphi. This article is about what came next: coding agents, on a Delphi codebase, with a team spread across four countries.

What "agentic" actually means

Autocomplete suggests the next line. An agent is given a task, reads the repository, edits files, runs the compiler and the tests, reads the errors, and tries again until the task is done or it gives up. The difference is the loop. A model that cannot run the build is guessing; a model that can is doing something closer to what a developer does on their first day in a new codebase, only faster and without the coffee.

Our agents run on developer machines and in the pipeline, against the same repository, the same build scripts and the same tests as a human. That last part is the whole point, and I will come back to it.

Why Delphi turned out to be a decent fit

I expected the language to be a problem. There is far less Object Pascal on the internet than TypeScript, and the models know fewer idioms. Two things made up for it.

The first is that Object Pascal is explicit. A unit declares its public surface in the interface section, types are strong, and the compiler refuses a lot of nonsense that a dynamic language would run. An agent working on a Delphi unit gets a precise, immediate signal when it is wrong, and it gets it from the compiler rather than from a reviewer three hours later.

The second is that the codebase is old in a good way. Twenty years of one team's conventions are remarkably consistent, and the agent picks them up from a handful of examples. Where a React codebase has five ways of doing everything, this one has one, and the agent follows it.

What does go wrong is predictable. The model occasionally invents a VCL method that sounds right and does not exist, or reaches for a Free Pascal idiom in Delphi code. The compiler catches all of it, which is why the agent must have the compiler. An agent writing Pascal without a build step is a liability.

What we let the agents do

We started with tasks where the cost of a mistake is low and the verification is mechanical.

Tests first. The codebase had none worth the name. Agents wrote unit tests for units that had never been tested, and Playwright tests for the frontend flows the support team said broke most often. A human reviewed every test for whether it tested the right thing; the agents did the typing. Within months we had coverage we would not have had by the end of the decade otherwise.

The database migration. Moving from Firebird to Microsoft SQL Server, with both still supported, meant translating a very large number of queries between two SQL dialects. This is tedious, pattern-heavy work with an obvious test: does the query return the same rows on both engines? Agents did the translation, the test harness ran it on both databases, and humans reviewed the ones that disagreed.

OpenAPI. Every REST API got an OpenAPI description, the subject of the REST article. Writing the descriptions from the existing endpoints is exactly the kind of exhaustive, structured task an agent does well and a human does badly on a Friday afternoon.

Archaeology. "Explain what this unit does and who calls it" is the question every new developer asks and nobody has time to answer. The agent answers it in a minute, with references, and is right often enough to be the first stop. The Kyiv and Sofia developers, who joined long after the code was written, use this more than anything else.

Refactoring under test. Once a unit has tests, an agent can restructure it with a safety net. Without tests we do not let it touch anything, which turned out to be a strong incentive to write tests.

What we do not let them do

Some of the code in this product decides whether a safe deposit locker opens. There are rules.

  • Security-critical logic is written by people. Agents can read it, explain it and write tests against it. They do not change it.
  • No secrets in the context. Connection strings, keys and customer data never go into a prompt. The agent works on a repository that does not contain them, which the DevSecOps work had already ensured.
  • No direct pushes. Agents work on branches and open pull requests. A pull request from an agent is reviewed exactly like one from a developer in their first month: carefully, and with the assumption that it is confidently wrong somewhere. I wrote about that confidence in 2023, and nothing since has changed my mind.
  • The pipeline decides. Build, tests, static checks and leak detection run on every pull request, whoever wrote it. The agent does not get a shortcut, and that is what makes the whole thing safe enough to use.

The instructions file

Every repository has an instructions file for agents at its root. Ours is short and boring: how to build each project from the command line, where the tests are and how to run them, the coding conventions that matter, which directories are off limits, and one rule in capitals: run the tests before you say you are done. The same file is useful for humans joining the team, which tells you something about what good onboarding documentation should have looked like all along.

What changed for a distributed team

With developers in Bari, Sofia, Kyiv and Gothenburg, a lot of work used to wait for someone in another time zone to answer a question. Agents absorbed much of that: the question about what a unit does, the first draft of a test, the translation of a query. Lead time on small changes dropped. Review load went up, because there are more pull requests, and the reviewers are now the bottleneck. That is a better bottleneck than the one we had.

The honest cost is attention. Reviewing agent output is a skill, and it is tiring in a way that writing code is not. We rotate it, and we keep agent pull requests small on purpose.

Where this goes

The agents that read code are the same technology that can read logs, tickets and device status. Giving them access to those, safely, is a different problem with its own rules, and it is the subject of a later article.

Questions for you

  • Have you used a coding agent on a language with little public training data? What did it get wrong, and how did you catch it?
  • Where is your line between what an agent may change and what only a human writes?
  • Has the review load replaced the writing load on your team, and what did you do about it?

Email me at bjorn.nostdahl@nostdahl.com. I am collecting instruction files; if yours is better than ours, I want to see it.

Continue exploring
← All articlesMore in AI →