DEV Community

Richard Ali
Richard Ali

Posted on

I let Muse ship an app on SnoozeStack. Here's the whole thing.

Why I did this

I created SnoozeStack — a backend platform with a specific bet: most developers don't have one app with a million users, they have ten apps with a hundred users each, and the infrastructure tax per project is what kills the other nine.

I wanted to test out Meta's new Muse AI Assistant and thought having it start from zero and setup a working public site on SnoozeStack would be a great test. If the platform is actually agent-friendly, an agent should be able to drive it end to end.

What got built

A guestbook app to test every layer that matters.

  • Database: a signatures table (id, name, message, created_at), declared in a committed schema.mjs
  • API: a guestbook function — POST writes a row, GET reads them back, newest first
  • Frontend: a static site with a form that talks to the API on the same origin
  • Hosting: all of it published as one immutable release to muse-demo.snzzz.com

Step 1: Sign up and log in

$ snoozestack user login
Logged in as richard+muse@raliworks.com
Enter fullscreen mode Exit fullscreen mode

The CLI uses a browser device-code flow — it prints a code that you approve it in the browser. Muse drove the whole thing except it couldn't click the email confirmation link during signup only because I didn't grant it access to my email.

Step 2: The project is a folder

This is the part I care about most. The entire application — functions, schema, sites, capabilities — is one project.toml that Muse can configure:

[project]
name = "muse-demo"

[[functions]]
name = "hello"
entry = "hello/index.ts"
auth = "optional"

[[functions]]
name = "guestbook"
entry = "guestbook/index.ts"
auth = "optional"
capabilities = ["db"]

[[sites]]
name = "www"
dir = "www"
primary = true
Enter fullscreen mode Exit fullscreen mode

Note capabilities = ["db"]. Functions declare what they may reach, and undeclared capabilities simply don't exist inside the handler. The agent wrote this correctly on its first try.

Step 3: The schema is code

import { defineTable, schema } from "snoozestack-js";

export const signatures = defineTable("signatures", {
  id: schema.uuid().primaryKey(),
  name: schema.text().notNull(),
  message: schema.text().notNull(),
  created_at: schema.text().notNull(),
});
Enter fullscreen mode Exit fullscreen mode

schema build compiles it to a schema.json

Step 4: The function is the only thing that touches the database

There is no client-facing table API, by design. The frontend can't query the database; it calls the function, and the function decides:

export default async function handler(req, capabilities) {
  const db = capabilities.db;

  if (req.method === "POST") {
    const { name, message } = await req.json();
    const id = crypto.randomUUID();
    db.query(
      "insert into signatures (id, name, message, created_at) values (?, ?, ?, ?)",
      [id, name.trim().slice(0, 60), message.trim().slice(0, 280), new Date().toISOString()]
    );
    return Response.json({ ok: true, id });
  }

  const { rows } = db.query(
    "select id, name, message, created_at from signatures order by created_at desc limit 50",
    []
  );
  return Response.json({
    signatures: rows.map((r) => ({ id: r[0], name: r[1], message: r[2], created_at: r[3] })),
  });
}
Enter fullscreen mode Exit fullscreen mode

Plain SQL, parameterized, one statement per call.

Step 5: Run it all locally

$ snoozestack dev
muse-demo is running
  app   http://localhost:8788/          site "www", primary
  api   http://localhost:8787/guestbook   the new function
Enter fullscreen mode Exit fullscreen mode

One process: the site, the functions, and the database. The agent tested the round-trip before publishing:

$ curl -X POST localhost:8787/guestbook -d '{"name":"Local Tester","message":"hello from dev"}'
{"ok":true,"id":"c2d2429d-81a4-454c-b1db-05ca520e5b82"}

$ curl -s localhost:8787/guestbook
{"signatures":[{"id":"c2d2429d…","name":"Local Tester","message":"hello from dev",
"created_at":"2026-10-02T05:48:52Z"}]}
Enter fullscreen mode Exit fullscreen mode

Write, then read, against the local database. Only then did it publish.

Step 6: Publish

$ snoozestack publish
Publishing "muse-demo" to production
  ok    Checking project        2 functions, 1 site
  ok    Planning schema         1 change against production
  ok    Uploading sites         "www": 1 uploaded
  ok    Building functions      2 functions bundled
  ok    Deploying release       release r3 is production — runtime healthy
Enter fullscreen mode Exit fullscreen mode

One command. The schema migrated against the production database, the site uploaded, the functions bundled — all recorded as immutable release.

Step 7: Verify it live

The agent didn't trust the deploy output — it wrote rows to the production database and read them back:

$ curl -X POST https://muse-demo.snzzz.com/api/functions/v1/guestbook \
    -d '{"name":"Muse","message":"First signature, written by the AI that built this."}'
{"ok":true,"id":"efba91fd-4baa-4956-b854-8989f46abde0"}

$ curl -s https://muse-demo.snzzz.com/api/functions/v1/guestbook
{"signatures":[{"id":"efba91fd…","name":"Muse",
"message":"First signature, written by the AI that built this.",
"created_at":"2026-10-02T05:49:30Z"}]}
Enter fullscreen mode Exit fullscreen mode

And the site: 200. Go sign it yourself — https://muse-demo.snzzz.com.

Some notes for improvements I can make to the platform.

Here's what actually happened:

  1. The email confirmation. Signup needed a human to click a link. Agents can't have inboxes (yet) and for people skeptical of handing over their email account to AI Agents I need some other route of verification.
  2. The schema needed a table. The scaffold's schema.mjs initializes with everything commented out, and publish refuses a schema with zero tables. Reasonable guardrail, slightly confusing error that could be improved.
  3. The SDK isn't on npm. snoozestack-js installs from a version-pinned tarball on snoozestack.com. The agent found it in the docs in about a minute. I'll explore publishing to npm.
  4. Function URLs differ locally vs. hosted. Locally it's localhost:8787/guestbook; in production it's /api/functions/v1/guestbook. The docs explain why, and the agent adapted — but it's the kind of thing that should probably just work the same.
  5. The site swallows function routes. A primary site at / intercepts paths before functions see them. Worth adding validation to prevent naming a function the same as a page.

None of these stopped the agent. But most of the time was spent on the agent reading docs.

Next Steps

I'll work on a more complex demo using the other features of SnoozeStack including user auth and custom domains.

What was Interesting

The interesting thing isn't that Muse wrote a guestbook. It's the shape of the work: the agent operated entirely through four surfaces — committed files, the CLI, the docs, and curl.

Muse was a great substitute to having a friend try out the platform. It's always difficult convincing people to test out your apps and Muse served as a great stand in for a potential customer.

And if you're a developer with tons of small apps you've built for yourself and friends, give SnoozeStack a try for simple affordable project hosting.


Built with SnoozeStack — run the backend locally, publish with one command. The demo app lives at muse-demo.snzzz.com.

Top comments (1)

Collapse
 
kyisaiah47 profile image
kyisaiah47 •

I'd like to know whether publish keeps the previous release serving when a schema migration fails, since the database change can happen before the functions and site finish deploying.