惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
T
Threat Research - Cisco Blogs
N
News and Events Feed by Topic
Hacker News: Ask HN
Hacker News: Ask HN
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 叶小钗
Security Latest
Security Latest
Spread Privacy
Spread Privacy
aimingoo的专栏
aimingoo的专栏
N
News and Events Feed by Topic
Webroot Blog
Webroot Blog
U
Unit 42
Cyberwarzone
Cyberwarzone
小众软件
小众软件
Scott Helme
Scott Helme
Engineering at Meta
Engineering at Meta
Microsoft Security Blog
Microsoft Security Blog
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
爱范儿
爱范儿
S
Schneier on Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Schneier on Security
Schneier on Security
Latest news
Latest news
GbyAI
GbyAI
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
The Register - Security
The Register - Security
WordPress大学
WordPress大学
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
PCI Perspectives
PCI Perspectives
Jina AI
Jina AI
AI
AI
NISL@THU
NISL@THU
I
Intezer
G
GRAHAM CLULEY
B
Blog
S
Secure Thoughts
IT之家
IT之家
宝玉的分享
宝玉的分享
Recent Announcements
Recent Announcements
Y
Y Combinator Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
有赞技术团队
有赞技术团队
V2EX - 技术
V2EX - 技术
Recorded Future
Recorded Future
Hacker News - Newest:
Hacker News - Newest: "LLM"

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Why I Built a Language Instead of a Framework
André Ahlert · 2026-05-17 · via DEV Community

A friend asked me, around the third week of working on Kilnx, why I had not just written it as an Express plugin. The question was fair. It was also the same question my own brain had been asking me for a month.

The honest answer took me longer to find than I would like to admit. This piece is what I would have said to him in November if I had already understood what I was doing.

The push

I had been shipping backends. Some of them were small. Most of them were not. A multi-tenant CRM where every query had to filter by org_id or you leaked data across customers. An internal admin tool with role-based pages, scheduled jobs, outbound webhooks signed with HMAC. A SaaS dashboard with background workers, rate-limited APIs, an LLM call inside a critical path. None of these are blogs. All of them carried the same shape.

The interesting work in each project was the domain. The uninteresting work was identical. Auth setup. Session management. CSRF wiring. Connection pool tuning. Migration script naming. Multi-tenant guards on every query. Webhook signature verification. The right way to call Claude from a background job without burning the bill on retry. By the time I had a first feature shipped, I had touched a dozen files and made forty decisions, none of which had anything to do with what the customer wanted.

I started counting. Two thirds of the lines in those projects were about plumbing. The other third was the product. The numbers held across three projects. The numbers held across two stacks. The plumbing was not a project artifact. It was the toolchain's signature, and it was showing up in every project I touched.

That is what pushed me toward a language. Not a feeling. A pattern that did not move when I changed teams, customers, or stacks.

Constitution

The repo has a file called PRINCIPLES.md. The first principle, numbered zero because it predates the others, reads:

The complexity is the tool's fault, not the problem's. Most web apps are not complex. They are lists, forms, dashboards, CRUDs. The complexity comes from the tools we use, not from the problem we are solving. Kilnx exists to prove this.

That sentence is a claim. The rest of the language is the test of the claim. If you accept the premise, the design follows. If you reject the premise, nothing about Kilnx makes sense. The interesting argument is whether the premise is true, not whether the design is clever.

I think it is mostly true. Not entirely. Some web work is genuinely complex and would be complex in any tool. But the line between "complex problem" and "complex tool" runs further toward the tool side than most engineers want to admit, and the way to find out which side a given complexity sits on is to build a tool that subtracts itself and see what remains.

Here is what a working slice of the language looks like. Authenticated task list, htmx delete, paginated query, all in one file.

model task
  title: text required
  done: bool default false
  created: timestamp auto

auth
  table: user
  identity: email
  password: password
  login: /login
  after login: /tasks

page /tasks requires auth
  query tasks: SELECT id, title, done FROM task
               WHERE owner = :current_user.id
               ORDER BY created DESC paginate 20
  html
    {{each tasks}}
      <tr>
        <td>{title}</td>
        <td>{{if done}}Yes{{end}}</td>
        <td><button hx-post="/tasks/{id}/delete"
                    hx-target="closest tr"
                    hx-swap="outerHTML">Delete</button></td>
      </tr>
    {{end}}

action /tasks/:id/delete requires auth
  query: DELETE FROM task WHERE id = :id AND owner = :current_user.id
  respond fragment delete

Enter fullscreen mode Exit fullscreen mode

That file is the whole app. Registration, login with bcrypt, sessions, CSRF on the htmx POST, parameter binding on every query, pagination, ownership check on delete. The Express equivalent is between four hundred and six hundred lines across eight files, depending on which middleware you copy versus extract.

The obvious objection

Build a framework, not a language. Rails exists. Phoenix exists. Django exists. Whatever you think is broken about backend work, somebody already wrapped your favorite host language in a thinner thing and called it a framework. Pick one and contribute.

That was the version of the argument I kept hearing, including from myself. It did not land for one structural reason. Frameworks always lose the constraint fight, and the constraint fight is exactly the fight Kilnx is trying to win.

A framework lives inside the host language. The host language gives you escape hatches at every level. You want to bypass the router? Reach for the HTTP server. You want to skip the ORM? Drop into raw SQL. You want to ignore the tenant guard? Comment it out, the language will let you. Every framework I have shipped real work on, by month six, has half its codebase in the official patterns and the other half in escape hatches. The escape hatches are not bugs. They are the price the framework pays to live as a tenant inside a general-purpose language.

I did not want a tenant. I wanted a contract.

What a language can refuse

Five things turned out to be impossible inside a framework that became natural inside a language.

Compile-time SQL safety. In any framework, your SQL lives in strings, or in an ORM that compiles strings, or in a query builder that pretends not to. The framework cannot validate your queries at compile time because the host language cannot see them at compile time. Kilnx queries are parsed by the same compiler that parses the rest of the program. A column rename in a model fails to compile every query that referenced it. SQL injection is not blocked by an escape function. It is blocked by the grammar refusing to interpolate untyped strings into SQL position.

Multi-tenant guards as syntax. Every SaaS backend I have shipped had the same bug class. Someone forgot to add WHERE org_id = :current_user.org_id to a query, and one tenant could read another tenant's data. The bug class exists because the host language sees the missing filter as legal code. In Kilnx, a tenant modifier on the model produces a fail-closed guard that the analyzer enforces on every query path. If you write a query that does not scope to the tenant, the compiler refuses to build the binary.

model invoice tenant
  amount: int required
  customer: text required

page /invoices requires auth
  query invoices: SELECT id, amount, customer FROM invoice
  html
    {{each invoices}}<p>{customer}: {amount}</p>{{end}}

Enter fullscreen mode Exit fullscreen mode

$ kilnx check app.kilnx
error: query in page /invoices is missing tenant scope
  app.kilnx:5: query invoices: SELECT id, amount, customer FROM invoice
  hint: tenant model 'invoice' requires WHERE org_id = :current_user.org_id
  hint: or use `unscoped: explicit-reason` to opt out for a specific query

Enter fullscreen mode Exit fullscreen mode

A framework can warn you. A language can refuse to build. The pattern is documented in the tenant rollout PR.

LLM agents as first-class language constructs. The piece I wrote last week argued that agents in production end up as tasks on the orchestrator you already run. The same logic applies one layer down. An agent inside a request handler is a task on the language you already run.

mcp linear
  command: linear-mcp-server
  env: LINEAR_API_KEY=:env.LINEAR_API_KEY

action /tickets/:id/triage
  agent classify
    prompt: "Classify ticket {ticket.body} into one of: bug, feature, support."
    permission-mode: plan
    max-budget-usd: 0.25
    max-turns: 3
    mcp: linear
  query: UPDATE ticket SET category = :classify.text, cost_usd = :classify.cost_usd
         WHERE id = :id
  respond fragment ticket-row

Enter fullscreen mode Exit fullscreen mode

agent spawns a Claude CLI subprocess. :classify.text, :classify.session_id, :classify.cost_usd, :classify.stop_reason are bound for the rest of the action. max-budget-usd is enforced by the runtime. mcp: linear mounts the MCP server declared at the top of the file. The frame around the agent is grammar, not glue.

Migrations as a controlled surface. kilnx migrate detects drift across five dimensions: orphan columns, type mismatch, NOT NULL mismatch, single-column UNIQUE mismatch, DEFAULT presence mismatch. Migrations themselves are additive, never destructive. The language took a position on what is safe to do automatically and what requires the human to look.

$ kilnx migrate app.kilnx
applying schema...
warning: orphan column
  invoice.legacy_status (DB has it, model does not declare it)
  hint: drop manually after data migration
warning: type mismatch
  user.id (DB: integer, model: uuid)
  hint: requires data migration plus ALTER, not auto-generated
warning: NOT NULL mismatch
  task.due_date (DB: nullable, model: required)
  hint: backfill defaults before tightening
warning: UNIQUE mismatch
  account.slug (DB: not unique, model: unique)
  hint: dedupe rows before adding the constraint
warning: DEFAULT presence mismatch
  task.done (DB: no default, model: default false)
  hint: review before relying on the default in new code
migration applied with 5 warnings.

Enter fullscreen mode Exit fullscreen mode

A framework can ship a migration tool. A language can make the migration tool part of the same compile pass that builds your routes.

Single-binary deploy. A framework runs on top of a runtime that you also have to deploy. Node. Python. Ruby. Each brings a package manager, a lockfile, a Dockerfile, a node_modules directory the size of a small operating system. Kilnx compiles a .kilnx file to a fifteen-megabyte binary that embeds the HTTP server, the database driver, the htmx JavaScript, and your application. scp it to a server and run it. The deploy story is ./myapp. A framework can shrink the deploy story. A language can collapse it.

Notice the pattern. A framework can make these things easier. A language can make their alternatives impossible. The asymmetry is the whole point.

What it costs

Building a language is more work than building a framework. This is the easy half of the trade-off to name. The repo is nineteen thousand lines of Go and three hundred eleven tests with race detection, to deliver something whose feature list on paper looks like a slightly opinionated web framework. If a small web framework was what I wanted, building the framework would have been the right answer.

The harder half is that a language has to take itself seriously. The grammar has to be coherent. The error messages have to be useful. The tooling has to exist. There is no falling back on someone else's ecosystem when something is missing. You either ship the LSP server or your users do not get autocomplete. You either ship the test runner or your users do not get tests. You either ship the playground or your users cannot evaluate the language without installing it. You either auto-generate an AGENTS.md for coding agents or your users get LLMs inventing keywords that do not exist.

The third cost is that a language refuses things, and refusing things is socially expensive. Every refusal is a fight with somebody who has a perfectly reasonable use case that the language does not serve. Frameworks can absorb those use cases with an escape hatch. Languages cannot. You have to look someone in the eye and tell them the language is not going to do that, and that the reason is that doing it would break the contract.

A friend told me that the part of building a language nobody warns you about is that you have to say no to a lot of people who are right.

What it gives back

The give-back is the part that justifies the cost. It is also the part that does not fit on a marketing page, because it has to be measured rather than read about. So measure.

The blog example in the repo is ninety-four lines in a single file. The Express equivalent is between four hundred and six hundred lines across eight files, depending on which middleware you copy versus extract.

Kilnx blog            Express + Prisma + EJS blog
──────────────────    ──────────────────────────────
app.kilnx        94   app.js                     62
                      routes/auth.js             88
                      routes/posts.js           104
                      models/Post.js             36
                      middleware/csrf.js         24
                      middleware/session.js      31
                      db/migrations/             47
                      views/                     94
                      ──────────                 ───
                      8 files                   486
1 file           94                            ~480

Enter fullscreen mode Exit fullscreen mode

The Kilnx version is short because the language absorbed the rest, not because the app does less. The same things ship in both columns. The difference is which side wrote them.

What disappears on the Kilnx side, never written by the user:

bcrypt password hashing       auto from `auth`
session cookies, signed       auto from `auth`
CSRF on every POST/PUT/DELETE auto on every action
SQL parameter binding         only form the grammar allows
HTML escaping in templates    only form the grammar allows
multi-tenant scoping          refused at compile time if missing
schema migrations             same compile pass that builds routes
LLM agent budget enforcement  required attribute on `agent` blocks
MCP server lifecycle          managed by the runtime
HTTP server, routing, logs    embedded in the binary

Enter fullscreen mode Exit fullscreen mode

The other give-back is harder to measure but bigger. The constraints stop you from drifting. There is no point at which you can decide to do auth a different way and have it cost you nothing. The decision was made when the language was designed. You inherit it. The cognitive load of every project drops because the design space is smaller.

For most product work, smaller design space is the gift you have been begging the universe for.

When a framework is the right answer

The inverse is real and worth naming.

If your work needs escape hatches more often than it needs constraints, a framework is the right shape. Custom integrations against an irregular set of third-party systems, custom protocols, custom transport, custom auth flows that do not fit any standard pattern. Days that are ninety percent edge cases. A framework lets you write the edge case directly in the host language without the language fighting you.

Convex made the same trade in the agent world. They accepted the determinism contract, and they paid the cost of not being able to do arbitrary side effects in mutations. For most product workloads that cost is fine. For some, it is too high. The same logic applies here. Kilnx accepts a constraint contract, and the contract is wrong for some workloads. The question is whether your workload is one of them, and the honest answer is usually no.

What the bet really is

The bet at the center of Kilnx is that a specific opinion, taken seriously, eats a category of work that nobody wanted to be doing anyway. Pick the right opinion, encode it past the point where users can opt out, and the opinion becomes leverage. The leverage shows up as code that did not need to be written. Two thirds of every project, in my experience.

A framework can host an opinion. A language can enforce one. The reason I built a language is that I wanted the enforcement, and I had counted the lines of plumbing in enough projects to know what the enforcement was worth.

Kilnx is in early release. The grammar is twenty-seven keywords. The compiler is a few thousand commits old. None of that matters as much as the bet does, and the bet is what is being tested in production over the next year.

The spreadsheet was real. The language was the honest answer to it.


I am building Kilnx, a declarative backend language that pairs with htmx, and Provero, where a lot of the language-shape decisions I write about are the day job. If the diagnosis here lands, that is the door.


André Ahlert is a product engineer. Contributor across Apache, Flyte, Backstage, HTMX, Hyperscript. Currently building Kilnx and Provero.