Back to blog

7 min read · 2026-05-08

How Vex's autonomous loops work

Vex isn't a chat box you have to prompt. It runs scheduled goals while you sleep — fetches your daily brief, triages email, summarises overnight news. Here's how the autonomous loop is actually wired.

The vex-scheduler

The Vex daemon runs a scheduler that polls a SQLite table called nebula_goals every 30 seconds. Each row is a goal: a user, a prompt, a schedule (one-shot, every-N-seconds, or cron), a next_run_at timestamp, and the last outcome.

-- Schema (simplified)
CREATE TABLE nebula_goals (
  id              TEXT PRIMARY KEY,
  user_id         TEXT NOT NULL,
  description     TEXT NOT NULL,
  status          TEXT NOT NULL,  -- active | paused | completed | failed
  prompt          TEXT NOT NULL,
  schedule_kind   TEXT NOT NULL,  -- one_shot | every_n_seconds | cron
  schedule_value  TEXT,
  next_run_at     TEXT,
  last_outcome    TEXT
);

When next_run_at is in the past and status is active, the scheduler fires runVexAgent with the prompt and the user's id. Whatever Vex outputs gets saved as last_reply; next_run_at ticks forward by schedule_valueseconds.

The daily-brief example

Mine fires at 07:00 UTC every morning:

{
  description: "Make today's daily_brief PDF and chat-message me",
  prompt: "Make today's daily_brief PDF, then send a chat message
           saying 'Good morning ☀️' with the chat_link.",
  schedule_kind: "every_n_seconds",
  schedule_value: "86400",
  next_run_at: "2026-05-08T07:00:00+00:00",
  status: "active"
}

When 07:00 UTC hits, Vex orchestrates four things:

  1. daily_brief tool combines RSS headlines + weather into a markdown brief.
  2. make_pdf renders it via pandoc + weasyprint.
  3. The PDF is uploaded to my Supabase Storage bucket and registered in files.
  4. pulse_send_chat_message drops a message in my "Vex autonomous" chat thread with a clickable link.

When I open Pulse, there's a fresh thread waiting with the brief attached. This is the difference between an AI assistant and an AI agent. The assistant waits for me. The agent does work.

UUID hygiene

The scheduler has a daemon-internal user ID convention ("raven") that isn't a real Supabase UUID. When autonomous tools need to talk to the database (file register, chat insert), they FK-fail on the string ID. The fix is a small resolveRealUserId() helper that maps "raven" to the actual auth.users UUID via the email lookup. Tools that need DB writes call it; tools that only do filesystem ops don't bother.

What can go wrong

  • Duplicate goals. If you accidentally insert the same goal twice (we did), both fire and the user gets two daily briefs. We caught and paused one. Add a UNIQUE constraint on (user_id, prompt, schedule_kind, schedule_value) to make this impossible.
  • Stuck goals. If next_run_at is set but the daemon crashed mid-fire, the goal can become a tight loop on restart. Cap last_run_at - next_run_at drift to one interval; skip stale fires.
  • Quiet failures. If the LLM errors mid-goal, you lose the artifact. The graceful-recovery layer in the agent loop surfaces partial work (PDF written but chat message failed → still save the PDF and notify next time).

Adding a new autonomous goal

You don't write code — you POST a row. Through the API, the user can schedule "summarise my unread emails every hour" or "post Norwegian market open headlines at 09:00 weekdays". The agent picks the right tools (mail-fetch, summarize, send-chat) automatically because Vex already has them.

Why this is rare

ChatGPT can't do this — it has no scheduler, no database, no persistent file system. Claude can't do this without a separate agent harness. Most "AI agents" are vendor-locked stacks (ZAPier Central, etc) where the LLM and the scheduler are different products.

Pulse ships them in one box, on your hardware. Your data never leaves your network, and your agent runs whether or not you're at the keyboard.

Try it

Self-host Pulse. Set a daily brief at the time you wake up. Add an email digest at the time you sit down. Wake up to a workspace that's been working.

Create your workspace