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» |
weekdayis 0 for Monday to 6 for Sunday. Weekdays are Monday to Friday.- Times are written
HH:MMand apply in the machine's time zone, which is filled in when the routine is created. - An interval must be at least one minute.
crontakes 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, orHUSKARL_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_listRead the files waiting, with their size files_readRead 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 sipsfiles_doneChange 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_FOLDERSputs 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.
routinesin 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.