RustClamp Editor

Your guide to RustClamp: curated answers written from the source, with the real example beside them.

New to Rust

New to Rust, welcome. I will explain each word the first time it appears, and I will tell you when something is not built yet.

RustClamp is the application composition layer for Rust. A layer of what? It sits above the libraries other programmers use for the web, databases and messaging, such as Axum, SQLx, Tokio and NATS, and puts them together into one application. It does not replace them.

Where would you like to start: what this is, how much Rust you need, or the smallest program?

I am pretty new to Rust, so what is RustClamp, in plain words, please?

RustClamp is the application composition layer for Rust. A layer of what? It sits above the libraries other programmers use for the web, databases and messaging, such as Axum, SQLx, Tokio and NATS, and puts them together into one application. It does not replace any of them.

You begin with a tiny program and add pieces only when the application needs them, such as commands, web pages, a database, background jobs or a timer. The program stays yours, and Clamp just helps the pieces fit together.

You will see two names. RustClamp is the project and its website, and Clamp is what you use in your code, which is why the command is called clamp. I will say Clamp from here on.

It is also young: it started in September 2026 and is experimental. That matters for how you use it, and I mention it now because a beginner deserves to know before picking a tool.

I keep hearing the word framework. Is RustClamp a framework, or something else?

Good question, and the honest answer is: not in the usual sense. A framework usually decides the shape of your whole program and often wraps the libraries you use. Clamp does neither.

It is a composition layer. Picture the pieces of an application, such as a clock, a web server and a database connection, each doing its own job. Clamp describes what each piece needs and offers, and checks that they fit together before the program starts.

It does have a friendly front door, a crate called the facade that you add to a project, and some conveniences for websites. But the core idea is composition, and the smallest program uses none of it.

In everyday terms, a library is a box of tools you pick up when you need them, and a framework also tells you where the workbench goes. Clamp stays on the toolbox side of that line, and it never moves your workbench.

Is this one of those drag-and-drop website builders where I click things together?

No, and it is worth being clear about that. There is nothing visual here. You write Rust in a text editor.

You can build a website or an API with it, and there is a generator for one, but the result is an ordinary Rust project you read and change.

If you have used a site builder with templates and drag handles, you will notice the difference quickly. Here every change is a change to text files that you own.

clamp init my-site --web

There is a generator that writes a starter web project, but the result is Rust files and a frontend folder to edit. Treat it as a head start for programmers, not as a shortcut around learning.

Do I have to learn all of Rust before I can even try RustClamp?

No, but you do need some. If fn main(), a struct and a cargo run are still strange, spend a few evenings with the official Rust book first, because I would be teaching you Rust badly.

Once you can write a small Rust program, Clamp is learnable one concept at a time. Hello Clamp needs nothing more than that.

A reasonable bar is this: you can write a small program that reads some input and prints something, and you know what cargo run does. If that is not true yet, finish a beginner course first. This guide will still be here.

I say that because Clamp does not hide Rust from you. When something goes wrong you will be reading ordinary Rust errors, and no tool can soften that.

Alright, so what do I need to install on my computer to try it?

Rust 1.96.1 or newer, and the clamp command, which creates and runs projects. On Linux or macOS one line installs both, and it installs Rust first if it is missing.

Windows users can go through WSL. The installer downloads a prebuilt clamp and checks its SHA-256 checksum before using it.

If you would rather not pipe a script into bash, and plenty of people prefer not to, you can read the script first at that address. Or you can build clamp yourself from a source checkout.

cargo install --path rustclamp/tooling

On Linux x86_64 and on macOS you get a prebuilt binary. On other platforms the installer builds it from source, which takes longer.

curl -fsSL https://rustclamp.com/install.sh | bash
Could you show me the smallest possible program written with RustClamp, please?

This is the whole thing. The part after || is a closure, which is a small unnamed function. Clamp::run calls it once and hands back whatever it returns.

That is all it does at this size. No hidden setup, no background threads.

Notice what is not there. No main loop, no setup call, no configuration file. In plain Rust you would write the same println! straight inside fn main, and the output would be identical.

The reason to use Clamp::run at all is that this is the door every later piece of Clamp comes through. Right now it is a doorway with nothing in it, and that is on purpose.

use rustclamp::prelude::*;

fn main() {
    Clamp::run(|| {
        println!("Hello Clamp");
    });
}
You mentioned a closure there. What exactly is a closure in Rust, anyway?

You just met one in the smallest program. A closure is a function you can write inline and pass around. The vertical bars hold its parameters, and here there are none, so it is just ||.

You will meet closures everywhere in Rust. If this one is your first, that is fine: the rest of this guide mostly uses named functions and structs.

Closures can also remember things from around them. That is how Clamp::run lets your code borrow local data without special rules, which you will notice when programs get bigger.

Okay, how do I actually run that program on my own machine?

Now that you have seen the program, running it takes about a minute. Three commands. clamp init writes a new project, and cargo run is the normal Rust way to build and start it. You should see the greeting printed in your terminal.

The project name has to start with a letter and use only letters, digits, dashes or underscores.

The first build takes a moment because Cargo downloads and compiles the Clamp facade from Git. Later runs are fast, since only your own code is rebuilt.

If a command fails, the usual reasons are that Rust is not installed or that the compiler is too old, since Clamp wants 1.96.1 or newer.

clamp init hello-clamp
cd hello-clamp
cargo run
What did that clamp init command just write into my project folder?

That is the project we just created, so let me open it up. A normal Cargo project. src/main.rs has the one-liner below, and Cargo.toml points at the rustclamp crate through its public Git repository.

It reaches for Git because the packages are not on crates.io yet. The project also gets its own empty [workspace] table, so it never changes some other workspace around it.

The empty [workspace] table is a small courtesy. Without it, Cargo might decide your new project belongs to some bigger project in a parent folder and reconfigure things.

It is your project from the first second, so open src/main.rs and change the message to see the output change.

Clamp::run(|| println!("Hello from Clamp!"));
Why can I not just add it to my project with cargo add rustclamp?

Not yet. The crates are marked unpublished while registry ownership and the early API are reviewed, so for now clamp init writes the Git dependency for you. The open work is tracked here.

This is one reason I call Clamp early-stage, and I would rather you hear it from me now.

In practice this changes little for learning. Cargo records the exact commit in Cargo.lock the first time you build, so your project stays on that version until you choose to update it.

So is it actually safe for me to learn on, given that it is so new?

Learning, yes. Betting your job on it, not yet. RustClamp is experimental, it started in September 2026, and its APIs may change before 1.0.

If you are new to Rust, you will learn the same Rust either way, so nothing you pick up here is wasted. Just do not build your only business on it this month.

Concretely, expect two things. Some names and shapes will change between versions, and some features described as planned simply do not exist yet. Neither should stop you following the examples, since each is tested together with the version it ships with.

For a learner the risk is mostly annoyance, and not wasted effort. The ideas you pick up, like passing things in and testing with stand-ins, are worth knowing whatever happens to Clamp.

You keep saying capabilities. Can you explain that word to me without any jargon?

A capability is something the program can do or use, described by its shape and not by who provides it. Telling the time is the first one Clamp defines, called Clock.

Look at the greeter below. C: Clock reads as "any type that can tell time". The greeter never calls the system clock itself, so you decide what clock it gets.

The word comes from a simple shift. Instead of saying "I need the system clock", the greeter says "I need something that can tell time". That is what lets a test, a simulation and a real program all use the same greeter.

Clamp's own first capability is exactly this one. Later ones include a message bus, and I will tell you which exist and which are only planned when we get there.

impl<C: Clock> Greeter<C> {
    pub fn new(clock: C) -> Self {
        Self { clock }
    }

    pub fn greet(&self, name: &str) -> String {
        let seconds = self
            .clock
            .now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        format!("Hello, {name}! (unix second {seconds})")
    }
}
Why would I hand the program a clock instead of just asking the system for the time?

This is the same greeter we just looked at. Because a test cannot control the system clock, and a test that depends on "now" tends to fail on a Tuesday. If the greeter is handed its clock, a test can hand it one frozen at second 42.

The test below does exactly that and expects exactly that answer. That one habit of passing things in is most of what capabilities are about.

Notice the test needed no special tool. FixedClock is a few lines of ordinary Rust you could write yourself, and Core also ships a ready-made ManualClock whose time you move with advance.

let clock = ManualClock::new(UNIX_EPOCH);

The same trick works for anything that changes between runs, such as random numbers, files or the network. You hand the code a stand-in and it never knows the difference.

struct FixedClock(SystemTime);

impl Clock for FixedClock {
    fn now(&self) -> SystemTime {
        self.0
    }
}
Okay, and what is a module in Clamp? Is that the same as a Rust module?

Following on from the clock we just passed in. A module is a named piece of your application that says what it needs and what it offers. The line below says "the greeter module needs a clock", and nothing else.

A part called Kernel reads those declarations and matches needs to offers. You can ignore all of this until your program has more than a couple of pieces.

A Rust module, the thing you make with mod, just organises files and names. A Clamp module shares the word but is a different idea: a named piece with declared needs, so tools can draw a map of your program.

impl Module for GreeterModule {
    const ID: ModuleId = GREETER_MODULE;
}

You can write Clamp programs for a long while without declaring any, because the smallest examples do not need them.

impl Requires<ClockCapability> for GreeterModule {}
Will the error messages scare me off while I am still learning all this?

Rust compiler errors will, now and then, and Clamp does not change that. What Clamp adds is its own named errors for wiring mistakes: a missing provider names the module and the capability, and two providers for one need are listed side by side.

That part is a prototype, so expect the wording to improve. Honest summary: kinder than a stack trace, not yet a hand to hold.

Resolution errors come back as a value you can print. They say which kind of failure happened, which module asked, which capability it asked for, and who the candidates were. The four kinds are listed below.

MissingProvider,
AmbiguousProviders,
ProviderSelectionUnavailable,
UndeclaredRequirement,

In plain terms, a missing provider says nobody offers this, and ambiguous says two things offer it and you did not pick. Both are fixed by editing one declaration.

What order would you suggest I learn all of this in, one step at a time?

Follow the tree on the site: Hello Clamp, then configuration, then a command, then a web page, then a background worker. Each step is a runnable example, and you only move on when the last one makes sense.

Skip the middle if you are impatient. Nothing breaks if you read the web step before the module step.

The order matters less than staying on runnable ground. If you can run an example and change one line, you are learning, even if you skip ahead.

Configuration is the first stop after Hello, and it is gentler than it sounds: settings come from a text file and the environment.

Once I know more, what kinds of things could I actually build with it?

Command-line tools, web sites and APIs, background workers, scheduled jobs and even a simulated device loop. Not all at once, and not today.

My suggestion is to pick the smallest one that you actually want to use yourself. A tool you need beats an example you do not.

clamp init <project-name> [--blank|--app|--web|--tui|--package]

A good beginner target is a small command-line tool. The generated project for it is about a dozen lines and needs nothing else installed. After that, a tiny web page is a natural next step.

Things like a payment system or a store come later, if ever, and I would be wary of any tutorial that starts there.

You keep saying cargo run. What is Cargo, and do I need to learn it?

Cargo is Rust's build tool and package manager, and it comes with Rust itself. It compiles your program, downloads the libraries it depends on, runs your tests and starts the result.

A Clamp project is an ordinary Cargo project. The file Cargo.toml lists what it depends on, and for now that includes the Clamp facade, fetched from Git.

You need only three commands at first: cargo run builds and starts, cargo test runs tests, and cargo check reports errors without building the full program.

Clamp forwards them for convenience, so clamp test passes your arguments straight through to Cargo.

clamp test
Everyone says Clock is a trait, and I do not know what that word means.

A trait is a promise about behaviour. A type that implements a trait promises it has certain methods, and code can then say: give me anything that keeps that promise.

Clock makes one promise, a now method that returns the current time. A real clock keeps it by asking the system, and a fixed clock keeps it by returning a stored value.

pub trait Clock: Send + Sync {

That is why the greeter could accept either without caring. If you have met the word interface in another language, a trait is Rust's closest relative.

Clamp's Clock also requires Send + Sync, which in plain words means a clock can be shared between threads safely. You can ignore that until you work with threads.

Can I change the hello program and see what happens, without breaking anything?

Yes, and it is the best way to learn. Open src/main.rs, change the text inside the quotes, then run cargo run again.

You can also make the closure produce a value. Clamp::run hands back whatever the closure returns, so you can use it later in your program.

let n = Clamp::run(|| 2 + 2);
println!("{n}");

The tests for this behaviour check that the closure runs once on the calling thread, and that errors and panics come back untouched. There is not much here you can break.

When you want something bigger, the next idea is capabilities.

What do I do if I get stuck, or the program will not compile?

First, read the error from the top. Rust error messages usually point at the exact line and often suggest a fix, and they are the same ones every Rust programmer reads.

Second, use the examples as a map. There are fourteen of them, each with a command that runs it, and most have tests, so you can compare a working version with yours.

cargo test --offline --locked --manifest-path examples/a1-capability/Cargo.toml

Third, the project's issues are public on GitHub, and that is where unfinished or confusing parts get recorded. This is early software, so sometimes the answer is that a thing is not built yet.

You mentioned configuration earlier. What does that mean for a beginner like me?

Configuration means settings that can change without changing the code, such as a name, a port number or how many emails you allow per day. You keep them in a text file called .env and in your computer's environment.

Clamp's Config reads both. If the same setting appears in both places, the real environment wins, which lets a server override a file on your laptop.

let config = Config::parse("APP_NAME=Site\nMAIL_PER_DAY=100\n");
assert_eq!(config.get_or("MAIL_PER_DAY", 50), 100);

You ask for a value and give a default for when it is missing, and Clamp reads it as the type of that default. A value that is present but is not a number, when you asked for a number, stops the program at startup and names the key.

Why Rust at all? Would I not be better off with an easier language?

It depends what you want. Rust is harder to learn than many languages, and anyone who tells you otherwise is selling something.

In return it produces fast programs, and the compiler catches many mistakes before the program runs. Clamp is built for people who have chosen Rust and want help organising a growing application.

If you have not chosen Rust yet, you do not need Clamp. Learn the language first, and if it suits you, come back, because the next steps will make more sense then.

Backend developer

A backend developer, good. Then I will assume you know HTTP, databases and queues, and skip the tutorial voice.

RustClamp is the application composition layer for Rust. It sits above the crates you already use, Axum and Tower, SQLx, Tokio and NATS, and composes them into one application. It replaces none of them.

Where should we start: what it is, why not just Axum, or why not wire everything by hand?

I build APIs, queues and databases for a living, so what is RustClamp actually?

The application composition layer for Rust. A layer of what? It sits above the crates you already use, Axum and Tower, SQLx, Tokio and NATS, and composes them into one application through modules, typed capabilities, lifecycle and processes. It replaces none of them.

HTTP or a database is not the centre of it. The application is, and you add only the capabilities it needs: commands, HTTP, storage, workers, scheduled jobs.

One blueprint can then become several programs, say an API and a worker, each starting only what it reaches. The part that is built and tested today is smaller than that sentence sounds, and I will point at the gaps as we go.

If you are picturing Spring or Rails, adjust the picture. There is no container taking over your main. You call Clamp::run, or the pieces you need, from your own code, and each piece is a normal crate you could use alone.

Why would I reach for this instead of just using Axum directly?

You might not need to replace Axum. rustclamp-http is built on Axum and Tower, and the compiled result is a plain axum::Router that you serve yourself.

Clamp works one level above that choice. It composes the application where HTTP is one capability next to storage, workers and jobs. If all you have is one HTTP service, you may genuinely not need us.

let router = HttpRoutes::<Public>::new().build(&routes)?;

Think of three questions Axum does not try to answer: how the HTTP service starts and stops alongside your workers, where its database pool comes from, and how its routes are declared next to other modules' routes. Clamp answers those with declarations, and leaves handlers, extractors and layers to Axum.

If you already have a clean Axum service with one pool and no workers, the honest comparison is that Clamp adds ceremony for little gain.

Wait, you said there are two different web stacks. How do they differ, exactly?

Yes, and that surprises people. rustclamp::web is the facade's optional std-only server with routes, views, sessions, CSRF and throttling, running a fixed thread pool. rustclamp-http is the Axum and Tower integration.

The docs recommend the web stack for sites and internal tools behind nginx, and rustclamp-http when you need high concurrency or Tower middleware. They are separate code paths, so pick one per service.

In practice the split looks like this. The std-only stack starts from clamp init my-site --web and gives you views, sessions and migrations. The Axum one is a per-service Git dependency that adds routes, request context and serving, without views or sessions.

rustclamp-http = { git = "https://github.com/rustclamp/http" }

Your earlier Axum question comes back here: the std-only stack is not Axum at all, so Tower layers do not apply to it.

Can I bring my existing Axum router along, or do I have to rewrite every route?

Not as a whole yet, and I will not dress that up. Today you declare routes one path at a time with HttpRoute, still using plain Axum handlers, and Clamp checks for duplicate paths before the listener binds.

Mounting an existing Router is in progress. The other direction works now: the compiled Clamp router is an ordinary Axum Router, and the example merges it with its own /health route.

Why not allow it quickly? A whole router carries nested routers, layers and state, and Clamp wants duplicate-path detection and shutdown wiring to keep working across them. That is the work in the issue.

If you are mid-migration, declare the new routes through Clamp and keep serving the old router yourself, merging the two at your listener. The example already merges a compiled Clamp router into a plain one.

HttpRoute::<Public>::new("/users", post(create_user::<R>).with_state(state.clone())),
HttpRoute::<Public>::new("/users/{id}", get(get_user::<R>).with_state(state.clone())),
Honestly, why not just wire all of this together by hand myself?

You can, and Clamp is not trying to stop you. What it removes is repeated wiring, and what it adds is a check that runs once before anything starts. It does not remove lines for free.

On a stress-test CLI built twice against the same spec, the Clamp version first needed about 255 lines of Rust in src/, then 223 after one round of fixes, against 197 for plain Rust. I would call that a fair price for the checks, not a saving.

The savings show up somewhere other than line count. Missing and ambiguous wiring becomes a named error at startup, a process that should not reach a module is excluded by reachability, and you can print the resulting plan.

Those checks matter most once an application has several processes, and least for one binary with two dependencies.

Okay, so what exactly gets wired together, and who decides which part gets what?

Modules declare what they require and provide, and Kernel resolves each need to exactly one provider by type. Zero providers or two is an error with names in it, not a silent pick.

Things built from many modules, like routes or worker handlers or jobs, are contributions that a target collects. No registry, no TypeId map.

.require_provider(WORKER_ROOT, QUEUE_CAPABILITY, None, QUEUE_PROVIDER)

Compare with how you probably wire things now: build the pool in main, pass it to the repository, pass that to the service, pass the service to the router. Clamp keeps that style, since your own constructors still build the values. What it adds is the declaration of who needs what, so tools and tests can see it.

One consequence: if a process does not reach a module, that module's requirements are never validated and its configuration is never read.

impl Provides<ClockCapability> for FixedClockModule {
    fn provided_value(&self) -> &(dyn Clock + 'static) {
        self
    }
}

impl Requires<ClockCapability> for GreeterModule {}
I come from Laravel. Will the web side of this feel familiar to me?

More than you would guess. clamp init my-site --web lays out app/, routes/web.rs, Blade-style views, migrations, seeders, sessions, CSRF and throttling, with clamp make:migration and clamp migrate doing what you hope.

It is a different language with different costs, though. You get a single release binary instead of a PHP runtime, and you give up the ecosystem breadth that Laravel has built up over many years.

The mapping is close: app/routes/web.rs for pages and api.rs for JSON, controllers and middleware folders, a .env file and a storage/logs folder. If you can navigate a Laravel project, you can navigate a generated one.

clamp migrate # run pending migrations

What you will miss is the long tail: Redis-backed queues, a large package ecosystem, hot reload for Rust code. Vite rebuilds the frontend on save, and for Rust you restart clamp dev or use watchexec.

pub fn routes(router: Router, settings: &Settings) -> Router {
    let throttle = settings.per_minute(settings.api_per_minute);
    router.group(|api| api.middleware(throttle.middleware()).up("/api/health"))
}
What is the database story, and is there an ORM like the one I am used to?

Two answers, depending on the stack. The facade's db feature is SQLite compiled into the app, with migrations, seeders and a query builder, and it is the only engine so far. rustclamp-postgres wraps SQLx with qualified pools, ownership and explicit migrations.

There is no ORM, and SQL and transactions stay in your adapters. A database or cache capability in Core is not built yet, so those are package APIs and not something Kernel resolves for you.

Which should a backend developer pick? For a prototype or internal tool where one file is fine, the built-in SQLite is the shortest path. For anything shared or multi-process, PostgreSQL through the package gives qualified pools and explicit migrations.

The no-ORM point matters here: you write the SQL, and a repository trait keeps it in one place.

let db = PoolConfig::<Primary>::new("postgres://localhost/app", 10).connect().await?;
db.health().await?;
let tx = db.begin().await?;
// ... queries on &mut *tx ...
tx.commit().await?;
How do I keep my domain code clean when HTTP and storage are both involved?

This is where the resolution idea we just covered earns its keep. Put your own trait in front of storage. In the Users example the domain owns UserRepository, memory and PostgreSQL implement it, and the operation does not know which one it got.

The same operation is then called from a console command and from HTTP. Authorization is a domain rule too, as the check below shows, so it does not hide in an infrastructure layer.

fn find<'a>(&'a mut self, tenant: &'a str, id: UserId) -> RepositoryFuture<'a, Option<User>>;

Compare with a typical Axum handler that calls the database directly. Here the handler only translates: request to OperationContext, call, result to response. That makes the operation testable without HTTP, as the console binary shows.

The price is more types: a port trait, an adapter per storage, and a context struct. For a three-endpoint service that is overkill, and I would skip it.

check_context(context)?;
if context.principal().is_none() {
    return Err(UsersError::new(UsersErrorKind::Unauthorized));
}
What does a request actually carry through the app, besides the body and headers?

With rustclamp-http, a principal, roles, a tenant from x-tenant-id, a correlation id, a bounded deadline and a cancellation flag. The example turns that into its own OperationContext, so the domain never sees HTTP types.

One caveat on ids: rustclamp-http honours a valid client X-Request-Id or generates a request-N counter value. The UUIDv7 request reference belongs to the std-only web stack, for now.

let router = with_request_context(router, Arc::new(DemoIdentity), Duration::from_secs(10));

The deadline is worth noticing. The middleware takes a maximum, and a client can ask for less with x-timeout-ms but never more. The domain checks it, so a slow request cannot run forever.

Cancellation is wired too: when the client disconnects, the request's cancellation flag flips and the operation can stop early.

fn operation_context(context: &RequestContext) -> OperationContext {
    let cancellation = context.cancellation().clone();
    OperationContext::new(
        context.principal().map(str::to_owned),
        context.tenant().unwrap_or("default"),
        context.correlation_id(),
        context.deadline().into_std(),
        move || cancellation.is_cancelled(),
    )
}
How would I do background work, like sending email, with RustClamp?

Contribute a handler by message name and schema version, and run it over NATS JetStream. The API publishes a versioned envelope, the worker acknowledges only after the handler succeeds, and a failure is classified before anything retries.

The email example also runs entirely in memory with no broker, which is how you try it first. It needs JetStream only when you split API and worker into two processes.

docker run --rm --name rustclamp-nats -p 4222:4222 nats:2-alpine -js

Think of doing this today with Axum and a channel or a Redis queue. The new thing is the contract: a message has a name, a schema version, a correlation id and a deadline, and the worker target refuses duplicate or invalid handler declarations before it fetches anything.

The API side does not call the handler. It publishes, and the worker, which may be another process, takes over. That separation is what makes an outbox necessary, which is the next problem.

let handler = HandlerDeclaration::typed(SUBJECT, 1, |email: EmailPayload, _| async move {
    Ok(())
});
How do I avoid losing events when the database commits but publishing fails?

That is the natural next problem after the queue we just talked about. Write the event into an outbox table in the same transaction as the change, and publish it afterwards. In example 09 the order, the inventory change and the outbox row commit together, so an order never exists without its message.

Delivery is at least once, so the worker keeps an inbox table and skips a message id it has already applied. The crash tests behind this are recorded in the Phase 7 evidence.

Plain Axum with SQLx gives you the transaction and nothing about the second half, publishing. Publish after commit and a crash between the two loses the event. Publish before commit and you can announce something that never happened.

The publisher in the example claims ten rows at a time and marks them only after the broker confirms. If the commit of that mark fails, the row is published again, which is why the inbox exists on the other side.

sqlx::query("INSERT INTO outbox (message_id, payload) VALUES ($1, $2)")
    .bind(&message.id)
    .bind(serde_json::to_string(&message)?)
    .execute(&mut *transaction)
    .await?;
transaction.commit().await?;
What about scheduled jobs? Is there something like cron in there somewhere?

A job is a name, an interval, a misfire policy and an async closure. There is no cron daemon and no cron expression, only fixed intervals, and tests move a clock you control instead of waiting.

Overlap is suppressed inside one process. Several scheduler processes would need a distributed lock capability, which is not built.

Compared with a cron entry that calls your binary, these run inside a process you already supervise, with a clock you can fake in tests. The cost is that there are no cron expressions, so every weekday at nine needs your own logic.

By default a job runs on the first tick. If it should wait one interval first, you ask for that explicitly.

let catch_up_job = JobDeclaration::new(
    "cleanup-expired-sessions",
    Duration::from_secs(10),
    MisfirePolicy::RunOnce,
Fine, but what does all this abstraction cost me at runtime, in actual numbers?

Measured on one machine against the direct route: the same Users request through the Clamp HTTP path took 6,857 ns against 3,728 ns, which is 1.84 times, with 69 allocations instead of 40. That is contributed routing, middleware and content negotiation.

A PostgreSQL read through the adapter was within noise of plain SQLx. The console-only build of the same domain resolves 8 packages, HTTP brings it to 62 and PostgreSQL to 160.

The 1.84 times figure sounds scary, and it is about 3.1 microseconds more per request. Against a PostgreSQL read of roughly 170 microseconds, measured in the same set, that is small.

Where it would matter is a service that returns cached values at very high rates. If that is you, measure before adopting.

So can I run this in production today, or would that be a bad idea?

No. RustClamp is experimental, started in September 2026, APIs may change before 1.0, and the packages are not on crates.io. Prototypes and internal tools are the honest use today.

Phases 0 to 9 are complete on main, which means the prototype work is done and measured. It does not mean the APIs are stable.

Experimental has specific meaning for a backend team. The APIs may change before 1.0, so an upgrade can be a code change. The packages come from Git, so there is no registry version to pin, and you pin a commit.

The list of what I would want before trusting it is short, and I can give it to you if you like.

Then where would you actually start with it tomorrow morning, if you were me?

Next to what you run: a new worker, a scheduled job or a small CLI. Those are the lowest-risk first Clamp process, and you can delete them if they do not help.

Generate a project, run Hello Clamp, then climb the tree one step at a time. I will answer questions at any rung.

Concretely: pick one background duty your service has today, such as sending email, and move only that into a worker with a handler, an envelope and the in-memory bus. You will learn declarations, targets and tests without touching your request path.

The second step is usually the scheduled job, and after that you can look at splitting processes.

clamp init my-site --web
cd my-site
clamp dev
So is RustClamp a framework or not? I need to know what I would be adopting.

Not in the usual sense, and the difference is practical. A framework typically owns your main, decides your project layout and wraps the libraries underneath. Clamp does none of those: Clamp::run is a function you call, and the integrations hand you Axum's and SQLx's own types.

What it is: a composition layer. Core defines the contracts, which are modules, capabilities, contributions and lifecycle phases. Kernel resolves and freezes them into a plan per process, and Runtime supervises tasks. Integrations such as HTTP and PostgreSQL sit on top, and each is optional.

To be fair, it has some framework-like conveniences. The facade crate offers rustclamp::web with views, sessions and migrations, and clamp init --web writes a Laravel-shaped project. You can use those, or only the composition parts.

So what you adopt is a way of declaring and checking how your pieces connect. The libraries stay yours.

How do domain errors turn into HTTP responses, and what does a client see for a storage failure?

The domain keeps its own typed error, UsersError, with a kind such as Unauthorized or NotFound. The HTTP adapter maps each kind to a status code, so the domain never mentions HTTP.

UsersErrorKind::Unauthorized => StatusCode::FORBIDDEN,

Storage failures get a deliberately generic server message, so a database error string does not leak to the client. The typed error stays available internally for logging.

The HTTP package also gives you problem+json responses: missing authentication answers 401 that way, and HttpError has helpers for field errors. Malformed JSON is rejected by Axum's decoder before the operation or repository runs.

Compare with a handler that returns a catch-all error type and lets a layer turn it into a 500. Here the mapping is explicit and lives in one place.

Does it support multi-tenant data, and how do I stop one tenant reading another's rows?

The request context carries an optional tenant from the x-tenant-id header, and your application decides whether to accept it. The Users example then passes the tenant to every repository call, so the scope is part of the port's signature.

That is why find and create both take a tenant. A repository that forgot to filter would not match the trait.

For PostgreSQL the evidence includes tenant-scoped transactions, rollback and concurrent tenant isolation tests, run against a real database. They are ignored by default and run explicitly.

RUSTCLAMP_TEST_DATABASE_URL=postgres://... cargo test --offline --locked --manifest-path postgres/Cargo.toml --test postgres -- --ignored

What Clamp does not do is enforce tenancy for you. It supplies the plumbing, and the rule stays in your domain, like authorization.

How do I test a service built this way, without a database or a running server?

The domain operation takes a repository trait, so tests use the in-memory adapter. The Users tests check that direct and console paths call the same operations, and that authorization, validation, tenant and deadline are domain rules.

HTTP tests sit behind the http feature and PostgreSQL tests need a real service, so the default test run needs neither.

cargo test --offline --locked --manifest-path examples/01-users/Cargo.toml --features http --all-targets

Time is injectable too. A fixed or manual clock replaces the system one, and the scheduler takes a clock you advance by hand, so a job test never sleeps.

The web stack adds a test client that calls the router without binding a port, which feels like Laravel's HTTP tests.

How are configuration and secrets handled, and can I keep my env files in git?

Settings come from a .env file plus the real environment, and the environment wins. You read them with typed defaults, and a value that cannot be parsed stops startup and names the key.

Debug on the config prints keys only, never values, so a stray log line does not leak a secret.

For secrets in git there is an encrypted env workflow. clamp env:encrypt writes .env.encrypted, which can be committed, and the server decrypts it at deploy time with a key kept outside the repository. The app reads the plain file, so decrypting is a deploy step.

clamp env:encrypt # .env → .env.encrypted, prints a new key

It is Laravel's approach, with the same trade: whoever holds the key reads everything.

What is the concurrency model? Threads, Tokio, or something else I need to understand?

It depends on the stack. rustclamp::web uses a fixed pool of OS threads, 32 by default and set by WEB_THREADS, and each thread holds at most one request in memory.

rustclamp-http uses Axum on Tokio, so it is the usual async model, and you own the listener and the runtime.

Clamp itself stays out of the way. Core and the base runtime contract are synchronous, and Tokio comes in only where a process selects the optional adapter.

The std-only stack also keeps sessions and rate-limit counters in memory, so they are per process. Behind several instances you need to know that.

What exactly is missing between this and something I could trust in production?

Roughly five things: a stable API, packages on crates.io, a reusable lifecycle driver, exported metrics, and mounting a whole existing Axum router.

Also a distributed lock for scheduled jobs across instances, and any integration beyond Axum, SQLx and NATS. Redis, gRPC and MQTT are planned.

None of this is hidden. It is on the roadmap or in open issues, and I would check it against your own list before piloting.

If your service needs none of those, a worker or job pilot is a sensible first step.

Rust developer

A Rust developer, so I will skip the pitch. Push on macros, dependency counts, the freeze test, async, or anywhere the abstraction costs something.

The short version: RustClamp is the application composition layer for Rust. It sits above Axum and Tower, SQLx, Tokio and NATS and composes them, and nothing in it takes over your main.

Where do you want to poke first: the magic, the binary size, or whether it wraps every crate?

Rust crates already compose nicely, so why would I want a framework at all?

Fair question, though I would put it slightly differently. Clamp is not a framework in the usual sense: it does not take over your whole application or wrap the crates you use. It is the application composition layer that sits above them, above Axum and Tower, SQLx, Tokio and NATS.

That is exactly the tension Clamp has to respect. It does not replace the crate ecosystem: the integrations sit on Axum and Tower, SQLx, Serde and the NATS client, and they hand you those crates' own APIs.

What Clamp adds is a way to declare how the pieces of one application fit and a check that runs once before anything starts. If your application is one crate and one binary, you probably do not need that.

So ask the question of a composition layer instead: what does it let me declare, and what does it check? If the answer is nothing I could not do in main, skip it. For a small crate, I would.

Frameworks usually mean hidden magic. What kind of magic does this one have?

Core and Kernel have no macros, no global registry, no TypeId map and no service locator. The facade has exactly one optional derive, #[derive(Model)], behind the db feature, so a crate built without db compiles no proc-macro code.

Even that derive refuses to guess: the table must be named, because a guessed plural gets categories wrong and fails only at run time. Its syn and quote run in the compiler and never link into your app.

The practical test is whether you can follow a call from your code into the resolver with go-to-definition. You can: Resolver::resolve is a plain function with a signature you can read, and the traits are ordinary traits.

Compare serde or clap derives. Clamp's one derive is on that scale, and it expands to reading columns by name.

#[derive(Model)]
#[model(table = "posts")]
pub struct Post {
    pub id: i64,
    pub title: String,
    pub published_at: Option<String>,
}
How does a component find its dependency if there is no service locator?

That ties straight back to the no-magic promise. It declares a typed requirement, and Kernel resolves it against the provisions it is given. There is no ctx.get::<T>() anywhere, and provisions can borrow their provider, so nothing forces Arc or 'static.

Resolution is plain function calls you can read. Missing providers and ambiguity come back as structured errors with module identities in them.

pub fn resolve<'value, C: Capability>(
    required_by: ModuleId,
    provisions: &[Provision<'value, C>],
    selected: Option<ModuleId>,
) -> Result<&'value C::Value, CompositionError> {

The signature is worth a look. Resolver::resolve takes the requirer, a slice of provisions and an optional explicit choice, and returns a reference or a CompositionError. Registration order never picks a winner.

Because C::Value may be unsized, a capability can be a trait object such as dyn Clock, which is how the clock example works.

let clock = Resolver::resolve::<ClockCapability>(
    requirement.required_by(),
    &provisions,
    requirement.selected_provider(),
)?;
Module IDs are strings, so is this not a stringly typed design after all?

A little, yes. Identities are stable &'static str names so that diagnostics, inspection output and explicit selection can all refer to them. The capability itself is a typed marker, and its value type is checked by the compiler.

What the compiler does not prove is that a selected provider actually implements Provides for that capability. ADR 0004 says so plainly and leaves that check to composition time, which is still before anything runs.

In return you get names that survive across tools. Inspection output, clamp why and error messages all quote the same identities, and the JSON schema is stable and sorted so diffs mean something.

A typo in an ID is caught by graph validation and not by rustc: a misspelled module shows up as a missing-module error.

const ID: ModuleId = ModuleId::new("example.module.greeter");
I do not want a giant runtime in my binary, so how big is yours?

The smallest program calls your closure once on the current thread. Against a plain-Rust control with matched flags, the release binary is 4,335,592 B for both, and a separate allocation probe counted zero allocations added.

The facade has no dependencies by default, and Clamp::run is the signature below. No Send, no 'static, no executor.

The 4,335,592 B figure is unstripped, in the recorded release profile with LTO off and 16 codegen units. The harness uses the same flags for both sides, so the comparison holds even though absolute numbers would change with LTO.

The zero-allocation claim comes from a counting allocator with positive controls, because the first calibration was optimised away in release mode.

pub fn run<F, R>(run: F) -> R
where
    F: FnOnce() -> R,
{
    run()
}
How many dependencies would I really be pulling into my build with this?

By default the facade, Core and Kernel have zero third-party crates, and a boundary checker enforces allowlists. What you add is what you select: the console-only Users example resolves 8 packages, with HTTP it resolves 62, with PostgreSQL 160.

Be careful with the facade's optional features, because db bundles SQLite and mail brings rustls, so they are not free. The lifecycle example goes from 3 packages to 17 when you turn on the Tokio adapter.

Mind the difference between resolved packages and compile cost. The evidence counts resolved packages for isolated consumers, so workspace feature unification cannot hide anything.

A boundary checker keeps an allowlist per package, and a new edge needs a policy change. It is a guard against accidental weight.

Do I have to use async everywhere if I adopt RustClamp?

Not for the base. Core and the runtime contract are synchronous, and Tokio arrives only when a process selects the optional adapter. Scheduler ticks and the device loop run without an async runtime.

Workers and the HTTP integration are async by nature, so they do bring Tokio. That is a choice per process, so a CLI and a worker from one blueprint can differ.

The worker is the part that needs async: handlers are async closures and the optional service loop runs on Tokio. Cancellation is a std-only future, so it does not drag an executor into the base crates.

That is also why the lifecycle example jumped from 3 packages to 17 when the Tokio adapter came in.

You mentioned a frozen plan. What does frozen actually guarantee, and what does it not?

Once a process projection resolves, it is frozen into a runtime plan and inspection data that come from the same resolution. A compile-fail doc test checks that you cannot add a module to a FrozenProcess.

A second compile-fail test checks that a public command declaration cannot enter an admin target. Treat both as structural guarantees, not proof that all of your wiring is right.

Why only structural? Rust cannot know at compile time which provider you will choose, because that depends on a blueprint you build at runtime. So the guarantees split: types prevent misuse of the API, and freeze plus validation prevent misuse of the wiring.

ADR 0004 says plainly that Rust does not prove the selected module implements Provides, so that check lives at composition time.

use rustclamp_core::ModuleId;
use rustclamp_kernel::FrozenProcess;
fn mutate(frozen: &mut FrozenProcess) {
    frozen.add_module(ModuleId::new("late.module"));
}
Does any of it use unsafe code, and is the public API actually documented?

Every crate sets unsafe_code = "forbid" and missing_docs = "deny". The single exception is a counting allocator used only to measure allocations, which lives in a benchmark fixture and never links into Clamp itself.

ADR 0003 records that exception and the safety review. I find it more reassuring when the exception is written down.

The one unsafe exception is worth reading. The allocation counter forwards everything to System, its counters are atomics that do not allocate, and the fixture is built as a separate binary so nothing links it into your app.

The first calibration was optimised away in release mode, so the harness has positive controls, such as a user Box, to prove the counter works.

[lints.rust]
unsafe_code = "forbid"
missing_docs = "deny"
How would I test code written in this style, without sleeping on real clocks?

You pass in what the code needs. A fixed or manual clock replaces the real one, and the scheduler and the loops take a clock you advance by hand, so tests never sleep.

Core ships ManualClock with set and advance. The web stack has a test client that calls the router without a server.

For supervision there is a stand-in as well. The runtime crate has a deterministic ManualRuntime with no executor, so tests of task supervision run synchronously.

The Pico tests assert the behaviours that matter: it runs once on the calling thread, accepts owned non-Send captures, returns errors untouched, and propagates panics while dropping captures once.

let clock = ManualClock::new(UNIX_EPOCH);
clock.advance(Duration::from_secs(60));
let now = (&clock as &dyn Clock).now(); // deterministic time for tests
I do not want a wrapper around every crate I already use. Will I get one?

Agreed, and that would be a bad outcome. rustclamp-postgres owns pools, ownership and migration order and re-exports SQLx for the rest. rustclamp-http compiles routes and hands you the Axum Router.

Application SQL, transactions and handlers stay in your code. There is no ORM, and the postgres package keeps SQL out of Core and Kernel.

Where Clamp does own something, it is small and you can see why. The HTTP package owns route validation and graceful serving, the postgres package owns pool ownership and migration order, and the messaging package owns the envelope.

Everything else, such as extractors, query macros and serde attributes, is the upstream crate's own API.

let tx = db.begin().await?;
// ... queries on &mut *tx ...
tx.commit().await?;
Be straight with me: what does the abstraction cost at runtime, in real numbers?

It depends where you stand. The Clamp HTTP path took 6,857 ns against 3,728 ns for the direct Axum route, 1.84 times, because it also does contributed routing, middleware and negotiation. A runtime-driven loop cost 5.661 ns per step against 0.627 ns for a bare loop.

Supervising a task through ManualRuntime was 601 ns against 7 ns for direct work. Those are real overheads on one machine, so decide whether your hot path is the place for them.

The per-step loop cost of 5.034 ns is the cancellation check, the clock advance and the iterator boundary, measured over 100,000 steps. For a firmware loop at 1 kHz that is invisible, and for a tight numeric kernel it would not be.

If a number worries you, the Kernel crate has benchmark targets for resolution and projection that you can run yourself.

What about compile times, since that is the thing that hurts most in Rust projects?

Pico adds 36.78 ms to a clean build of a trivial program, about 16 percent, and 1.67 ms to an unchanged rebuild. The bigger numbers come from what you select: a clean build of the Users example is 3.439 s console-only, 17.940 s with HTTP and 40.850 s with PostgreSQL.

Those are small samples on one Ryzen laptop CPU, so read them as budgets, not as a ranking.

The comment-edit rebuild is a fairer day-to-day number than a clean build: 10.86 ms over plain Rust in the Pico harness. For integrations, the real cost is the dependency graph you opted into, not Clamp's own code.

So keep HTTP and PostgreSQL out of crates that do not need them. The console-only build of the same domain is 3.439 s clean.

Which Rust version does it need, and can I just put it in my Cargo.toml?

Rust 1.96.1 is the tested minimum, edition 2024, resolver 3. The crates are not on crates.io, so you depend on them from Git.

Publishing waits on registry ownership and an API review, which is an open issue.

Edition 2024 and resolver 3 are used throughout, and the toolchain is pinned, so a fresh checkout builds with the same compiler as the checks.

The companion crates are pinned too: a lock file in the main repository records compatible commits for Core, Kernel, Runtime and the integrations.

rustclamp-core = { git = "https://github.com/rustclamp/core" }
Does any of this work without std, say on a small embedded target?

Partly. Example 12 is a no_std library using alloc::Vec and one Core module identity, with no Kernel or Runtime. It shows source compatibility of the consumer on a host.

It does not show that Core builds on bare metal, because Core's Clock still exposes std::time::SystemTime. I would not call it embedded-ready.

Why would you care? If you write a library, such as a decoder, you can depend on Core's identities without pulling the standard library into the library itself.

The next boundary is Core's own Clock, whose method returns std::time::SystemTime. Until that changes, I would call it host-side portability.

#![no_std]
#![forbid(unsafe_code)]
#![deny(missing_docs)]

extern crate alloc;
What would actually convince me that this is worth my time as a Rust developer?

Code, controls and a way to reproduce. Every number I quoted comes from a harness in the repo that builds a plain-Rust control with identical flags, alternates paired runs and keeps the raw samples. Missing measurements are recorded as null, never zero.

Run it yourself, then tell me where it is wrong. Design decisions are in the ADRs, and later ones amend earlier ones in the open: ADR 0017 amends ADR 0004, for instance.

You can also read the decisions. Each ADR records context, decision and consequences, and several include experiments that went against the first idea.

If you find a mistake, say so in an issue. The repository's own progress notes show site copy being corrected when it overstated, so the habit exists.

python3 rustclamp/tools/pico.py --measure rustclamp/docs/evidence/reports/phase1-pico.json
So where does Clamp sit: is it a framework, a library, or something in between?

In between, and closer to a library in how it treats your code. You call it, it does not call you. Clamp::run is a function, the resolver is a function, and nothing owns main unless you choose the facade's web app.

It is a composition layer made of small crates. Core has contracts and identities with zero dependencies, Kernel does resolution, projection and freeze over Core, Runtime supervises tasks, and the facade is the front door you depend on.

Integrations sit above those: HTTP over Axum and Tower, PostgreSQL over SQLx, messaging, worker and scheduler. Each is its own optional crate, and you can use Core and Kernel with none of them.

rustclamp-kernel = { git = "https://github.com/rustclamp/kernel" }

The facade does add framework-shaped conveniences, like the std-only web feature and the clamp init templates. Think of them as a starter kit built on the layer, and not the layer itself.

Do providers use generics or trait objects, and what does that do to lifetimes and my binary?

A capability is a marker type with an associated Value that may be unsized, so the value can be a trait object. The clock example returns a dyn Clock reference from provided_value.

pub trait Capability {
    type Value: ?Sized;

Resolution hands back a reference with the lifetime of your provisions, so nothing is boxed or reference-counted just to resolve. The docs say no allocation is needed on the success path.

Your own code can stay generic. The greeter in example 01 is Greeter<C: Clock>, monomorphised per clock, and Clamp never sees that type.

So dynamic dispatch lives at the composition seam, if you choose it. Inside your domain you decide.

Provisions borrow their values. What if I need Arc or static state, like a spawned server?

There are three forms. A plain Provision borrows from an object you own, which is the lightest. SharedProvision holds an Arc, and FactoryProvision builds the value lazily and can fail with a typed error.

On a frozen process, resolve_shared gives you an owned Arc handle that can move into 'static state such as a server or a spawned task, without Box::leak.

pub fn resolve_shared<C: Capability>(

The owned form arrived after a second app needed it, which is how the project says it decides: ADR 0018 deferred it until then.

The base idea still holds. The application owns and constructs values, and Clamp only matches them to requirements.

What do the error types look like, and do they work with the question mark and Box dyn Error?

Composition failures are structured. CompositionError has a kind, the requiring module, the capability, an optional qualifier and the sorted candidates, so you match on it and never parse a string.

ProjectionError and the target errors implement Display and Error, so they propagate with ? into Box<dyn Error>. That was a specific fix after an early app had to print them with debug formatting.

impl Error for ProjectionError {}

Exhaustive matching has a cost. Adding UndeclaredRequirement to CompositionErrorKind was a breaking change, and the ADR says so.

Worker errors follow the same spirit: DeadReason gained Malformed, and it is flagged as breaking.

How do the feature flags work across the crates, and can unification give me surprises?

The facade has no dependencies by default, and features opt in: config, web, db, crypto, mail, auth, markdown and uuid. They stack, so auth turns on web, db and crypto.

auth = ["web", "db", "crypto"]

In the runtime crate, tokio and signal are separate features, so an app that never waits for Ctrl-C does not get the signal crates. That split came from a stress-test finding recorded in ADR 0017.

Cargo unifies features across a workspace, which can hide dependencies. The measurement protocol therefore measures isolated consumers as well as workspace builds, and the boundary tool lists resolved closures per package.

So when you weigh a feature, build a small crate that depends on only what you need and run cargo tree there.

How do async handlers and cancellation work, and does it tie me to Tokio?

CancellationToken is std-only. It has cancel, is_cancelled and a cancelled() future that needs no executor-specific support, and waiters deregister on drop, so thousands of jobs sharing one root token do not pile up wakers.

Handlers are async closures, and the optional worker service spawns attempts onto Tokio. On shutdown it lets running attempts drain until a timeout, and then it cancels the root token.

pub async fn run(mut self, shutdown: impl Future<Output = ()>) -> io::Result<ServiceReport> {

Cancellation is cooperative. It cannot undo committed effects, and a blocking task that ignores it may outlive its join timeout, which the runtime README says outright.

So you are tied to Tokio only where you choose the service feature or the HTTP integration. Core and the base runtime are not.

Where does this feel awkward as Rust code? Tell me what annoys the authors themselves.

The project names its own ceremony: stable string identities for applications, processes, executions and modules. ADR 0017 treats that as part of the model and not boilerplate to remove.

Targets are verbose. A command tree or route table means a vector of module and declaration pairs handed to TargetComposition, and then two expect calls in the example because the result is nested.

.expect("the CLI target accepts these declarations")
.expect("a target produces a command tree");

The borrowed-provision style means constructing values first and wiring second. That is fine in main, and awkward if you expect a container to build things for you.

The stress tests also found real gaps, such as no lifecycle runner and errors without Display. Some were fixed and the rest are listed in the ADRs.

NestJS developer

Coming from NestJS, good. I will use Nest words where they help and say so when the match is poor.

RustClamp is the application composition layer for Rust. It sits above Axum and Tower, SQLx, Tokio and NATS and composes them into one application, so it is closer to Nest's module system than to Nest as a whole.

Shall we start with the differences, how modules compare, or what Nest has that Clamp lacks?

Is RustClamp basically NestJS for Rust, or is that the wrong way to see it?

There is overlap, since both structure applications with modules, but no. If I said yes you would expect decorators, a DI container and a controller layer, and you would be disappointed on all three.

It is closer to say Clamp composes a whole application out of typed capabilities, and HTTP is just one of them.

Where Nest says module, provider and controller, the closest Clamp words are module, capability or provider, and contribution. They rhyme without meaning the same thing, so I would rather map them one by one with you.

And a quick answer to the question under the question: if you want Nest ergonomics in Rust, Clamp is not that, and I will say so as we go.

Yes, lay it out. What is actually different between NestJS and RustClamp?

Nest centres on server-side architecture: modules, providers, controllers and a runtime DI container. Clamp does not start from HTTP, and it is explicitly not a DI container, a web framework or an ORM.

The practical differences are that resolution is by type and checked before the process starts, wiring is frozen afterwards, and the same model runs a CLI, a worker or a device loop. Nest runs one kind of thing very well, and Clamp tries to run several kinds from one blueprint.

Nest sits on an HTTP server, Express or Fastify, and gives you decorators over it. Clamp has no such base. The HTTP integration is one optional crate, and a worker or CLI process has no HTTP in it at all.

Then the container. Nest builds your graph at runtime by reflection. Clamp expects you to construct the values yourself, and uses the declarations to check and to order startup, not to instantiate.

If I know Nest modules well, will Clamp modules feel familiar to me?

Partly. A Clamp module has a stable identity and declares what it requires and provides, but it does that with separate trait impls, so a module grows by adding declarations and not by editing a central list. There is no @Module({ imports, providers }) array to maintain.

The trade is verbosity. You write the impls, you name the module with a string identity, and the compiler checks the shape.

const ID: ModuleId = ModuleId::new("example.module.greeter");

A Nest module also has exports. The Clamp equivalent is simply that another module can require a capability this one provides. There is no export list to maintain.

One more difference: a Clamp module does not construct itself. Your code builds the value and hands it in as a provision.

pub struct GreeterModule;

impl Module for GreeterModule {
    const ID: ModuleId = GREETER_MODULE;
}

impl Requires<ClockCapability> for GreeterModule {}
Where do my providers and injection tokens go in this model of the world?

That is the same Kernel step we just touched on with modules. A capability is the typed contract, and a provider is a module that provides it. Kernel finds one provider by type. If there are two and you have not chosen, that is an error listing both, which is the opposite of last-registration-wins.

You can select, default or replace a provider in the blueprint, and each of those shows up in inspection output as how it was chosen. Note this is a declaration style, not decorators.

Nest has provider scopes, injection tokens and custom providers with useFactory. The nearest Clamp thing is FactoryProvision, which builds a value lazily and can fail with a typed error.

You can also see how each choice was made. Inspection output records whether a provider was unique, defaulted or explicitly selected, which is the sort of thing you debug by logging in Nest.

.require_provider(GREETING_COMMAND, ClockCapability::ID, None, CLOCK_PROVIDER)
.require_provider(CLOCK_PROVIDER, EPOCH_CAPABILITY, None, EPOCH_PROVIDER)
.require_provider(WORKER_ROOT, QUEUE_CAPABILITY, None, QUEUE_PROVIDER)
What replaces controllers and decorators when I want to define my routes?

Routes are contributions. A module hands an HttpRoute with a path and an ordinary Axum handler to the HTTP target, which validates paths and compiles one router. Today that is one path per declaration.

The generated --web app has an app/http/controllers/ folder if you want that layout, plus Laravel-style route files. Neither stack has decorators.

The handler is an ordinary Axum function, so request parsing is Axum extractors. In the std-only stack it is a function from a request reference to a response, with accessors for route parameters, query values and form fields.

Routes from different modules are collected by the target, so a duplicate path is an error at composition and not a shadowed route at runtime.

HttpRoute::<Public>::new("/users/{id}", get(get_user::<R>).with_state(state.clone())),
What about guards, pipes and interceptors? Is there anything equivalent to those?

Middleware, mostly. With rustclamp::web you stack .middleware() on a route group, with sessions, csrf(), an authenticate function and a role("admin") guard. With rustclamp-http you add Tower layers and a require_role helper.

Validation lives in request types under app/http/requests, and authorization is meant to be enforced inside the domain operation. There is no pipe abstraction to learn.

Nest guards run before handlers and can read metadata set by decorators. The Clamp equivalent is plain middleware order. A route group declares which middleware wraps it, and a role guard such as role("admin") is the same mechanism.

The deeper rule is the one in the example: authorization is meant to be enforced inside the domain operation, with middleware as the first line and not the only one.

router.group(|web| {
    web.middleware(sessions.middleware())
        .middleware(csrf())
        .middleware(authenticate)
Does it have auth and email built in, like the Nest packages I normally use?

Both exist in the std-only web stack as optional features, in prototype form. Auth does login and logout, throttled attempts, one role per user, sessions that reload the user each request, registration with email verification and password reset. Mail is SMTP over STARTTLS with an outbox table and a daily cap.

The first admin is made with a command, and a forbidden route answers as 404 by default. If you need OAuth or social login, I do not see that in the source.

Err(Refused::Throttled(wait)) => /* ask the user to retry later */,

Compared with Passport and a guard, the scope is narrower and more opinionated. There is one role column, the session holds only the user id, and the user is reloaded on every request, so blocking takes effect on the next one.

Login attempts are throttled per email and client, and an unknown email and a wrong password cost the same Argon2 check, so neither the message nor the timing reveals which emails exist.

cargo run -- user:create ada@example.com admin "Ada"
Is there a TypeORM or Prisma equivalent somewhere in RustClamp?

No ORM, and that is deliberate. The web stack's db feature is SQLite compiled in, with a migration builder, seeders, a query builder and transactions. The Postgres package gives you SQLx pools and ordered migrations and keeps SQL in your adapters.

The one macro in the facade is an optional #[derive(Model)] that reads a row into a struct by column name. Anything cleverer you write by hand.

Compared with TypeORM entities, the model is a plain struct with an optional derive that reads columns by name, and the table is named explicitly. Relations, lazy loading and a repository pattern are yours to write.

Query building exists for SQLite, and for PostgreSQL you write SQL with SQLx. Neither hides the transaction boundary.

Schema::create("posts", |table| {
    table.id(); // INTEGER PRIMARY KEY
    table.string("title");
    table.timestamps(); // created_at, updated_at
})
Is there something like Bull or the Nest queues for background jobs?

There is a worker that runs handlers over NATS JetStream. A handler is declared by message name and schema version, payloads are decoded before the handler runs, and a failure is classified as retryable, permanent or unknown-outcome. Retries are bounded, and dead-lettering is explicit.

No Redis-backed queue yet. Redis is on the roadmap under integrations, which means planned and not built.

fn claim(
    &mut self,
    limit: usize,
) -> impl Future<Output = io::Result<Vec<Claim<Self::Receipt>>>>;

Bull gives you a Redis-backed job queue with a dashboard. Here the queue is JetStream, the dashboard is whatever you point at NATS, and Clamp's part is the handler contract and the retry classification.

The worker service adds backpressure, bounded concurrency and a timed drain over any transport you implement with claim and settle, so it is not welded to NATS.

let handler = HandlerDeclaration::typed(SUBJECT, 1, |email: EmailPayload, _| async move {
    Ok(())
});
And what does scheduling look like compared with the Nest schedule module?

A job is a name, a fixed interval, a misfire policy (run once to catch up, or skip) and an async closure, driven by a clock you control. There are no cron expressions, so "every weekday at 9" is not directly expressible.

On shutdown it stops admitting new runs and lets the active one finish. Overlap is prevented inside one process only.

pub async fn run_until(

Nest's schedule module puts decorators on methods, with cron strings. Here you declare jobs as data, which means a test can build the same declaration and drive it with a manual clock.

There is one driver method, run_until, behind the tokio feature, so you do not hand-write the tick loop.

let catch_up_job = JobDeclaration::new(
    "cleanup-expired-sessions",
    Duration::from_secs(10),
    MisfirePolicy::RunOnce,
How would I test this compared with the Nest testing module and provider overrides?

There is no container to override. You pass the thing under test what it needs, such as a fixed clock, an in-memory repository or an in-memory message bus. A web app's router can be called without a server through a test client.

For wiring, a process test can assert which modules a projection includes. That is closer to checking your module graph than to mocking providers.

The big difference from Nest's testing module is that nothing needs a container to start. A test constructs the pieces, hands them in, and asserts.

For the wiring itself, the process tests start both projections together and check that the CLI and the worker reach what they should. That is the closest thing to testing your module graph.

use rustclamp::web::testing::Client;
#[test]
fn health_returns_json() {
    let (routes, _db) = my_site::app().test("");
    assert_eq!(Client::new(&routes).get("/api/health").status, 200);
}
Can I do what Nest microservices do, with messages between separate services?

Some of it. One blueprint can project into several processes, such as an API and a worker, that talk through a versioned JSON envelope over NATS JetStream. The email and order examples run that way as separate binaries.

No gRPC, MQTT, Redis or other transports yet. Those are on the roadmap for the integrations stage, not in the code.

Nest gives you transporters, a pattern-matching message handler and a client proxy. Clamp's messaging package gives you a typed envelope with a name and schema version, and the worker target dispatches by exact name and version.

Request-response over a transport is not something I can see in the source, so if you lean on send-style calls, plan for events and commands only.

What replaces onModuleInit and the shutdown hooks I rely on in Nest?

Core defines opt-in phase traits: Initialize, Start, Ready, Drain and Stop. A module implements only the ones it needs, and example 05 derives start and stop order from the resolved dependencies.

Be aware that the dispatcher in that example is local to the example. A reusable lifecycle driver is an open design decision, so Clamp does not run those hooks for you yet.

pub trait Drain: Module {

In Nest, onModuleInit is called for you. Here the phase traits are traits you implement, and what is missing is the thing that calls them in order across modules.

The example shows the order such a driver would use: providers first on the way up, consumers first on the way down, with a drain step before the stop.

What would I have to unlearn coming over from Nest to this model?

Decorators and reflection become traits and explicit declarations. A runtime container becomes a plan that is resolved and frozen before the process starts. Request-scoped providers do not exist, because request state is call data and not a capability.

The thing you will probably miss is convenience. The thing you may gain is that a wiring mistake shows up as a named error at startup and not as an undefined at 3 a.m.

The biggest shift is where mistakes surface. In Nest, a missing provider appears when the app boots. In Clamp it appears when the blueprint freezes, before any side effect, and the error names the module and the capability.

The second shift is who instantiates. You do.

Could I build the same backend I would build in Nest, today?

Many of the pieces exist: HTTP routes, a database (SQLite built in, PostgreSQL via SQLx), auth, mail, workers, jobs, tests. The pieces are younger and fewer, and the APIs may change before 1.0.

If you need WebSockets, gRPC, GraphQL, OAuth, or a deep plugin ecosystem, check the roadmap before you promise anything to anyone.

What I would check against your list: OAuth or social login, GraphQL, gRPC, Redis queues and OpenAPI generation. I cannot find any of those in the source, so assume they are yours to bring, or not available.

What you do get that Nest does not give you out of the box is a single release binary and exactly repeatable simulation loops, which may or may not matter to you.

How big is the ecosystem around this, honestly, compared with Nest?

Small. Nine public repositories, fourteen runnable examples, and one set of docs. Nest has years of packages, tutorials and Stack Overflow answers, and Clamp has me.

The upside of building on Axum, SQLx and Tower is that the libraries underneath are the Rust ones, so their ecosystem is available to you.

You can read all of it in an afternoon, which is an advantage and a warning. The docs site, the ADRs and the evidence pages cover the model, and the examples are the tutorial.

Questions go to GitHub issues. It is a small community in the sense that it is very early.

How would I try this out from a Nest background without porting everything?

Generate a web app and read the generated routes, views and tests. Then add a worker or a scheduled job beside it. Do not port your whole service on day one.

If you want the closest feel to a Nest module graph, run the process example and ask it why a module is in a process.

A good first port from Nest is a queue consumer. It has a clear message contract, no HTTP surface, and an obvious way to compare behaviour.

Keep the Nest app running, publish the same messages to the Clamp worker in parallel for a while, and compare. If it disappoints, delete one process.

clamp init my-site --web
cd my-site
clamp dev
Nest calls itself a framework. Is RustClamp one too, or is that not the right word?

Not quite the right word, and the Nest comparison shows why. Nest owns your application: it boots, it builds the container, it decides how controllers are discovered. Clamp does not own your main.

It is a composition layer. A layer of what? It sits above the crates you already use, Axum and Tower, SQLx, Tokio and NATS, and composes them into one application. It declares modules, typed capabilities, lifecycle phases and processes, and checks them before anything runs.

It also ships framework-style conveniences, to be fair. The std-only web stack, the clamp init --web layout, sessions, auth and mail are there if you want a Laravel-shaped project.

So think of Nest's module system plus its DI container, minus the container and minus owning your app. That half is Clamp, and the other half is yours.

How do I validate request bodies the way I use DTOs and class-validator in Nest?

In the std-only web stack, validation is rules attached to form fields. request.validate takes pairs of a field name and a rule string, such as required, email, a maximum length, integer or url, and returns validated text or an invalid result.

let rules = [("email", "required|email"), ("name", "required|max:100")];

On invalid input with sessions, errors and the old input are flashed and the user is redirected back. Without a session you get a 422. Uploads have rules too: file, image and mimes.

JSON bodies differ. The web stack's form accessor does not parse JSON, so a JSON endpoint inspects the body and uses a serializer. With rustclamp-http, Axum's decoder does the work, and malformed JSON is rejected before your operation runs.

There are no decorators on types, so a DTO is an ordinary struct, and its rules live next to the handler or in your domain.

What replaces request-scoped providers and transient instances, which I use a lot in Nest?

Request state is call data and not a capability. The principal, tenant, correlation id, deadline and transaction are passed to the operation as a context value, so there is no per-request container.

For a transaction per request, the Users example creates an infrastructure repository instance for one execution, and that instance holds the transaction. The domain only sees its repository trait.

When you do need several providers of one capability, qualifiers keep them apart in the type system. The PostgreSQL package distinguishes Primary from Analytics pools that way.

Singletons are the default in a sense, since provisions are values you constructed once. Transient behaviour is just constructing another.

Nest has forwardRef for circular dependencies. What happens here if two modules need each other?

Kernel reports a cycle as an error with the closed module path, so you see exactly which modules form the loop. There is no forwardRef and no lazy proxy to paper over it.

Validation is iterative, so a chain of 2,048 modules does not overflow the stack, and projection through 500 declared modules measured about 1.86 ms at the median.

The usual cure is the same as in Nest: split out the thing both of them need into a third module, or invert one direction behind a trait.

A cycle only matters if a process's roots can reach it. Unreachable modules are not validated, so a dormant cycle elsewhere does not stop yours.

What is the equivalent of dynamic modules and forRoot, where configuration shapes a module?

Configuration is passed into constructors. The web package pattern makes it explicit: a package takes its settings from the app's Config through a From<&Config> impl, and the app adds it with one explicit line.

There is no auto-discovery. Cargo cannot register a package without build-script tricks, so the dependency plus a .package(...) call is the registration.

For composition that depends on runtime choices, explicit declarations stay first-class. CapabilityRequirement::new and Provision::new let you declare things at the call site instead of through traits, and ADR 0004 keeps them on purpose.

let provisions = [Provision::<ClockCapability>::from_module(&clock)];

Compared with forRoot, you get the same outcome, a module shaped by configuration, with the wiring visible in one place and nothing hidden behind a static method.

How do configuration and logging compare with ConfigModule and the Nest logger?

Config reads .env and the environment, and the real environment wins. A key that is set but does not parse stops startup and names the key, and a missing required key does the same.

Teams usually read everything once into a settings struct that the rest of the app uses, which is close to a typed ConfigService.

let per_minute: u32 = config.get_or("API_PER_MINUTE", 60);

Logging has PSR-3 style levels, a LOG_CHANNEL such as single, daily, stderr or a stack of them, and one line per entry. Every request line ends with ref= and a UUIDv7, so one search finds the whole request.

Logging never takes the app down, since a failed write is ignored. I read no structured JSON logger in the source, so if you need one, bring your own.

Architect

An architect, good. Then I will talk in boundaries, contracts and failure modes, with code only where it settles an argument.

RustClamp is the application composition layer for Rust. A layer of what? It sits above the crates you already use, Axum and Tower, SQLx, Tokio and NATS, and composes them through modules, typed capabilities, lifecycle and processes. It replaces none of them.

Where do you want to push first: the capability model, resolution and ambiguity, or what freezing means?

I do not care about syntax. What does RustClamp change architecturally?

It makes application capabilities explicit. Infrastructure stops spreading through the domain, and the application composes what it needs at declared boundaries. The layers are small: Core contracts at the bottom, Kernel and Runtime coordination above, integrations such as HTTP and PostgreSQL above that, and dependencies only ever point down.

I should be upfront about the state of it. Resolution, projection and freeze exist as tested prototypes. A single end-to-end path from blueprint to a running production process does not exist yet, and I will keep marking where that matters.

To make it concrete: in a typical Axum and SQLx service, the pool, the router and the background loop are wired in main, and the dependency edges between them exist only in your head. Clamp turns those edges into declarations that a tool can read, check and print.

The cost is that you maintain the declarations. If your system is small enough to hold in your head, that is a bad trade.

What is the real difference between a capability and a contribution in your model?

A capability is used while the application runs, and exactly one provider is resolved for each requirement. A contribution is used while a subsystem is being built: any number of modules contribute, and a target collects them into a command tree, a route table or a handler registry.

The target owns the domain rules, such as unique command names. Core and Kernel only carry generic declarations. Because public and admin commands are different Rust types, handing a public declaration to an admin target is a compile error.

A rule of thumb I use: if it is consumed while running, it is a capability, and if it is consumed while assembling something, it is a contribution. A clock is a capability and a route is a contribution.

Qualifiers add one more axis. The same capability can have several providers for different purposes, kept apart by a type, such as primary and analytics databases.

/// A public command contributor whose declaration cannot enter an admin target.
///
/// ```compile_fail
/// use rustclamp_example_contribution::{AdminCommands, CommandDeclaration, HelloModule};
/// let _: CommandDeclaration<AdminCommands> = HelloModule::contribution();
/// ```
How does resolution actually work, and what happens when two modules both provide a Clock?

Kernel matches each requirement to the provisions it is given. One provider resolves directly. None gives a missing-provider error naming the module and capability, and two with no choice made give an ambiguity error with the candidates sorted, the same in either registration order.

You can resolve the ambiguity by selecting a provider explicitly, by declaring a default, or by replacing one provider with another. Two equal-precedence defaults are still an ambiguity, and inspection output records how each provider was chosen.

pub fn select_provider(
pub fn default_provider(
pub fn replace_provider(

Two details architects ask about. An optional requirement with no provider activates nothing, and a many-cardinality requirement collects every provider. Neither is a way around ambiguity, because each is a different declared shape.

Also, when a provider is replaced, only the replacement's reachable subtree is included. Swapping a PostgreSQL adapter for a memory one in a test process drops its dependencies from that plan.

pub enum CompositionErrorKind {
    MissingProvider,
    AmbiguousProviders,
    ProviderSelectionUnavailable,
    UndeclaredRequirement,
}
Why make ambiguity an error instead of letting the last registration win?

Because last wins hides real bugs, and it makes the answer depend on registration order. An error that names the consumer, the capability and every candidate is something a person can act on at startup.

Cycles get the same treatment: they are reported as a closed module path, and validation is iterative, so a chain of 2,048 modules does not grow the stack. Projection time for a chain through 500 declared modules measured a median of about 1.86 ms, and it happens once, before a process runs.

It has an operational benefit as well. Because the candidates are sorted, the same error text appears on every machine and in every run, so you can search for it and compare logs.

The companion guard is that explicit selection is recorded in inspection output, which turns an implicit decision into a reviewable one.

You keep saying freeze. What does freezing the plan actually guarantee?

Composition is mutable and execution is structurally immutable. Freezing consumes the blueprint and produces a FrozenProcess holding two views of the same resolution: a compact runtime plan, and structured inspection metadata. What you inspect and what runs come from one place.

A compile-fail doc test checks that you cannot add a module to a frozen process. That is a structural guarantee, so it does not freeze the values your modules hold, and it does not prove your wiring is right.

Why freeze at all? It gives you one point where validation completes before any side effect, and it gives tools a stable object to inspect. Without it, inspection and execution can drift apart.

pub fn resolve<'value, C: Capability>(
    &self,
    consumer: ModuleId,
    provisions: &[Provision<'value, C>],
) -> Result<&'value C::Value, CompositionError> {

ADR 0017 added FrozenProcess::resolve so the blueprint stays the single declaration. You supply constructed values, and the frozen plan decides which provider each consumer gets.

pub struct FrozenProcess {
    runtime: RuntimeProjection,
    inspection: ProcessProjection,
}
What does reachability mean, and what happens to modules a process does not use?

A process is defined by its roots. Kernel walks the requirements and contributions reachable from those roots and ignores everything else, so a module only the CLI uses is never validated, configured or started by the worker.

In the example, a dormant module requires a database provider that was never declared. It sits in the exclusion list as unreachable and does not break the CLI process. That is also why worker-only configuration is read only when the worker is included.

pub fn exclusions(&self) -> &[ExcludedModule] {

The consequence for architecture is that processes become cheap to carve out. A worker that reaches a clock and a queue does not drag your HTTP stack into its plan.

A dormant module is not forgotten, either. It still appears in the excluded list with a reason, either explicitly excluded or unreachable.

application
    .add_execution(CLI_EXECUTION, CLI_ROOT)
    .add_execution(WORKER_EXECUTION, WORKER_ROOT)
    .add_process(CLI_PROCESS, vec![CLI_EXECUTION])
    .add_process(WORKER_PROCESS, vec![WORKER_EXECUTION])
Does that reachability remove code from the binary, or only from the runtime plan?

Only from the runtime plan, and the example says so itself: runtime reachability does not prove compile-time or dependency stripping. One binary that selects CLI or worker at runtime still contains both.

Stripping is done with Cargo features and separate build targets, and the example contrasts the two styles. The CLI-only target activates Core without Kernel, while the worker target activates both. Cargo does that work, and Clamp does not pretend otherwise.

So if binary size matters, express the split with Cargo features and separate binaries, and let Clamp validate each one. The two mechanisms stack, and neither replaces the other.

The example's own README warns that its tiny binaries exercise different amounts of behaviour, so the size comparison is illustrative and not a causal overhead claim.

How is start and shutdown order decided, and who runs it?

Order comes from the resolved requirements. Providers initialize before their consumers and stop after them, so Database starts before Users, Users before Worker, and shutdown runs in reverse after draining the consumers first. Core defines opt-in phase traits, and a module implements only the ones it needs.

Here is the honest part: the dispatcher that drives those phases lives inside example 05. A reusable lifecycle driver is an open design question, since it needs decisions on ordering, failure policy per phase and where it lives. So the order is proven, but nobody runs it for you yet.

For architecture reviews, the useful artifact is the edge list: worker requires users, and users requires database, printed from the resolved requirements. It is the same graph that orders startup, so the diagram and the behaviour cannot disagree.

Failure policy is the unsettled part. The example covers cleanup of only the resources that were acquired, and cleanup that continues after injected errors, but a general policy per phase is exactly what the open decision has to settle.

pub trait Initialize: Module {
pub trait Start: Module {
pub trait Ready: Module {
pub trait Drain: Module {
pub trait Stop: Module {
How do you test lifecycle and time-based behaviour without sleeping on real clocks?

Time is a capability, so tests supply their own. Core ships a ManualClock, and the scheduler takes whatever clock you pass to tick. The test below runs a ten second interval, jumps forty seconds, and asserts that exactly one misfire was skipped.

The lifecycle example uses the same idea with fake resources and a fake clock. Its 5 ms to ready and 14 ms through shutdown are configured durations that check ordering and deadlines, and they say nothing about hardware speed.

pub struct ManualClock(std::sync::Mutex<SystemTime>);

The same principle covers randomness and input. The device loop takes a seeded random source and explicit input ticks, so a test can replay the same sequence and compare every state.

What this does not give you is protection from real-clock bugs in your own code. If a module reads the system time directly, it bypasses the capability, and nothing flags it.

advance(&clock, Duration::from_secs(40));
let late = futures_executor::block_on(scheduler.tick(&clock));

assert_eq!(late.invoked, 1);
assert_eq!(late.misfires_skipped, 1);
What is a process projection, and how do I inspect one?

One application blueprint resolves into separate projections, such as a CLI and a worker. Each lists the modules it includes with an inclusion reason and a root-to-module path, the selected providers, and the exclusions. The clamp commands read a versioned JSON document that the application exports from those projections.

pub fn includes(&self, module: ModuleId) -> bool {

Inspection never parses your source, starts the app or opens a connection. That document is the contract for tools, and doctor exits non-zero when the composition is invalid.

The output serves people and tools alike. Every command takes --json, the document has a schema version, and diagnostics carry stable codes, so you can diff architecture between commits the way you diff code.

Counts in the output are structure, and not estimates of memory or time, which the docs state explicitly.

clamp tree architecture.json --process web
clamp why architecture.json storage.postgres --process web
clamp doctor architecture.json
How do you keep process-specific configuration from leaking into other processes?

Configuration is read after the projection says the module is included. In the example, the worker concurrency setting is only read when the worker root is part of the frozen process, so the CLI starts fine when the setting is missing or invalid.

The worker fails with a named setting error and a reason of missing or invalid. It is a small thing, but it is the kind of boundary that becomes expensive to retrofit.

It generalises. Anything with a startup side effect, such as reading a setting, opening a pool or binding a port, belongs after projection and only for included modules. That keeps failure modes per process.

The tests assert the corollary: the CLI ignores missing or invalid worker configuration while the worker reports it.

let worker_concurrency = if projection.includes(WORKER_ROOT) {
    let value = read_setting().ok_or(StartupError::WorkerSetting {
        process,
        setting: WORKER_CONCURRENCY_SETTING,
        problem: WorkerSettingProblem::Missing,
    })?;
Does process here mean an operating system process, and how does work cross between them?

No. A process id is a logical projection, and example 04 runs the CLI and worker projections in one in-memory host with independent state. Sharing across real OS processes is not demonstrated there.

Separate binaries are exercised in the email and order examples, over NATS JetStream. Crossing the boundary is always explicit: a domain event is mapped by hand to a versioned envelope with a name, schema version and correlation id, so the event type itself carries no serialization traits.

Because crossing is explicit, you can reason about it. The envelope has no broker acknowledgement state, so transport concerns stay in the transport, and the schema version lets producers and consumers evolve separately.

The initial envelope promises JSON payloads only, with no binary payloads or schema evolution tooling, which ADR 0006 states outright.

MessageEnvelope {
    id: context.message_id,
    name: "users.user-created".to_owned(),
    schema_version: 1,
    correlation_id: context.correlation_id,
    causation_id: None,
Can I move one capability, say a database, behind a service boundary later?

Whole processes, yes: one blueprint projects into several programs, each starting only what its roots reach. Moving a single capability behind a service boundary is planned, not built.

What you can do today is put a message boundary between two processes of the same blueprint, as the worker examples do. If a capability needs a network hop right now, you design that hop yourself.

The design principle on the processes page is the one to hold on to: crossing a process boundary will always be explicit in Clamp. That is a promise about how the feature will look when it arrives.

In the meantime, a capability that must be remote can be a trait in your domain whose adapter makes the call, which is ordinary hexagonal architecture.

Where does Clamp lock me in, and where does my domain stay free?

Your domain stays on your own ports. In the Users example the domain owns UserRepository, memory and PostgreSQL are adapters, and the same operation serves a console and HTTP. Clamp owns composition and not your business model.

It leaks in a few places, though: stable string identities for applications, processes, modules and capabilities, the Requires and Provides impls, and contribution types. The project itself calls the identities ceremony and treats them as part of the model.

pub struct Users<R> {

You can measure your exposure. Count the modules that import Clamp crates beyond Core's identity types, and count the contribution targets you wrote. Domain crates that import nothing from Clamp are fully portable.

The example's domain depends on neither HTTP nor the database, which is also why the same operation serves a console.

/// Domain-owned persistence port; adapters may use memory or PostgreSQL.
pub trait UserRepository: Send {
Concretely, what would exiting Clamp cost me in a year?

The domain and the crates underneath stay yours, and the license is MIT or Apache-2.0. For a module built only on Core, exit is mostly replacing resolution with constructor calls. Example 01 does that already: the greeter takes any Clock, and the binary hands it one, with nothing from Clamp except Core's Clock trait.

What you lose is the checking and the inspection. You would be wiring by hand again, and any contribution targets you wrote would become plain tables. I would put that in your pilot criteria on day one.

A practical exit plan has three parts. Keep domain crates free of Clamp imports, keep wiring in one composition module, and keep the inspection output in CI so you know what the wiring was.

If you ever need to leave, that last artifact doubles as the spec of what to rebuild by hand.

fn main() {
    println!("{}", Greeter::new(SystemClock).greet("world"));
}
Who owns validation rules in a contribution target, and can I write my own?

The target owns them. The CLI target validates unique command names and ordering, the HTTP target validates and compiles route paths, and the worker target rejects duplicate name and version pairs. A required contribution with no target is a structured error, so it is never a silent no-op.

pub struct TargetComposition<T: ContributionTarget, Q: Qualifier> {

A third-party target can be written against the public traits alone, and the example's tests include one. Assembling two command declarations took a median of 86 ns on one machine, so the machinery is cheap.

Why this matters architecturally: the rules for a subsystem live with the subsystem. Core never learns what a command or a route is, so adding a new kind of target does not touch it.

Required contributions with no target are errors and optional ones are dropped, so an unconsumed declaration never silently disappears.

let target = CliCommandTarget::<PublicCommands>::new();
let commands = TargetComposition::<_, PublicCommands>::new(vec![
    (HelloModule::ID, HelloModule::contribution()),
    (GoodbyeModule::ID, GoodbyeModule::contribution()),
])
Is there a record of why decisions went the way they did, including the ones that went badly?

Yes, in the ADRs, and they include inconvenient numbers. ADR 0004 compared additive trait declarations with explicit ones and found no compile-time difference. ADR 0017 later amended it after a stress test where the Clamp CLI needed more Rust than the plain version: 223 lines against 197, and 117 crates against 114.

Another stress test, a job runner, needed 806 lines against 585 in plain Rust at first. That led to moving the worker loop, with its retries and drain, into the worker package, since the app's runner file had spent 293 of its roughly 795 lines on it.

Read them in order and you can watch the model move. Additive traits came first, explicit declarations stayed, frozen resolution replaced hand-built compositions, and the worker loop moved into the worker package after two apps wrote the same one.

The lifecycle driver is the biggest decision still open.

What parts of the model are least proven right now?

Three come to mind. There is no single blueprint-to-production path yet. There is no reusable lifecycle driver, so the order is proven but dispatched by example code. And sharing between real OS processes is not demonstrated outside the messaging examples.

Also open: provider selection is validated at composition time, not by the Rust compiler, and exported metrics and out-of-process capabilities are planned. I would rather you hear that from me than discover it on a Friday.

If I had to rank them for an architect, the lifecycle driver comes first, because it decides who owns ordering and failure policy. The end-to-end pipeline comes second, because it decides whether the checks you saw guard production startup.

The process-sharing question comes third. Examples 08 and 09 show separate binaries, but not shared state beyond a broker and a database.

Is this a framework in the architectural sense, with inversion of control, or something else?

No inversion of control over your whole application, which is the thing that usually earns the word. Your main stays yours, and Clamp's functions are called by your code.

Architecturally it is a composition layer with four moving parts. Core holds contracts and identities with no dependencies. Kernel resolves requirements, projects a process and freezes the plan. Runtime supervises tasks. Integrations sit on top and wrap ecosystem crates without hiding them.

A layer of what? It sits above Axum and Tower, SQLx, Tokio and NATS, and replaces none of them. Dependencies point only downward, and a layer you do not use is not in your binary.

The facade crate adds starter conveniences such as the std-only web stack. Treat those as a product built on the layer, which you can ignore.

What are qualifiers, and how do I keep two databases or two clocks apart?

A qualifier is a type that gives a capability a second identity. It lets one application hold several providers of the same capability for different purposes, and each requirement names the qualifier it wants.

The PostgreSQL package uses Primary and Analytics, so a pool for one cannot be passed where the other is expected. In the same way, public and admin routes are different qualifiers of the HTTP target.

pub fn resolve_qualified<'value, C: Capability, Q: Qualifier>(

Inspection output lists the resolved capability together with its qualifier, and error values carry it, so an ambiguity error says which qualified requirement failed.

ADR 0017 notes that the frozen resolve path supports unqualified requirements only until a process needs the qualified variant.

What about optional dependencies, and requirements that want every provider and not just one?

Both exist as different declared shapes. An optional requirement with no provider resolves to nothing and activates nothing, and with exactly one provider it returns that provider.

A many-cardinality requirement collects every provider, which suits something like a set of health checks. Ambiguity does not apply, because you asked for all of them.

pub fn resolve_many<'value, C: Capability>(

In a process projection, an optional requirement with no selected provider is recorded as absent in the inspection data, so you can see it was considered.

Neither shape lets you dodge a real conflict. Multiple candidates for an optional requirement remain ambiguous.

What is in the inspection JSON, and can I use it as a CI gate for architecture rules?

The document has a schema_version, an application id and a status. A resolved document lists processes, modules, providers, contributions and structural counts, and an invalid one carries diagnostics with a stable code, a severity, a message and structured context.

Output is stable on purpose: semantic ids, sorted collections and resolved inclusion paths, so a diff means something. Consumers should keep unknown fields and codes, and a changed meaning gets a new schema version.

As a gate, clamp doctor exits with status 1 when the composition is invalid and 2 for a command or file problem, so a CI step can fail on it. Rules beyond validity, such as the CLI never reaching the database, you would write against the JSON yourself.

clamp graph architecture.json --json

Outside the schema are configuration values, lifecycle outcomes, message delivery, transaction state and device state.

How does this scale with hundreds of modules, and what does projection actually cost?

Projection time for one reachable chain through N declared modules grew from about 0.35 microseconds at one module to 71 microseconds at fifty, 201 microseconds at a hundred, and 1.86 milliseconds at five hundred.

Growth is faster than linear across that range, so I would not promise a flat line. It runs once, before the process starts, and composition construction is outside the timed region.

Cycle detection is iterative, so a chain of 2,048 modules validates without growing the stack.

Treat these as one machine's medians. If you expect thousands of modules, run the benchmark in your own environment.

Who owns resources like a database pool, and what happens to them at shutdown?

Both ownership styles are modelled. In the managed path the lifecycle owns the database, so its initialize and stop hooks run and it is closed after shutdown. In the external path the caller owns it, neither hook runs, and it stays open.

Two projections from one blueprint can share an application-owned database while each keeps its own process state. Stopping one leaves the other running, and stopping both leaves the database to its owner.

The PostgreSQL package expresses the same idea as managed or external pool ownership. A managed pool is closed by the process, and an external one is left to its owner.

The example calls this a fixture proof and not automatic resource injection in Kernel, and says detached-work and restart ownership modes are not modelled yet.

How do I keep data consistent when work crosses a process boundary? Where is the line?

Clamp does not give you distributed transactions, and it does not pretend to. The pattern it demonstrates is a local transaction plus an outbox on the sending side, and a durable inbox on the receiving side.

Delivery is at least once, so duplicates are normal. The receiver records each message id in the same transaction as its effect, so a duplicate is skipped and acknowledged.

ON CONFLICT (message_id) DO NOTHING RETURNING message_id",

For effects with unknown results, such as a payment timeout, the failure classification says retrying is unsafe. The order stays reserved, and a person or a reconciliation job settles it.

The evidence covers specific crash windows. It does not claim exactly-once delivery at the broker, and ADR 0006 says handlers must tolerate duplicates.

SRE / platform

Platform and operations, good. I will talk about drains, retries, crash windows and footprints, and I will say plainly when something is design and not running code.

RustClamp is the application composition layer for Rust. It sits above the crates you already operate, Axum and Tower, SQLx, Tokio and NATS, and composes them into one application. It replaces none of them.

Where should we begin: shutdown, failure handling, or readiness and health?

I run production systems, so what does RustClamp actually do for operations?

It keeps lifecycle explicit. A module opts into phases such as initialize, start, ready, drain and stop, and the order comes from the resolved dependencies. Liveness, readiness and per-component health are separate values, not one boolean.

Now the part you will want in writing: that model is proven in an example with fake resources. A reusable lifecycle driver that sequences it for you is not built yet, so I will mark each answer as running code or as design.

For comparison, a typical Axum service gives you graceful shutdown on one listener and nothing across the rest. The idea here is that one set of rules covers every component, listener, worker and scheduler, so draining consumers first is a property of the plan and not of whoever wrote main.

Whether that pays off depends on how many moving parts you run.

What order does shutdown happen in, and what happens to work still in flight?

The design says shutdown starts by refusing new work, not by destroying dependencies. Readiness goes false and admission closes, in-flight work drains within a deadline with consumers first, remaining cancellable work is cancelled, participants stop in reverse dependency order with providers last, and final diagnostics are flushed within a bound.

Cancellation stops work and does not undo effects that already happened. The worker service takes the knobs you would expect, shown below.

Compare it with what you do today. A signal handler cancels a token, tasks race to exit, and the process drops resources in whatever order the destructors run. The design replaces the race with an order.

The order is also testable. The lifecycle tests assert reverse-order stop, cleanup of only acquired modules, and bounded deadlines.

pub struct ServiceConfig {
    pub concurrency: usize,
    pub capacity: usize,
    pub poll: Duration,
    pub drain_timeout: Duration,
    pub handler_timeout: Option<Duration>,
    pub retry: RetryPolicy,
}
Which parts of that shutdown story exist in running code today, and which are only design?

Running code first. rustclamp-http serves on a listener you own and drains for a timeout that starts at the shutdown signal. The worker service releases waiting messages, lets running attempts finish until drain_timeout, then cancels the rest and releases them without counting the attempt. The scheduler stops admitting runs before active ones finish.

pub fn stop_admission(&self) {

Design only: the orchestrator that sequences all of those across modules. In example 05 the dispatcher belongs to the example, and a reusable driver needs its own decision record on ordering and failure policy per phase. Today you wire the shutdown signal yourself.

The std-only web stack has a matching handle. serve_until with a Shutdown object stops accepting connections, answers requests already sent with Connection: close, closes idle keep-alive connections, and waits up to a grace period.

What nothing does yet is coordinate the order across all of them. Your shutdown code calls each one in the sequence you choose.

rustclamp_http::serve(
    listener,
    router,
    rustclamp_http::ShutdownReceiver(shutdown_rx),
    Duration::from_secs(5),
)
.await?;
Do you separate liveness from readiness from health, or is it one boolean?

Separate. The lifecycle status keeps started, ready, alive, accepting-work and health as different values, because alive does not mean ready and ready does not mean healthy. Health is per component, and a process is never ready after a startup failure or a failed required health check.

For HTTP you expose the endpoint yourself: the Users example answers /health with ok, and the web stack has an up route that reports the database. I do not see ready-made probe formats for any orchestrator, so you map the values to your platform.

router.group(|api| api.middleware(throttle.middleware()).up("/api/health"))

The model exists in the example as status values: started, ready, alive, accepting work and per-component health. In a real service you map them yourself, for instance ready to your load balancer check and alive to your supervisor.

One rule worth copying: never report ready after a startup failure or a failed required health check. The example's tests assert it.

When a handler fails, how does the system decide whether retrying is actually safe?

The handler decides, by classifying the failure. Retryable means it failed before any external side effect, permanent means the input or operation is invalid, and unknown outcome means a remote side effect may have completed. Only retryable failures are authorized for another attempt.

That is how a timeout never turns into a duplicate payment. In the order example a payment timeout is recorded as unknown, the inventory reservation stays, and nothing is charged again automatically.

DispatchError::Handler(HandlerFailure::Retryable(_)) if attempt < self.max_attempts => {

Compared with a generic retry middleware that retries on any error, explicit classification costs you a decision at each failure site. That is the point: the code that knows whether a side effect happened is the code that should say so.

Unclassified errors do not slip through, because the handler's return type forces one of the three.

pub enum HandlerFailure {
    Retryable(HandlerError),
    Permanent(HandlerError),
    UnknownOutcome(HandlerError),
}
What are the retry and backoff numbers, and who owns the policy?

The policy is a value: a maximum number of attempts plus any backoff function, with a linear helper that waits base times the attempt number. The email example uses a stepped table of 1, 5, 15 and 30 seconds, up to five deliveries, then copies the message to email.dead.

The worker service retries in-process, so the message stays claimed while it backs off. A broker that must own redelivery, such as a NATS nak with a delay, keeps using deliver() directly. Each attempt can also have its own timeout, and a timeout is recorded as unknown outcome.

Two operational consequences. Retries in the worker service hold the message, so a long backoff occupies a slot of capacity. And retry state is in-process, so a crash resets the attempt count, which broker redelivery then covers.

If you need broker-owned redelivery with a delay, use the pure classifier and let the transport nack with the returned delay.

pub fn linear(max_attempts: u32, base: Duration) -> Self {
    Self::new(max_attempts, move |attempt| base.saturating_mul(attempt))
}
What ends up in the dead-letter queue, and can I tell why it got there?

Every dead letter carries a reason from a fixed list, with the attempts made and the error when there was one. Invalid envelope bytes go straight to the dead-letter subject, and a valid envelope with no handler is terminally rejected but stays in the source stream for inspection.

The example confirms the dead-letter publish before it acknowledges the source message. Delivery is still at least once, including that small gap, so dead-letter consumers should tolerate a duplicate.

/// The message's deadline passed before it ran.
Expired,

Operationally, the useful split is between reasons you fix in code and reasons you fix in data. Permanent and invalid payload usually mean a code or producer fix, retry exhausted usually means a dependency was down, and unknown outcome needs a human to reconcile.

Expired means the message deadline passed before it ran, which is distinct from a handler timeout.

pub enum DeadReason {
    NoHandler,
    InvalidPayload,
    Permanent,
    RetryExhausted,
    UnknownOutcome,
    Expired,
    Malformed,
}
Have you actually crashed this mid-flight to see whether messages get lost or duplicated?

Yes, on PostgreSQL 18.6 and NATS JetStream 2.11. Orders kept committing while the publisher was stopped and while the broker was down, and the outbox rows were published once those came back. A worker killed before its inbox transaction committed was redelivered after the 45 second lease and fulfilled the order once.

A replay induced by clearing the published flag was logged as a duplicate and created no second fulfilment. An injected failure on the outbox insert rolled back the inventory update and the order. The evidence covers those tested crash windows, and it does not claim exactly-once broker delivery.

sqlx::query("UPDATE outbox SET published_at = now() WHERE id = $1")

Notice what the tests did not do: they did not inject packet loss or disk faults. The windows are specific: publisher down, broker down, worker killed before commit, a replay, and a forced failure on the outbox insert.

A broker restart also caused a publish acknowledgement timeout followed by a duplicate delivery, which the inbox absorbed. That one was observed and not scripted.

Can you show me how the outbox publisher and the inbox are actually written?

The publisher claims at most ten pending rows with FOR UPDATE SKIP LOCKED, publishes each, waits for the broker confirmation, and marks it published. It holds row locks while it publishes, so several publisher processes skip rows another one has locked.

The inbox side inserts the message id in the same transaction as the effect. A conflict means the effect already happened, so the worker skips it and acknowledges. Inbox ids are kept without expiry, because pruning them would weaken deduplication for older redeliveries.

Operational detail: the publisher logs failures and retries at 500 ms intervals, and the stream rejects new messages at 10,000 messages or 64 MiB, leaving the outbox row pending. So a broker outage becomes a growing outbox table and not lost messages.

Watch the outbox size and the age of the oldest pending row. Those are the two numbers I would alert on, though I do not see a metric for them in the source.

"SELECT id, payload FROM outbox WHERE published_at IS NULL
 ORDER BY id LIMIT 10 FOR UPDATE SKIP LOCKED",
"INSERT INTO inbox (message_id) VALUES ($1)
 ON CONFLICT (message_id) DO NOTHING RETURNING message_id",
How can I reproduce those crash tests on my own machine and break it myself?

You need Docker for PostgreSQL and NATS with JetStream, then you run the publisher and the worker in separate terminals and create orders. Stop one piece, create more orders, start it again, and watch the outbox drain.

The payment command has a local stub with four outcomes, approve, decline, timeout and retryable, so you can see compensation and the unknown-outcome rule too. There is also an inspector that prints the process projections without needing either service.

A good drill is to kill the worker mid-order and wait for the lease. The examples use a 45 second acknowledgement lease, so redelivery is not instant, and you should plan for that gap.

Then clear the published flag on a row to induce a replay, and watch the inbox log a duplicate.

docker run --rm --name rustclamp-orders-nats -p 4222:4222 nats:2-alpine -js
cargo run --offline --locked --manifest-path examples/06-create-order/Cargo.toml --bin outbox-publisher
cargo run --offline --locked --manifest-path examples/06-create-order/Cargo.toml --bin orders-worker
When something breaks at 3 a.m., how do I trace one request through the logs?

In the std-only web stack every request gets a UUIDv7 reference. It comes back in the X-Request-Id header, shows on the built-in 500 page, and is written as ref= on every log line for that request, so one grep finds all of it. A client's own header is never used as the reference.

In rustclamp-http it is different today: a valid client X-Request-Id is honoured, otherwise you get a request-N counter that restarts at deploy. ADR 0021 proposes moving it onto UUIDv7, but it is a proposal. Workers carry a correlation id and a message id in the envelope, and the examples have an optional tracing feature that puts both in spans.

[2026-09-30 11:28:40] production.INFO: order placed by 42 ref=01928f3c-6a1e-7c4d-9b2a-5e0f1d3c8a77

The time ordering is the operational gift. A UUIDv7 starts with its creation time, so references sort by time, and a range of references gives you everything between 10:00 and 10:05 as a plain string comparison over log lines.

Minting costs about 0.3 microseconds per request.

Do you ship metrics and tracing exporters, or do I bring those myself?

Exported metrics are planned and not built. Logging goes through rustclamp::log with LOG_LEVEL and LOG_CHANNEL, and Clamp does not force a monitoring stack on you.

For workers you get hooks to attach your own: the service reports ServiceEvent callbacks, live stats and a final report with returned, cancelled and drained counts. The inspection JSON describes architecture, not runtime cost, and its counts are structure and not memory or time.

pub fn on_event(mut self, observer: impl FnMut(&ServiceEvent<'_>) + 'static) -> Self {

Compared with a stack that ships a Prometheus endpoint, this is a gap. What you do get is hooks in the worker service, request references in logs, and tracing spans as an opt-in feature in the examples.

The architecture page lists observability under later coordination work, so I would not plan around it arriving soon.

What does the binary and dependency footprint look like once I add HTTP, Postgres and a worker?

It grows with what you select. The Users example with only a console adapter is 521,712 B with 8 resolved packages, HTTP brings it to 1,982,280 B and 62 packages, and PostgreSQL to 5,651,192 B and 160. The plain Pico program is byte-identical to plain Rust at 4,335,592 B in that harness.

The email worker example resolves 122 packages and the order example 201. The tens of megabytes you may see in the evidence are unstripped debug builds, which are development artifacts and not release estimates.

Debug builds are huge because of debug info, and the evidence says they are not release estimates. For release, the Users ladder is the best guide: roughly half a megabyte for console, two for HTTP and five and a half with PostgreSQL.

Strip and LTO were not part of those numbers, so expect your own release profile to shrink them.

How does deployment look for a web app, and where are the sharp edges?

One release binary plus the public/ folder, listening on 127.0.0.1 behind a reverse proxy such as nginx that terminates TLS. Run it under systemd or in a container with restart on failure. A panicking handler answers 500 and the server keeps running.

Sharp edges: a fixed thread pool, 32 threads by default, sessions and rate-limit counters held in memory so they reset on restart and are not shared between processes, and no HTTPS inside the binary. For high concurrency the docs point you to the Axum integration.

One more edge: the app exits if the configured PORT is taken, which suits supervisors and surprises people who expect fallback ports. In development it picks the first free port from 8080.

Paths are relative to the working directory, so run the binary from the folder that holds public/ and storage/, and set that as the unit's working directory.

APP_ENV=production
PORT=8080 # fixed port for the proxy; the process exits if it is taken
LOG_CHANNEL=stderr # let systemd or the container collect logs
LOG_LEVEL=info
TRUST_PROXY=true # rate limits by X-Forwarded-For, behind exactly one proxy
How do schema migrations behave in production when more than one instance starts?

There are two stories, so choose on purpose. rustclamp-postgres never runs a migration when a pool connects, because automatic schema changes at startup are unsafe once more than one process deploys. You build an ordered plan and run it explicitly.

The web stack's SQLite database migrates every time the app starts and refuses to serve if a migration fails, which suits one process and one file. Its commands run through your app, and rollback undoes the last batch.

For PostgreSQL the plan is a validated ordered list, and run applies the versions not yet present. Ordering is checked before anything runs, so a duplicate or out-of-order version is a plan error.

A pragmatic pattern is to run migrations as a separate deploy step before rolling the new binary, which is the safe default the ADR argues for.

clamp migrate # run pending migrations
clamp migrate:rollback # undo the last batch
clamp migrate:status # ran (with batch) and pending
What happens to scheduled jobs when I run several instances of the same service?

Overlap suppression is process-local: each scheduler runs a job at most once at a time, and it makes no claim about other processes. Several scheduler processes need an explicit distributed lock capability, and that is not built.

pub async fn tick(&self, clock: &dyn Clock) -> TickReport {

So either run the scheduler in one process or make the jobs idempotent. Other limits worth knowing: a misfire either skips stale intervals or runs once, the target caps declarations at 128 jobs, and shutdown closes admission and then drains the active job.

Idempotent jobs are the practical answer. A job that cleans expired sessions can run twice with no harm, while a job that sends a report needs a guard in your own data.

The tick result reports skipped misfires, so you can log when a slow job made you miss intervals.

If we try it and it adds toil, what does the rollback story look like?

Start with a narrow pilot next to the system you already run, such as a worker or a scheduled job in its own process. Measure restart and drain behaviour, dead-letter volume, memory, and how many lines of wiring you wrote. If it adds toil, delete the process and nothing else has changed.

Keep it off the critical path, because it is experimental, and pin an exact commit since there are no crates.io releases. I would call that the whole rollback plan, and it is short because the pilot was small.

Because the pilot is a separate process, rollback is a deploy action and not a code revert. Keep its configuration separate, and keep its message names and versions out of anyone else's subjects.

Write the rollback down before the pilot starts, with the name of the person who presses the button.

Is this something I must adopt wholesale, or can I use only the parts I want?

You can use parts. Clamp is a composition layer and not a framework that owns your process, and the pieces are separate crates. The worker loop, the scheduler and the HTTP integration can each be adopted alone.

A layer of what? It sits above the crates you already operate, Axum and Tower, SQLx, Tokio and NATS, and composes them. You keep their metrics, their configuration and their failure behaviour.

Core, Kernel and the facade have no third-party dependencies by default, so adopting the composition parts adds nothing to your supply chain. Integrations add exactly what they wrap.

The facade also includes a std-only web stack and project templates, and nobody has to use those.

How do logs behave in production: channels, rotation, and what happens if a write fails?

The log channel is a setting. LOG_CHANNEL can be single, daily, stderr, or a stack of several, and stderr is the one for systemd or containers.

Log::info("server started");

The daily channel writes one file per UTC day and keeps 14 by default. Each entry is one line with line breaks escaped, so an entry never spans lines.

A failed write is ignored, so logging never takes the app down. That is the right choice for availability, and a poor one if you rely on logs as an audit trail.

Levels follow the PSR-3 names, from debug to emergency, and LOG_LEVEL can be silent.

How are SIGTERM and Ctrl-C handled, and can a signal arrive before the handler is installed?

Behind the runtime's signal feature, shutdown_signal resolves on SIGINT, and on SIGTERM as well on Unix. The older wait_for_ctrl_c remains.

pub fn shutdown_signal() -> io::Result<impl Future<Output = ShutdownSignal> + Send + 'static> {

It installs its handlers when you call it, and a signal that arrives before the first poll is delivered to the future instead of killing the process. That was fixed after a worker stress test found the gap.

The feature is separate on purpose, so an app that never waits for signals does not pull in the signal crates. If you upgrade from an older checkout, add signal alongside tokio.

What you do with the signal is your code. Typically you hand it to serve or to the worker service as the shutdown future.

What stops a flood of requests or messages from exhausting memory or the broker?

Bounds appear in several places. The worker service keeps claimed and unsettled messages within concurrency plus capacity, so it never claims more than it can hold.

The in-memory bus is bounded and returns a full-queue envelope to the publisher instead of dropping it. The JetStream stream in the email example is capped at 10,000 messages and 64 MiB with a discard-new policy, so publishing fails and does not delete older queued work.

On the web side, the std-only server accepts bodies up to 1 MiB, or multipart uploads up to 10 MiB, and each thread holds at most one request in memory. The Axum integration has body limit helpers.

Rate limiting is a fixed window per client that answers 429 with a Retry-After header. Behind a proxy, trust the forwarded address only when exactly one proxy sets it.

login.middleware(Throttle::per_minute(5).trust_forwarded(true).middleware())
What does an upgrade look like, and how do I find out what will break before I do it?

Two things move separately: the clamp CLI on your machine, and the Clamp crates your project builds against. Neither changes your code for you.

clamp self-update # the latest release

A generated project follows the main branch, and Cargo.lock pins the commit it last saw. To move, run cargo update and then your tests. Commit the lock file so every machine and the server build the same commit.

Before 1.0, main can break. Read the Unreleased section of each changelog you depend on. The upgrade page lists breaking changes with what to do, such as the worker handler signature change and the Send + Sync bound on Clock.

For stability, replace the branch with a fixed rev in Cargo.toml. Message schema versions and the inspection schema version are separate and explicit.

What are the hard limits of the built-in web server that I should know before deploying?

A fixed pool of worker threads, 32 by default and set with WEB_THREADS. A thread never waits on an idle connection while another is queued, and busy keep-alive connections take turns.

Keep-alive connections stay open for up to 1000 requests or 5 seconds idle. Bodies are capped at 1 MiB, or 10 MiB for multipart uploads, and each thread holds at most one request in memory, so the worst case is the thread count times the upload cap.

Sessions and rate-limit counters live in memory, so they reset on restart and are not shared between processes. A full session store drops its least recently used tenth at once.

There is no TLS in the binary, and it listens on localhost, so a proxy is part of the design. For high concurrency the docs send you to the Axum integration.

What happens if startup fails halfway through, with a database open and a worker half started?

In the lifecycle example, validation and freeze happen before any side effect, so a bad composition fails before anything opens. If a later step fails, cleanup runs for only the resources that were actually acquired.

Cleanup continues after injected errors, so one failing stop hook does not strand the rest. Cleanup diagnostics keep both the phase and the module identity.

The process is never ready after a startup failure or a failed required health check, which is what a rolling deploy needs to see.

Again, this is proven in the example with fake resources, and a reusable driver that does it for you is not built.

CTO / engineering lead

An engineering lead, so I will argue against adopting RustClamp unless the numbers hold. Maturity, risk, pilot design and exit cost, in plain terms.

RustClamp is the application composition layer for Rust. It sits above the crates your team already uses, Axum and Tower, SQLx, Tokio and NATS, and composes them into one application. It replaces none of them.

Where do you want to start: why at all, how mature it is, or what the risks are?

Why should I let my team adopt yet another framework instead of what we have?

You should not, unless it measurably reduces friction in your architecture. Newness is not a reason, and neither is a nice homepage. My default advice for a core product today is no.

There is a narrower case where it earns a look: an application that is growing from one responsibility into several, and where wiring those pieces is where your pain is.

Put differently, the unit of adoption is not the whole project. You can adopt one process, one crate or one habit, such as declaring wiring in one place, and the rest of your stack stays what it is.

That is the only framing under which I can recommend a trial.

What concrete problem is it trying to solve for a team like mine?

Applications grow from one responsibility into many: HTTP, jobs, events, databases, background work, sometimes several processes. Clamp gives those pieces one composition model, and it checks the wiring once before anything runs.

If you run a single HTTP service and the wiring is not hurting, you probably do not need this. I would rather say that now than have you find out in a sprint review.

The symptoms to look for in your own codebase: a main.rs that has become a wiring diagram, tests that need half the system to start, and a worker and an API that share code but not a clear boundary.

If you recognise none of those, close this tab.

Be honest with me: how mature is this, and what does experimental mean here?

Experimental, plainly. It started in September 2026, the APIs may change before 1.0, and the packages are not on crates.io, so projects depend on Git. The tested minimum is Rust 1.96.1.

publish = false

Phases 0 to 9 are complete on main, which records prototype work and measurements. It does not mean the APIs are stable or that production use is supported, and the roadmap says so in those words. The crates.io question is tracked openly.

What exists: nine public repositories, fourteen runnable examples, decision records, and an evidence trail with raw measurements. What does not: a stable API, published packages, or a track record in production.

So can my team run this in production for anything that matters today?

No. Prototypes and internal tools are the honest fit. There is no stable API, a reusable lifecycle driver is not built, exported metrics are planned, and several scheduler processes would need a lock capability that does not exist.

If a pilot is going to fail, it should fail somewhere cheap. A worker or a scheduled job next to your existing system is cheap, and you can delete it.

The specific blockers, in the order a CTO would care: no stable API, no lifecycle driver, no exported metrics, no locking for several scheduler processes, and an auth stack that the repository shows no external audit for.

The web stack has real safeguards, such as CSRF checks and strict security headers, but safeguards are not the same as a track record.

What are the real adoption risks I should write down before agreeing to a pilot?

Maturity, ecosystem size, training cost and architectural commitment. API churn is the first one, since breaking changes land in the changelog and the project is moving quickly.

I cannot tell you anything about staffing or the bus factor from the source, so ask the maintainers on GitHub directly. That one belongs on your list as an open question.

Rank them for your own context. For a team with strong Rust skills, ecosystem size matters less. For a team new to Rust, training cost dominates, and Clamp adds its own concepts on top of Rust's.

Architectural commitment is the one people underestimate. Once modules and processes are declared, other people build on those declarations.

How would you design a low-risk pilot, and what should we measure to judge it?

Pick something narrow next to the existing system: a worker, a scheduled job or a CLI. Write success and kill criteria before you start, then measure runtime, memory, compile time, lines of wiring, testability, operational complexity and how hard it is to remove.

Build a plain-Rust twin of the same slice if you can, since that is how the project's own evidence is made. You can also gate CI on the architecture: clamp doctor exits with status 1 when a composition is invalid.

Define the twin up front. If the pilot is a worker, write the same worker in plain Rust using the crates directly, compare lines, dependencies and test effort, and keep both until you decide.

The project does exactly this in its own evidence, with a direct twin for the device loop and plain Rust controls elsewhere.

clamp inspect architecture.json
clamp doctor architecture.json
What does it cost my team in learning time and day-to-day friction?

Beyond solid Rust, a handful of concepts: module, capability, contribution, process, freeze, and stable string identities. The fourteen runnable examples are the ladder, and each step builds on the last.

clamp init <project-name> [--blank|--app|--web|--tui|--package]

Day to day, compile times scale with what you select. The Users example takes 3.439 s clean as a console build, 17.940 s with HTTP and 40.850 s with PostgreSQL. The identities are ceremony, and the project calls them that.

Onboarding goes quicker if people read the examples in order. Each adds one concept, and the tests describe the expected behaviour, which is a decent substitute for a training course.

Day to day, the visible cost is verbosity: stable identities and declaration vectors that a hand-wired app would not have.

How does it compare with plain Rust on a real app, in lines and complexity?

The stress tests are in the ADRs, and they are not flattering. A CLI built twice needed 223 lines of Rust against 197 for plain Rust after one round of fixes to Clamp, and 117 crates against 114. A job runner started at 806 lines against 585.

So Clamp does not make code shorter. What you are buying is checked wiring and inspection, so that is what a pilot should test. On the micro level, Pico adds 36.78 ms to a clean build and 0 B to the binary.

Read the lines honestly. Clamp was larger than plain Rust in both stress tests at first, and in the CLI test it still was after the fixes. The project's answer is that the extra lines buy checks and inspection, so test whether you value those.

The job-runner test also recorded 14 gaps, checked each against the source, and implemented five changes, which tells you the authors read their own bills.

If the project stops being maintained, what would getting out cost us?

Your domain logic, data contracts and the crates underneath stay yours, and the license is MIT or Apache-2.0, so a fork is possible. Exit means replacing resolved wiring with ordinary constructor calls and giving up the inspection tooling.

The entry point is the easy end of that: the smallest Clamp program is your code inside one closure that returns its result unchanged. Put that exit path into your pilot criteria on day one.

Budget the exit like any dependency. If the project stopped tomorrow, your pilot would keep compiling against the last commit you pinned. The risk is not breakage, it is the absence of fixes.

Forking is allowed by the licence, which keeps the worst case bounded.

use rustclamp::prelude::*;

fn main() {
    Clamp::run(|| {
        println!("Hello Clamp");
    });
}
Does it lock us into Axum and SQLx, and could we swap those out?

The integrations are built on them and hand you their APIs: the compiled route table is a plain Axum Router, and the postgres package re-exports SQLx. You can use ecosystem middleware without a wrapper.

The flip side is that there is no non-Axum HTTP integration and no non-Postgres package beyond the facade's SQLite. Another integration means writing one, using the same public contribution traits.

let router = HttpRoutes::<Public>::new().build(&routes)?;

The upside of building on Axum and SQLx is that your hiring and tooling story is theirs. Anyone who knows those crates can read the integration code.

The downside is that you inherit their release cadence, so an Axum major version becomes the integration's problem to follow.

rustclamp-http = { git = "https://github.com/rustclamp/http" }
What does the dependency and supply-chain picture look like for a security review?

The facade, Core and Kernel have no third-party crates by default, and resolved-dependency allowlists per package are enforced by a boundary check, so a new edge needs an explicit policy change. An integration brings only the crates it wraps.

Count what you select: the console-only Users build resolves 8 packages, HTTP 62 and PostgreSQL 160. The facade's optional features add real weight, since db bundles SQLite and mail brings rustls.

For a review, ask for the resolved package lists per example. They are in the evidence: 2 for the capability-only example, 10 for the scheduler, 122 for the email worker and 201 for the order example.

The allowlist lives in the repository, so a reviewer can diff it between versions instead of trusting a statement.

"rustclamp-core": set(),
"rustclamp-kernel": {"rustclamp-core"},
What is the security posture, and has anyone outside the project audited it?

Every crate forbids unsafe code and denies missing docs. The web stack hashes passwords with Argon2id, encrypts with XChaCha20-Poly1305 from the RustCrypto crates, sets a strict Content-Security-Policy and other headers, checks CSRF, throttles logins, and answers a forbidden route as 404 by default.

I found nothing in the repository that records an external security audit, so assume there has not been one. An experimental project with an unaudited auth stack is a reason for a pilot, and a reason not to put it in front of customers.

"rustclamp-macros": {"proc-macro2", "quote", "syn", "unicode-ident"},

For your threat model: auth and sessions are in the std-only web stack, the less battle-tested of the two. If you use only the Axum integration, you bring your own auth, and the domain-level authorization pattern still applies.

Treat the crypto choices as reasonable and unreviewed: Argon2id with 19 MiB and 2 passes, and XChaCha20-Poly1305 from the RustCrypto crates.

[lints.rust]
unsafe_code = "forbid"
missing_docs = "deny"
What does the road to 1.0 look like, and are there dates?

There are no dates. The first six stages are released in prototype form: foundations, lifecycle, services, messaging, loops and tooling. Next is integrations such as WebSocket, gRPC, MQTT and Redis.

AI and MCP adapters come later, and they sit over inspection and are never required to build or run. 1.0 means stable APIs, profiles and recipes, and packages on crates.io after registry ownership and an API review.

The stages are ordered by evidence and not by calendar. A stage is released when its source is public and its examples run from main, so a slip does not make a promise false.

Two items matter most to a CTO: the crates.io decision, and what 1.0 means by stable APIs. Neither has a date.

What would you tell me not to use it for yet, with no sugar-coating?

A core product that needs stable APIs. Anything that needs cargo add. Redis, gRPC or MQTT transports, exported metrics, and a distributed lock for several scheduler processes, since those are planned and not built.

Also bare-metal embedded work, because Core is not shown to build there, and mounting your whole existing Axum router, which is in progress.

And one more: anything where a single person is the only one who understands the declarations. The model is small but new to everyone, so pair on the first pilot.

None of this says the project is bad. It says the project is early.

Give me a simple rule I can use to decide whether to adopt it.

Adopt it for a new worker, scheduled job, CLI or internal tool where wiring is your pain, where you can delete it, and where you are happy to track a moving main. Write the kill criteria first: wiring bigger than plain Rust, compile times you cannot live with, or an exit you cannot afford.

Do not adopt it for your core product this quarter. Revisit when the roadmap says stable APIs and crates.io, and bring your pilot numbers with you.

A cheap way to run the decision is a one-page pilot charter with scope, owner, time box, three success numbers, three kill numbers and the rollback step. If a team cannot write that, it is not ready to pilot anything.

Is this a framework, a library or a platform? I need to know what category to budget it under.

Budget it as a library family with a composition layer on top, and not as a platform. It is not hosted, it has no runtime service, and it does not own your deployment.

A layer of what? It sits above the crates your team already uses, Axum and Tower, SQLx, Tokio and NATS, and composes them into one application through modules, typed capabilities, lifecycle and processes. It replaces none of them.

It is not a framework in the usual sense either. It does not take over your main, impose a project layout or wrap the ecosystem. The facade does offer conveniences, such as a std-only web stack and project templates, and those are optional.

So the line item is engineering time and a Git dependency, and not a licence or a service.

What skills do my engineers need to be productive with it, and how does that affect hiring?

Solid Rust first. Then async Rust if you use workers or the HTTP integration, since handlers and the service loop are async, and SQL if you use the PostgreSQL package.

On top of that sit the Clamp concepts: module, capability, contribution, process, freeze and stable identities. The examples teach them in order, and the tests describe the behaviour.

I cannot tell you anything about the hiring market for these skills from the source, and I will not guess. What I can say is that nothing here needs Rust beyond what Axum and SQLx users already know.

The honest risk is the bus factor inside your own team. If one engineer understands the declarations, you have a single point of failure, so pair on the first pilot.

Is there commercial support or an SLA, or is it just a GitHub repository?

I see no commercial support or SLA in the source. It is a public repository with an issue tracker, decision records and documentation, under licences that provide the software as-is.

That is normal for an early open source project, and it is part of why I say prototypes and internal tools today.

The substitute for support is evidence you can read yourself: raw measurements, decision records that include reversals, and examples that run.

If your organisation requires a vendor, this is not a fit yet, and I would rather you know before the pilot.

If the pilot succeeds, how would we expand it safely across other services?

In rungs, each with its own go or no-go. A pilot worker or job, then a second process from the same blueprint, then shared wiring moved into declarations, and only then conventions across teams.

Put the inspection check in CI at the second rung. clamp doctor failing on an invalid composition gives you a cheap guardrail while more people touch the declarations.

clamp doctor architecture.json

Keep each service's domain on its own ports, so one service can leave Clamp without dragging others along.

Do not standardise early. The APIs may change before 1.0, so a company-wide template is a liability right now.

What numbers could I show leadership after the pilot to justify the decision either way?

Use the project's own measurement categories, because they are defensible: lines of wiring against a plain twin, resolved dependency count, clean and incremental build time, binary size, and the effort to write the tests.

Add operational numbers from your side: incidents attributed to the pilot, time to diagnose with request references, restart and drain behaviour, and dead-letter volume.

Include the cost of the exit, meaning how long it would take to delete the pilot, which should be short.

And report the negative results. The project's own evidence shows more lines and slower micro paths, so a pilot that finds the same is behaving as expected.

What are the alternatives, and when would you tell me to use one of them instead?

The main alternative is plain Rust: Axum, SQLx and a hand-written main. For one HTTP service with one pool, that is a fine answer, and the project says as much when it talks about not replacing Axum.

If your team is not on Rust, none of this applies, and a mature framework in your own language will serve you better.

Clamp earns a look when an application is growing several kinds of process, such as an API, a worker and a scheduled job, and the wiring between them is where the cost is.

I would not claim a feature-by-feature comparison with other Rust projects, because I only know this repository.

What will it do to our CI minutes and build times, and how do we gate on it?

Build cost is driven by what you select. In the Users example a clean build is 3.439 s for the console build, 17.940 s with HTTP and 40.850 s with PostgreSQL. The Pico layer itself adds 36.78 ms to a clean build of a trivial program.

Incremental rebuilds are cheap: 1.67 ms over plain Rust for an unchanged build, and about 10.86 ms for a comment edit in the Pico harness.

The project's own check runner formats, lints, tests, builds docs, checks dependency boundaries and runs the examples across all packages in one command.

python3 rustclamp/tools/check.py

For your pipeline, the useful gates are tests, the architecture check with clamp doctor, and a dependency-count check if you want to notice weight creeping in.

I have an existing project

You already have something running, so I will assume you want to keep it. I will tell you where Clamp can meet your code and where it cannot yet.

RustClamp is the application composition layer for Rust. It sits above the crates you probably already use, Axum and Tower, SQLx, Tokio and NATS, and composes them. It does not replace them, so nothing in your project has to change on day one.

Shall we start with the rewrite question, with your stack, or with wrapping your existing main?

I already have a Rust application running. Do I really have to rewrite it?

No. Start where you are. A rewrite is the thing I would try to talk you out of, because a composition layer should earn more of your architecture over time and not demand it on day one.

The adoption path is a series of small edges: wrap what you have, add one new piece, and stop at any point where it stops paying for itself.

Think of what a rewrite risks: behaviour you have forgotten you rely on. Clamp's pieces are additive and optional, which is the opposite approach, and nothing takes over until you hand it something.

The only thing I would ask you to rewrite on day one is your expectations.

What does starting small mean in practice for an app that is already running?

Pick one boundary or one new piece: a worker, a scheduled job, a CLI, or just wrapping your existing main in Clamp::run. Each is either its own process or a closure, so none of it touches the rest of your code.

If it does not help, you delete it. I would call that the only adoption plan that survives contact with a real codebase.

Pick by pain. If you have a cron script, try the job. If you send email inside the request, try the worker. If you have an admin.rs full of one-off functions, try the command tree.

In each case, make the first version boring on purpose.

Can I wrap my existing main function in Clamp without changing what it does?

Yes, and it is almost a no-op by design. Clamp::run calls your closure once on the current thread and returns its result unchanged. Errors and panics keep ordinary Rust behaviour, and there are no Send or 'static bounds.

It starts no runtime and no threads, and in the benchmark it adds 0 B to the binary. The flip side of a no-op is that it gives you nothing yet, so it is just a doorway to the next step.

It does not care about your runtime. If your main is async, the closure can build a runtime and block on it, or you can skip Clamp::run and call the pieces directly, as several examples do with Tokio.

The honest summary is that wrapping is a gesture. It buys you the entry point everything else plugs into, and nothing more.

use rustclamp::prelude::*;

fn main() {
    Clamp::run(|| {
        println!("Hello Clamp");
    });
}
How do I know whether my stack, say Axum and PostgreSQL, fits with Clamp?

The integrations are Axum and Tower for HTTP, SQLx for PostgreSQL, NATS JetStream for messaging and Serde for envelopes. If you are on those, there is a path, and if you are not, the worker, scheduler and CLI parts do not depend on HTTP at all.

channel = "1.96.1"

The honest test is to name the one piece you would add first and check that its integration exists.

Also check the Rust version. Clamp needs 1.96.1 or newer, edition 2024, so a project pinned to an older toolchain has to move first.

That is sometimes the biggest hurdle, and it has nothing to do with Clamp's design.

Yes, I am on Axum and PostgreSQL already. What does adopting Clamp look like for that?

Keep them. rustclamp-http is built on Axum and Tower, and it hands you the compiled Router. Today you declare routes one path at a time, still with plain Axum handlers, and Clamp checks for duplicate paths before the listener binds.

The direction that works now is the other one: the compiled Clamp router is an ordinary Axum Router that you can merge into yours, as the Users example does with its health route. Mounting your whole existing router into Clamp is the part that is not built.

Your handlers and extractors do not change. What changes is that route registration goes through a contribution, so the target can see all routes, detect duplicates and compile them before the listener binds.

Keep your existing layers. The compiled router is yours to wrap with any Tower layer or ecosystem middleware.

Ok(Router::new()
    .route("/health", get(|| async { "ok" }))
    .merge(compiled))
What exactly is the open issue about mounting a whole Axum router?

A route is one path plus a method router, so an existing Router with nested routers, layers and state cannot be handed over. You would redeclare every route as a contribution, and that makes "keep your Axum app" true for your handlers and not yet for your routing tree.

The issue asks for a contribution that mounts a whole router, with the same duplicate-path checks and shutdown wiring. Until it lands, I would not start with the HTTP layer of a big service.

Until it lands, merging a compiled Clamp router into your own router, as the example does, is the supported direction.

The issue also asks for a test that serves a pre-built router with a nested router and a layer unchanged, which tells you what done will mean.

No, we use something else for the web layer. Does Clamp still make sense for us?

Maybe, but not for HTTP. The only HTTP integration is Axum, plus the facade's own std-only web stack, and I see nothing for other web frameworks.

The worker, scheduler and CLI pieces do not care what serves your HTTP, so a Clamp worker or job process can sit beside your app. If you do not need those, you might not need us.

Your web layer is not the point for a worker or job anyway. Those talk to your database and your broker, and whatever serves HTTP is irrelevant to them.

If you later want Clamp's HTTP integration, that means Axum, so it would be a migration in its own right.

We already have a SQLx pool in the app. Can Clamp adopt it instead of making its own?

Yes. rustclamp-postgres can adopt an application-owned pool, and it tracks ownership: a managed pool is closed by the process, an external one is left to its owner. Pools are typed by qualifiers such as Primary and Analytics.

SQL, transactions and error mapping stay in your adapters, and there is no ORM. Migrations are explicit and never run when a pool connects.

The ownership flag matters at shutdown. Hand Clamp an externally owned pool and it will not close it, which is what you want if other code in the process shares it.

Migrations are explicit and ordered by version, and nothing runs them on connect, so it will not touch your schema unless you call it.

let db = PoolConfig::<Primary>::new("postgres://localhost/app", 10).connect().await?;
db.health().await?;
I would like to add a background worker next to my app. How does that look?

Contribute a handler by message name and schema version, and let your app publish a versioned envelope. Run it over NATS JetStream, or start with the in-memory bus, which needs no broker at all.

The worker acknowledges only after the handler succeeds, so delivery is at least once and the handler must tolerate duplicates. As a separate process it needs little: the email example's worker and API need only a NATS_URL.

docker run --rm --name rustclamp-nats -p 4222:4222 nats:2-alpine -js

The message contract is the main thing to design. Choose a name and a schema version, and keep the payload small, because it is a public interface between your app and the worker.

On the app side, mapping from your domain event to the envelope is explicit, so your existing types need no serialization traits.

let handler = HandlerDeclaration::typed(SUBJECT, 1, |email: EmailPayload, _| async move {
    Ok(())
});
And a scheduled job? What is the cleanest way to add one to my app?

A job is a name, a fixed interval, a misfire policy and an async closure, driven by a clock you control. There are no cron expressions, and overlap is suppressed inside one process only.

By default a job runs on the first tick, and first_run_after_interval() opts out of that. If you run several instances, you need your own lock.

pub fn first_run_after_interval(mut self) -> Self {

If your existing job runs in a thread today, the declaration wraps an async closure around the same function, so the body does not change. The new things are a name, an interval and a misfire policy.

A test can then drive it with a manual clock and check how many invocations happened.

let catch_up_job = JobDeclaration::new(
    "cleanup-expired-sessions",
    Duration::from_secs(10),
    MisfirePolicy::RunOnce,
Could I start by adding a small command-line tool to my project instead?

Yes. Commands are contributions: a module hands a named declaration to a target, which rejects duplicate names and builds the command tree. Public and admin commands are different types, so they cannot be mixed up.

For a new standalone tool, clamp init my-tool --tui gives you an interactive loop, and --app gives a composition root with a sample health module.

If your project already has an argument parser, nothing forces a change. The command tree here composes commands contributed by separate modules, which is a smaller thing than a parser.

It suits admin commands that need a clock or some other capability, because declared requirements make that explicit.

pub fn contribution() -> CommandDeclaration<PublicCommands> {
    CommandDeclaration::new("hello", hello)
}
How would I actually add this to my existing Cargo project, if not cargo add?

With a Git dependency per crate, as in the line below. Use the facade for Clamp::run, or the specific package you need, such as rustclamp-http or rustclamp-postgres. You need Rust 1.96.1 or newer.

Crate names are not reserved on crates.io yet, which is an open issue. Pin a commit in the meantime, because the APIs may change before 1.0.

Pin it properly. In a project that was not generated by clamp init, use rev to pin a commit, and commit your Cargo.lock.

Run cargo update deliberately, read the Unreleased changelog sections, and treat each bump like a dependency with no semver promise.

rustclamp-core = { git = "https://github.com/rustclamp/core" }
So could the migration really happen gradually, one piece at a time, over months?

That is the intended way. Wrap your main, add a worker or a job, move more work behind it, and later split pieces into processes from one blueprint, inspecting the result with clamp. Each rung should earn the next.

Every rung is still a prototype, so expect to track changes. Stopping early is a legitimate outcome, and not a failure.

A sensible cadence is one rung per release cycle, with the previous rung kept in production for a while. That way a problem points at the newest piece.

The inspection commands can print the architecture at each rung, which is handy for the written record.

And what if the pilot does not help? How do I get back out?

Remove it. An adoption path needs an exit path, and this one is short because the pilot is its own process or a closure around your existing code. Your domain should stay on your own ports and traits the whole time.

Decide in advance what you will measure, such as lines of wiring, runtime, memory, compile time and how hard it is to remove. If it loses on those, do not use it for that case.

Make the failure cheap to see. If the worker is the pilot, run it in parallel with the old path and compare outputs for a while, then switch.

And write down what failed, as an issue on the project if it is a gap. Several current features came from exactly that kind of report.

Part of my system is not written in Rust. Does any of this help there?

Not directly. Clamp is a Rust composition layer, and if your non-Rust services are the problem you probably do not need us.

The message envelope is plain JSON with a name, schema version and correlation id, by design, so a non-Rust producer could write one in principle. I have not seen that tested in the repo, so treat it as an idea and not a feature.

If you want to try the envelope idea, start with a producer and a consumer that you control at both ends, and keep the schema version in your contract.

Beyond that, I would keep Clamp for your Rust services and treat the others as they are.

Will adopting this force me to restructure my project around its way of doing things?

Not if you take it in small steps. Clamp is a composition layer and not a framework that owns your application, so nothing takes over until you hand it something. Clamp::run is a function you call.

A layer of what? It sits above the crates you already use, Axum and Tower, SQLx, Tokio and NATS, and composes them. It does not replace them, so your handlers, queries and types stay yours.

What it does ask is that new pieces declare what they need and offer, with a stable identity. That is a habit for new code, and old code can stay as it is.

The facade has framework-style conveniences for new projects, such as a std-only web stack and templates. An existing project can ignore them.

My main is already async with Tokio. How does Clamp fit with that?

Clamp::run is synchronous and starts nothing. If the closure returns a future, that future is just a value, and Clamp does not poll it.

So you have two options. Build your runtime inside the closure and block on it, or skip Clamp::run and keep your own async main. Several examples do the latter, with Tokio's current-thread flavour.

#[tokio::main(flavor = "current_thread")]

Tokio enters a Clamp-based process only where you choose it: the HTTP integration, the worker service or the optional runtime adapter. Core and the base runtime contract stay synchronous.

Your own runtime configuration stays yours, including worker threads and signal handling.

I already load configuration with my own crates. Do I have to switch to Clamp's Config?

No. rustclamp::config is an optional facade feature, and the composition parts do not require it. Your own config loading keeps working.

If you do try it, it reads .env and the environment, with the environment winning, and typed reads with defaults. Parse failures stop startup and name the key.

assert_eq!(config.get_or("MISSING", 50), 50);

The more interesting idea is process-scoped configuration. A setting is read only when the module that needs it is part of the running process, so a CLI does not fail on a missing worker setting.

You can adopt that idea without the crate, by reading worker settings only inside the worker binary.

I already use the tracing crate and my own logging. Will Clamp fight with that?

No. Clamp does not force a monitoring stack. Core and Kernel do not log, and the optional integrations expose their own events for you to handle.

The examples have an opt-in tracing feature that adds spans carrying the correlation and message ids, so tracing works through the ordinary crate.

The std-only web stack has its own logger with levels and channels, which only applies if you use that stack. The Axum integration leaves logging to you, with Tower's trace layer as an option.

The worker service reports events and stats through callbacks, which you can forward to whatever you already use.

Where would Clamp pieces live in my workspace, and how do I run several processes from it?

Keep one composition module that declares modules, processes and requirements, and let each process be its own binary. The email example has separate API and worker binaries over the same crate.

cargo run --offline --locked --manifest-path examples/03-email-worker/Cargo.toml --bin email-worker
cargo run --offline --locked --manifest-path examples/03-email-worker/Cargo.toml --bin email-api

Domain crates should not import Clamp beyond maybe Core's identity types. Adapters, such as an HTTP route table or a PostgreSQL repository, live next to the integration they use.

Use Cargo features to decide what each binary contains, because runtime reachability decides what starts and not what compiles.

Each generated project is its own Cargo workspace, so it never changes the one around it.

What happens to my existing tests, and how do I test the new Clamp pieces?

Your existing tests keep running, because Clamp does not touch code you did not hand it. The new pieces are tested the way the examples test them.

A handler or a job is an ordinary function, so unit tests call it directly. Time is a capability, so a fixed or manual clock replaces the real one.

cargo test --offline --locked --manifest-path examples/04-scheduler/Cargo.toml

For wiring, process tests assert which modules each projection includes, and that unrelated configuration is not read. The email worker's in-memory bus lets you test API and worker together without a broker.

The examples run with the standard Cargo test command and no special runner.

How do I explain this to my team and manager without it sounding like a rewrite proposal?

Lead with the boundary and not with the technology. Say you want to try one new background piece with a deletion plan, and that it is separate from the services people care about.

Give them numbers they can check: a plain Rust twin of the same piece, a dependency count, a build time and a time box.

Be upfront about the status. It is experimental, APIs may change, and packages come from Git. Naming the risk yourself is what earns the trial.

And say out loud what is not built, such as mounting your whole Axum router. Nobody objects to a pilot that has already listed its own limits.

What can I build?

You want to see what you could make, so I will point at things you can run and not read you a feature list. Every example I mention has a command attached.

RustClamp is the application composition layer for Rust. It sits above the crates people already use for web, data and messaging, Axum and Tower, SQLx, Tokio and NATS, and puts them together into one application.

What sounds closest to what you have in mind: a command-line tool, a web app, or something that works in the background?

I am not sure what I want to make yet. What can RustClamp actually build?

Let us not start with a feature list, because those are cheap to write. Start with Hello Clamp and follow one application as it grows: configuration, a web page, a worker, scheduled jobs, even a device loop.

Every step on the learning tree is a runnable example, so what I tell you about is something you can run. Tell me which direction pulls you and I will go there.

A tip: choose by what you want to keep. If it has to run on one box with a database file, the web path is quickest. If it has to survive restarts and retries, look at the worker and outbox. If it has to be exactly repeatable, look at the device loop.

You can also read the examples as a menu. There are fourteen, numbered in the order the model builds up.

A command-line tool sounds like my kind of thing. What does that look like?

Smallest first: a plain Rust program inside Clamp::run, which adds nothing over plain Rust. clamp init my-tool --tui writes an interactive loop like the one below, and when the tool grows, commands can be contributed by separate modules.

That is a good place to learn the model, since it needs no server and no database. A command tree has unique names and a stable order, checked before anything runs.

Generated TUI projects are tiny on purpose. The whole thing is a loop around standard input, and the Clamp part is the entry point.

When the tool grows, you can add commands through a command tree whose names are checked to be unique.

Clamp::run(|| {
    println!("Clamp TUI. Type a line, or `quit` to exit.");
    for line in io::stdin().lock().lines().map_while(Result::ok) {
        if line.trim() == "quit" {
            break;
        }
        println!("you said: {line}");
        // ...
    }
});
I would like to build a web app. What do I get out of the box?

Run clamp init my-site --web for a Rust server with routes, Blade-style views, sessions, CSRF, throttling, a Vite and Tailwind 4 frontend, and generated tests. clamp dev starts the frontend watcher and the server together, and you need Node and npm for the frontend.

The server is the facade's std-only web feature, behind nginx for real traffic. It is prototype-grade, and it is also pleasantly small.

The generated layout mirrors Laravel: routes, controllers, middleware, requests, models, views, a .env file and a storage/logs folder. If someone on your team has used Laravel, they will find things.

The generated tests call the app through a test client without starting a server. They check things like a JSON health route, a 404 for unknown paths and security headers on every response.

cp .env.example .env
clamp key:generate
clamp dev
What would you suggest as a good first project to learn it with?

Something you will use yourself, and small enough to finish in a weekend. A command-line tool you already wish existed, or a nightly job you currently run by hand, both teach the model without needing infrastructure.

Avoid starting with a distributed system. The ladder exists so that each step adds one new idea.

Time-box it. A weekend is enough to wrap one real task, run it twice, break it on purpose and delete it.

If you finish with an opinion, you have learned more than a week of reading would give you.

I want an HTTP API backed by PostgreSQL. Is there an example I can run?

Yes, the Users example. One operation is called from a console command and from an HTTP server, with memory or PostgreSQL behind a domain-owned repository. The console build resolves 8 packages, HTTP brings it to 62, and PostgreSQL to 160.

curl localhost:3000/users/1 -H 'Authorization: Bearer demo'

It uses the Axum-based integration, so you get Tower middleware too. Routes are declared one path at a time, and mounting an existing router is still in progress.

You can call it with curl. The demo identity accepts a bearer token, creating a user returns 201, and a missing header fails because no principal means no authorization.

It also exposes a streaming endpoint and a health route, so you see more than create and read.

cargo run --offline --locked --manifest-path examples/01-users/Cargo.toml --bin users-console -- create Ada
cargo run --offline --locked --manifest-path examples/01-users/Cargo.toml --features http --example 01-users-http
Can I build a site with user accounts, login and email?

In the std-only web stack, yes, in prototype form. Optional features add login with throttling, one role per user, sessions, registration with email verification, password reset, and outgoing mail through an SMTP outbox with a daily cap.

You create the first admin with a command. OAuth and social login are not in the source, so they are not something I can promise.

The first admin command prints a generated password once. There is no seeder with a default password, which is a small decision that avoids a classic mistake.

Registration, email verification and password reset exist when both auth and mail are enabled. Mail goes through an outbox with a daily cap, so a burst of signups cannot blow through your sending limit.

cargo run -- user:create ada@example.com admin "Ada"
What does the frontend side look like, and can I use Vue or React?

The generated app has a Vite and Tailwind 4 frontend, with views in app/resources/views using {{ }} templates. The changelog also lists clamp init NAME --vue and --react, which mount components on server-rendered pages.

It is server-rendered pages with islands, and not a single-page app framework. Node and npm are required for the frontend build.

Views are Blade-style templates. Double braces escape by default, a raw form prints trusted HTML, and there are @if, @foreach, @extends and @include directives. There are no expressions, so you work out counts and labels in the handler.

A missing view variable is an error with the view and line in the log, which is friendlier than a blank page.

Where does my data live, and what databases can I use?

Two options today. The facade's db feature is SQLite compiled into the app, with file migrations, seeders, a query builder and transactions, and it is the only engine in that stack. For PostgreSQL there is a package built on SQLx with qualified pools and explicit migrations.

A database or cache capability resolved by Kernel is not built, and Redis is on the roadmap and not in the code. There is no ORM.

SQLite means the database is a file next to your binary, with WAL and a busy timeout set for you. That is delightful for prototypes and internal tools, and it is also why it is the wrong tool for several writer processes on different machines.

Seeders run in file-name order, each in a transaction, so a failed one leaves nothing half inserted.

clamp make:migration create_posts # app/database/migrations/<UTC stamp>_create_posts.rs
clamp migrate # run pending migrations
Something that works in the background sounds useful. What can it do for me?

Three pieces. A worker handles messages over NATS JetStream with bounded retries and dead-lettering, an outbox and inbox keep database changes and messages in step, and a scheduler runs jobs on a clock you control. Each is a runnable example.

The email worker even runs in memory with no broker, which is the easy way to try it. The order example is the serious one, and it was tested by killing processes.

The mental model is a back office with a ticket system. The front desk, your API, writes a ticket and carries on. The back office takes tickets one at a time, retries the ones that fail for good reasons, and puts the rest in a drawer for a human.

None of it needs a web server. Workers and jobs are processes in their own right.

How does one application turn into several separate programs, like an API and a worker?

You declare one blueprint with several processes, and each process starts only what its roots reach. The order example derives four projections from one blueprint, an API, an outbox publisher, a worker and a scheduler.

The inspector prints them without needing PostgreSQL or NATS, so you can look at the shape first. Running them as separate binaries over JetStream is shown in the email and order examples.

The inspector is a good first look because it is cheap. It prints each process, the modules it reaches, which provider each resource comes from, and who owns the database and broker clients and drains them on shutdown.

Then you can run the real pieces and kill them, which is where the example earns its keep.

cargo run --offline --locked --manifest-path examples/06-create-order/Cargo.toml --bin phase7-inspect
You mentioned a device loop. What on earth is that, and who would build one?

A loop that advances in fixed steps with integer units and a seeded random source, so the same seed and the same inputs give the same states every time. Think a simulation step, a game tick or a firmware loop, and not a web request.

It is a proof with a fake sensor and a CAN decoder target, and no real hardware or embedded target is claimed. The loop itself uses only the standard library.

The fake sensor validates raw readings as they arrive, reports missing and stale samples, and has a bounded queue of eight. That is how real input would behave, minus the hardware.

A direct example is a plain Rust twin that prints the same three lines, so you can compare the composed version with the one you would have written by hand.

pub fn step(state: &mut State, input: Input, random: &mut Seeded) {
    state.position_mm += state.velocity_mm_per_step;
    state.velocity_mm_per_step += i64::from(input.delta);
    state.noise = random.next_u64();
    state.steps += 1;
}
Can it serve a static site, and is that what the RustClamp sites run on?

There is an example that serves a folder of static pages through the public HTTP route target on a local port. It returned byte-identical responses to the Python preview for the checked routes of both RustClamp sites.

It is a hosting proof, and not production. File reads are synchronous, the sites are still built by a Python generator, and production is not switched to Rust.

cargo run --offline --manifest-path examples/08-site-server/Cargo.toml \
  --example 08-site-server -- <folder>

It matters as a proof that the HTTP target is general. The same HttpRoutes<Public> that serves the Users API serves a folder of files.

The honest caveat from the example's README: it uses synchronous file reads for clarity and is not meant as a production asset server.

What is it definitely not for? I would rather hear the limits now.

It is not an async runtime, an ORM, a DI container or a website builder, and it does not replace Tokio, Axum or SQLx. If all you need is one endpoint, plain Axum is a perfectly good answer.

Also not yet: stable APIs, gRPC, MQTT or Redis transports, exported metrics, and bare-metal embedded targets. Those are on the roadmap as planned, not built.

Anything that needs an ORM with relations, or a content admin generated from models, is yours to build. The pieces for forms, auth and uploads are there, and the glue is not.

And it will not make a slow program fast. It adds small measured overheads and does not remove yours.

What is coming next on the roadmap, and is there a date?

No dates. Next in line are integrations such as WebSocket, gRPC, MQTT and Redis. After that come AI and MCP adapters over inspection, which are never required to build or run, and then 1.0 with stable APIs and packages on crates.io.

You can follow it on the roadmap page, and the open work is in the GitHub issues.

Concretely: a stage called integrations, with WebSocket, gRPC, MQTT and Redis. The HTTP package's README already lists a ws feature, so check the source before you rely on the roadmap page there.

Beyond that come adapters for AI tooling that sit over inspection and are never required.

If I build something with this, can I actually ship it to users?

Carefully. RustClamp is experimental, it started in September 2026, APIs can change before 1.0, and the packages are not on crates.io. Prototypes and internal tools are the honest fit today.

I would not put your only product on it this month. Build the thing, learn the model, and keep your domain on your own traits so that you can move.

The route to shipping something real is: keep it internal, pin a commit, put a proxy in front, and plan on reading the changelog at each update.

Plenty of people run internal tools on pre-1.0 software for years. The difference is that they chose the risk on purpose.

So is RustClamp a web framework, or what kind of thing is it exactly?

Not primarily a web framework, and not a framework in the usual sense. It is the application composition layer for Rust. It sits above crates like Axum and Tower, SQLx, Tokio and NATS, and composes them into one application.

Web is one of the things you can compose. There is an Axum integration, and a std-only web stack in the facade for sites and internal tools. A command-line tool, a worker, a scheduler and a device loop are composed with the same model.

That is why the learning tree starts with Hello Clamp and not with a server. The core idea is composition, and what you build with it is up to you.

If all you want is a quick website, the generator gets you there, but you will meet the composition ideas sooner or later.

Can I build something real-time, like a chat or a live dashboard, with this?

Partly, and I would check before promising anything. The Users example has an event-stream endpoint, so streaming responses exist in the Axum integration, along with disconnect cancellation and graceful drain.

"/users/{id}/stream",

For WebSockets, the roadmap lists them under the next integrations stage. The HTTP package's README lists an optional ws feature with an upgrade route that closes sockets with a close frame on shutdown, so read the source and the issues before relying on it.

There is no built-in push channel to browsers. The messaging package has a topic bus for in-process fan-out and JetStream carries messages between workers, which is not the same as pushing to browsers.

If real-time is the heart of what you want, try the streaming example first and keep your expectations modest.

Could I build a blog or a small content site with it, with markdown and an admin area?

Yes, in the std-only web stack. Views are Blade-style templates, an optional markdown feature converts to safe HTML, and the auth feature gives you login with roles and an admin guard.

The markdown converter shows raw HTML as text and points non-web links to a harmless anchor, so user-written markdown cannot inject scripts. Anything printed with the raw syntax must be HTML you made yourself.

<div>{!! post.body !!}</div>

Content goes in SQLite with migrations and seeders, and images can use the uploads feature. The pieces for a blog are there, and the glue is yours to write.

There is no generated admin panel or content model. You build the admin pages as routes behind a role guard.

Can I build data pipelines or batch processing, like ingesting files and transforming them?

In small, reliable pieces, yes. A worker consumes messages with bounded concurrency and retries, a scheduler triggers batch runs on a clock you control, and an outbox keeps database changes and messages in step.

The job runner stress test is the nearest example: a spool-directory worker with two processes, typed handlers, retries, dead-lettering, bounded concurrency, crash recovery and graceful drain.

What it is not is a dataflow or stream-processing engine. There are no windows, joins or connectors, and delivery is at least once, so steps should be idempotent.

For modest ingestion jobs on one or a few machines, the shape fits, and the failure handling is the part you would otherwise write yourself.

How do file uploads and storage work if I build a site that accepts images?

Handlers reach named disks through the request. A disk is a folder, and the template defines a private local disk that is never served and a public disk served at /storage.

Multipart forms can send up to 10 MiB, other bodies are capped at 1 MiB, and larger ones get a 413. Validation rules include file, image and mimes, and the image rule checks both extension and content and excludes SVG.

Uploaded filenames and content types are untrusted. The store call picks a new UUIDv7 filename and keeps only the extensions you allow.

let photo = request.file("photo").expect("required image passed validation");

Paths are refused if they are empty, absolute or contain parent segments, so no path can leave the disk.

How would I put what I build on a real server, and what do I need besides the binary?

A web app ships as one release binary plus the public/ folder, and it listens on 127.0.0.1 so a reverse proxy faces the internet and handles HTTPS.

Build the frontend first, then the binary, copy both with your .env, and run from the folder that contains public/ and storage/. Under systemd or in a container, restart on failure.

cargo build --release # target/release/my-site

Set APP_ENV=production and the session cookie becomes HTTPS-only. Set TRUST_PROXY only behind exactly one proxy, so rate limits key on the right address.

Workers and jobs deploy as their own binaries, with only the settings they need.

Why should I trust it?

Skepticism is a sensible starting position, and I am not going to argue you out of it. I will show you where to look, what the numbers say and where they stop.

RustClamp is the application composition layer for Rust. It sits above Axum and Tower, SQLx, Tokio and NATS and composes them, and it replaces none of them. That is a claim you can check in the code.

Would you like to start with the evidence, the list of what is wrong, or who is behind it?

I do not trust a new framework, and a nice homepage will not change that.

Good. You should not trust it because a homepage says so, and I am not going to ask you to. What I can do is point at things you can read, run and measure yourself.

The source is public, the numbers come with their controls, and I will say plainly what is not built. If it cannot survive that, you should walk away.

So treat me as a guide to the evidence, and not as the evidence. Anything I claim should end at a file you can open, and if I cannot point at one, I should say so.

You can test that right now by asking me for the source of any number I give you.

Fine, show me evidence. What has actually been measured, and against what control?

The smallest Clamp program is measured against a plain Rust program with matched toolchain, profile, flags and output. Runs are paired and alternate order, the raw samples are kept, and the later phases measure HTTP, PostgreSQL, tasks, loops and projection scale the same way.

All numbers come from one machine, a Ryzen 5 PRO 4650U on Linux, with small sample counts. The docs call them budgets to defend and not guarantees, and I agree with the docs.

The measurement protocol also lists what each prototype must report: dependency count, clean and incremental compile, binary size, startup and allocations. When one is missing, it is recorded as unknown and never as zero.

Phase 1 is the Pico comparison. Later phases cover capability resolution, contributions, lifecycle, HTTP and PostgreSQL, messaging, loops and tooling.

Skip the pitch: what is actually wrong with it right now, in your view?

It is experimental, so APIs may change before 1.0. The packages are not on crates.io, there is no reusable lifecycle driver, exported metrics are planned, and mounting a whole existing Axum router is not built.

I also found no external security audit in the repository. The project's own progress notes show it correcting site copy that overstated how finished a phase was, which is a good sign about the authors and a fair warning about the copy.

Most of that list is missing pieces. For bugs, the issue tracker is the place to look, and I would read it before you decide.

The project files its own problems there, which is useful for you.

What are the headline numbers for the smallest program against plain Rust?

The release binary is identical at 4,335,592 B, and an allocation probe counted zero added allocations. A clean build costs 36.78 ms more, about 16 percent, and an unchanged rebuild 1.67 ms more. Those come from five paired repetitions with fresh target directories.

Process wall time differed by 0.054 ms at the median, but the sample ranges overlap almost entirely, so I would not claim any startup penalty or benefit from that.

The clean-build delta is the one that stings: about 16 percent on a trivial program. It is the cost of compiling the small facade crate, so it should matter less as your own code grows.

The comment-edit rebuild is closer to daily life, at 10.86 ms over plain Rust.

| Clean-build median | 232.40 ms | 269.18 ms | +36.78 ms |
| Unchanged rebuild median | 133.48 ms | 135.15 ms | +1.67 ms |
| Release binary size | 4,335,592 B | 4,335,592 B | 0 B |
| Entrypoint allocations | 0 | 0 | 0 |
What are the caveats I should know before I believe any of those numbers?

One machine, small samples, and uncontrolled OS page caches. Process timings include spawn, output and exit, so they say little about initialization. The lifecycle example's 5 ms to ready and 14 ms to shut down are configured fake-clock durations that check ordering, and they are not hardware speed.

The protocol also says a missing measurement is unknown and never zero. I take that as the standard to hold me to.

One caveat worth underlining: the HTTP comparison is a micro-benchmark of one path with one workload. It says contributed routing and middleware cost about 3.1 microseconds per request there, and it says nothing about your service.

The authors say similar things in their own words, which is the main reason I trust the numbers at all.

The protocol compares an implementation with a control under matched conditions.
Interpret paired medians with their raw sample range and machine conditions.
How could I reproduce those measurements myself instead of taking your word?

Clone the repositories side by side and run the harness. It builds isolated consumers, alternates the order of paired runs, and writes a versioned JSON report with compiler, target, machine, source fingerprint and raw timings. You need Python 3.11 or newer and Rust 1.96.1.

The check runner does the formatting, lints, tests, dependency-boundary checks and example runs across the packages in one go.

Read the report before you believe it. It records the compiler version, the target, the machine and CPU, memory, a source fingerprint, the exact commands, the cache conditions and the raw timings.

Compare your report with the one in the repository. If yours disagrees wildly, that is a finding, and an issue would be welcome.

python3 rustclamp/tools/pico.py --check
python3 rustclamp/tools/pico.py --measure rustclamp/docs/evidence/reports/phase1-pico.json
python3 rustclamp/tools/check.py
Where does Clamp actually lose to plain Rust? I would like the uncomfortable numbers.

The same Users request through the Clamp HTTP path took 6,857 ns against 3,728 ns direct, 1.84 times, with 69 allocations against 40. A runtime-driven loop cost 5.661 ns per step against 0.627 ns, and the composed device binary was 669,872 B against 446,760 B.

On code size, a CLI built twice came to 223 lines of Rust against 197 for plain Rust, and a job runner started at 806 against 585. A PostgreSQL read was within noise of plain SQLx, which is the one place I would say it broke even.

A different way to read the HTTP number: the Clamp path pays about 3.1 microseconds extra for routing, middleware and content negotiation, while a PostgreSQL read through the adapter was within noise of plain SQLx at roughly 170 microseconds.

I would put these in a table for your own workload before deciding anything.

How experimental is experimental? Is that just a polite way of saying broken?

Not broken. The examples run, the tests pass locally, and the crash tests were run against real PostgreSQL and JetStream. Experimental means the APIs may change before 1.0, it started in September 2026, and the packages are not on crates.io.

publish = false

Phases 0 to 9 are complete on main, but the roadmap is explicit that completion records prototype work, and not stability or production support. Prototypes and internal tools are the honest use today.

One concrete sign: the changelog has a section of breaking changes in the unreleased version, such as views moving to templates and the worker handler signature changing. That is what experimental looks like in practice.

The upgrade page tells you what to change, which says something good about the care taken.

Give me the full list of things that are not built yet, with nothing hidden.

Mounting a whole existing Axum router is in progress, and packages on crates.io are an open issue. A reusable lifecycle driver, exported metrics and a distributed lock for several scheduler processes are not built either. Neither is moving a single capability out of process, or a Core capability for databases and caches.

On the roadmap and not in code: Redis, gRPC and MQTT integrations, an MCP adapter, stable 1.0 APIs, and a bare-metal build of Core.

I would rather you hear a long list now than discover items on it later. The roadmap page and the open issues are the live version of this list.

If an item you need is missing, that is a legitimate reason to stop, and a legitimate reason to open an issue.

Who is behind this project, and will they still be around next year?

I cannot see staffing, funding or plans from the source, so I will not guess, and I cannot promise next year. What I can show is public history: nine repositories under one organization, a trail of decision records, and a license that lets you fork.

If the answer matters to your decision, ask on GitHub and weigh the reply yourself. That is the honest way to settle it.

You can check one more thing yourself: the commit history and the issue tracker show how quickly questions get answered and how decisions get argued. That is better evidence of care than any statement from me.

I do not know the size of the contributor base from the source, so I will not claim a community.

What license is it under, and what happens to contributions from other people?

MIT or Apache-2.0, at your option, as recorded in ADR 0011. Unless you state otherwise, a contribution you submit is dual licensed the same way, without extra terms.

Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or

The packages are 0.1.0 with publishing disabled for now. The license is settled, and registry ownership is the open part.

Dual licensing means you can pick whichever suits you, and companies often pick Apache-2.0 for its explicit patent grant. That is general knowledge about the licences, and not something from the repository.

The companion crates carry the same dual licence text, so the whole family is covered.

What do the tests actually prove, and what do they not prove?

The Pico tests check that the closure runs once on the calling thread, that errors come back untranslated and panics propagate. Compile-fail tests check that a frozen process cannot change, and resolver tests cover missing, ambiguous and cyclic cases, including a chain of 2,048 modules. Live runs killed processes against PostgreSQL 18.6 and JetStream 2.11.

They do not prove exactly-once broker delivery, stable APIs or production behaviour. The docs also say local runs do not establish hosted CI status, so treat the check runner as the source of truth for it passes.

The tests I find most informative are the failure-path ones: a dormant invalid module not breaking the CLI, a required contribution with no target being reported, and a failed startup cleaning up only what it acquired.

The tests I would add are the ones you can only write against your own workload, which is why a pilot matters.

cargo test --offline --locked --all-features
What stops RustClamp itself from doing something dangerous, like unsafe code or surprise dependencies?

Every crate forbids unsafe code and denies missing docs. A boundary checker enforces an allowlist of resolved dependencies per package, so a new edge needs an explicit policy change, and Core and Kernel have none.

The one unsafe exception is a counting allocator in a benchmark fixture that never links into Clamp itself, and the ADR records it. Crypto uses the RustCrypto crates, and I found no external audit.

Dependency boundaries are the second line. The boundary tool lists, per package, the exact resolved crates that are allowed, so a transitive dependency appearing in your build is a visible diff.

Because Core and Kernel allow none, adding one there would need an explicit policy change and a reason.

[lints.rust]
unsafe_code = "forbid"
missing_docs = "deny"
If I wanted to read the code myself, where should I start?

Start with examples/00-pico, then a2-module and a4-process. After that, read ADR 0004 and ADR 0017 in the facade repository, since they show a design decision being tested and then amended.

The resolution logic is in the Kernel crate, and the measurements live under docs/evidence. Everything is on GitHub.

If you prefer a map: the main repository holds the examples, decision records and evidence. Core, Kernel and Runtime are readable in a sitting or two, and the integrations are separate repositories.

Start with the tests next to each example, since they say what the authors consider important.

git clone https://github.com/rustclamp/rustclamp
cd rustclamp
cargo run --offline --example 00-pico
Is this an official Rust project, or endorsed by the Rust team in some way?

No. It is a composition layer built for Rust, independent of the Rust project and not endorsed by it. I would be wary of anything that implied otherwise.

It does lean on the ecosystem that already exists, namely Axum, Tower, SQLx, Serde and the NATS client, and it hands you their APIs.

Practically, that means no official guidance covers Clamp, and you will not find it in the Rust documentation. The RustClamp docs site is the reference.

The Rust project's tools, such as Cargo, still apply to everything here.

Then how would I try it cheaply, so that being wrong costs me almost nothing?

Pick a small project with clear success criteria and an easy rollback, such as a worker, a scheduled job or a CLI next to something you already run. Time-box it, and write down beforehand what you will measure.

Measure runtime, memory, compile time, how much wiring you wrote, testability, operational complexity and how hard it is to remove. If it loses on those, do not use it for that case.

A concrete trial: take one background task, build it twice, and give yourself a week. Compare the two on the numbers you chose, and let the numbers decide and not enthusiasm.

Send what you find to the project. Negative reports are useful.

What should make me stop using it, so that I can walk away without regret?

Wiring that ends up bigger than a plain Rust twin of the same slice. Compile times you cannot live with. API churn that costs you more than Clamp saves. A piece you need that is only on the roadmap, such as Redis.

Also if you cannot remove the pilot cleanly, since an adoption path needs an exit path. I will not take it personally if you leave.

Write the criteria down before you start, with numbers. Wiring more than some fixed percentage larger than the plain twin is a criterion, and feels clunky is not, though it is still worth noting.

If a criterion trips, stop and say so. That outcome is a success of the trial and not a failure.

What exactly is the claim here? Is it a framework, a library, or marketing for glue code?

The claim is narrower than most. RustClamp calls itself the application composition layer for Rust, and not a framework. It sits above crates like Axum and Tower, SQLx, Tokio and NATS, and composes them through modules, typed capabilities, lifecycle and processes.

You can test that claim in the code. Core defines contracts with zero dependencies, Kernel resolves and freezes them, Runtime supervises tasks, and the integrations are optional crates that hand you the upstream types.

Is any of it glue code? Partly, honestly. The value is not in the glue but in the checks that the glue is consistent: missing and ambiguous wiring becomes a named error before anything runs.

If that does not justify itself on your project, the right call is plain Rust, and the project says as much about Axum.

Has the project ever been wrong and said so? Show me a reversal, not a success story.

ADR 0004 compared two ways to declare modules and found no measurable compile-time difference, noting the trait fixture's metadata was larger. ADR 0017 later amended it, after a stress test showed an edge declared twice with nothing keeping the two in sync.

ADR 0020 amended an earlier rule that retry and dead-letter policy belong to the transport adapter, after two apps wrote the same loop by hand. The loop moved into the worker package.

The Pico allocation calibration was optimised away in release mode, and the harness was corrected with positive controls. The phase 9 notes record homepage and roadmap copy being corrected when it overstated how finished a phase was.

None of this proves quality. It does show that mistakes get written down, which is the habit I would look for.

Has anyone outside the project used or reviewed it? I want to see someone else's experience.

I cannot show you that. The repository records the authors' own stress tests, which built apps twice, once in Clamp and once in plain Rust, but I see no third-party case studies or external review.

The stress tests are worth reading anyway. They produced real gaps, fourteen in one case, and the authors checked each against the source and changed what was justified.

So the honest state is that the evidence comes from the project itself, with raw data you can inspect. That is more than nothing and less than independent confirmation.

Your pilot would be the first independent data point you control, which is also why the pilot should be small.

Is there a quick way to check the zero bytes claim myself without running the whole harness?

A rough one, yes. Build the Pico example and an equivalent plain program with the same profile and flags, then compare the file sizes of the two release binaries.

cargo run --offline --example 00-pico

In the recorded run both were 4,335,592 B, with the release profile at opt-level 3, no LTO and 16 codegen units. The facade function is marked inline and only calls your closure, which is why equality is plausible.

It is a sanity check and not a replacement for the harness, which also controls toolchain, output and run order. A tiny difference from metadata or paths would not surprise me.

For allocations you need a counting allocator, so use the repository's fixture and do not hand-roll one.

Why should I pipe your install script into bash? Walk me through what it actually does.

You should not have to take my word, so read it first. It is a short shell script served at the install address, and it is the same file that lives in the website repository.

It needs curl, installs the Rust toolchain through rustup if Cargo is missing, finds the latest release, downloads the archive for your platform and its checksum file over HTTPS, and verifies it with sha256 before using it. If no prebuilt binary fits, it builds clamp from source with a locked install.

if command -v sha256sum >/dev/null 2>&1; then sha256='sha256sum'; else sha256='shasum -a 256'; fi

It works in a temporary directory that is removed on exit, and it appends the Cargo bin directory to your zsh or bash startup file when that directory is missing from your path.

One caveat from reading it: the checksum comes from the same release as the archive, so it catches corruption and mismatched downloads, but it is not a signature that proves who published it. If that matters to you, build from source.

Not technical, just curious

No programming knowledge needed, then. I will use plain words and everyday pictures, and I will tell you honestly how finished things are.

In plain words, RustClamp is a layer that sits on top of the ready-made parts programmers already use, and puts them together into one working program. It does not replace those parts.

Where would you like to start: what this is, what a framework is, or who it is for?

I am not technical at all. What is RustClamp, explained the way you would to a friend?

It is a toolkit that helps programmers put computer programs together. Picture a clamp in a workshop, the kind that holds two pieces of wood tight while you work on a piece of furniture. RustClamp does that job for the pieces of a program, so they fit together properly.

It does not write the program, and it does not replace the programmer. It just makes the assembling less fiddly and a little more reliable.

A layer of what? Programmers rarely build everything from scratch. They use ready-made parts for the web, for storing data and for passing messages, with names like Axum, SQLx and NATS. RustClamp sits above those parts and puts them together into one working program, the way a head chef coordinates stations without cooking every dish.

It does not replace any of those parts.

You called it a toolkit. What is a framework, and why would a programmer want one?

Think of a restaurant kitchen with stations: one for prep, one for the grill, one for washing up. A framework is the kitchen layout. It decides where things go, so that every cook is not inventing their own arrangement during the dinner rush.

RustClamp is not quite that. It does not dictate your kitchen. It is more like the order board that connects the stations, so the grill knows what prep will deliver and the dishwasher knows what is coming.

Programmers like frameworks because they save arguments about layout. They dislike them when the framework starts to dictate everything, which is why the difference matters.

The next question people ask is whether RustClamp is one, and the short answer is no.

Who is this actually for? Would someone like me ever use it directly?

It is for programmers, so most people never touch it. If you are curious, you might be a founder, a manager, a student or a friend of someone who builds software, and this is a fair way to understand what they are talking about.

It becomes interesting to a business when a software project starts to grow from one simple thing into several things that need to work together. That is the moment people start to feel the need for some kind of order.

Think of a mechanic and a car owner. You do not need to be the mechanic to decide whether a new tool in the garage is worth the money, and understanding what it is for helps that decision.

If you are a student or a curious friend, this guide is for you in the same way: to understand, and not to operate.

So is it like building with Lego, where I just snap pieces together?

Close, with one difference. Each piece says what it needs and what it offers, a bit like a brick that says how it connects, and a checker makes sure everything fits before the program starts. You find out at the planning stage that a piece is missing, and not at midnight when customers are using it.

Unlike Lego, the pieces are written by programmers, so there are no bricks to buy.

The checker is the part I like. Imagine a model kit that tells you, before you start gluing, that step four needs a piece you do not have, and tells you which piece.

In RustClamp, that kind of message appears before the program starts, and it names the missing piece.

You keep saying Rust. What is Rust, and why does anyone care about it?

Rust is a programming language, in other words the language programmers use to tell a computer what to do. It is known for making fast programs and for catching a whole family of mistakes before the program ever runs, a bit like a proofreader who is strict but helpful.

RustClamp is made for programs written in Rust. If a programmer chose another language, they would not use it.

Programmers choose Rust the way a builder might choose a stricter building code: more rules while you build, fewer surprises in the finished house.

It is also known for being a little hard to learn, which is the price.

What kinds of things can people actually make with it?

Simple tools that you run from a text window. Websites and web services, which are like the shop front of a business. Background workers, which are like the back office that sends emails and processes orders while nobody is watching.

Also jobs that run on a schedule, like an alarm clock for a program, and even simulated machines that repeat exactly the same way each time. Every one of these has a runnable example on the learning tree.

Each of those has a runnable example on the learning tree. If you click through, you can watch them grow from a program that prints one line into something with several moving parts.

They are demonstrations, and not products you can buy or install as an app.

What is the big idea behind it? Why not just use the usual tools?

The idea is to start with a tiny program and add pieces only when you need them, and to pay for nothing you do not use. In the smallest test, a program with RustClamp came out exactly the same size as one without it.

The second idea is a checklist before takeoff. Before the program starts, it checks that every piece has what it needs, so that mistakes show up early and with a clear message.

Think of packing for a trip. A small bag is nice until you need a coat. The idea here is that you carry the coat only on the days it rains.

That is also why people measured it: a tiny program stayed tiny.

Is it finished? Could a company rely on it today?

No. It is experimental. It began in September 2026, and the details can still change before a first stable version, so a company should not build its main product on it yet.

A good picture is a prototype car on a test track. It runs, people measure it, and it is not ready to sell. It suits experiments and small internal tools today.

Test tracks are where cars find out what is wrong with them. That is where RustClamp is: people try it, measure it, and write down what breaks.

So if someone offers you RustClamp as the foundation of something your business depends on, ask them to wait.

That is quite candid. Why does this guide say so many things are unfinished?

Because trust is built on being told the limits. The project writes down what is measured, what is only planned, and what is not built, and I would rather disappoint you now than mislead you later.

For example, a few things are on a future list and not in the code yet, such as more ways to connect to other systems. I will happily say not built yet as often as it is true.

Think of a restaurant that tells you which dishes are not ready yet. You might order less, but you trust the menu more.

That is my aim here.

Can a non-programmer try anything, or is it only for people who write code?

You can read along with no tools at all. The learning tree on this site shows the steps in order, from a tiny first program to something that grows, and each step is a real example.

Running one needs a computer and some patience, and it helps to have a developer friend nearby. If you would like to start from nothing, there is a gentle conversation for people who are new to Rust.

If you want a taste without installing anything, read the first two steps on the learning tree. They show what the program looks like and what it prints.

If you do install it, ask a developer friend to sit with you for the first run. It is faster, and it is more fun.

I am a student. Is this a good way to learn programming or Rust?

Not as your first step, and I would rather say so. First learn a little Rust itself, using the free official book, because I would be teaching you badly otherwise.

After that, the examples here are a nice ladder. Each one adds a single idea, which is how learning tends to work best anyway.

Learning to program is like learning to cook. Start with a simple recipe, burn something, try again. RustClamp is more like a set of kitchen stations you will want later.

So do not feel behind if you have not touched it.

A friend of mine builds software. What should I tell them about it in one minute?

You could say: RustClamp is an early-stage toolkit for programs written in Rust. It starts tiny, adds pieces as the program grows, and checks that the pieces fit before anything runs.

Then add that it is honest about being experimental, that the source is public and free to use under an open license, and that a good way to try it is on one small background job.

If your friend asks what makes it different, say it keeps the program small and checks that the parts fit before it starts.

If they ask whether to use it for their company's main product, say not yet.

Where would you suggest I go from here if I want to look around?

Open the learning tree and read the first two steps, even if you do not run anything. If you would like to start from zero, there is a conversation for people new to Rust, and I am happy to explain anything in plain words.

You can also come back and ask me about a word you did not understand. That is what I am here for.

And if you ever want the developer version of this conversation, pick a different role at the top, and I will go into much more detail.

There is no wrong order. You can come back to any question here.

Everyone says framework. So is RustClamp a framework or not, in plain words?

Not quite, and that is a deliberate choice. A framework in the usual sense is like a franchise kitchen: it comes with the layout, the rules and the uniforms, and you work its way.

RustClamp is more like a coordinator. It sits above ready-made parts that programmers already use, and puts them together into one program, checking that each part has what it needs. It does not move your kitchen around.

It does ship a few conveniences, like a starter kit for websites. Those are optional, and the core idea is the coordination.

The proper name for it is the application composition layer for Rust. Composition is the word programmers use for putting parts together.

Why would anyone need something to put the parts together? Can the programmers not just do it?

They can, and many do. For a small program, putting the parts together by hand is fine, like cooking dinner for two.

The trouble starts when the program grows into several programs: one that serves a website, one that sends emails in the background, one that runs jobs at night. Then someone has to keep track of what depends on what, and what must start first.

Think of a restaurant that grows from one table to a dining room, a takeaway counter and a catering service. Without an order board, things get dropped.

RustClamp is that order board for programs, and it checks the board before the doors open.

Is it free to use, and who pays for it? There is usually a catch somewhere.

It is free to use. The code is open and licensed under two permissive licences, MIT or Apache-2.0, and you can pick either.

That means a company can use it, change it and even build commercial products on it without paying the project.

I see no paid plans, hosting or support offers in what I read, so I cannot tell you how the project is funded.

Free to use does not mean free of cost, though. The real cost is people's time to learn it and to look after what they build with it.

Is it safe to try? Could it break my computer or leak my data?

Running an example is no riskier than running any program you download, so use the same caution. The installer is a script you can read first, and it checks a fingerprint of the file it downloads.

The project forbids a risky kind of code in its own crates, and its tests check behaviour. I can say it is built with care, and I cannot say it has been independently audited, since I found no sign of that.

If you only read and explore, nothing at all is installed. If you do install, ask a developer friend to look through the steps with you.

The rule of thumb is the same as for any new software: try it on something that does not matter first.