Back to Blog
|6 min read

Skills Were Dead Prompts. Routines Made Them Alive.

#ai#automation#saas#devtools
Skills Were Dead Prompts. Routines Made Them Alive.

A few weeks ago I shipped Skills — a way to save AI prompts as reusable templates inside ContextForge, so my best prompts wouldn't keep dying in a Notion page nobody opens.

It worked. I stopped losing them.

Then I noticed something worse.

I had a Skill that drafts marketing copy every time we ship a feature. Another that summarizes my weekly progress. Another that generates LinkedIn posts. They were all sitting there, clean and named, in a tab I'd open every Monday morning and slowly click "Run" on like a factory line.

That's when it hit me: I built a library, not an assistant. Skills were just dead prompts with a database row.

So I built Routines.

What a Routine actually is

A Routine is a Skill on a cron expression. That's it.

You pick a Skill (the prompt template). You pick a schedule — daily, weekly, monthly, or any 5-field cron you write yourself. You pass the input variables. You hit Create. From then on, ContextForge fires the Skill on schedule, forever, until you pause or delete the Routine.

Skill:        "Write a LinkedIn post about {{topic}} in my voice"
Routine:      Daily at 9am Chicago, topic = "shipping in public"
Result:       A fresh draft in my execution history every morning,
              ready to copy-paste and post.

It sounds trivial because it is trivial as a user experience. The complexity is hidden, which is the point.

The first design was wrong

My first instinct was Postgres-native: use pg_cron (Supabase has it built-in) plus pg_net to make outbound HTTP calls every minute. Schedule lives in the database, no extra infra, beautiful.

I started writing it. Then I hit a wall I'd already climbed once.

ContextForge has two auth models. Dashboard users authenticate with a Supabase session JWT. MCP clients (Claude, in particular) authenticate with API keys we verify ourselves inside edge functions. Both eventually call the same skill-execute function via a "service-role bridge" — the function probes the database with the service role key, then trusts an x-cf-user-id header for the actual user identity.

This bridge took me two weeks to get right when I shipped Skills. I documented it in CLAUDE.md. I have memories about it. It's load-bearing.

pg_cron would have meant recreating it inside Postgres functions. PL/pgSQL talking HTTP to my own edge functions, replaying the auth dance, debugging it without proper logs. I'd be writing the same nasty integration twice.

So I pivoted. Vercel Cron pings a Next.js API route every minute. The route queries for due Routines, then fires each one against the same skill-execute edge function the dashboard already calls. Same path, same headers, same auth bridge. One source of truth for "running a Skill" — manual, MCP, scheduled, all go through the same door.

Sometimes the right architecture is the one that doesn't make you rewrite a thing you already trust.

The unglamorous parts

Three things bit me harder than the design:

1. Hobby plans don't allow per-minute cron.

I shipped to Vercel. The deploy failed instantly:

Hobby accounts are limited to daily cron jobs. This cron expression (* * * * *) would run more than once per day. Upgrade to Pro.

Pro is $20/month. I had to make a decision: pay it, downgrade the feature to daily-only, or use an external cron service (cron-job.org, free, hits my endpoint with a shared secret). I picked Pro. The math is simple — 5 Premium users at $15/month covers Pro + Supabase Pro, and the feature is worth nothing if I cripple it to daily-only just to dodge $20.

2. CRON_SECRET hates whitespace.

The first deploy after Pro upgrade failed with:

The CRON_SECRET environment variable contains leading or trailing whitespace, which is not allowed in HTTP header values.

I had copy-pasted the secret from a terminal. It had a trailing newline. Five minutes of "why" before I realized.

3. The Premium gate I wired in stripe-webhook was missing the Routines limit.

I added a max_routines column on organizations. Updated the dashboard to gate behind it. Tested local with a flipped plan column — perfect. Deployed.

Then a paying Premium user opened Routines and saw the upgrade banner. Their plan column said premium, but max_routines was still 0.

Reason: the Stripe webhook handler that bumps plans had a PLAN_LIMITS constants object. It set max_projects, max_spaces, max_collaborators, etc., — but I'd added max_routines to the database without adding it to that constants file. Subscribers got upgraded everywhere except the column my new gate checked.

One-line fix in _shared/plan-limits.ts plus a backfill migration:

UPDATE organizations SET max_routines = 999 WHERE plan = 'premium';
UPDATE organizations SET max_routines = 0   WHERE plan IN ('free', 'pro');

Embarrassing. Also the kind of bug that only ships if you have actual paying users hitting it, which is its own kind of milestone.

What it looks like in practice

The dashboard side is dead simple. Open any Skill-enabled project, click Routines, click + New Routine. The form is one screen:

  • Pick the Skill

  • Name the Routine

  • Pick a preset (daily, weekly, etc.) — the cron expression auto-fills, but you can override

  • Pick a timezone (defaults to your browser's)

  • Paste your input params JSON

Hit create. You land on the Routine detail page with a ▶ Run now button (fires immediately, ahead of schedule), a Pause/Enable toggle, execution history, and a Danger Zone with delete.

Every fire writes a row to the audit log: status, output, tokens used, cost. Click any row to expand and see the rendered prompt + final output. Same component the manual Skill runs use — Routines didn't get their own special view, they get a trigger_type=scheduled tag and they're one query away from filtering.

Schedule it from Claude

This is the part I'm most proud of.

The MCP exposes seven new tools — routines_list, routines_get, routines_create, routines_update, routines_toggle, routines_run_now, routines_delete. From inside any Claude session connected to ContextForge, you can say:

"Schedule the LinkedIn Post skill to run every weekday at 9am Madrid time."

Claude picks the right skill, infers schedule_preset = "daily", fills the cron expression, sets timezone = "Europe/Madrid", and creates the Routine. You never touch the dashboard.

This matters more than it sounds. The pitch for ContextForge has always been "let agents act on your memory, not just read from it." Routines is the first feature where Claude can schedule its own future work — close a loop the agent owns, end-to-end.

What it costs

Honesty section:

  • Vercel Pro: $20/month. Required for per-minute cron.

  • Supabase Pro: $25/month. Required for production edge functions, which you needed anyway if you have any users.

  • LLM tokens: $0 from me. Every Skill (and therefore every Routine fire) charges your own Anthropic or OpenAI key. ContextForge does not bill per execution.

  • Routines = Premium feature: $15/month per user. Two Premium users break even on infrastructure.

If you're already paying for Vercel Pro + Supabase Pro for other reasons (any SaaS does), Routines is pure margin from the first paying user.

What's next

Skills + Routines is the start of what I'm calling the agentic OS for context. A library of named prompts (Skills), the ability to run them automatically (Routines), and an agent (Claude via MCP) that can create both for you on demand. The next layer is chains — Routines that fire other Routines, conditional triggers, "if this Skill output contains X, fire that Skill with Y."

But that's the next article.

For now: I have 26 users on the free plan. None of them have Routines yet. But the day one of them upgrades to Premium and starts seeing AI-generated marketing copy show up in their notifications at 9am every Monday, this whole thing pays for itself.

If that sounds useful — give ContextForge Memory a try. The Skills tier is free. Routines is the upgrade.


ContextForge Memory is a persistent memory layer for AI coding agents. Built BYOK for all LLM operations. Docs

Share this article

Related Articles