Where Does Temporal Even Run When You’re All-In on Vercel?

Part 2 of 8: Durable AI for InterlinedList


In Part 1 I talked myself into Temporal as the orchestration layer for the AI features I want to build. Durable memoization, a model cascade, a hard budget gate: all the mechanics that push the LLM bill down. Good story. Then I hit the question that stops most “let’s just add Temporal” plans cold, and it stopped me too.

Where does it actually run?

InterlinedList is 100% serverless on Vercel. Every route is a function that spins up, does its thing, and gets torn down. That model is wonderful right up until you need something that never stops running. And Temporal needs exactly that.

The Two Parts That Don’t Fit

Temporal has two moving pieces, and neither of them is a serverless function.

The first is the Temporal Service, the durable-state backend. It’s the thing that remembers where every workflow is, what’s completed, what’s pending, and what to retry. It’s stateful infrastructure. You don’t invoke it and let it die.

The second is the Worker. A worker is a long-lived process that polls a task queue, picks up work, and runs your activities. Polls it continuously. As in, forever. A Vercel function that shuts down after a request cannot be a Temporal worker, because the polling loop dies the instant the function returns. You can’t flag your way out of this in a config file. It’s a straight-up mismatch between “poll a queue for hours” and “respond in seconds then vanish.”

So for a while this felt like a dead end. Adding Temporal seemed to mean un-serverless-ing the whole app, and I did not build InterlinedList on Vercel just to go stand up a fleet of always-on boxes.

One New Always-On Thing. That’s It.

The resolution I kept circling back to is smaller than the fear suggested. I introduce exactly one new always-on component: a small @temporalio/worker process running somewhere cheap and boring, like Fly.io, Railway, or a small VM. Something in the five-to-twenty-dollars-a-month range. Alongside it, either self-hosted Temporal OSS or a low-tier Temporal Cloud namespace for the service.

That’s the entire footprint. One worker, one service. Vercel doesn’t change shape at all.

What does change is what the Vercel routes do. They stop doing work and become thin clients. Authenticate with the getCurrentUser / getCurrentUserOrSyncToken helpers I already have, gate with isSubscriber, start a workflow, then hand the UI a workflow id to poll. The route never does the long-running work itself. It just points at it.

The route sketch is almost embarrassingly short:

export async function POST(request: NextRequest) {
  const user = await getCurrentUserOrSyncToken(request);
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  if (!isSubscriber(user.customerStatus)) return NextResponse.json({ error: "Forbidden" }, { status: 403 });

  const handle = await client.workflow.start(generateArtifactWorkflow, {
    taskQueue: "ai",
    workflowId: `gen-${user.id}-${crypto.randomUUID()}`,
    args: [{ userId: user.id, prompt }],
  });
  return NextResponse.json({ workflowId: handle.workflowId });
}

Auth, gate, start the workflow, return the id. That 16k-token document generation that would blow right past the function’s duration budget? It never runs in the function. The function kicks it off and gets out of the way.

The Reuse Win: No Second Codebase

This is the part that made me stop worrying. The worker is not a new codebase. It imports the same lib/ code the app already runs.

The activities (the actual units of work Temporal executes) call into modules that already exist or are already planned. safeFetch from lib/security/ssrf.ts for any link crawling, so the SSRF guards apply exactly as they do today. detectLinks from lib/messages/link-detector.ts. The post-status.ts modules under lib/bluesky/ and lib/mastodon/ for cross-post fan-out. The DSL parser and validator in lib/lists/dsl-*.ts for anything list-shaped. And the AI writer library from the companion genai plan, lib/ai/, which doesn’t exist yet but will be wrapped as activities rather than rewritten.

Zero business-logic duplication. That matters more than it sounds. It means the IDOR checks and subscription gating I already trust stay exactly where they live. A worker activity that writes a list row runs through the same where: { id, userId } ownership filter as the web route. I’m not maintaining two versions of “can this user do this” and praying they stay in sync. There’s one version, and the worker just calls it.

What the Foundation Actually Contains

Before any feature ships, there’s a foundation phase, the groundwork everything else in the plan stands on. It’s the worker, the service, the client wiring, and then a small set of cost-control primitives that every later proposal reuses:

  • An llmCall activity with a model-cascade policy (cheap model first, escalate only when the work demands it) and per-target max_tokens ceilings: roughly 1k for a message, 4k for a list, 16k for a document.
  • An assertBudget activity that reads spend before it lets a call through, so a runaway job throws instead of burning credits.
  • response cache keyed by hash(system + prompt + model), so identical requests never re-bill.
  • Task queues with rate limits tuned to whatever the provider or the user’s BYO key allows.

Net-new dependencies are modest: @temporalio/client on the Vercel side, the @temporalio/worker / workflow / activity trio on the worker host, and one LLM SDK. That’s the whole shopping list. (AI assistance in the product is still Coming Soon. This is me brainstorming the machine that would power it, not shipping it.)

Scaffold the Unfamiliar, Own the Verification

I’ve been doing a lot of this alongside Claude lately, and one lesson keeps repeating.

A durable-execution worker is new territory for me. When something’s unfamiliar the instinct is either to avoid it or to throw the whole thing at the model and hope. Both are wrong. What works is to spec the seams and delegate the boilerplate.

I give Claude the hard constraints, not vibes. “The app is serverless on Vercel and stays that way. Reuse this lib/ code, here are the exact modules. Here are the auth and gating seams. The worker imports, it does not reimplement.” With those rails, Claude is great at generating the worker scaffold and the client wiring, the boilerplate I’d get subtly wrong on the first pass because the framework is new to me.

But I do not let it hand me generated infrastructure and call it done. I own correctness, and for a durable workflow, correctness has a specific proof: a Temporal replay test using TestWorkflowEnvironment that simulates a worker crash mid-workflow and asserts the workflow resumes deterministically, picking up from the last completed activity instead of re-running (and re-billing) the ones already done.

That test does the real work. It’s how “a retry doesn’t re-pay for the LLM call” stops being a claim in a blog post and turns into something I’ve watched go green in CI. The model writes the code I don’t know how to write yet, and I write the test that proves it’s right.

With the foundation standing and provably resumable, Part 3 turns it into the first real feature: a Generate button that survives a timeout and asks before it writes a single thing.

Adron brainstorming and working on InterlinedList.

I’ve Been Brainstorming Durable AI for InterlinedList, and It Kept Pointing at Temporal

Part 1 of 8: Durable AI for InterlinedList


I’ve been chewing on a question for weeks. If InterlinedList is going to have real AI features (the “Coming Soon” ones on the pricing page, the ones I actually want to use myself) what does the plumbing behind them look like? Not the prompts. The plumbing. And every time I sketched it out, I hit the same wall in the same four places.

So I did the thing I’d tell anyone else to do. I stopped sketching in a vacuum and pointed Claude at the actual repository.

The Wall Is Serverless, and I Put It There

InterlinedList is 100% serverless on Vercel. That was a deliberate choice and mostly a great one. It’s also the thing shaping every AI idea I have, whether I like it or not.

A serverless function has a duration ceiling. It has no durable memory between requests, it can’t pause and wait for you to click “yes,” and when it fails partway through expensive work, it has no idea what it already paid for. For a request/response app that serves pages, none of that matters. For anything that calls a language model, all of it matters. A lot.

Here are the four places I kept getting stopped.

Scheduling is already fragile, and this one isn’t hypothetical. InterlinedList has scheduled publishing today (a Subscriber feature). The cron at app/api/cron/publish-scheduled-messages/route.ts runs every minute. I checked vercel.json, it’s a literal * * * * *. It fans a due post out to Bluesky, Mastodon, LinkedIn, and X, then flips the message out of “scheduled” state. There’s no lock and no idempotency key. So if the cross-posts succeed but that final UPDATE fails (a reaped Neon connection, a cold-start hiccup) the message stays “due” and the next tick republishes it. To every platform. Again. That’s a slow-moving double-post waiting for a bad minute, a bug I already own, sitting in production, with zero AI anywhere near it.

Long generation doesn’t fit. A 16k-token document, streamed out of a model, will blow past a serverless function’s budget before it finishes. There’s no clean way to say “keep going, I’ll be back.”

Batch and agentic work has nowhere to live. “Tag every untagged message.” “Add twenty rows to this list matching these criteria.” “Research my saved links and draft a doc.” Those are multi-minute, multi-step, retry-heavy jobs. A function that dies at ninety seconds is the wrong shape for all of them.

And there’s no cost governor. This is the one that kept me up. Every retry re-bills the model. There’s no budget ceiling, no cheap-model-first policy, no response cache, no dedup. One runaway loop and you’ve torched real money.

The Expensive Line Item Is Never the Infrastructure

Once I reframed the money question, the design mostly wrote itself. This reframe runs under the whole series, so it’s worth stating plainly.

The expensive thing about AI features is tokens, not servers. Basically never servers.

A durable-workflow engine (Temporal, in this case) costs on the order of tens of dollars a month, flat. One small always-on worker, maybe a managed namespace. That number does not move when your users get busy. The model bill does, and it moves a lot.

So the question isn’t “can I afford Temporal.” It’s “does Temporal make the model bill smaller.” And the reason it kept showing up in my brainstorm is that its mechanics do that, structurally:

  • Durable memoization. A completed step is persisted. When a six-step pipeline fails at step five, the retry re-runs step five, not the four already-billed model calls before it. A retry never re-invokes an LLM call you already paid for. This is the single biggest saver in the whole list.
  • Model cascade. Cheap model first (Haiku, Flash) and escalate to an expensive tier only when the work demands it, or the user explicitly asks for long-form.
  • Batching. Tag fifty messages in one call instead of fifty calls. The per-request overhead and the duplicated context collapse.
  • Rate-limit obedience. Cap concurrency to stay under provider limits, so you don’t trip a 429 storm whose retries waste both wall-clock and, on some providers, tokens.
  • Response and URL dedup. Identical prompts get served from cache. A link that’s already been fetched isn’t fetched again. An embedding is computed once, ever.
  • A hard token-budget gate. Check spend before each call and refuse to exceed a per-user ceiling. A leaked key or a runaway agent can’t burn unbounded credits.
  • Confirm-before-spend. Pause and wait for the user to approve the plan before generating the expensive part. You don’t pay to build fifty list rows against a schema the model guessed wrong.

Every one of those pushes the bill down. The infrastructure that enables them is a rounding error against what it saves. And I want to be clear that “cheap as possible, relative to running raw Claude or ChatGPT calls” is why the architecture looks the way it does in the first place. I didn’t bolt cost control on at the end. I designed around it, and everything else grew out of that constraint.

Brainstorm Against Your Real Code, Not a Whiteboard

Now the part about working with an LLM to build this, because that’s what this series is modeling.

I didn’t design this on a whiteboard. I opened Claude, gave it the real repo, and asked it to map the idea onto the code that already exists. It found the every-minute cron and traced the exact failure path where the double-post lives. It pointed at lib/security/ssrf.ts and the safeFetch guard that any link-crawling feature has to route through, and at the link detector and metadata fetcher already sitting in lib/messages/. It also noticed there’s no lib/ai/ directory yet. Nothing generative is built, which lines up with AI being “Coming Soon” rather than shipped.

The output of that session was a grounded proposal (temporal-solutions.md) that complements an earlier BYO-key and MCP plan I’d written. That earlier plan covered what the AI produces and who pays for the tokens. It said nothing about orchestration, durability, or cost. This new one fills that gap, and it fills it against real file paths, not imagined ones.

Steal this if it’s useful. Don’t design architecture in a vacuum and then go hunting for where it fits. Let the agent read the actual repository first. Have it map your idea onto the code that exists, name the real hazards, and pressure-test it into something concrete before a single line of implementation. Explore first. The proposal you get back is grounded because the agent actually went and looked.

There’s a catch I want to name up front, because I try not to write brochure copy. Temporal has parts that fundamentally cannot run on Vercel serverless: a durable-state backend and long-lived worker processes that poll continuously. That’s a real new piece of always-on infrastructure in an app I deliberately built to have none. It’s the scariest unknown in the whole plan, and I’m not going to pretend it away.

Next post, I go straight at it: where Temporal actually runs when you’re all-in on serverless, and how the Vercel side stays a three-line client that just kicks off a workflow.

Adron brainstorming and working on InterlinedList.

InterlinedList Is Live: Lists, Posts, and Markdown Finally in One Place

It’s the 73rd Day of 2026! Per my previous post, I promised updates and thus, updates delivered!

There’s a problem I’ve run into repeatedly over the years. Actually, it’s more like a pattern of problems.

I’ve got:

  • notes scattered across markdown files
  • lists living in some task app
  • social media posts written in drafts somewhere else
  • and half-finished ideas bouncing between GitHub issues, notebooks, and random documents.

Individually, each of these tools is “fine” yet fragmented and leaves ideas, messaging, and lists leaking and losing ideas to the nebulous.

That’s exactly the mess that led me to build InterlinedList.

And now it’s live: 👉 https://interlinedlist.com

What InterlinedList Actually Is

At its core, InterlinedList is a platform that ties together three things that are usually awkwardly separated:

  1. Lists
  2. Social media posting (to your other accounts too, not just on IntelinedList)
  3. Markdown documents

Each of these solves a different part of the “organize your thinking and output” problem. But the real value shows up when they’re connected.

InterlinedList brings them together into a single system.

Not another note app.
Not another scheduling tool.
Not another task manager.

Instead, it’s a workflow** platform for ideas that turn into posts, lists, and documents.

Lists That Connect to What You Do

Everyone has lists. They might be all over the place. With InterlinedList you can create your own lists, with whatever schema of columns you want.

Ideas lists.
Research lists.
Feature lists.
Writing queues.
Project breakdowns.

The problem is most list tools treat lists like dead data. You write them down, check things off, and that’s about it. InterlinedList treats lists more like launch points. A list item can become:

  • a social media post
  • a markdown document
  • a reference entry
  • a trackable idea

Instead of bouncing between five tools, the list becomes the center of gravity. Which is how most people actually work. Over time, my intent is to bring these features to be even more seamlessly connected. Eventually, there will even be options to bring together your LLMs you prefer to extend the capabilities of each of these things in your workflow.

Social Media Posting Without the Chaos

Posting to social platforms today usually looks like this:

  • Write something somewhere
  • Copy it into another platform
  • Schedule it somewhere else
  • Lose track of what you’ve already posted

InterlinedList brings posting directly into the workflow.

You can:

  • draft posts
  • schedule posts
  • organize posts into lists
  • connect posts to notes or markdown docs
  • refer to your cross-posted posts from InterlinedList (for example, see image!)
Screenshot of a social media post by Adron Hall discussing Ba Bar in University Village, featuring images of the restaurant and links to Mastodon and Blue Sky.

The goal is simple: make posting part of your idea workflow instead of a disconnected chore.

The first integrations include platforms like:

  • Mastodon
  • Bluesky

And the idea is to keep expanding that ecosystem. More to come and also open to ideas!

Markdown Documents That Fit the Workflow

If you’re like me, markdown is where the real thinking happens.

Articles. Notes. Research. Drafts. Documentation.

But markdown tools often exist in their own isolated worlds.

InterlinedList allows you to maintain markdown documents directly alongside your lists and posts, making it possible to move naturally between: writing, organizing, and publishing.

Why These Three Things Belong Together

This was the key realization. Lists, posts, and markdown aren’t separate activities. They’re three phases of the same process:

  1. Capture the idea → lists
  2. Develop the idea → markdown
  3. Share the idea → social posts

Most tools treat these as unrelated workflows. InterlinedList treats them as one continuous pipeline. Which means less context switching, less tool juggling, and far fewer lost ideas.

Early Access Offer

To kick things off an early access offer, I’m doing something simple. If you’re interested in organizing ideas, posts, and documents in one place, now’s a great time to jump in.

The first 10 users who sign up will receive a full-featured subscription account for free.

No trial. No feature restrictions. Just the full platform.

Built Because I Wanted It

Like a lot of the things I’ve built, InterlinedList started as something I wanted for myself.

I needed a place where:

  • research lists
  • post drafts
  • markdown articles
  • and publishing

could actually live in the same ecosystem.

After building it and using it, the obvious next step was to open it up so others could use it too. Let me know what you think!

With that, stay tuned, the team has a lot more coming!

** I’d add that, this is absolutely a work in progress and the team will be working to bring together more of the workflow concept and features to bridge this set of tooling together to be even more seamless.