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
Reference: IPC

Reference: IPC

The app and huskarld speak JSON lines over a Unix socket. The types are in crates/huskarl-ipc/src/lib.rs. huskarld's own client and the test scripts use the same.

The socket

  • Path: ~/.huskarl/run/huskarld.sock, or $HUSKARL_SOCKET.
  • Mode 0600, in a folder with 0700. The daemon also checks that whoever connects is the same user (peer_cred). Others are disconnected.
  • The daemon is the only one that writes to the database. Everything that changes something goes through the socket.

The frames

One JSON message per line, ended with \n. A line that cannot be read is skipped.

From the client:

{"id": 7, "method": "send", "params": {"bot": "b1", "text": "hi"}}
{"id": 1, "method": "list_bots"}

id is the client's own number. params is left out for requests without fields.

Replies from the daemon, one per request, with the same id:

{"kind": "reply", "id": 7, "result": {"type": "ok"}}
{"kind": "reply", "id": 1, "result": {"type": "bots", "data": [ … ]}}
{"kind": "reply", "id": 8, "error": "no such bot"}

Events, to clients that have said subscribe:

{"kind": "event", "event": "delta", "bot": "b1", "text": "He"}

Replies can arrive in a different order than the requests. Some requests (decide, check, models, agents, felagi_connect, connect_token, browser, suggest_bot) take time and run on their own.

Version

The first message is hello:

{"id": 1, "method": "hello", "params": {"version": 1}}
{"kind": "reply", "id": 1, "result": {"type": "hello", "data": {"version": 1, "daemon_version": "0.1.0"}}}

PROTOCOL_VERSION is 1. If the versions differ, the daemon replies with the error the app speaks protocol X, this daemon Y; update the older one.

Requests

method params Reply (type)
hello version hello
subscribe – ok, and events from then on
list_bots – bots
create_bot name, role, instructions, agent, model bot
update_bot id, patch (see below) bot
suggest_bot description suggestion
models agent (elyra, claude, grok) models
agents – agents
agent_login agent (claude or grok) ok, then the agents event
items bot, before (seq or null), limit (at most 1000) items, newest last
send bot, text ok
interrupt bot ok
actions bot (or null), all actions, newest first. Only waiting ones without all
decide action, approve, payload (edited, or null), at (optional, when to carry it out) action. One that leaves is scheduled until its execute_at
undo action action, rejected. An error if it has gone
explain action explanation
delete_bot bot ok, then the bot_deleted event
local_model on (true, false, or left out to ask) local_model with available and on
status – spoken: what waits and what works, in a sentence. Pro and Business only
plan – plan: plan (trial, free, solo, pro, business), trial_days_left, licensee, valid_until, bots, has_key, error, device_name, shop
activate key plan, or why elyracode.com refused it
release_mac – plan
preview_rules bot, policy rule_preview: days, calls, unasked (title, at, first_recipient, was), asked
phone_chat on (true, false, or left out to ask) phone_chat with whether the phone may write to the bots
keep_awake on (true, false, or left out to ask) keep_awake with whether it is on
export_bot bot recipe
import_bot recipe, rehearsal (default true) bot
replay of (a run or a proposal), instructions and model (optional) ok; the comparison comes as an item in the bot's thread
routines bot (or null) routines
create_routine bot, name, prompt, schedule, condition routine
set_routine_enabled id, enabled routine
delete_routine id ok
connections bot connections
connect bot, connector (gmail, outlook, teams, github) open_url with url: the sign-in, or for GitHub where to make a token
connect_token bot, connector (github, workspace, felagi), token ok, or the error the service gave
disconnect bot, connector ok
check bot checked
memories bot memories
save_memory bot, id (null for a new one), text ok
delete_memory id ok
presence active ok
ntfy enable (true, false, null to ask), server ntfy with server and topic
browser bot, command (see below; control takes record) browser, with recording
away since away
search query search_hits
stats days (1–365) stats
rule_suggestions bot rule_suggestions
decide_rule_suggestion bot, key, accept ok
felagi_connect server, token felagi
felagi_disconnect – felagi
felagi_status – felagi
set_language lang (en or nb; anything else is en) ok

update_bot and patch

Fields that are missing or null are not changed. The app always sends the whole form.

model, run_token_budget, daily_budget_usd and mail_alias tell three states apart:

  • The field is left out: do not change.
  • The field is null: remove the value.
  • The field has a value: set it.
{"name": "…", "role": "…", "instructions": "…", "model": "anthropic/claude-sonnet-5-5",
 "policy": {"rules": [{"class": "send", "recipient_domain": "firma.no", "decision": "allow_logged"}]},
 "run_token_budget": 1000000, "daily_budget_usd": 2.5,
 "browser_domains": ["tripletex.no"], "agent": "claude", "delegates": false, "rehearsal": false,
 "mail_alias": "kh+utlegg@firma.no"}
  • class: read, draft, internal, send, finance, destructive.
  • decision: allow, allow_logged, propose.
  • browser_domains is checked with the same rules as in the app, and an invalid list gives an error.
  • A model from a different provider than the agent's (for example xai/… for Claude Code) gives an error.

schedule and condition

{"kind": "daily", "time": "07:00", "tz": "Europe/Oslo"}
{"kind": "weekdays", "time": "07:00", "tz": "Europe/Oslo"}
{"kind": "weekly", "weekday": 0, "time": "08:00", "tz": "Europe/Oslo"}
{"kind": "interval", "minutes": 10}
{"kind": "once", "at": "2026-10-20T07:00:00Z"}
{"kind": "cron", "expr": "0 7 * * 1-5", "tz": "Europe/Oslo"}

Over IPC, tz must be included for times of day. Only the gateway's routines_create fills it in.

{"kind": "new_mail"}
{"kind": "run_finished", "bot": "<bot-id>"}
{"kind": "webhook", "token": ""}

The daemon fills in an empty webhook token.

browser and command

{"do": "watch", "on": true}
{"do": "control", "user": true}
{"do": "mouse", "kind": "mousePressed", "x": 640, "y": 400, "button": "left", "clicks": 1, "dx": 0, "dy": 0}
{"do": "key", "key": "a", "text": "a", "modifiers": 0}
{"do": "open", "url": "https://tripletex.no"}
{"do": "back"}

The coordinates are in the page's pixels, 1280 × 800. mouse and key do nothing without control with user: true first.

Replies

type data
hello version, daemon_version
ok –
bots / bot bot(s): id, name, role, instructions, agent, model, color, status, last_line, pending, last_activity_at, created_at, policy, run_token_budget, daily_budget_usd, spent_today_usd, browser_domains, delegates, rehearsal, mail_alias, confidential
items transcript items: id, thread_id, seq, content (with kind), created_at
actions / action proposals: id, bot, run, tool, class, payload, proposal (title, recipients, subject, body, flags with kind, value, detail), status, result, created_at, decided_at, executed_at, execute_at
rule_suggestions key, class, domain, approved
recipe huskarl_recipe, name, role, instructions, agent, model, policy, run_token_budget, daily_budget_usd, browser_domains, delegates, routines. See Recipes
explanation action, trigger (kind: message, routine with name and prompt, felagi with task, task with from and task, task_reply with to, replay, other with trigger), asked, steps (at, tool, class, decision, detail, ok), said, decision, reason (kind: locked, default, class_rule, domain_rule with domain, giver, mandate with mandate, only_to with domain), decided_then, audit_intact
routines / routine id, bot, name, prompt, schedule, condition, enabled, next_run_at, last_run_at, created_by, created_at
connections bot, connector, account, connected_at
memories id, bot, text, written_by (bot or user), after_outside_text, updated_at
away per bot: bot, state (needs_you, failed, working, finished), line, runs, cost_usd, pending
browser open, url, title, user_in_control
suggestion bot (as for create_bot) and routine (name, prompt, schedule, condition) or null
models provider, id
agents kind, installed, logged_in, detail
search_hits bot, item, seq, at, snippet (the matches are between the characters \u0002 and \u0003)
stats agent, model, runs, completed, untouched, cost_usd, proposals, approved_unchanged, approved_edited, rejected
felagi server, workspace, connected, error, tasks
checked a list of [name, {"Ok": "…"}] or [name, {"Err": "…"}]
ntfy server, topic (both null when it is off)
open_url url

status for proposals: pending, scheduled, executing, done, failed, rejected, uncertain, simulated (rehearsed, never carried out). status for bots: idle, running, waiting, failed, interrupted.

Events

event Fields When
plan plan the plan changed: activated, renewed, lapsed, or the trial ended
bot bot a bot was created or changed (status, last line)
bot_deleted bot a bot was put away
item bot, item a new item in the thread
delta bot, text streaming text from the reply being written. Replaced by an item
action action a proposal was created or changed status
routine routine a routine was created or changed
routine_deleted id, bot a routine was deleted
frame bot, jpeg (base64), width, height a picture of the browser, while someone is watching
browser bot, state where the browser is, and who is in control
agents agents an agent was signed in (or the attempt ended)
memories bot, memories the memory changed
connections bot, connections the connections changed

A client that falls more than 1024 events behind loses the oldest. The daemon logs a client fell N events behind.

Trying it by hand

printf '%s\n' '{"id":1,"method":"hello","params":{"version":1}}' '{"id":2,"method":"list_bots"}' \
  | nc -U ~/.huskarl/run/huskarld.sock