# An innovative approach to building business software

> Open source. The Cordango format, compiler, CLI and generator are Apache-2.0. A definition compiles to a working application and to source code you own, byte-identically on every run. Open toolchain, proprietary platform, and the line is drawn by job.

Source: https://www.cordango.com/open-source/
Language: en

OPEN SOURCE

# An innovative approach to building business software.

Models write code well and write the same code twice badly. So put the model where it is strong and a compiler where it is not: the model writes a declarative specification, a deterministic compiler turns that specification into the application, and a shared runtime carries what every business app needs identically. The format, the compiler, the CLI and the generator are Apache-2.0. It is early, lots to work on, expect bugs.

[The code on GitHub →](https://github.com/cordango/cordango) [What we are betting on →](https://www.cordango.com/open-source/#bet)

Architecture cordango 0.8.0

\# three layers, and what each one is for SPECIFICATION expense\_claim.cordango.yaml declarative · reviewable · ~16 lines written by a person or by a model COMPILER cordango build same input, same bytes, every run emits the app, and its source RUNTIME Cordango.Standalone 8,152 lines, shared once not rewritten per application ✓ all three apache-2.0 · no account · no network

## Sixteen lines in, a persistence layer out.

Switch between the tabs. The first file is what a person writes: one aggregate per file, top-level `entity:`, `role:` or `lifecycle:`. The other three are what the `dotnet-vue` generator emits from it, unedited.

`type: money` becomes a `decimal` property, a `numeric(18,4)` column and a migration. `indexed: true` becomes a `HasIndex`. `type: reference` with `onDelete: setNull` becomes a nullable foreign key and the delete behaviour behind it. Role files become the authorization the controller inherits, so there is no route where a permission check can be forgotten, because no route is hand-written.

What is absent from the input is the point. No SQL, no DTO, no mapper, no controller, no form. All of it is derivable from the declaration, and anything derivable is the compiler's job.

-   ENTITY Typed fields, relationships, delete behaviour, indexes
-   ROLE `grants:` per entity, enforced at the route
-   LIFECYCLE States, guarded transitions, and the effects they fire
-   EXCLUDED Your records. The definition carries schema, never rows

[The compiled App Definition →](https://www.cordango.com/app-definition/)

expense\_claim.yaml ExpenseClaim.cs Configuration.cs Controller.cs

\# apps/expenses/entities/expense\_claim.cordango.yaml entity: expense\_claim fields: submitted\_by: type: reference targetEntity: person targetApp: platform onDelete: setNull spent\_on: type: date required: true indexed: true amount: type: money currency: EUR required: true

// generated/api/Entities/ExpenseClaim.cs public sealed class ExpenseClaim : IRecord, IHasTrackingFields { \[JsonPropertyName("id")\] public string Id { get; set; } = ""; \[JsonPropertyName("submitted\_by")\] public string? SubmittedBy { get; set; } \[JsonPropertyName("spent\_on")\] public DateOnly SpentOn { get; set; } \[JsonPropertyName("amount")\] public decimal Amount { get; set; } }

// generated/api/Data/ExpenseClaimConfiguration.cs builder.ToTable("expense\_claim"); builder.HasKey(e => e.Id); builder.Property(e => e.SubmittedBy) .HasColumnName("submitted\_by").HasMaxLength(64); builder.Property(e => e.SpentOn) .HasColumnName("spent\_on").IsRequired(); builder.Property(e => e.Amount) .HasColumnName("amount") .HasColumnType("numeric(18,4)").IsRequired(); builder.HasIndex(e => e.SubmittedBy); builder.HasIndex(e => e.SpentOn);

// generated/api/Controllers/ExpenseClaimController.cs // CRUD, with the definition's roles enforced on every // route. Add your own endpoints in a class of your own. \[Route("api/expense\_claim")\] public sealed class ExpenseClaimController : RecordsController<ExpenseClaim> { public ExpenseClaimController( IRecordStore<ExpenseClaim> store, AppPermissions permissions, ICurrentUser user) : base(store, permissions, user) { } }

## The generator is a function, not a sampler.

Ask a model for the same application twice and you get two of them. Different file layout, different names, a different opinion about which layer owns the permission check, and a bug in one that is absent from the other. Nothing differed between the runs except the path taken through the distribution. That is what sampling is, and no amount of prompting removes it.

`cordango build` has no such freedom. Given a definition and a generator version the output tree is fixed: no timestamps, no generated identifiers, no absolute paths, no culture-sensitive formatting anywhere in the emitted files. CI builds the same fixture twice and diffs the trees. Seeded sample data is reproducible from `--seed 42`, and `SEED_DATE=today` is an explicit run-time opt-out so the build itself stays pure.

What this buys is reviewability, not correctness. A diff between two builds contains your change and nothing else, which is what makes a generated tree something you can put under review at all. A wrong definition still compiles into a wrong application, very reliably.

-   TEST `Two_runs_produce_byte_identical_trees()`
-   TEST `The_build_metadata_carries_no_clock_and_no_machine()`
-   HASH `definitionHash` lands in the build record, so two builds compare in one line
-   REFUSE Unsupported constructs raise `CORD21xx`/`CORD23xx` and fail the build, rather than emitting less than you declared

[Determinism.Tests →](https://github.com/cordango/cordango)

Build record Reproducible

\# .cordango/build/expenses/diagnostics.json { "definitionHash": "f36ee2e7b76dac98…", "cordangoVersion": "0.8.1", "valid": true, "incomplete": \[\], "fills": \[\] } \# rebuild on another machine, three weeks later: \# same hash, same tree, same bytes. \# no clock, no rng, no abs paths, no locale.

## The model never emits the application.

If the definition is the generated artefact and the application is compiled from it, the expensive half is never sampled. We measured that across five applications on Anthropic's token counting endpoint, comparing definition source against a from-scratch implementation of the same app: between 90.6 and 98.4 per cent of output tokens are never spent. Output tokens only, so treat the figure as a floor.

The comparison only holds if the runtime is counted on the codegen side, and it is. An agent starting from an empty directory has no package to depend on, so it emits identity handling, record access, permission enforcement and the audit trail into the application, then emits them again into the next one. That is 8,152 lines and 141,596 tokens, paid per application on that side and once on this one.

The claim is narrower than the percentages suggest. Scaffolding cost collapses; domain cost does not move, because somebody still has to decide what an expense claim is. What changes is the ratio between them, and the absolute figures stay small: under ten euros on the largest app we measured.

[Method, figures, and where this does not apply →](https://www.cordango.com/blog/why-ai-writes-yaml/)

count\_tokens Sept 2026

\# output tokens to write one application \# source scratch expenses 3,245 204,551 task manager 5,825 313,519 crm 14,570 377,295 time and leave 45,425 495,013 budget planner 45,296 483,818 90.6% to 98.4% never generated \# scratch counts the 8,152-line runtime: an agent \# starting from an empty directory emits that too, \# once per application.

## Others started down this road long before us.

And they did the hard part. JHipster has generated Spring applications from a domain language since 2013. Wasp compiles a declarative spec into a working React and Node app. Lowdefy builds real applications out of YAML, Apache-2.0 and mature, and for internal tools over a database you already have it is genuinely good. All of it works.

We wanted more than it gave us. The declaration to own the data model rather than point at one, so schema and migrations are derived instead of maintained. The output to be source code a company keeps and deploys with us nowhere in the loop. And one input to produce one set of bytes, so a generated tree reviews like any other diff.

So Cord YAML declares a domain rather than a screen. Entities, relationships, roles and lifecycles, and no query anywhere in it. The screens, the API, the schema and the migrations are derived from that, and `cordango build` emits them as an application you own.

-   LOWDEFY Declares pages, blocks and requests, against data that already exists
-   CORDANGO Declares entities, roles and lifecycles. The data model is part of the declaration
-   THEIRS Interpreted per request by their runtime, and stays config
-   OURS Compiled to source, byte-identically, and yours to keep

cordango.yaml lowdefy.yaml

\# cordango · declares a domain entity: expense\_claim fields: amount: type: money currency: EUR \# …/workflows/lifecycles/claim\_flow.cordango.yaml lifecycle: claim\_flow stateField: status transitions: approve: from: \[submitted\] to: approved \# no connection, no query, no block tree. \# schema, API and screens are all derived.

\# lowdefy · declares a screen and its data lowdefy: 3.x.x connections: - id: my\_api type: AxiosHttp properties: baseURL: https://example.com pages: - id: home type: PageHeaderMenu requests: - id: get\_users connectionId: my\_api properties: url: /users \# the database already exists. this config is \# interpreted per request, and stays config.

Both are worth reading if this is your problem. **Wasp** compiles `main.wasp` into a React and Node application. **JHipster** shipped an MCP server in 2026 so an agent can author the JDL and drive the CLI, which is the same division of labour we are betting on.

## Split by job, and asserted rather than remembered.

The obvious way to split a codebase for open core is by feature: open what is cheap to give away, withhold what took longest. What that produces is something nobody can finish anything with unless they pay you. A demo wearing a licence file.

This is split by job instead. Authoring an application and proving it correct is one job, and all of it is open and runs with no account and no network, because a validator you can only run against somebody else's server is not a validator, it is an API call. Running many companies on one system is a different job, and that is the platform.

The line falls in an awkward place, which is why it is a test rather than a convention. `Cordango.Compiler` owns the grammar, the parser, the typed AST and author-time checking, because an author has to be told an expression is wrong whether or not it ever runs here, and a generator in any language needs the tree to translate. Evaluation lives in the platform. The build fails if an `Eval` method appears next to the parser, which is exactly where somebody would reach for it. Apache-2.0 cannot be revoked once it has shipped.

-   OPEN Format, schema, compiler, CLI, generator SDK, the `dotnet-vue` target
-   CLOSED Hosted runtime, cross-app company model, governance, expression evaluation
-   YOURS Generated source is not covered by the licence. Fork it, ship it, sell it
-   HONEST Calling Cordango open source would be a stretch. Open toolchain, proprietary platform

[What is open, item by item →](https://www.cordango.com/portable/)

The boundary Enforced by test

// backend/…/OssBoundaryTests.cs Cordango.Compiler  open ✓ ComputedExpr.Parse() grammar + typed AST ✓ ComputedExpr.Validate() author-time errors ✗ ComputedExpr.Eval\*() asserted absent // the failure message, if anyone adds one: // "The CLI creates source code; the platform // executes. Put it in ComputedEval instead."

## What we are betting on.

Everything above this line ships today and can be verified from the repository. This part cannot. The bet is that the artefact a team maintains stops being the code and becomes the specification, and that once it does, the step from specification to running software has to be a compiler rather than a model, because that is the step you need to re-run and diff.

It is also why the toolchain is open. If the bet is right, the format matters more than any implementation of it, and a format nobody else can parse or target is not a format. If it is wrong, you still have an application and its source, which is what you would have had anyway.

Three questions we have no finished answer to.

-   TODAY One registered target, `dotnet-vue`: ASP.NET Core, EF Core, PostgreSQL, Vue 3 and Vuetify, with a Dockerfile and readable migrations
-   NEXT A Node target, in progress, not registered and not usable yet
-   SEAM `Cordango.SourceGen` is the generator contract a target implements. It is what makes a second one possible at all
-   RUNTIME Output depends on no service of ours and one Apache-2.0 package. `--runtime source` vendors that away too
-   STATUS Early. Lots to work on, expect bugs

01 

### Does a definition stay reviewable at scale?

The argument rests on the definition being smaller than the code and readable by a person. That is easy to show on an expenses app and unproven at two hundred entities. We do not know where the ceiling is, or whether it is a property of the format or of how an application is split across files and aggregates.

02 

### Where is the runtime boundary?

The 8,152 shared lines are the ones every application needs identically. We drew that line by hand, from the apps we have built. We do not know which side of it the next unusual requirement falls on, and both failure modes are expensive: a runtime that absorbs domain logic, or a domain that keeps reimplementing the runtime.

03 

### What is a model actually good at here?

If a model authors the definition and a compiler emits the application, the interesting work moves up a layer. Authoring a specification is a different task from writing code, and it is not obvious what a format should expose to make a model good at it. Nobody has a settled answer, us included.

Unregistered targets are listed so the direction is visible, not so you can pass them to `--target`. When one lands it appears on the roadmap first.

## Read the others for yourself.

Their docs, not our summary

-   [Lowdefy — config-first web stacklowdefy.com/](https://lowdefy.com/)
-   [Wasp — a DSL for full-stack web appswasp-lang.dev/](https://wasp-lang.dev/)
-   [JHipster Domain Languagewww.jhipster.tech/jdl/intro/](https://www.jhipster.tech/jdl/intro/)
-   [GitHub Spec Kit — spec-driven developmentgithub.com/github/spec-kit](https://github.com/github/spec-kit)

MORE

## Further reading

-   [**Portable by design** A definition you own, built into software you own](https://www.cordango.com/portable/)
-   [**App Definition** How the app works: domain, data, experience, logic and access](https://www.cordango.com/app-definition/)
-   [**Roadmap** What we have built, and what comes next](https://www.cordango.com/roadmap/)
-   [**Blog** The thinking behind Cordango](https://www.cordango.com/blog/)

NEXT

## Clone the examples. Diff two builds yourself.

Sixteen applications, written the way we write them. Run cordango build twice and compare the trees before you take our word for any of this.

[Join the beta →](https://www.cordango.com/contact/) [GitHub →](https://github.com/cordango/cordango)
