Melian

The Loom

The loom runs day and night, threads of scheduled work weaving themselves into the fabric of each day.

Overview

Melian's job system handles scheduled and recurring tasks. From morning briefings to nightly integration tests, jobs run autonomously on cron schedules or human-readable time expressions. Each run is recorded, output is routed to the channel you configure, and the self-healing pipeline keeps everything working.

Job Interface

export interface Job {
  id: string;
  name: string;
  prompt: string;
  schedule: string;
  enabled: boolean;
  created_at: number;
  created_by: string;
  last_run_at: number | null;
  last_run_status: string | null;
  model: string | null;
  tools: string | null;
  output_routing: string;      // "log" | "imessage" | "push" | "imessage+push"
  max_concurrent: number;
  category: string;
}

export interface JobRun {
  id: string;
  job_id: string;
  started_at: number;
  completed_at: number | null;
  status: string;              // "success" | "error" | "running"
  result: string | null;
  error: string | null;
  conversation_id: string;
}

Categories

Categories are seeded at startup with a fixed display order:

Category Purpose
core System maintenance: integration tests, self-healing, learning review
home Smart home automation: night-time lights, sensor checks
tasks Task management: Remarkable sync, follow-ups
reminders User-created reminders and notifications
uncategorized Default for jobs created without a category

Seeded Jobs

These jobs are created on first boot if they don't already exist. Some are always seeded; others depend on their integration being configured.

Always seeded:

Job Schedule Output Category
learning-review daily at 2:00am log core
integration-tests daily at 2:00am log core
self-healing daily at 2:30am log core
daily-morning-briefing daily at 7:00am imessage+push core
night-time-lights daily at 9pm log home

Conditional (seeded when integration is configured):

Job Condition Schedule Output
email-summary email.yaml present */30 6-22 * * * log
calendar-briefing calendar.yaml present daily at {morning_briefing} imessage+push
remarkable-task-sync tasks.yaml sync per config log
task-followup tasks.yaml followup per config imessage+push
daily-standup tasks.yaml standup per config imessage+push
evening-checkin tasks.yaml evening_checkin per config imessage+push

Self-Healing

The self-healing pipeline runs every night at 2:30am, after integration tests complete at 2:00am.

Pipeline steps:

  1. Phase 0: Learn from recently merged fix PRs
  2. Phase 1: Collect diagnostics via DiagnosticCollector
  3. Phase 2: Triage each issue via agent router, classifying as melian tier (simple, agent can fix) or claude tier (complex, needs Claude Code CLI)
  4. Phase 3a: Melian-tier fixes via agent router with tool use (creates PRs through tools)
  5. Phase 3b: Claude-tier escalation spawns Claude Code CLI subprocess (30-minute timeout per fix)
  6. Runner handles notification (iMessage/push) with PR links

If no fixable errors are found, the job exits cleanly with a success status and no notification.

Schedule Formats

Jobs accept standard cron expressions and a small set of human-readable shorthands:

"0 9 * * *"           → every day at 9am
"*/30 * * * *"        → every 30 minutes
"0 2 * * *"           → daily at 2am
"0 9 * * 1-5"         → weekdays at 9am

The human-readable forms resolve to cron before storage:

"every 30 minutes"    → */30 * * * *
"daily at 2:30am"     → 30 2 * * *
"weekdays at 9am"     → 0 9 * * 1-5

API

Method Path Description
GET /jobs List all jobs with category grouping
POST /jobs Create a new job
GET /jobs/:id Get a job plus its recent runs
PUT /jobs/:id Update job fields
DELETE /jobs/:id Delete a job
POST /jobs/:id/run Trigger a job immediately
GET /jobs/:id/runs List all runs for a job
GET /jobs/:id/runs/:runId Get a specific run
GET /jobs/categories List all categories
POST /jobs/categories Create a new category
DELETE /jobs/categories/:name Delete a category
POST /jobs/preview-schedule Preview next occurrences of a schedule expression

Output Routing

Value Behavior
log Result stored in the job_runs table only (no external notification)
imessage Result sent to your iMessage self-chat
push Web push notification with a summary
imessage+push Both iMessage and web push