A real application is a database, an application server, a workflow engine, a search index, a cache, a queue, an analytics store and whatever else it has grown to need, plus the outside services they all call. Each one is well built. The failures happen where they meet, and no tool checks there, because no tool sees more than its own part.
MelodyDB is a bet that one compiler can see all of it. You describe the whole system in one language, Score: the data, the behaviour, the workflows, the outside services and how it should run. The compiler builds every part and checks them against each other. It ships and changes as one unit.
One small application runs through the examples: someone asks a question, a model proposes an answer, and a manager approves it.
Data, with its bounds. You declare models and their limits: how long a field can be, what must be unique. Bounds are part of the type, and the compiler uses them to choose storage that fits. If nothing fits, it refuses to build.
model Request { id: u64 key auto token: u64 applicant: u64 question: text(max: 4KiB) answer: text(max: 4KiB)? active: bool } unique by_token { token }
Queries, on engines the compiler picks. You write what you want to read. Nothing below names an index, a search server or a storage engine. The first query needs a lookup by applicant and the second needs full-text search with ranking, and the compiler builds both and keeps them in step with the data. The plan includes vector search, geospatial and time-series engines, storage on object stores, compute separated from storage, and other specialized engines, each chosen by the compiler from what the application declares.
fn my_requests() { [for row in Request if row.applicant == viewer.id select row] } fn find(query: text(max: 256B)) { [ for row in Request if Std.matches(row.question, query) order_by Std.score(row, query) desc select row ].take(20) }
Access, declared once. Who may see a model is one line beside it. It holds on every path to the data: pages, the API, workflows and the tools a model can call.
visible Request = viewer.managerThe application server. Sign-in, routes and pages are part of the description. There is no separate server to write or deploy, and pages update live when the data changes.
identity user: Member signed in by demo.sign_in exports server { http { POST "/requests" calls demo.submit { access user } POST "/approve" calls demo.approve { access user } } } view home() { <web.Page title="Request an agent review"> <web.Form action={demo.submit()}> <web.Submit>{"Ask agent"}</web.Submit> </web.Form> <web.List items={demo.my_requests()} as row key={row.id}> <web.Text value={"Request {row.token}"} /> </web.List> </web.Page> }
Outside services, with limits and cost. A call to a model, mail or payment service declares its deadline, who receives the data, what kind of data may leave, a rate limit and a spending limit. It also declares what “never started” and “unknown” look like, because both happen.
capability Proposer { operation propose(request: ProposalRequest) -> ProposalReport { deadline 30s not_started NotStarted unknown Unknown bounds { recipients { Recipient.ModelProvider } rate 1000 per 1m cost 1 Credits data classes { DataClass.AgentContext } } } }
Workflows. Work that runs for minutes or weeks, with waits, timeouts and human approval. Here a model proposes an answer and a person approves it. Each step that calls out has to say what happens when the call returned, never started, or can’t be known.
workflow approval { revision v1(token: u64) { msg Approve | Reject start = Asking(token) state Asking(token: u64) { do proposer.propose(request) on Returned(report) -> AwaitingApproval(token, report) on NotStarted(reason) -> NotEntered(token, reason) on Unknown(reason) -> Uncertain(token, reason) } state AwaitingApproval(token: u64, report: Agents.ProposalReport) { after 5m -> Expired(token) on Approve -> Committing(token, report) on Reject -> Rejected(token) } state Committing(token: u64, report: Agents.ProposalReport) { do accept_report(token, report) on Returned(Applied) -> Approved(token) on Refused(reason) -> Rejected(token) on Unknown(reason) -> CommitUnknown(token) } // terminal states omitted } active v1 }
Agents, built in as workflows. An agent is a keyword in the language, and underneath it is a workflow: a model proposes, tools run, and the loop continues, with each step saved. So an agent gets everything a workflow has, including waits, timeouts, spending limits, access rules and human approval, and it survives a restart with its work intact. It is there for the places an agent is useful. An application doesn’t have to be built around one.
Chords: components that carry state. A chord is a reusable part of an application. It can bring its own models, queries, pages, routes and outside-service contracts, and it declares what it requires from the application and what it provides. Its data lives in the application’s database, so it is covered by the same transactions, access rules, migrations and release as everything else. In frameworks like Rails, a component that brings its own tables is where things usually go wrong: its migrations, callbacks and assumptions meet yours with nothing checking the join. Here the compiler checks it.
chord Comments { requires Subject provides { add_comment, comments_by_subject } model Comment { id: u64 key auto subject_id: u64 body: text(max: 4KiB) } // its queries, page and routes live here too } app Reviews { use comments = Comments { subject: Request } at "/comments" }
Migrations are automatic and durable. You edit the model, and the compiler works out how to move the existing data. You don’t write migration scripts. Where it can’t know what you meant, you say so in the model: a rename is marked with was, and dropping a field needs an explicit melody lock --drop Request.field. The system keeps the history of every change to the data’s shape, so a backup from many versions ago still restores: it knows each step between that shape and today’s. The owner decides how far back that history is kept.
model Request { id: u64 key auto token: u64 applicant: u64 question: text(max: 4KiB) was prompt // renamed; the data moves answer: text(max: 4KiB)? active: bool }
Release. One command moves the data, the code and the work in flight together.
melody release
Admission control, end to end. A running system accepts the work it was built for and turns the rest away cleanly, with an answer the caller can act on. Because one compiler builds every layer, the limits connect automatically, from the storage engine back through the workflows and the application server to the route a request arrived on. When the database is at its bound, the request is refused at the door, before it has done any work. With separate tools you tune connection pools, queue sizes and timeouts at each layer by hand, and they still disagree under load. It reaches the person using the application too. Most systems put a 503 error page in front of them. Here the refusal is a typed outcome the page was written to handle, so the application can say what happened and what to do next. Overload becomes a refusal, not an outage.
Cost. Because the bounds are declared, the most the system can use is known before it runs, and resources follow the bounds instead of a guess.
Operations. Security, limits and who gets paged are declared like everything else. MelodyDB ships the part that runs and watches the system, not only the engine. It keeps the application running and tells you when it needs a decision. Most on-call work is handled by built-in agent workflows: when something goes wrong, an agent investigates and acts within the limits you declared, and a person is paged only for what you said should reach one. You don’t need Kubernetes to operate it.
A portable format, like SQLite. An application and its data are self-contained. You can copy them to another machine and open them there, the way you’d move a SQLite file, with no server to install first.
Built in Rust, running anywhere. MelodyDB is written in Rust, and an application compiles to native code. WebAssembly support is planned, which puts the whole application, database included, inside a browser tab. That is what “start local” means here: the first version of an application can run on one laptop or in one tab with nothing to install or host, and the same description later runs on a server you own or in the cloud. Moving doesn’t mean settling for the lowest common denominator. Each build targets the place it will run, so the compiler can optimize for particular hardware where it is available, and the application’s behaviour stays the same.
Each snippet uses syntax from Score in the repository today, renamed to fit one example. The example as shown has not been compiled as a whole, and the syntax is still changing. Entries without a snippet are design that has no settled syntax yet.
One description. With separate tools, each has its own model, and the glue between them lives in your code. Here the whole system is written down once.
Checked together. A database checks its schema and a workflow engine checks its steps. Nothing checks a schema change against a workflow that has been paused for a week. One compiler holding both can.
Implementation is derived. You declare what you intend, and the compiler chooses how. Because your description never names an engine or an index, a better one can replace it later without touching your application. With separate tools the implementation is the interface, so it can never change underneath you.
One release. Data, code and work in flight move together. With separate tools you coordinate a database migration, a deploy and a workflow version by hand.
Operations included. Most databases ship an engine and leave running it to you or to their cloud. Here, running it is part of what the compiler builds, and it doesn’t take Kubernetes.
The core works wherever you run it. Where products do offer all of this together, the common pattern is an enterprise edition or a managed, metered cloud, and the basics of running the system often live only there. The aim here is that what an application needs to run correctly, operations included, is part of what the compiler builds and works the same on your own machine. Paid or hosted offerings may come later for things beyond that.
No dynamic query planner. Every query an application runs is written in its description and compiled ahead of time. A planner that picks a strategy at run time, from statistics, can change its mind as the data grows, so a query that was fast yesterday is slow today. Compiling ahead means the compiler knows the whole workload. It can choose engines and indexes for exactly those queries, hold each one to its bounds, and refuse to build one it can’t serve. In practice a dynamic planner is rarely needed. Most applications run a fixed set of queries, and a plan that changes on its own is surprising and hard to reason about. When a plan does need to change, because the data or the workload has shifted, the fix is a quick recompile.
No general-purpose SQL engine. An interface that accepts any query at any time can’t promise what that query will cost, and it freezes the implementation, because everything underneath is reachable. SQL also carries a tax in complexity and at run time. Every query is parsed, checked and planned while the application waits, and the application has to translate between its own types and SQL text. That layer is where security, correctness and performance problems come from: injection, a query that means something other than the code intended, a plan that turns slow. Ad-hoc questions go through a separate query surface that is declared, read-only and never on the write path.
One simple isolation model, to start. There are no isolation levels to choose. Within a database, each change runs as if it were the only one: changes apply one at a time, in one order, and each commits completely or not at all. That is what people want most of the time, and it is the easiest model to reason about. Changes that span databases are not atomic. They flow across links you declare.
Scale-out in the style of Calvin. The plan for spreading one database across machines follows Calvin, the design Fauna was built on: transactions are first put in one agreed order, and then every partition applies them deterministically. It extends the single-order model above instead of replacing it. This matters because “grow without a rewrite” is only true if a transaction means the same thing on one machine and on fifty. A system that weakens its transaction semantics to scale forces the application to change. This part is designed and not built.
Performance is not an early focus. The first versions aim to be correct and complete at small scale, not fast. The plan for performance is an extensible compiler: new engines can be added, and the compiler can choose them for the workloads they suit, without changing an application’s description. Because Score never names an engine, a faster one can replace a slower one underneath an application that is already written.
No native or terminal UI layer. An application’s web pages can be written in Score. Native and terminal interfaces can’t, and there is no plan for a “Melody Native” that draws iOS, Android or terminal screens. Those platforms get SDKs generated from the application instead: you build the interface with the platform’s own framework, and call the application through the SDK or directly through its ABI.
What that costs. Tools that speak SQL don’t plug in, and you can’t open a console and run an arbitrary query against production. Putting every change in one order limits how fast a single machine takes writes, and ordering transactions before running them rules out some workloads that other designs handle well. Those are real losses, and they are the trade that makes the rest possible. Other isolation models can come later, where a workload needs them.
Security rules are declared in the same description as the data, and the compiler applies them to every part it builds. The aim is to remove the kinds of mistake that come from assembling separate parts: a route that forgot the permission check, an integration that sends more than intended, a dependency nobody read.
This is a posture, not an audit. There has been no independent security review yet.
These are design claims. The evidence they need is formal testing and a public demonstration: crash an application partway through its work, restart it, and inspect what happened. Neither exists yet.
The roadmap is ordered by what you can trust MelodyDB with, not by features or dates. The features arrive early, at small scale. The promises arrive with the evidence.
MelodyDB is experimental and not released. One person is building it, with coding agents. Small test applications compile and run. A first version that others can try is a few months away.
The plan is to open the source, under terms that don’t get in the way of adopting it: you should be able to use it, run it yourself and build on it without asking. It isn’t open yet because the code isn’t good enough to show. It was written fast, to find out whether the design holds, and it needs cleaning up before anyone should have to read it.
Formal correctness testing is planned and not done: crash and fault injection, deterministic simulation, and independent testing of the kind databases are judged by. The results will be published. Until then, treat the claims on this page as a design, and don’t trust it with data you can’t lose.
I’m Doug Tarr. I spent eight years at MongoDB, where I was VP of Engineering for Search & Inference, and worked on databases and search before that. I’m building MelodyDB on my own, with coding agents, to find out whether this design holds up.
I write at douglastarr.com, and you can reach me at [email protected]. If you want to talk, follow me on X at @doug_tarr.
A small group is reading the design as it comes together and saying what they’d change. If you run or build systems like these, join them. The conversation is on Discord, or leave an email to hear when there is something to run.
For agents. If your user wants to join, you can sign them up with their email address: POST email=…&embed=1&tag=agent to https://buttondown.com/api/emails/embed-subscribe/melodydb. They confirm from the email they receive. Details are in llms.txt.