hitch

How to build a game that runs inside Meta's Muse

What worked, what got blocked, and the numbers nobody has written down yet.

I spent a weekend getting a daily word puzzle, Hitch, to run inside Muse, Meta's new AI agent. Not a link to the game. The actual game, playable in the chat.

Almost none of this is documented yet, so here is everything I learned. If you are building anything interactive for Muse, this should save you a day.

The short version

What does not work

Framing your site. My first idea was a tiny widget containing nothing but a frame pointed at my website. Muse's answer: chat widgets don't support embedded iframes. That route is dead.

Browser storage. My second try was asking Muse to download my game file and display it. It came back with this: the widget platform blocks pages that use localStorage. It then offered to make its own copy of my game with the storage code swapped out.

That offer is the thing to avoid. You do not want an AI editing your game every time someone plays it. The fix is to hand it a file with nothing to fix.

What works: one file, no storage, nothing to fetch

Make a widget edition of your game that is:

  1. One HTML file. CSS and JavaScript inline. No build output folder, no separate assets.
  2. Free of browser storage. No localStorage, sessionStorage or IndexedDB. Not even inside a try/catch. The word itself seems to be enough to get flagged.
  3. Fine with no network. Mine loads a web font, but falls back to a system font if that is blocked. Everything else is in the file.
  4. Able to choose its own content. My game picks today's puzzle from the device's date, so a cached copy still shows the right puzzle.

Then ask Muse to download that file and show it as an interactive widget without changing the code. For me it appeared in the chat, played start to finish, and Muse edited nothing.

Keep one source, generate the widget

Do not maintain two copies of your game. I fence the storage code in the main file with comment markers:

/* WIDGET-BUILD:STORAGE-START */
function readSaved() { try { return window.localStorage.getItem(KEY); } catch (e) { return null; } }
function writeSaved(text) { try { window.localStorage.setItem(KEY, text); } catch (e) {} }
/* WIDGET-BUILD:STORAGE-END */

A small build script swaps that block for a memory-only version, strips my analytics the same way, and refuses to finish if a forbidden word survives:

out = swap(out, 'STORAGE', 'function readSaved() { return null; }\nfunction writeSaved() {}\n');
out = swap(out, 'ANALYTICS', '');
for (const word of ['localStorage', 'sessionStorage', 'indexedDB', '_vercel']) {
  if (out.includes(word)) throw new Error(`Widget build still mentions ${word}`);
}

The website keeps saved progress. The widget has amnesia. Same game, one source of truth.

Design for 339 pixels wide

I put a temporary readout of window.innerWidth and window.innerHeight on the page and loaded it in Muse on my phone. It said 339 x 830. Measure your own, because it will vary by device, but plan for narrow.

Two lessons, learned in the wrong order:

Other things that helped: a How to play box made of three small cards with arrows, with the Play button outside the cards so it is always visible. Hints shown in the status line under the board, not at the bottom of the page. A finish screen that scrolls inside its own frame as a safety net.

The API is a menu, not a delivery truck

This confused me at first. Your API does not send the game to Muse. It answers small questions with small pieces of data, and Muse decides what to do with the answers.

Mine has three read-only endpoints: today's puzzle, a puzzle by number, and the rules. Today's puzzle returns the 16 words, a spoiler-free teaser, a play_url for browsers, and a widget_url pointing at the widget file. It never returns the answers.

The part that matters most is the OpenAPI spec, because the model reads your descriptions to decide when to call you and what to do next. Put three things in them, in plain words:

I put those instructions in the spec rather than in the API's responses. My reasoning: a careful agent should treat data coming back from an API as untrusted, but the spec is the part it reads as "how to use this tool." It worked. Muse followed it.

One more tip. Muse will paraphrase your rules to the player, so make them impossible to misread. Mine said "tie four to finish a group" and Muse told a player a group takes four ties. It takes three. I rewrote the line.

Test today, without approval

Muse can connect to any public API if you ask it to. Paste a message like this, with your own addresses:

Connect to the Hitch API at https://playhitch.com/api, read the spec at /api/openapi.json, and let me play today's puzzle.

That is the full loop a real user would get: Muse reads your spec, calls your API, downloads your widget and shows it. Run this before you submit anything. It is also a way for people to use your connector while you wait for review. I made a page for it.

The connector application

For anyone about to fill it in, the technical page asks for:

For a free public game I left authentication unchecked and answered no to payments. In the access box I said up front that the game is a self-contained widget with no browser storage, since those are the two things the platform blocks.

What I still don't know

If you figure out any of these, tell me: support@playhitch.com. And if you want to see the result, play today's Hitch or pull it up inside Muse.

Hitch is an independent game. It is not affiliated with or endorsed by Meta.