Inspiration

I am bad at accountability.

Not casually bad. I mean I have started over more times than I want to write down. This year the list was specific: clear the debt, rebuild the credit, move, get healthy, build consulting income on the side. I had tried the habit apps and the streak counters and the journal with the nice paper. Capture was never my problem. The problem was that nothing ever held me to the record.

What actually worked was a ritual, not a tool. End the day by naming what moved, what got in the way, and the one commitment I am carrying into tomorrow. End the week by reading that record back and making a single decision about it. Boring, repeatable, and very hard to keep alone.

So I started running the ritual with an AI assistant. And about a week in I hit the thing that turned into this project.

An agent is good at exactly the parts I am bad at. It reads the week back to me. It notices I have deferred the same commitment four days running, which I would not have noticed, because I am the one deferring it. It drafts the entry while I talk. All of that is real help.

But the moment it writes the record for me, the record stops being mine. And accountability you can hand off isn't accountability. It's a report.

That's the whole thing. Let the agent do everything up to the decision, and nothing past it.

What it does

GYST — get your stuff together — is a human-owned daily and weekly ritual ledger. The entire product is one boundary, and the front page says it three ways:

  1. The ledger supplies bounded context.
  2. You and the agent prepare a visible draft.
  3. A human action commits the record.

The daily ritual asks four things. What moved. What blocked you, and what kind of blocker it was — internal, external gate, capacity, clarity, dependency. How yesterday's commitment actually went. And the single commitment you're carrying into tomorrow. One. Not a list.

The weekly ritual is the part I'd been missing for years. It hands you findings computed in Postgres and bounded to the current week: you've failed to complete this specific commitment three times, this blocker type keeps recurring, here's a win you wrote down and then never mentioned again, here's work you did that wasn't on any priority you named, here's a date coming at you. Not advice. Observations with the evidence attached. You supply the decision, the arrow — up, steady, down — and up to five dated priorities.

Around that: reminder emails on your own schedule in your own timezone, portable JSON and Markdown exports, and account deletion that actually deletes. And a demo you can open with no account and no email, which drops you into a fictional ledger with a full committed week behind it and today left open to conduct.

Why this belongs on WebMCP

The ritual is a conversation. The record is a row in Postgres with an owner. Those want two different interfaces, and WebMCP is the reason I didn't have to pick one.

The site declares the verbs, in its own language. A general-purpose browser agent has to drive my form by looking at it and clicking at it. Instead the page publishes gyst.record_blocker, gyst.score_previous_commitment, gyst.set_weekly_decision — the actual vocabulary of the ritual, with strict schemas and descriptions that say what I mean: Draft-only. Do not invent wording. This never commits or closes the record.

Auth and isolation come along for free, and they're real. The tools run in the signed-in tab, as the person, against owner-scoped routes. There is no API key to hand an agent and no separate MCP server sitting somewhere holding privileged credentials. Row-level security on every user-owned table means an agent cannot reach another owner's ledger. Postgres enforces that. Not a system prompt asking politely.

And the capability set is the argument. Fourteen ritual tools. Every one of them is read-only or draft-only. There is no commit tool, no delete tool, no export tool, no history tool, no SQL tool. Not disabled — absent. That distinction is the whole reason this belongs on WebMCP instead of in a chat window: I got to express "the agent may prepare, only the human may decide" as a real boundary instead of an instruction I hope it follows.

Here's what that makes possible that I couldn't do before. I can talk through my day out loud, the way I'd talk to a person, while a structured and owned ledger fills in behind me — and then press commit myself. Before this I had to choose. A chat log gave me help with no structure and no ownership. A form gave me structure and ownership with no help, alone, at eleven at night, which is exactly when I skip it.

How I built it

Next.js 16.3.3 on the App Router, React 19, strict TypeScript, Tailwind 4, on Vercel. Supabase Postgres 17 is the only durable ledger. A stateless Cloudflare Worker on a */15 cron sends the reminders through Resend. Turnstile guards signup and demo entry, verified by Supabase Auth rather than by my app. AGPL-3.0, code is public.

The WebMCP layer is a client component on each ritual route. It registers that ritual's seven tools through document.modelContext.registerTool with an AbortSignal, so the daily surface exists on /daily, the weekly on /weekly, and both unregister when you navigate away. Three site-level tools — status, open daily, open weekly — live on the public pages and expose no account data at all.

Each draft mutation posts to an owner-scoped route handler, which validates against a strict JSON schema and writes only draft columns through a SECURITY INVOKER RPC. Every one returns uncommitted: true plus the fields it touched, and the page turns that around and shows it to you: record blocker updated blocker text; it was not committed, with the affected fields marked in the form. The ordinary form stays fully usable with no agent present. And when the tools don't register in your browser, the page says so instead of quietly doing nothing.

Commit is a human-only server action with expected-version optimistic concurrency, and a committed session is immutable.

I also built it the way the product behaves, which was not an accident. Four lanes — a root orchestrator, a database and security lane, an application and WebMCP lane, and an infrastructure and release lane. Agents did bounded work freely inside their lane. Every irreversible step — remote migration, secret, deployment, real email, merge — stopped at a gate and waited for me. The build process and the product are making the same argument, which is either good design or a personality trait.

Challenges I ran into

The judge demo almost shipped broken, and the schema is what caught it. My plan called for a shared demo account. But ritual_sessions is unique on (user_id, kind, period_start) and committed sessions are immutable, so the first judge to commit would have left every judge after them unable to do the one thing I most wanted them to do. I replaced it with a Turnstile-protected anonymous sign-in and a SECURITY INVOKER seed function that refuses permanent accounts, refuses to overwrite a ledger that already exists, closes its seeded days through the ordinary draft-to-committed path, and derives every date at call time so the demo never goes stale. Anonymous users take the same authenticated role as everyone else, so all 38 owner-only policies apply to them unchanged. Nothing was relaxed to make the demo work. Owner isolation went from something I assert to something you can watch happen.

I put Turnstile in the wrong layer. I was verifying the challenge in the application before calling signup, which felt fine until I looked at it properly. It can't protect the anonymous sign-in endpoint, because that endpoint is publicly reachable with the browser's own publishable key. My check wasn't in the path. Verification moved into Supabase Auth and the secret left my app entirely.

The agent was editing my draft silently. Early on it would call a tool and the page would just change. Which is the exact feeling I built this thing to prevent. (Nothing quite like watching your own product do the thing you wrote it to stop.) The field-level provenance markers and the live "recent agent draft changes" panel came out of that afternoon.

And I kept wanting to add a commit tool. Three times, at least. Every one of them would have made a better ninety-second video and a worse product.

Accomplishments I'm proud of

Fourteen WebMCP tools with zero write authority over the ledger, and a test that proves it rather than a paragraph that claims it — a source-level contract check that fails if a commit path ever appears in the tool surface. Nine pgTAP files and 270 database assertions, including negative cross-owner tests. 92 unit tests. Six Playwright specs. A deployed thing a stranger can open and use in one click.

And a UI that tells the truth when agent assistance isn't available, instead of pretending. That one's small and I like it the most.

What I learned

Capability design beats prompt design, and it isn't close. Everything I first tried to enforce with a well-written tool description ended up enforced by a schema, a policy, or the deliberate absence of a tool. Only the last one actually held.

I learned to verify at the layer where the endpoint is reachable, not the layer where my code happens to sit. That's a lesson I'll be reusing.

And I learned that a boundary nobody can see isn't a boundary, it's a claim. Which is why every draft change announces itself as uncommitted, in words, on the page.

Here's the uncomfortable part, because I'd rather write it than leave it out. I built this during a ten-day sprint with a deadline. The real test isn't the submission. It's whether I'm still committing days in November, when nobody is judging anything and the ledger only matters to me. I've written before about building a system and then only half using it. I know exactly what that failure looks like, because it's mine.

Almost everyone is doing accountability. Almost nobody has a system for it. A ritual with a record you own is a system. A streak counter is a habit, and habits don't compound. They accumulate, until you miss a day and delete the app.

What's next for GYST - Get Your Stuff Together

Sender warming and a DMARC record, deliberately, so the reminders stop landing in spam. Weekly patterns that span months instead of one week, because the interesting stuff is the thing you've been avoiding since June. An agent-readable trace for why a finding fired, so it can explain itself instead of just reporting. And read-only sharing with a coach or an accountability partner — bounded context goes out, commit authority stays home.

Then six months of my own list, one committed day at a time.

Built With

Share this project:

Updates

posted an update —

Release: 5e4ac8e (production, READY) · supersedes e5d664a Change: PR #26 — Turnstile challenge renders on every arrival, not only full page loads Status: Drafted — not yet posted

We fixed a defect in how the Cloudflare Turnstile challenge rendered on the account pages.

What was wrong. The challenge widget appeared only on pages loaded directly. If you arrived at Create account or Sign in by clicking a link inside the app, the challenge area stayed empty and the submit button stayed disabled — with no error message, because nothing had failed. Refreshing the page cleared it.

Why. The Turnstile script is fetched once per full page load, and the widget was being created only in response to that script's load event. A page reached by in-app navigation mounts after that event has already fired, so nothing ever asked the widget to render. The framework's script cache correctly declined to fetch the source a second time; the component was simply waiting for a signal that was never coming again.

Scope. The one-click judge path was not affected. Open the demo sits on the landing page, so its challenge renders on the initial page load and always worked. The defect only affected visitors who navigated to the account pages from elsewhere in the app.

What changed. The challenge is now a single shared component that renders on every way it can be reached — a direct page load, an in-app navigation after the script has loaded, and an in-app navigation while the script is still in flight. It also releases its widget when the page is left, so returning to a form always presents a fresh challenge.

No change to the security model. Verification is still delegated to Supabase Auth, and Turnstile remains the only abuse control on anonymous sign-in. No server-side verification code was touched, and only the public site key is ever sent to the browser.

Verification. Lint, type checks, and the 187-test unit suite pass. A regression test covering in-app navigation was added, since the previous suite reached these pages only by direct load — which is why the defect went unnoticed. The fix was confirmed in a browser across every arrival path and is live in production.

Log in or sign up for Devpost to join the conversation.

Submission history