Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Huskarl AI colleagues on your own Mac Starf An activity monitor for Apple silicon that never invents a number etrans An SSH and SFTP client for macOS Elyra VM Virtual machines for macOS, Linux and Windows on your Mac Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra Workspace A desktop workspace for coding agents Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Release notes
Changelog
Elyra

Routines

A routine is a prompt the bot gets again and again: at fixed times, or when something has happened. Routines are run by the daemon, so they also run when the window is closed.

Creating a routine

There are two ways:

  • Ask the bot in the chat. "Go through my inbox every weekday at 07:00." The bot calls routines_create. That is the class Change internally, so it happens at once and is logged. The thread gets the line "⏱ Created routine «Morning triage» – every weekday at 07:00".
  • From the proposal when you create the bot. See Getting started.

A routine needs a name, a prompt and a time. Routines appear as chips in the thread's top bar, for example "Morning triage · every weekday at 07:00". A click on a chip pauses the routine ("· paused") or starts it again. When it starts again, it runs from the next time.

Ask the bot to delete a routine, and it calls routines_delete. A bot sees and deletes only its own routines.

huskarld routines lists all routines with their next run.

Times

kind Example Shown as
daily {"kind": "daily", "time": "07:00"} every day at 07:00
weekdays {"kind": "weekdays", "time": "07:00"} every weekday at 07:00
weekly {"kind": "weekly", "weekday": 0, "time": "08:00"} every Monday at 08:00
interval {"kind": "interval", "minutes": 60} every hour
once {"kind": "once", "at": "2026-10-20T07:00:00Z"} once, Oct 20 at 09:00 (in local time, here Oslo)
cron {"kind": "cron", "expr": "0 7 * * 1-5"} cron «0 7 * * 1-5»
  • weekday is 0 for Monday to 6 for Sunday. Weekdays are Monday to Friday.
  • Times are written HH:MM and apply in the machine's time zone, which is filled in when the routine is created.
  • An interval must be at least one minute.
  • cron takes five fields: minute, hour, day, month and weekday.

When it runs

The scheduler looks for routines that are due every 20 seconds (HUSKARL_TICK_SECONDS).

  • The machine was asleep or off: a routine that has missed one or more times runs once when the machine is back. The next run is the next time after that. To keep a plugged-in Mac from falling asleep by itself, turn on Keep the Mac awake for routines under ⌘, → General (see Settings). For bots that should work every night, a Mac that is always on, such as a Mac mini, is the place for Huskarl: the daemon starts at login, and the bots go on when no one is at it.
  • The bot is busy: that time is skipped.
  • The daily budget is used up: the routine does not run, and the thread says why. See Approvals and rules.

A run begins with the line "⏱ Routine «…» started". The bot is told that the routine is due, that you are probably not watching, and that it should finish with a short summary of what it did and what is waiting for you. If you are not looking at the window when the routine finishes, you get a notification with the bot's last line.

Conditions

A routine can have a condition (only_when). It then runs only when the condition holds. The condition is checked by the daemon without the agent, so a routine that has nothing to do costs nothing.

new_mail: when new mail has arrived

"Every 10 minutes when new mail has arrived."

  • At each time, the daemon looks for mail in the bot's inbox since last time. The mailbox is the bot's Gmail, Outlook or Testpost.
  • Only the timestamps are fetched. The prompt gets the count and the time, for example "2 new mails came to the inbox since 09:40", but nothing from the messages. The bot reads sender and subject with the tools, wrapped as outside text.
  • Use an interval. The designer suggests 10 minutes.

This is how each service asks:

Service Query
Outlook $filter=receivedDateTime gt … with only receivedDateTime
Gmail labelIds=INBOX and after:<seconds>, checked against internalDate
Testpost the time of the messages that are not archived

run_finished: when another bot is done

"When Inbox Manager is done, write a summary."

  • The routine is due right after the other bot has completed a run. The other bot is given by name (after_bot).
  • Only completed runs count, not those that failed.
  • The bot gets the other bot's last reply, wrapped as outside text.
  • A bot cannot wait for itself.

No loops. A run that was itself started by a run_finished routine wakes no others. Two bots waiting for each other therefore run once each and stop.

webhook: when something on this machine calls it

For Shortcuts, scripts and local services. The routine gets its own address:

POST http://127.0.0.1:47811/hooks/<token>
  • The address is shown in the thread when the routine is created: "⏱ Created routine «…» – when the webhook is called (webhook: POST http://127.0.0.1:47811/hooks/…)".
  • The token is 32 characters and is the key. Each webhook routine has its own.
  • The daemon listens only on 127.0.0.1. The port is 47811, or HUSKARL_WEBHOOK_PORT. If the port is taken, the daemon logs it and works without webhooks.
  • The body is stored (the first 16,000 characters) and the routine is due at once. The bot gets the number of calls, the time and the last body, wrapped as outside text.
curl -X POST --data "New order 1043 from Acme" http://127.0.0.1:47811/hooks/<token>
Response When
202 Accepted (body Received) the token belongs to a routine that is not paused
404 Not Found unknown token or the routine is paused (body No routine has this webhook), or not POST /hooks/…
413 Payload Too Large the body is over 64 KB

new_files: when files come to a folder

For receipts, invoices and anything else that arrives as a file. The routine names a folder (folder, for example Utlegg), and Huskarl makes ~/Huskarl/Utlegg for it. Drop a file there, from Finder, a scanner app or a download, and at the routine's next time the bot gets: "One new file came to the folder «Utlegg» (~/Huskarl/Utlegg). Their names: …", with the names as outside text.

"Make Expense Manager enter every receipt I put in a folder" is enough; the bot sets up the routine itself, best with a short interval such as every five minutes. Looking costs nothing: Huskarl checks the folder without the bot, and an empty folder starts no run.

  • A file is new by its name, not its date: a receipt dragged in keeps the date it was made, and still counts. Hidden files and folders do not count.

  • The bot gets three tools, for its own folders only:

    Tool Class What
    files_list Read the files waiting, with their size
    files_read Read a text file as text (the first 200 KB); a PDF as its text, or a scan as a picture of its first page; a picture as a picture, made with macOS's own sips
    files_done Change internally moves a handled file to Done/ in the folder, so it is not seen again
  • Only a plain name in the folder is read: not ../…, not a path, and not a link that leads out of the folder. What a file says is outside text.

  • HUSKARL_FOLDERS puts the folders somewhere other than ~/Huskarl.

From the share sheet. A shortcut makes any app's Share menu a way in: in Shortcuts, make one that receives files from the share sheet and has the one action Save File to ~/Huskarl/Utlegg (ask where to save: off). Turn on Show in Share Sheet in its details. A photo of a receipt, a PDF from Mail or a page from Safari then goes to the bot with Share → the shortcut. For text, a shortcut can call a routine's webhook instead (see above).

before_meeting: before each meeting

"Give me a brief 15 minutes before every meeting." The routine names how long before (minutes, 1 to 240, 15 by default). At each of its times, Huskarl looks in the calendar of the mailbox the bot is connected to, without the bot, and runs it only if a meeting starts within that time. The bot gets the meeting, as outside text: "A meeting starts soon: Subject: Tilbud til Acme, Start: …, Organizer: …, Attendees: …, Location: …" and the invitation's text.

  • Pair it with a short interval, such as every 5 minutes: with 15 minutes and every 5 minutes, the brief comes 10 to 15 minutes before.
  • Each meeting is run once. A meeting moved to another time counts as a new one.
  • It needs the calendar permission: see Mail.

The time alongside the condition

For run_finished and webhook, the routine is due as soon as it happens. The time (schedule) is an extra check, for example once a day. At each time, the routine runs only if something has happened that it has not seen.

The watermark

Each routine with a condition remembers the newest thing it has seen: the newest mail, run or webhook call (routines.watermark). Only what came after the watermark counts. A new routine starts from the time it was created, so old mail does not count.

The watermark moves only when the routine actually ran. If the bot is busy, what has happened waits until the next time and is not lost.

Outside text and memory

What another bot wrote, and the body from a webhook, is outside text. Memories the bot writes in such a run are held back until you accept them. See Memory.

Not yet

  • Creating, changing or deleting routines yourself in the app. The chips can only pause and start again.
  • routines in the terminal only lists. There are no commands to create, pause or delete.
  • Conditions that are combined (for example new mail and another bot done).
  • A share extension of Huskarl's own. Shortcuts does the job today.