Skip to content
swayam
~ swayam.tape
morning_brief30 7 * * *+2h 14mok|linkedin_engage0 9 * * *+4h 02mok|freelance_trawl0 */4 * * *+39mok|briefings_roll0 22 * * *+10h 18mok|inbox_sweep*/30 * * * *+12mok|
swayam // self-driven

> crontab.schedule(
claude_code)

Swayam is the cron half of a Claude Code session. A row in scheduled_routines wakes a dedicated CC instance on a pg_cron tick, streams the run back into swayam_messages, and the dashboard paints it live. No Vercel cron. No webhooks. No cold starts.

pg_cron
inside postgres
<200ms
wake → first token
REPLICA IDENT FULL
partial-row stream
swayam-channel
attention-isolated
routine.tail · live

[07:30:00] morning_brief wake // swayam-channel

[07:30:00] swayam_chat_respond_begin message_id=8a4f...

[07:30:01] swayam_chat_respond_chunk +178ms · "Good morning. Inbox …"

[07:30:02] swayam_chat_respond_chunk +412ms · "3 calendar holds, 2 …"

[07:30:04] swayam_chat_respond_finish ok

>

Capabilities

What Swayam does, and what it doesn't.

Honest scope. Recruiters skip past tools that overclaim; this one tells you the seam where the magic stops.

It can

  • 01

    Schedule any prompt as a cron-fired routine

    Pick a cron expression and a timezone, paste a prompt template, and Swayam wakes a dedicated Claude Code instance on the dot. Jitter is configurable so a Monday 9am batch doesn't stampede.

  • 02

    Stream the run live, no polling

    swayam_messages has REPLICA IDENTITY FULL, so every UPDATE event carries the full partial-content blob. The dashboard paints the assistant tokens as they arrive over Supabase Realtime — sub-second from cron tick to first painted character.

  • 03

    Isolate routines from your chat session

    Routines fire on a dedicated swayam-channel, not on Setu's setu-channel. Different CC instance, different cwd, different MCP server. A scheduled code-refactor never steals attention from your live chat thread.

  • 04

    Auto-comment, auto-draft, auto-anything

    LinkedIn Engage, Morning Brief, freelance trawl — every routine is just a prompt + a schedule. Each writes back to its own row, surfaces "Needs code fix" escalations, and queues drafts for human approval where it matters.

It can't (yet)

  • 01

    Run on the Vercel cron tier

    Swayam is NOT a Vercel-cron product. The schedule lives inside Postgres (pg_cron) so a serverless cold-start can never miss a tick. If you need Vercel-cron-style integrations, Swayam is the wrong tool.

  • 02

    Talk to an LLM that isn't Claude Code

    The dispatcher hands routine prompts to a Claude Code CLI instance attached to the swayam-channel MCP. Generic OpenAI / Anthropic API calls aren't the shape. Multi-vendor support is doable, but it's not what Swayam ships today.

  • 03

    Operate without a desktop relay

    Swayam needs Sutra (the Electron daemon) running somewhere reachable — usually the operator's Mac. The cloud knows when to fire and what to send; Sutra is the one that actually attaches Claude Code to the channel.

[ wake protocol ]

> cron · dispatch · answer · paint

pg_cron fires inside supabase, the desktop relay turns the row into a wake frame, a dedicated claude code instance answers, the dashboard repaints over realtime — all on one channel, one schema, one operator identity.

protocol.livetx · armed
  1. 01

    pg_cron · supabase

    cron.job row · fires on schedule

    each scheduled_routines row registers a cron.job at insert. zero cold-start tax.

  2. 02

    sutra dispatcher

    electron main · sse bridge

    branches on metadata.channel — swayam routines write swayam_messages, broadcast swayam_chat_message.

  3. 03

    swayam claude code

    sutra/brains/swayam

    dedicated CC instance, own cwd, own MCP server. attention-isolated from operator chat.

  4. 04

    local mcp

    127.0.0.1:9000/swayam/api/mcp

    swayam_chat_respond_begin/chunk/finish — streaming UPDATEs land in swayam_messages.

  5. 05

    browser dashboard

    realtime postgres_changes

    replica identity full means UPDATE events carry the partial-token blob. live paint, <60ms.

p50 wake

180ms

p99 paint

760ms

cold starts

0

auth

shared supa jwt

[ DECISION 01 ]

channel carve-out from setu

routines used to wake the same CC session that handles operator chat. mixing code edits with timer-triggered work tanked per-task attention. swayam now runs on a dedicated swayam-channel with its own CC instance.

[ DECISION 02 ]

dual-table / dual-broadcast bifurcation

the dispatcher branches on routine.metadata.channel. swayam routines write to swayam_messages and broadcast swayam_chat_message; legacy setu routines stay on chat_messages + chat_message. one field, total isolation.

[ DECISION 03 ]

pg_cron over vercel cron

schedules live as cron jobs inside supabase. no cold starts, no 10-second function timeout, no json deploy step. the cron row IS the source of truth — change the cron expr, the next fire moves.

[ DECISION 04 ]

shared mcp fabric

swayam CC can reach any sibling product's MCP — publish a sandesh post, file a sankalp application, ping the setu chat — because they all share the same supabase project + jwt. one operator identity, five surfaces.

How it's built

Four choices that survived contact with prod.

Each is in production traffic. Each has a debug session behind it — the kind of detail recruiters skim past.

Case 01

pg_cron, not Vercel cron

Vercel's cron tier is bursty and has a cold-start tax. For a routine that wakes Claude Code every 30 minutes, that tax compounded into missed ticks. Swayam needed a schedule that lives where the data lives.

0cold-start misses
  1. Each routine row in scheduled_routines registers a pg_cron job at insert. The job is a single SQL call that bumps next_fire_at and writes a wake row to the dispatcher table.

  2. sutra's dispatcher subscribes to that table over Realtime. When a wake row appears, it broadcasts on swayam-channel — the CC instance reacts in under 200ms.

  3. Jitter is honored cooperatively: the job adds a per-routine random offset before the broadcast so a Monday 9am batch doesn't stampede the same CC session.

excerpt · 01.code
-- migration: register pg_cron from a routine row
insert into cron.job (schedule, command, jobname)
select metadata->>'cron',
       format(
         'select swayam_enqueue_wake(%L)',
         id::text
       ),
       'routine_' || id::text
from scheduled_routines where id = new.id;

Case 02

Channel isolation — routines vs chat

When routines and live chat shared the setu-channel CC instance, every cron fire would pre-empt whatever the operator was typing. Same attention head, two tasks, lousy outcome.

2 channelsone operator
  1. Bifurcated 2026-05-13. swayam-channel runs its own CC instance with its own cwd and its own brain at sutra/brains/swayam/.

  2. metadata.channel on every scheduled_routine row dictates which dispatcher path the wake takes. Default is 'swayam' for new routines; legacy 'setu' rows still appear in this grid but route to setu-channel.

  3. SSE bridge sutra/src/main/mcp/swayam-sse-stream.ts filters swayam_chat_message broadcasts only. The setu-SSE bridge no longer receives routine wakes — only direct operator chats.

excerpt · 02.code
// sutra/src/main/routines/dispatcher.ts
const channel = routine.metadata?.channel ?? 'swayam'
const targetTable = channel === 'setu'
  ? 'chat_messages'
  : 'swayam_messages'
const broadcastEvent = channel === 'setu'
  ? 'chat_message'
  : 'swayam_chat_message'

Case 03

Streaming UPDATEs, not polling INSERTs

A long routine (the LinkedIn Engage trawl) takes 30 seconds to fully respond. If the dashboard waited for the final INSERT, the operator would stare at a blank panel for half a minute.

<200msfirst painted token
  1. REPLICA IDENTITY FULL on swayam_messages — every UPDATE event carries the full row including the partial-content blob.

  2. The dashboard subscribes via supabase-js Realtime to postgres_changes on swayam_messages. New UPDATE events repaint the streaming assistant row in place — no second SELECT, no polling loop.

  3. A subscribe-before-load handshake plus a lastSeenCreatedAt resync closes the race where a wake landed during the initial page render.

excerpt · 03.code
alter table swayam_messages
  replica identity full;

-- now UPDATE events carry the full row.
-- the dashboard repaints partial assistant
-- content as it streams in, no second SELECT.

Case 04

Auth without a login form

Swayam shouldn't be a second auth surface. The operator already signs in at setu.devfrend.com. Swayam needs to read the same JWT and bind it to the same auth.uid().

1 JWTthree apps · same auth
  1. No /api/* routes proxy data. The browser reads swayam_messages and scheduled_routines directly via @supabase/supabase-js — RLS authors the rows by auth.uid().

  2. Unauthenticated visit → SignedOutView with a link to ${NEXT_PUBLIC_SETU_ORIGIN}/login. Setu sets the supabase cookie; Swayam reads it on the next visit.

  3. Sharing the Supabase project (avcnoywxnkajfuobftmr) with Setu + Sandesh means a Setu JWT validates identically against Swayam's policies. No proxy hop, no token exchange.

excerpt · 04.code
// lib/supabase/server.ts
export async function createClient() {
  const cookieStore = await cookies()
  return createServerClient(URL, ANON_KEY, {
    cookies: {
      getAll: () => cookieStore.getAll(),
      setAll: () => {}, // read-only on swayam
    },
  })
}

Open to work

I scheduled it, streamed it, and bound it to RLS — I can do the same for you.

Swayam is one of seven dogfooded apps I ship in parallel. Same Supabase project, same MCP fabric, same Claude Code stack. Happy to walk a hiring panel through any layer live — pg_cron, dispatcher, Realtime, the streaming UI, or the brain itself.

Senior / staff full-stackAI engineer · MCP & agent infraNext.js · TypeScript freelancepg_cron · Supabase Realtime · automation