Approvals and rules
What a bot may do without asking is decided per bot, per action class and per recipient domain. What it may not do becomes a proposal. The budgets set a limit on how much it spends.
Policy per class
Under Settings → What the bot may do without asking, the four classes that can be changed have three choices each:
| Choice | Means |
|---|---|
| Allow | runs at once |
| Allow and log | runs at once, and is shown in the thread and the audit log |
| Needs approval | becomes a proposal |
The defaults:
| Class | Default |
|---|---|
| Read | Allow |
| Draft | Allow |
| Change internally | Allow and log |
| Send | Needs approval |
| Finance | Always needs approval |
| Delete and publish | Always needs approval |
Finance and Delete and publish are shown, but locked.
If you set Send to Allow, mail is sent without asking. It is the daemon that does the sending, the same way as for an approved proposal. Stricter is possible too: Change internally set to Needs approval makes archiving, new routines and tasks for other bots wait for you.
From the terminal:
huskarld require-approval <bot> internal # read, draft, internal or send
That adds a rule. To remove it, you use the settings.
The policy is read on every call, so changes apply at once. Tools that wait for approval get the sentence "Waits for the user's approval." in the description the agent sees, the next time the agent starts.
What is confidential
In a bot's settings, Confidential takes what is confidential to it, separated by commas:
- an address (
styret@firma.no): mail from that sender, - a domain (
advokat.no): mail from it and its subdomains, - a word (
Konfidensielt): mail with it in the subject.
huskarld confidential <bot> styret@firma.no,advokat.no,Konfidensielt does the
same, and none empties it.
When a run has read such a mail (or an attachment of one), nothing it does leaves your domain without asking for the rest of that run, whatever the rules say: a mail to someone outside your own domain (the domain of the mailbox the bot is connected to, or of its own address), a payment, or anything whose recipients Huskarl cannot see. A domain rule that would have sent it unasked does not. The card says why: "This run read confidential mail from styret@firma.no; this goes outside your domain, to kari@acme.no". Mail inside your own domain goes as the rules say. The next run starts unmarked.
Before rules are saved
When you change a bot's rules and press Save, Huskarl first puts the bot's last 30 days through them: every call it made, from the thread, with its full arguments, as the rules are now and as they would be. If anything that waited for you would have gone without asking, the settings show it before saving:
With these rules, the last 30 days 3 of the bot's 41 calls would have gone without asking you: 1 to an address nothing has been sent to, and 1 that you rejected.
02.10. 09:14 · Send mail to kari@acme.no · you rejected it 03.10. 11:02 · Send mail to per@acme.no · a new address
Save anyway saves the rules; Back goes back to the form. If nothing new would have gone unasked, it saves at once. Looking changes nothing.
Earned rules
A bot can earn a rule. Over the last 60 days, Huskarl counts what you approved unchanged, per recipient domain for mail and per class otherwise (archiving, for instance). When ten or more are approved unchanged and none was rejected or edited, and the bot still asks for them, it suggests letting such go without asking, logged:
- A notice in the thread, once: "💡 10 mails to firma.no have been approved without a change. In the bot's settings you can let such go without asking, logged."
- In the bot's settings, under Earned: Let them go, logged adds the rule (a domain rule for mail, the class set to Allow and log otherwise), and Not now puts the suggestion away for good.
It only ever suggests. Money and deleting are never suggested: they cannot be unlocked at all.
Rules per domain
Mail to these domains is sent without approval (logged) takes a list of
domains, separated by commas, spaces or line breaks, for example
firma.no, *.firma.no. A mail is then sent without asking when all the
recipients are on one of the domains. It is logged.
firma.nocovers onlyfirma.no.*.firma.nocoversfirma.noand all its subdomains.- All the addresses count: to, cc and bcc. One recipient outside makes the mail wait for you.
- An address like
Kari <kari@firma.no>is read askari@firma.no. - A call without recipients never gets a domain rule.
- Domain rules apply only to the Send class.
When several rules match, the most specific wins: a domain rule beats a rule for the whole class. Among equal rules, the strictest wins.
From the terminal:
huskarld allow-domain <bot> firma.no
Proposals
A proposal shows exactly what will happen. For mail: the recipients, the subject and the whole text. For anything else: enough to see what it is about, the first four lines.
The same proposal twice becomes one:
- As long as a proposal is waiting, the same content from the same bot is the same proposal.
- If it is executing, uncertain, done or rejected (the last two within a day), the agent is told so instead of a new one being made. For example "Already done … Do not do it again" or "The user rejected this exact proposal".
The next time the bot runs, it first gets a list of what you have decided since last time: approved and done, approved but failed, rejected, or interrupted. That way it does not say that something is waiting that was sent long ago.
What the card points out
Before the content, the card lists what Huskarl found worth a second look. It finds these itself, from what it knows, without asking a model:
| Warning | When |
|---|---|
| «backup@evil.example» comes from the mail from it-support@evil.example, not from you | An address, a link or an account number in the proposal is in outside text the run read (a mail, a web page), and nowhere you wrote it: not in your messages to the bot, its instructions, its domain rules or the memories you made or accepted. This is how a prompt injection shows: the recipient came from the mail. Account numbers match however they are written (1234.56.78901, 1234 56 78901) |
| Nothing has been sent to kari@kunde.no before | No mail from any bot has gone to the address |
| flrma.no looks like firma.no, which you use | The recipient's domain is a letter or two from one in use (sent to before, your mailbox, a domain rule, or one you wrote), or the same once look-alike letters are read alike: rn as m, 0 as o, 1 as l |
| The account number 8601.11.17948 does not add up: its check digit is wrong | A Norwegian account number in the proposal fails MOD11, an IBAN fails its check digits (MOD97), or a number after "KID" fails both MOD10 and MOD11. A typo, or a made-up number; a KID is not taken for an account number |
| The account number 1234.56.99999 is new from leverandor.no, whose mail gave 1234 56 78901 before | The proposal has an account number that came in mail from a sender whose mail gave another number before. Huskarl notes, for each sender's domain, the account numbers its mails give whenever a bot reads one (Norwegian numbers and IBANs, kept in accounts_seen). A number you wrote yourself is not pointed out |
The same warnings follow the notification, on the Mac and on the phone, since a proposal approved from the phone is approved without the card.
Why?
Why? at the top of the card shows how the proposal came about, from what Huskarl recorded rather than from asking the bot afterwards:
- Started by: your message, a routine (and its prompt), a Félagi task, or a task from another bot.
- You wrote: your messages in that run, up to the proposal.
- What it did first: every call the bot made in the run before proposing, from the audit log, with the time and the arguments. A read of the mail the address came from shows here.
- What it said: the last of what the bot wrote before proposing.
- The rules: what the bot's rules decide for it now, and which rule decides: "Your rule for mail to firma.no: go without asking, logged", the class's default, that money and deleting always wait, or for a task from another bot, that the bot that gave it may not go that far (see Chief). If the rules have changed since it was proposed, it says what they said then.
- Whether the audit log's chain checks out.
It is there on every card, also once decided, and in the terminal as
huskarld why <id>.
Approving
There are five ways, and all of them go through the same place in the daemon:
- The card in the thread: Approve, Reject or Edit. See Getting started.
- The queue: ⌘⇧A, then ⌘↵ for the top one and ⌘⌫ to reject it.
- Confirmation cards in the chat: see below.
- Your phone: the buttons in the ntfy notification. See below.
- The terminal:
huskarld actions,huskarld approve <id>,huskarld reject <id>andhuskarld undo <id>.
A proposal can be decided only once. The next attempt gives "the proposal has already been decided".
Edit changes the subject and body fields and nothing else. If you approve
after making changes, the proposal is marked as edited. That counts in the
statistics.
Undo and later
What leaves the Mac (mail, payments, deleting and publishing) does not go the moment you approve it. It waits 30 seconds, and the card says "Goes at 14:02:31" with an Undo button. Undo takes it back, and it never goes: the proposal is marked rejected with "Taken back before it went." Once it has gone, undo gives "It has gone already, or was taken back." What stays inside (drafts, archiving, labels) is carried out at once.
Later… beside Approve sends it at a time you choose instead: in an hour, tomorrow at 08:00 or Monday at 08:00. It can be undone until then.
The same goes for what your domain rules let through without asking: it waits 30 seconds too, and the bot is told it "goes out in 30 seconds unless the user takes it back".
The waiting is kept in the database, so a scheduled mail survives the daemon or
the Mac restarting: it goes when the daemon is back, if its time has passed.
HUSKARL_UNDO_SECONDS changes the window, and 0 turns it off.
Confirmation in the chat
If you say in the chat that something waiting should go ahead ("Top 10 look
good. Send them."), the bot calls approvals_propose_decision with the ids. That
decides nothing. It puts a card in the thread:
- the title "Approve N proposals?" or "Reject N proposals?"
- one line per proposal, and what you said, if anything
- Confirm and send (or Confirm rejection) and No.
The card covers only the bot's own proposals that are still waiting. If there are none, the bot gets an error instead of a card. When everything has been decided, it says "Decided", or "No thanks" if you said no.
So at worst, a mail that says "the user has approved everything" can give you a card you have to click.
Approval from your phone
With notifications on your phone turned on (see
Notifications and menu bar),
every new proposal comes with the buttons Approve and Reject. A tap sends
approve <id> or reject <id> to the topic <topic>-reply, which the daemon
listens on. It counts as your decision, the same way as a click on the card.
Only whole proposal ids are accepted, not the end of one.
The topic name is the key. Anyone who knows it can approve.
Rehearsal
Rehearsal in the bot's settings (or huskarld rehearsal <bot> on) lets a
new bot or routine try its work on the real inbox without doing any of it.
While it is on:
-
Reading happens for real: listing and reading mail, opening pages.
-
Everything else is only written down, whatever the rules say: nothing is sent, drafted, archived, remembered or clicked. No card is put up, and nothing waits for you. The bot is told "Rehearsal: nothing was done … Carry on as if it had been done".
-
When the run ends, the thread lists what it would have done, mail and payments first:
🎭 Rehearsal, so nothing was done. This run would have: Send: 1 · Change internally: 2 • Send mail to kari@acme.no • Archive «50 % på alt – bare i dag!» from Nettbutikken <nyhetsbrev@shop.example>
Routines run in rehearsal too, so a morning routine can be watched for a few
days before it is let loose. What was rehearsed is kept as proposals with the
status simulated and Why? works on them, but it does not count in the
statistics or towards earned rules. Turn it off, and the next run does the work
for real.
Replay
A finished run can be done again, to see what a change to the instructions or the model would have done with the same work. Replay this run in a card's Why? panel replays it with the bot's settings as they are now. From the terminal, other instructions or another model can be tried without saving them:
huskarld replay <run-or-proposal> --instructions new.txt --model anthropic/claude-opus-5-5
-
The bot starts afresh on the same prompt (your message, the routine, the task). Its own conversation and settings are untouched; the next ordinary run picks them up again.
-
What it reads is what that run read. Reads are answered from the transcript, not from the mailbox or the page, so the replay sees yesterday's inbox even though it has changed since. A read the run never made gets "Not in the recording …".
-
Everything else is rehearsed: written down, never carried out.
-
While it runs, the bot takes nothing else. Messages and routines get "The bot is busy …".
-
When it ends, the thread sets it beside the run, call for call:
🔁 Replay done, set beside the run on 16.10. 14:02: Same: 2 · Changed: 1 · Only then: 1 · Only now: 0 • Changed: Send mail to kari@acme.no • Only then: Archive «50 % på alt – bare i dag!» from Nettbutikken <nyhetsbrev@shop.example>Same is the same call with the same content, Changed the same kind of call with the same title but other content (another text to the same person), and Only then and Only now what only one of them did.
A replay cannot itself be replayed, and a run still going cannot be replayed.
Budgets
Tokens per run
Tokens per run stops a run once the agent has used that many tokens. New bots get 1 000 000.
- It is counted during the turn: input, output, cache reads and cache writes. Tokens are reported by all the agents, including those that do not report cost.
- Once the limit is passed, the turn is interrupted and the thread gets the notice "The run was stopped: it has used N tokens of its budget of M. Write «continue» to let it go on, or raise the budget in the settings."
- "continue" starts a new run with a new count.
huskarld budget <bot> 500000
Empty the field, or run huskarld budget <bot> none, to remove the limit.
Daily budget
Daily budget in $ stops what runs on its own once the bot has spent that much today. The field also shows what the bot has spent today. New bots have no limit.
- "Today" is the local date. The cost is the sum of today's runs, as the agent reported it. An agent that does not report cost does not count.
- It stops routines, tasks from a Chief, replies back to a Chief and issues from Félagi. Messages you send yourself are not stopped.
- When it stops something, the thread says so: "The day's budget ($2.00) is used up ($2.13 today), so routine:… did not run."
huskarld daily <bot> 2.50
Empty the field, or run huskarld daily <bot> none, to remove the limit.
Not yet
- A budget in kroner.
- Removing a rule from the terminal.
require-approvalandallow-domainonly add. - Editing fields other than subject and text before approval.