The thesis.
Canlang is built around a practical bet: much of the software a company needs consists of records, permissions and workflows tailored to its own operations. It targets companies replacing subscription software with custom business applications. Its intended authors are AI agents. The language gives those agents a compact way to describe business rules, while shared defaults supply standard authentication, teams, storage and interface behaviour.
Take expense approval. A model defines the stored record; a scenario defines who may approve it and under which conditions. The browser form derives its inputs from that scenario. An MCP client invokes the same scenario as an agent tool. Attached inline examples specify an eligible approval, an unauthorised caller and attempted self-approval. Updating a business rule means editing its owning declaration, with fewer separate implementations for an agent to keep aligned.
The form and the agent tool invoke the same business operation.
That is Canlang’s syntactic bet: reduce the amount of application structure an agent must invent and repeat. Static types, explicit authority checks and executable examples supply the verification overlap. The language targets tasks, approvals, bookings, invoicing and similar operational software; its requirements prioritise economical generation and maintenance without claiming a measured cost advantage.
What it looks like.
Each package organises code into three sections: Given declares data and rules, When declares operations, and Then declares presentation. Here, a reviewer can approve a submitted expense, but never their own. This excerpt adapts ExpenseFlow; the app header and create/submit workflow are omitted to focus on approval.
Given role reviewer Expense { purpose:text trim max=300, amount:money, submitted_by:user server=actor, status:enum(draft,submitted,approved)=draft, decided_by:user?, decided_at:datetime? } policy Expense read=members where=row.submitted_by==actor policy Expense read=reviewer invariant Expense: row.amount.minor>0 lock Expense fields=purpose,amount,submitted_by when=row.status!=draft lock Expense fields=status,decided_by,decided_at when=row.status==approved fixture pending=Expense {purpose="Travel",amount=money(25,"EUR"), submitted_by=other,status=submitted} When # Approve a submitted expense belonging to another teammate. scenario approve(expense:Expense) by=reviewer require expense.status==submitted and expense.submitted_by!=actor do set expense {status=approved,decided_by=actor,decided_at=now} examples expense=pending as,expense.status,expense.submitted_by -> expense.status,expense.decided_by reviewer,submitted,other -> approved,self reviewer,draft,other -> error(rule_failed) members,submitted,other -> error(forbidden) reviewer,submitted,self -> error(rule_failed) Then page / title="Expenses" list Expense order=-created text row.purpose,row.amount,row.status form approve arguments={expense=row}
Four example rows test valid approval, invalid state, missing role authority and self-approval. The form binds the same operation to each listed expense; locks freeze submitted details and the final decision.
Every authority rule answers a distinct question: policy governs who may read, by governs who may invoke an operation, require enforces business preconditions, and lock protects stored fields from later edits. Browser and MCP calls use the same operation runtime, which enforces the operation’s schema, permissions and guards. Neither interface can turn an ordinary member into a reviewer.
Distinctive moves.
- Declare the differences; share the machinery. Standard CRUD, record identity and versions, team scope, authentication and the application shell use defaults pinned to the language version. Forms derive their controls from operation schemas. Translations sit inline beside authored text. Larger products compose apps and packages while preserving the original owners of their records, roles and operations. Source stays compact by removing repeated setup while keeping permissions and business conditions explicit.
- Test the operation where it is declared. An
examplesblock attaches to its operation and reuses shared fixtures; table rows vary callers, inputs, initial state and independently authored expectations. The runner executes the operation in isolated state and checks its returned result, persisted changes or rejection code. It distinguishes permission failures (forbidden) from business-rule rejections (rule_failed) and checks that expected rejections leave the example’s captured state unchanged. There are no separate step definitions to synchronise with the application. - Extend the vocabulary without losing the business contract. A static
$"..."prompt can define a reusable model or field trait, a customGivenkeyword, a scoped style or a custom view. Custom keywords can introduce semantics beyond the built-in vocabulary through checked compiler and runtime integration contracts. Generated JavaScript, CSS and declaration contributions are saved beside the app as editable files. Ordinary builds reuse those files without model calls; scenarios retain their authored permissions, guards anddobodies.
The generation workflow makes those extensions reviewable. Generation stages an isolated candidate; acceptance requires passing compilation, the platform build, inline examples and the app’s browser tests before replacing the saved implementation. This requires authored examples and browser tests, plus the optional browser-automation add-on. Failed candidates preserve the current implementation. The committed PromptStudio fixture exercises generated field validation, a reusable Unicode uniqueness keyword, styled custom views and authenticated create/edit/archive operations. Generation extends what the app can express; acceptance still depends on independently specified behaviour.
Maturity.
Canlang ships a Rust compiler, TypeScript runtime packages, editor tooling and an MIT-licensed standalone distribution. The v0.1.1 release bundles the compiler, catalog, private Node runtime, local workerd binary and production dependencies for macOS arm64 and Linux x64 with glibc 2.39 or newer. Developers and agents can install the toolkit and run local applications without assembling a source-build environment; browser automation is an optional add-on.
Applications compile to JavaScript targeting workerd / Cloudflare Workers, with D1 storage, Durable Objects and R2 selected as required. Remote deployment still requires resource preparation and configuration. The repository records successful installed-toolkit checks, the PromptStudio generation-to-browser workflow, and local file and queue workflows, including authenticated uploads and selected local restart and replay recovery cases. These establish working application paths beyond parsing and code emission.
Development remains active. The completion record still lists unsupported query, composition and UI cases, along with broader workflow and deployment work. The trade-off is deliberate: a shared business-app model makes applications concise, while specialised foundations or novel extension semantics can require compiler/runtime development. Adoption depends on whether a company’s complete workflow fits the implemented paths. Comparative agent-success and token-cost improvements remain to be demonstrated.
Agent tooling.
The authoring loop is check, repair and exercise the declared behaviour. With --format=json, can check and can compile return structured diagnostics with source spans and stable error codes. can explain explains a diagnostic code with guidance and valid/invalid examples. can fmt supplies canonical formatting, and can lsp backs the VS Code extension with navigation, completion and rename support. can policy extracts a summary of declared access rules, while can docs generates a localised Markdown reference from checked declarations.
can test executes compiled inline examples through the operation runtime. can gen manages explicit prompt generation, candidate inspection and refinement; can validate runs compilation, the build, inline examples and browser checks against saved implementations without model calls. These commands let an agent move from source diagnostics to concrete behavioural checks, keeping generated code on disk for inspection and direct editing.
- PactverificationBoth address the repeated glue around application code. Pact makes intent, effects and error outcomes explicit on functions and routes and bundles HTTP and SQLite; Canlang derives browser forms and application MCP interfaces from records, operations and their declared permissions.
- ilosyntacticBoth seek to reduce the cost of agent-authored code. ilo works on compact general-purpose notation, feedback and retries; Canlang removes repeated business-application structure through canonical operations, shared defaults and interfaces derived from declarations.
- MarshaorchestrationBoth use natural-language intent in code generation. Marsha compiles function descriptions and examples into tested Python through an LLM; Canlang uses explicit prompts for checked language extensions and custom presentation, saving their implementation while ordinary scenarios retain authored guards and bodies.