Become a member!

Delphi AI Skills: correct Delphi code from AI agents

🌐
This article is also available in other languages:
🇮🇹 Italiano  •  🇪🇸 Español  •  🇩🇪 Deutsch  •  🇧🇷 Português

Code has become cheap to write. What costs now is getting it written well: delphi-ai-skills teaches AI agents (Claude Code, Codex, Cursor, Gemini) the real DelphiMVCFramework API, with directives, style, security and tests built in.

Logo of the open source project delphi-ai-skills: skills for AI coding agents dedicated to DelphiMVCFramework.

As of today delphi-ai-skills is public: a set of open source skills (Apache-2.0) that teach AI coding agents to write DelphiMVCFramework code that compiles, follows the right conventions and is secure from the start. It works with Claude Code, Codex, Cursor, Gemini CLI, Windsurf, Continue and with any other tool able to read instructions written in Markdown.

In short

  • What it is: seven open source skills (Apache-2.0) that teach an AI agent the real API of DelphiMVCFramework 3.5.0, instead of leaving it to guess.
  • Who it is for: anyone writing REST APIs, Minimal APIs or web apps in Delphi with the help of an AI agent.
  • Supported agents: Claude Code, Codex, Cursor, Gemini CLI, Windsurf, Continue, and any agent that reads Markdown.
  • Installation: git clone, then install_in_claude.bat (or _codex, _cursor, _gemini).
  • What changes: no more MapGroup in place of Prefix, no ToFree freeing the returned object a second time, endpoints filtered on the authenticated user and real DUnitX integration tests.
  • Repository: github.com/danieleteti/delphi-ai-skills

Code has become cheap. Direction has not.

For thirty years the expensive part of the job was typing. You had the design in your head, and the bottleneck was turning it into lines: writing the controller, the DTO, the tests, the boilerplate, the CRUD. Whoever produced more correct lines per hour was worth more.

That scarcity is over. Today an AI agent writes me a full CRUD controller, tests included, in thirty seconds. It is no longer a conference novelty: it is a normal day for anyone programming in 2026. And when something becomes abundant, value moves elsewhere.

It moves to direction. If code is cheap to produce, then the interesting question is no longer “how long does it take you to write it”, but:

  • does it follow our conventions, or does it invent new ones every session?
  • does it use the API the framework actually has, or the one the agent imagines the framework has?
  • is the endpoint it just generated filtered on the authenticated user, or does it read the order of whoever passes an ID in the URL?
  • the code I just accepted with a single Tab keystroke, who maintains it two years from now?

The risk of cheap code is exactly this: producing a lot of mediocre code very fast, plausible when you read it and wrong in the details, each piece with its own little personal style. An agent left to its own devices is extremely fast at building technical debt.

The answer is not to slow the agent down. It is to guide it: give it directives, operating modes, a house style and security rules that are built in, so that the right road is also the default road. That is exactly the job skills do.

What is a skill for an AI agent

A skill is a Markdown file that the agent loads on demand, only when your request matches its description. All that sits in the agent’s context is the name and one line of description, for each installed skill. When you ask “create a DMVCFramework controller”, the agent recognises the match and only then opens the file, which explains the real API of the framework: the exact unit names, the attributes, the object ownership rules, the idioms, the known traps.

Diagram: the user request is matched against the skill descriptions, the agent loads SKILL.md and opens the reference files only when needed; the security skill always kicks in when there is input from the outside.
Loading on demand: the heavy documentation comes into play only when the task calls for it.

It is a simple mechanism, but with a precious property: the context window stays free. The main skill, dmvcframework, keeps the operational core in its SKILL.md (bootstrap, controllers, responses, ownership, middleware, typical mistakes) and moves the heavy material into reference/ files that the agent opens only if the task requires it: the ActiveRecord ORM, validation, the dependency injection container, the backend servers, dotEnv, SSE.

The concrete problem: plausible, uncompilable Delphi code

Ask an agent without skills for a Minimal API with DelphiMVCFramework. You will get code that is well written, well indented, well commented, and that does not compile.

Comparison between Delphi code generated by an agent without skills (MapGroup, OkResponse, ToFree: invented names and a double free) and the code generated with the skills (Prefix, Ok, correct ownership).
Same request, same model. On the left the names the framework would have if it were ASP.NET, on the right the ones it really has.

The wrong names are not random: they are the ones the framework would have if it were ASP.NET Core, because the model has read millions of lines of ASP.NET Core and far fewer of DelphiMVCFramework. So out comes MapGroup instead of Prefix, OkResponse instead of Ok, middleware classes that do not exist, and a ToFree around the returned object that frees it twice, because the framework already takes care of freeing it.

There is one question that unmasks an uninstructed agent in ten seconds:

In a DelphiMVCFramework functional action, should I use ToFree on the object I return?

The right answer is no: the framework frees the returned object, and ToFree would free it a second time. An agent without skills usually answers yes, and its code does a double free.

Every skill in the project was born by first reproducing the mistake: you make the agent fail, you document the API the framework really has, you run the same task again and check that it now gets it right. No API name written from memory: everything verified against the sources and the samples of DelphiMVCFramework 3.5.0 (silicon).

The seven skills for DelphiMVCFramework

Skill What it covers
dmvcframework The core: engine and backend server bootstrap, controllers and functional actions, routing attributes, IMVCResponse, object ownership rules, the ActiveRecord ORM, the Repository pattern, the dependency injection container, validation, middleware, JWT, SSE, dotEnv configuration.
dmvcframework-minimal-api Minimal APIs: routes as anonymous methods, with no controller classes. Route groups (Prefix, MapGet/MapPost/MapMethods), type-driven argument binding, file uploads, endpoint filters and HTTP filters, .AsWeb for TemplatePro/HTMX handlers.
dmvcframework-webapp Server-side web apps: TemplatePro templates (inheritance, blocks, filters, partials), fragments, ViewData and its ownership rules, cookie/JWT login, static files, the HTMX helpers on the Delphi side.
dmvcframework-ui The presentation layer the wizard generates: Bootstrap 5.3, the blocks of baselayout.html, the brand tokens in style.css, dark/light mode with data-bs-theme, toasts, the HTMX activity classes.
dmvcframework-security Secure coding, with the DelphiMVCFramework API that implements each control: access control and IDOR, mass assignment, SQL injection, XSS in TemplatePro, CSRF, path traversal, uploads, SSRF and open redirect, security headers, JWT hardening, secret management.
dmvcframework-testing Integration tests and unit tests: in-process IMVCServer with IMVCRESTClient and DUnitX, test patterns for CRUD, authentication and authorization, database fixtures.
htmx-skill An index of every page of the official htmx.org documentation, each with its own description, so the agent goes and reads the right page instead of remembering htmx its own way.

They are complementary: the Minimal API and web app skills take the core for granted for entities, validation and DI, and the web app skill delegates markup to dmvcframework-ui and htmx syntax to htmx-skill instead of duplicating them. Install them all: it is the agent that picks the right ones.

If you are not familiar with the topics the skills teach the agent, here is the human starting point: the Minimal APIs in DMVCFramework, the Delphi web apps with TemplatePro and, on the AI side, the MCP server for DMVCFramework with its agent. The skills are the missing piece: they do not give the agent new tools, they give it the knowledge to use well the ones it already has.

Security: not a final check, but a precondition of the generated code

This is the part I care about most, and the reason why dmvcframework-security is not a skill you invoke at the end of the job, but a mandatory dependency of the other server skills: any endpoint receiving input from a client pulls it in.

Code generated in a hurry always gets the same points wrong. The most common, and the most serious, is IDOR: the endpoint that trusts the ID in the URL.

// WRONG: any authenticated user can read anyone's orders
function TOrdersController.GetOrder(const ID: Integer): IMVCResponse;
begin
  Result := OKResponse(TMVCActiveRecord.GetByPK<TOrder>(ID));
end;

The ID comes from the URL. Nothing ties it to whoever is calling. The rule the skill imposes on the agent is that the ownership check must be the query itself, not an if bolted on afterwards:

function TOrdersController.GetOrder(const ID: Integer): IMVCResponse;
var
  lOrder: TOrder;
begin
  // the ownership check IS the query
  lOrder := TMVCActiveRecord.GetOneByWhere<TOrder>(
    'id = ? and customer_id = ?', [ID, CurrentUserID], False);
  if lOrder = nil then
    Exit(NotFoundResponse);      // same answer as "not yours": do not leak existence
  Result := OKResponse(lOrder);
end;

The difference between the two versions is three lines. But the first one is a data breach waiting to happen, and in a world where code is accepted with a Tab keystroke, it is the version that ends up in production. A rule written into a skill is worth far more than a rule written into a checklist nobody rereads: the skill is applied while the code is being born, not afterwards.

The same goes for style and for project directives. The skills do not just teach “what this function is called”: they teach how we work here. For instance, they require starting from a project created with the IDE wizard, instead of inventing a half correct bootstrap from scratch, and respecting the backend server the project already has: if you are running under WebBroker with ISAPI or Apache, the skills recognise it, keep it and never propose a migration.

Tests: the counterweight to cheap code (DUnitX and IMVCRESTClient)

If there is one thing the era of abundant code makes more important, not less, it is tests.

The reasoning is simple. Before, you wrote the code yourself: you had it in your head line by line, and you could afford (wrongly, but you could) to trust that understanding. Today the code arrives from an agent, in quantity, and you skim it. The only thing that tells you whether that code really does what you think is no longer your reading: it is a failing test. The test has become the place where your intention lives, and the code is just the way, cheap by now, of satisfying it.

And there is a second, even more practical reason: the test is the only language in which the agent can check itself. An agent with a suite to run closes the loop (I write, I compile, I run the tests, I fix) without you having to act as a human compiler on every lap. Without tests, the agent has a single source of truth: you.

That is why dmvcframework-testing is not a side skill. It teaches the agent the real test stack of DelphiMVCFramework: DUnitX as the runner, an in-process IMVCServer (Indy Direct) and IMVCRESTClient as the fluent HTTP client. No external server to start, no WebModule: the server is born and dies inside the test.

function StartTestServer(APort: Integer; out AEngine: TMVCEngine): IMVCServer;
begin
  AEngine := TMVCEngine.Create(
    procedure(Config: TMVCConfig)
    begin
      Config[TMVCConfigKey.DefaultContentType] := TMVCMediaType.APPLICATION_JSON;
    end);
  AEngine.AddController(TCustomersController);

  Result := TMVCServerFactory.CreateIndyDirect(AEngine);
  Result.Listen(APort);
end;

Notice a detail that is worth the installation of the skill all by itself: this works even if the production application runs as an ISAPI under IIS or as an Apache module. The host is not what you are testing, the engine, the controllers and the middleware are. An uninstructed agent, asked for tests on a WebBroker project, answers by trying to start IIS.

Integration tests are real HTTP against the in-process server, and the skill requires asserting on the status code and on the body, not on the fact that the call did not blow up:

procedure TCustomersTests.Create_Returns201_And_Location;
var
  lResp: IMVCRESTResponse;
begin
  lResp := FClient.Post('/api/customers',
    '{"firstName":"Daniele","lastName":"Teti","email":"d.teti@example.com"}');

  Assert.AreEqual(HTTP_STATUS.Created, lResp.StatusCode);
  Assert.IsNotEmpty(lResp.HeaderValue('Location'));
end;

procedure TCustomersTests.Post_Invalid_Email_Returns422;
var
  lResp: IMVCRESTResponse;
begin
  lResp := FClient.Post('/api/customers',
    '{"firstName":"Bob","lastName":"X","email":"not-an-email"}');
  // validation failed => 422, and the action never ran
  Assert.AreEqual(HTTP_STATUS.UnprocessableEntity, lResp.StatusCode);
end;

But the part I really care about is another one, and it is the point where the testing skill and the security skill shake hands. Test the lock, not just the key. For every protected resource, the test that matters is not the one checking that the owner gets a 200: it is the one checking that anyone else does not get there.

procedure TSecureTests.NoToken_Returns401;
begin
  // the test that catches the [MVCRequiresAuthentication] you forgot
  Assert.AreEqual(HTTP_STATUS.Unauthorized, FClient.Get('/api/admin/stats').StatusCode);
end;

procedure TOrdersTests.User_Cannot_Read_Another_Users_Order;
begin
  // order 1 belongs to user1
  FClient.SetBearerAuthorization(TokenFor('user2'));
  // 404, not 403: a 403 would confirm the order exists
  Assert.AreEqual(HTTP_STATUS.NotFound, FClient.Get('/api/orders/1').StatusCode);
end;

This is the IDOR test, that is to say exactly the hole from the previous section, caught in three lines. It is also the test almost nobody writes, because a 200 for the owner gives the reassuring feeling that the endpoint “works”. The rule the skill imposes on the agent is sharp: for every verb of every resource that has an owner, there must be a test proving that another user is rejected. A well filtered GET and a wide open DELETE are still a data breach.

The whole point is here: by asking “write the integration tests for the Customers endpoints, including the authorization cases”, the agent does not just produce tests, it produces the tests you would not have felt like writing. And it is the kind of work where cheap code is a real blessing, instead of an elegant way of piling up debt. The skill also brings along scripts/run-tests.bat, so the agent compiles and runs the suite by itself, and sees the red before telling you it is done.

How to install the Delphi skills in Claude Code, Codex, Cursor and Gemini

Clone the repository and run the script for your agent (Windows):

git clone https://github.com/danieleteti/delphi-ai-skills.git
cd delphi-ai-skills
install_in_claude.bat

For Claude Code nothing else is needed: the skills are discovered on their own. For Codex, Cursor and Gemini CLI there are install_in_codex.bat, install_in_cursor.bat and install_in_gemini.bat, which copy the skills and write the pointer into the agent’s instruction file (AGENTS.md, .cursor/rules/*.mdc, GEMINI.md), because those agents do not discover them by themselves.

Each script accepts a project path, if you prefer to install the skills inside the repository and version them for the whole team:

install_in_claude.bat C:\DEV\my-project

Then you create the project with the IDE wizard (File → New → Other → Delphi Projects → DelphiMVCFramework), build it once, launch the agent inside that folder and ask for what you need:

Add a Customers controller with full CRUD, backed by an ActiveRecord entity on the customers table

One last piece of advice that makes a remarkable difference: if you have the DelphiMVCFramework sources on disk, tell the agent (“the DMVCFramework sources are in C:\DEV\dmvcframework”). The skills know they may be incomplete or out of date, and in that case they are instructed to verify instead of guessing: they read the sources and the samples, and if they are still not sure they say so, instead of making things up.

A real session, start to finish

Enough theory. Here is what a real session looks like inside a project generated by the wizard, with Claude Code. You never “call” a skill: you describe the task, and the skill loads by itself.

$ cd C:\DEV\OrdersAPI
$ claude

> add a Customers controller with full CRUD, backed by an
  ActiveRecord entity on the customers table

● Skill(dmvcframework)
  ⎿  SKILL.md loaded
  ⎿  reference/activerecord.md opened (entity + mapping)
● Skill(dmvcframework-security)
  ⎿  SKILL.md loaded (the endpoint accepts client input)

● Read(EngineConfigU.pas)    wizard project detected, Indy Direct backend
● Read(Controllers.Base.pas) house style: functional actions, IMVCResponse

● Write(Entities.Customer.pas)
● Write(Controllers.Customers.pas)
● Edit(EngineConfigU.pas)     +1 line: AddController(TCustomersController)

  Done. Notes:
  - GET /api/customers/($id) is filtered on the authenticated customer,
    not on the ID alone: a user cannot read another user's row.
  - The object returned by OKResponse is freed by the framework:
    no ToFree, it would be a double free.
  - The "id" field is excluded from deserialization (mass assignment).

Those three closing notes are not politeness: they are the rules of the skills that worked while the code was being born. An agent without skills, on the same prompt, would have handed you an unfiltered GetByPK(ID), one ToFree too many and a DTO that lets the client write the id.

You carry on in the same conversation, and the testing skill enters the scene on its own:

> now write the integration tests, including the authorization cases

● Skill(dmvcframework-testing)
  ⎿  SKILL.md loaded

● Write(Tests.Server.pas)      in-process IMVCServer, no WebModule
● Write(Tests.Customers.pas)   9 tests: CRUD, 404, 422, 401, IDOR
● Bash(scripts\run-tests.bat)
  ⎿  Running... 9 tests, 8 passed, 1 FAILED
     [FAIL] User_Cannot_Read_Another_Users_Order
            expected 404, got 200

  The IDOR test fails: OrdersController.GetOrder still looks up by
  PK alone. I'll fix it (it was outside the scope you gave me,
  do you confirm?)

This is the point where the loop closes: a test written by the agent found a hole in code the agent had not written. With code generation alone that bug would have stayed there. It is the reason why, in a world of cheap code, directives and tests are worth more than speed.

The same job with Codex, Cursor, Gemini or any other agent

The skills are pure Markdown, with no proprietary syntax: the content is identical across all agents, only the way the agent discovers that those files exist changes.

Agent How it finds them What you see in the session
Claude Code It discovers them by itself in ~/.claude/skills or in the project’s .claude/skills A Skill(dmvcframework) line when it loads it
Codex (or any agent that reads AGENTS.md) The Skills section written into AGENTS.md by the installer, listing the files and when to open them The agent opens skills/dmvcframework/SKILL.md before writing
Cursor One .cursor/rules/*.mdc rule per skill, with alwaysApply: false: Cursor activates it based on the description The rule shows up among the active ones in the context
Gemini CLI The Skills section in GEMINI.md, read at startup The agent reads the file when the task matches
Windsurf, Continue, the others Commit skills/ into the repository and add a section to the agent’s instruction file Depends on the agent
No configuration at all Paste the SKILL.md into the chat before asking for the code Works with any model

With Codex the same session as before takes the following shape: the request is identical, and only the trace with which the agent declares it has read the skill changes.

$ codex
> add a Customers controller with full CRUD (DMVCFramework)

  Reading skills/dmvcframework/SKILL.md, as required by AGENTS.md.
  This endpoint receives client input: I'm also reading
  skills/dmvcframework-security/SKILL.md.
  ...

Watch out for one detail: on agents other than Claude Code, loading is not guaranteed, it depends on how much that agent respects its own instruction file. That is why, with Codex, Cursor or Gemini, it is worth naming the framework in the prompt (“create a DMVCFramework controller”, not “create a controller”): it is the strongest trigger. And if you suspect the skill has not been loaded, say it explicitly:

Use the dmvcframework skill. Then create the controller for Orders.

There is also a ten second way to figure out whether the agent in front of you is instructed or not. Ask it:

In a DelphiMVCFramework functional action, should I use ToFree on the object I return?

If it answers no, because the framework already frees the returned object and ToFree would free it twice, the skill is active. If it answers yes, its code is doing a double free and you will find out in production.

Frequently asked questions about delphi-ai-skills

What is delphi-ai-skills? It is an open source project (Apache-2.0) collecting seven skills for AI coding agents, dedicated to DelphiMVCFramework. A skill is a Markdown file the agent loads on demand and that teaches it the real API of the framework: units, attributes, object ownership rules, idioms and known traps.

Which AI agents do they work with? Claude Code, Codex, Cursor, Gemini CLI, Windsurf, Continue and any agent able to read instructions in Markdown. The repository includes installation scripts for the first four.

Why does an AI agent without skills write Delphi code that does not compile? Because it writes from memory, and falls back on the patterns of the frameworks it has seen far more of, typically ASP.NET Core. It then generates plausible but non existent names, for example MapGroup instead of Prefix or OkResponse instead of Ok, and a ToFree on the returned object that causes a double free, since the framework already frees it.

Do the skills also handle security? Yes, and it is the most important part. dmvcframework-security is a mandatory dependency of every server skill: it applies to any endpoint receiving input from a client. It covers access control and IDOR, mass assignment, SQL injection, XSS in TemplatePro, CSRF, path traversal, uploads, SSRF and open redirect, security headers, JWT hardening and secret management.

Can the skills write tests? Yes, with the real test stack of the framework: DUnitX as the runner, an in-process IMVCServer with Indy Direct and IMVCRESTClient as the HTTP client, with no external servers and no WebModule, even when the production application runs as an ISAPI or as an Apache module. They cover CRUD tests, authentication tests and authorization (IDOR) tests.

Do the skills create the Delphi project from scratch? No, and that is a deliberate choice. The project must be created with the IDE wizard, built once, and only then do you launch the agent inside that folder. The skills recognise the wizard layout and add features inside it, instead of inventing a half correct bootstrap.

Do they work on an existing WebBroker or ISAPI project? Yes. The default backend for a new project is Indy Direct, but an existing WebBroker project (ISAPI under IIS, Apache module) is fully supported: the skills recognise the host, keep it and never propose a migration. Everything above the host, that is controllers, routing, ActiveRecord, validation, DI and middleware, is identical on every backend.

Version 0.1.0 of the Delphi AI Skills, and the road ahead

This is a first release. The skills are written and verified against DelphiMVCFramework 3.5.0, but they have not yet been through dozens of real projects: expect a few rough edges, and please report them. As long as we are in 0.x the very shape of the set is to be considered unstable: skills may be split, merged, renamed or removed as real usage shows what is actually needed.

There is only one criterion for contributing, and it is not negotiable: every statement must be verifiable against the sources or a sample of DelphiMVCFramework, citing the file. No API name written from memory, ever. It is exactly the discipline we demand from the agents, and it would be odd not to apply it to ourselves.

The project is here: github.com/danieleteti/delphi-ai-skills. Issues and pull requests are welcome.


Writing code, by now, is the easy part. Deciding what code you want, and making sure that is the code that comes out even when a machine is the one writing it, is the craft that remains. The Delphi AI Skills exist for this: to give an AI agent the directives, the style, the security and the tests with which we want Delphi code to be born.

Comments

comments powered by Disqus