0.4.0 · · Release on GitHub
Kernl 0.4.0: Projects With Approval, Voice, and Agents That Resume

Kernl 0.4.0 is the largest version so far. The structural change: an office stops being a single thing and starts working per project, and nothing the agents write goes out until you approve it.
Projects: nothing goes out without your approval
An office used to be just an office. Now it can serve several products or businesses, and runs, memory, learnings, letters and schedules belong to a project.
The change that really matters is a different one. In a project run, tools that publish or send something write a draft to an outbox instead of sending it. Nothing goes out until you approve it. Only then does it leave by email, newsletter, WhatsApp or LinkedIn, respecting do-not-contact flags and unsubscribe links. Pending approvals show on the Work overview.
That's the difference between an agent that drafts something for you and an agent that writes to your customers while you aren't looking.
Talking to the Chief
There's a microphone button in the chat and in the Chief's channel, with spoken replies you can switch on, plus voice notes on Telegram.
What makes it interesting is that the default engines are local and free: whisper.cpp to listen, Piper to speak. Prefer the cloud and speech recognition can run on Groq or OpenAI, with the voice on OpenAI or ElevenLabs. The Spanish Piper voice is about 110 MB and downloads itself the first time it's used.
Mail and WhatsApp, from the interface
You connect a mailbox from Settings or the setup wizard: it detects the provider from the address, tests IMAP and SMTP before saving, and walks you through app passwords where the provider needs them. Messages appear in the mail view with HTML bodies rendered and remote images blocked, and Gmail accounts split into Primary, Updates, Promotions, Social and Forums.
WhatsApp links with a QR code or a phone code, and the session survives updates, so you aren't asked to scan the QR again on every one.
Runs that pick up where they stopped
A run cut off by a restart continues from where it was. Tool calls whose result was lost are never repeated: the agent is told their outcome is unknown, which is the honest answer and avoids sending the same email twice.
Automatic resumes are capped (KERNEL_AGENT_RESUME_WINDOW_MS, KERNEL_AGENT_MAX_AUTO_RESUMES), so a run that crashes the kernel cannot crash it on every boot.
The 3D world
The dashboard opens straight into the world, with an Offices tab beside it listing each one with its status. New offices are built on fixed lots: creating, growing or deleting one never moves another, and free lots show a "Lot available" sign you can click to open the wizard. Create an office while the world is open and you watch it go up — a crew raises the walls, then the agents arrive at their desks.
Extensions can contribute their own building to the world. In the screenshot above, the Warehouse on the right is exactly that.
Eight security fixes
The ones that carry the most weight:
- A workspace tool took the caller's office from an argument. An explicit
flow_idoverrode the caller's identity, so any agent could reach another office's private workspaces. - Forge credentials were stored in plaintext (GitHub, GitLab, Gitea), GitHub App private keys included. They're encrypted now.
POST /api/comms/inboundfailed open: with no token configured it accepted any email. It now answers 503 until you set one.- Twitter/X keys, tokens and cookies were sent to the dashboard in every account listing.
- The Docker sandbox failed open: an invalid egress allowlist silently left the network open. Sandboxed processes also no longer inherit the kernel's environment and secrets.
What will break when you upgrade
This is the part worth reading before, not after:
- Without
TIMEZONE, the kernel uses the machine's timezone, not UTC. It moves more than "today": scheduled agents run at that local time. One set to0 8 * * *ran at 08:00 UTC and now runs at 08:00 yours. SetTIMEZONE=UTCto keep the old behaviour. - Reminder and event times given without a zone are local time.
2026-03-01T09:00meant nothing definite before; it is now 09:00 in yourTIMEZONE. - Set
KERNEL_ENCRYPTION_KEYbefore upgrading if you use GitHub, GitLab or Gitea connections, and keep it. Losing it afterwards makes those tokens unreadable. - Issues is gone: the page, its tab, its extension and the
kernel_issues_*tools are out of the core. Synced issues stay in the database, but nothing reads them. - The Docker stack starts the realtime engine and the WhatsApp bridge by default, so a plain
docker compose upnow pulls two more images.
How to upgrade
curl -fsSL https://lifekernl.com/install.sh | sh
The same command installs and updates. On macOS and Windows the update runs from the app, with the "Update now" button. Full detail is in the CHANGELOG, and rpm, deb, dmg and msi binaries are on the release page.
Changelog
Upgrading from 0.3.2
- Without
TIMEZONE, the kernel now uses this machine's timezone, not UTC. On a Mac that is your own zone; in a container it is still UTC. It moves more than "today": scheduled agents run at that local time (an agent set for0 8 * * *ran at 08:00 UTC and now runs at 08:00 yours). SetTIMEZONE=UTCto keep the old behaviour. - Reminder and event times given without a zone are local time.
2026-03-01T09:00meant nothing definite before (reminders compared it as if it were UTC); it is now 09:00 inTIMEZONE. Existing reminders and events stored that way are rewritten as UTC instants on the first boot, read the same way. Times withZor an offset are unchanged. - Set
KERNEL_ENCRYPTION_KEYbefore upgrading if you use GitHub, GitLab or Gitea connections, and keep it. Their tokens and GitHub App private keys were stored in plaintext; with a key configured they are encrypted on the first boot. Changing or losing the key afterwards makes them unreadable: re-add the connection. Without a key they stay as they were. - Creating a contact with a phone or email that already exists merges into that contact instead of adding a duplicate, on every road (tool, RPC, HTTP) — it now goes through the same service as the MCP tool.
- Deleting a task over HTTP is a soft delete now, like everywhere else; a deleted task no longer appears in the dashboard list.
- Inbound email (
POST /api/comms/inbound) needsCOMMS_INBOUND_TOKEN, sent in theX-Inbound-Tokenheader. A relay that sent?token=or relied on the token being unset stops delivering until it is updated. - An empty or malformed request body is a 400 (413 when over 10 MB) on
the HTTP API, where most routes used to answer 500. Some 500 messages now
read
Error: …. - Issues is gone. The Issues page, its Work menu tab, its extension and
the
kernel_issues_*tools are removed from the core. Agents or scripts that called those tools stop finding them. Synced issues stay in the database, untouched, but nothing reads them any more. - The Docker stack now starts the realtime engine (
mtw-request) and the WhatsApp bridge by default. They no longer need--profile torrents, andBRIDGE_ENABLEDandRUST_BRIDGE_ENABLEDdefault totrue. A plaindocker compose uppulls two more images and creates the volumesmtw-torrents,mtw-whatsapp-sockandmtw-whatsapp-data. Torrent downloads now live in a volume, so recreating the container keeps them. Set both variables tofalseto keep the old, kernel-only stack. - Host folders moved to an opt-in overlay,
docker-compose.host.yml. The defaultdocker-compose.ymlno longer mounts host folders into the kernel (it used to mount one fixed path). If you browse a host folder with the Filesystem Commander, or runclaude_codeagents on your Claude Code login (now mounted read-write so the CLI can refresh its session), setKERNL_FS_ROOTin.envand add-f docker-compose.host.ymlto everydocker composecommand for the stack.HOST_HOMEnow defaults to your$HOME. - Voice is on unless
VOICE_ENABLED=false. The default engines are local and free (whisper.cpp to listen, Piper to speak). The old variables still work:VOICE_STT_PROVIDER=local-whispermeans whisper.cpp, andVOICE_TTS_PROVIDER=systembecomesauto. The Spanish Piper voice (about 110 MB) downloads into the data volume the first time it is used. - A model picked for a Claude Code agent that is not a Claude model moves the agent to the kernel's own executor. Before, the choice was saved but never used, and every run failed on its first turn.
Added
- ECC as a default skill source. The marketplace catalog now subscribes to
affaan-m/ECC (MIT, ~290 skills), pinned to
v2.2.1. Only the subscription is added — no skill is installed. It lands once per database, so unsubscribing sticks. The catalog fills on the next repo sync (every 6 hours, or "Sync" in the marketplace). - The top agent's channel runs on any connected model, with a model picker in its header; providers that are logged out stay in the menu, dimmed, with the reason.
- Update progress shows in the banner, not only in Settings → About.
- Job hunting is an extension (
automation/job-hunter), installed and active by default; its handlers keep their names, so existing agents keep working. - The dashboard opens on the 3D world, which is listed before Offices. A new Offices tab beside it lists every office with its status. Switching between the two does not reload the world.
- Extensions can add their own buildings to the 3D world. An extension that declares a world plugin (paid extensions included) gets a lot on the floor and draws its building there.
- New offices are built on fixed lots. Creating, growing or deleting an office never moves another one. Free lots show a "Lot available" sign, and clicking one opens the new-office wizard. An office created while the world is open is built on screen: a crew raises the walls, then the agents arrive at their desks. You can skip the animation.
- Projects. An office can serve several products or businesses. Runs,
memory, learnings, letters and schedules belong to a project. In a project
run, tools that publish or send something write a draft to an outbox
instead, and nothing goes out until you approve it. Approved drafts go out
by email, newsletter, WhatsApp or LinkedIn. Do-not-contact flags and
unsubscribe links are respected. A project can connect its own data
through a snapshot pull and signed webhooks (
docs/project-connector-v1.md). Pending approvals show on the Work overview. - Agents can search businesses on Google Maps once a Google Places API key is saved under Settings → Integrations.
- Talk to the Chief. A microphone button in the chat and the Chief's channel, with spoken replies you can switch on, plus voice notes on Telegram. Speech recognition and voice are set up under Settings → AI → Voice, with a microphone check. Speech recognition can use whisper.cpp, Groq or OpenAI; the voice can be Piper, OpenAI or ElevenLabs.
- Connect a mailbox from Settings or the setup wizard. The provider is detected from the address, IMAP and SMTP are tested before saving, and the card walks you through app passwords where the provider needs one. Connected accounts are listed with sync health and unread counts, and each one links to its mailbox.
- IMAP mail in the mail view. Messages are decoded, HTML bodies are shown with remote images blocked, and each account shows its sync status. Gmail accounts are split into Primary, Updates, Promotions, Social and Forums tabs.
- Mail triage and analysis can run on a chosen provider
(
comms.llm.provider, optionalcomms.llm.model). If that provider fails or is disconnected, the default chain takes over. - Link WhatsApp from Settings or the setup wizard with a QR code or a phone code. The session survives updates, so you won't be asked to scan the QR again.
- The native packages bundle the realtime engine and the WhatsApp bridge. Linux, macOS and Windows installs (MSI and zip) supervise them as sidecars; on Windows they talk over named pipes.
- Token usage per model, caller and day under Settings → AI → Token usage. Every model call is counted, including Claude Code subscription calls and agent runs, which were not counted before. Models whose provider reports no cost are priced from the LiteLLM price table.
- Interrupted agent runs resume. A run that was cut off by a restart
picks up where it stopped. Tool calls whose result was lost are never
repeated; the agent is told their outcome is unknown. Older runs can be
resumed by hand (
kernel_agents_resume). Automatic resumes are capped (KERNEL_AGENT_RESUME_WINDOW_MS,KERNEL_AGENT_MAX_AUTO_RESUMES), so a run that crashes the kernel cannot crash it on every boot. Runs also record conditions (model ready, interrupted, resumed, aborted) with a reason. - Declarative office workspaces. An office can declare what its home
folder must hold: git repos to clone, files to seed, and the MCP servers
and skills its Claude Code agents share (
kernel_agents_workspace_apply). The folder is brought up to that spec before each run. Existing checkouts are never reset, and a run whose folder can't be prepared fails with the reason. - Office agents can ask the Chief. The Chief answers what it can and passes the rest to you in My Office. Agents that are not answered when a colleague writes to them are woken again until they acknowledge the letter.
- The Chief's office shows what needs you. Failures are grouped and explained, and solved ones are marked. Each one can be fixed in one click or handed to the Chief. Dismissals are remembered.
- The Chief can file Kernl's own bugs. Bugs show in a tab of My Office with the run's context redacted. A bug becomes a GitHub issue only after you approve it.
- Tool-permission requests show inline in the Chief's channel and in chat, with "Allow all" for the rest of the session.
- A reworked agent drawer. Overview and Settings tabs sit under a one-line header. The model can be switched from a chip in the header. Run and stop use the same button, and the schedule is an on/off switch. The mandate opens by default, rendered as markdown. Hovering a skill explains it, translated by the cheapest external model.
- Readable LIVE and HISTORY tabs. Each step shows the call next to its result, and every row opens to its full detail. Repeated identical calls fold into one "×N" entry, and failed calls are marked. When an agent acts on another agent, the step shows as a card naming the recipient and the outcome.
- The 3D world comes alive. Letters between colleagues fly from desk to desk. The Chief's commands name the agent they target. Clicking a meeting table shows what the two agents there are working on.
- Visual changes to the 3D world. There is an optional photographic realism layer. The reception, the headquarters and the DevOps office were redesigned, the lobby has matte carpet, and agents walk in through the door. DevOps rack nameplates open the repo.
- The Social overview shows what needs attention across mail, contacts and IRC. Its menu keeps only Overview, Mail, Contacts and IRC.
- Extension settings can offer Kernl's mail accounts as choices.
- A pending store purchase can be resumed or cancelled. Before, closing the Stripe tab left the card on "Waiting for payment" for up to 24 hours.
- License comes first in Settings. A pasted license is saved in one step, and a license copied from an email whose line breaks became spaces is accepted.
- New chats start on the model you last picked in a chat.
Fixed
- A provider out of quota could stop the fallback to the next one. When the table that remembers exhausted providers could not be written, the error escaped and the run failed instead of moving on; the write is now best effort and the fallback continues.
- Claude Code could not be connected from the macOS app. The .dmg did not
ship the
claudeCLI, so the sign-in step printed a bareclaudethat answered "command not found", and a Pro or Max subscription could not be used at all. The CLI now ships inside the app, as it already did on Windows, and the sign-in command, the chat provider and the agents all run the same binary. - Long replies on Discord and Telegram were lost. The platforms reject a message over 2000 / 4096 characters and nothing split it; replies are now split at paragraph, line or word boundaries, keeping code blocks intact.
- Extension pages dropped the text of sanitised content (RSS items, news, email bodies and threads): the shared sanitiser stripped text nodes.
- A slow dashboard write could run twice. When the WebSocket was slow the dashboard also sent the request over HTTP, so a slow "Run", "Create" or "Publish" could execute twice. Writes now go by one road only, and an error the kernel answered is not retried.
- The dashboard's two roads answered differently. Every action reachable
by WebSocket RPC and HTTP now runs one function. Among what that fixed:
"Run now" over WebSocket left
{{event.*}}in the goal and skipped the circuit breaker; agent updates over WebSocket dropped fields such as wake-on-inbox; chat over WebSocket lost attachments; news column feeds and the architecture map never worked over WebSocket; failures were shown as success. - Food search always failed (nutrition, including the MCP tool).
- Other fixes found while unifying: an RSS feed could not be disabled
over WebSocket; the training page's actions never reached the kernel over
WebSocket; removing a shopping item over HTTP answered 404; deleting a
contact with interactions failed; creating a learning resource without every
field failed; a
#tagin notes search was a syntax error; a finance transaction stored a signed amount but moved the balance by its absolute value;GET /api/agents/:id/memoryignored?limit=; revoking an MCP server's sign-in never did anything; the Google status fallback asked the wrong endpoint. - An office draft that fails validation gets one repair attempt, and the error names the model that failed.
- "Today" was the UTC day. In UTC-3 it became tomorrow at 21:00: the
morning briefing, due-today and overdue tasks, the calendar, the agenda,
"completed today" counts and the date agents are told all switched early,
and an evening event or reminder showed up under the next day. Every day
boundary now follows
TIMEZONE, and times in briefings and notifications are shown in it. - A daily or weekly reminder drifted an hour at each DST change; it keeps
its local time now.
/remind … at 9ammeant 09:00 in the server's zone (UTC in a container); it means yours. - A reminder due earlier today did not count as overdue in
/statusand the briefings until the next day (its time was compared as text against a differently formatted "now"). - Listing events up to a date left out that day's events.
- A migration that failed halfway stayed half-applied, unrecorded, and ran again on the next boot over the partial schema. Each migration now applies whole or not at all.
- Tools took any
limit. 37 tools passed an unboundedlimitstraight to SQL; it is now capped per tool (a larger value is clamped, not refused). Tool arguments with a default are no longer listed as required, and.optional().default()arguments are published with their real type. - System → Processes always said "System registry not available". The
page never subscribed to the channel it reads, and the channel left out
the
availableflag the page waits for. - A campaign's page showed no recipients; it now lists each one with its status and error, and counts sent, failed and pending.
- Channel settings opened empty. The WhatsApp and generic channel forms read a field nothing sent; they now show the saved configuration, with password fields masked.
- Creating, pausing or cancelling a subscription could silently do nothing when the live connection was down; it now falls back to HTTP.
- A run that failed before starting stayed "active" forever (no tools, no usable provider): it is now always removed from the active runs.
- A provider's rate-limit reset of
750mswas read as 750 minutes, and so waited the 15-second cap. Milliseconds and hours are now understood. - Google credentials saved from the dashboard were never read, so linking Gmail always said "credentials not configured". Saved settings now apply at boot, before any module starts, and saving takes effect without a restart. Real environment variables still win.
- Email triage failed on every batch with reasoning models. They ran out of tokens mid-thought, or a bracket in their reasoning broke parsing.
- The mail inbox stuck on "Loading" after you picked an account. An action the live connection doesn't know now falls back to HTTP.
- A rejected license replaced the one that worked. The current license now stays active when a pasted one is refused.
- A tool result could be labelled with the wrong tool when one turn made two calls (for example, an agent update shown as ToolSearch output).
- The portable Linux launcher failed when started through a symlink on
PATH. - An agent opened on the History tab from a link showed no runs.
- The Chief reported to itself, walking into My Office to deliver its own report.
- Live agent events were shown twice, and two parcels could be set down in the same spot in the 3D world.
Security
- A workspace tool no longer takes the caller's office from an argument.
An explicit
flow_idused to override the caller's identity, so any agent could reach another office's private workspaces. CLI agents, whose identity arrives with the request, also get their own office now. - Attachments and chat streams answered
Access-Control-Allow-Origin: *. They follow the server's opt-in CORS allow-list now. HTML and SVG attachments download instead of rendering inline from the kernel's origin. - Forge credentials are encrypted at rest (see Upgrading). A connection whose credentials the current key can't open is flagged as unreadable, says so in its test and list, and is never sent to the forge.
POST /api/comms/inboundfailed open. WithoutCOMMS_INBOUND_TOKENit accepted any email; it now answers 503 until the token is set, accepts it only in theX-Inbound-Tokenheader (no longer?token=), and compares it in constant time.- Twitter/X API keys, tokens and cookies were sent to the dashboard in every account listing. They are masked now; leaving a credential blank when editing keeps the stored one. Channel settings mask their secrets the same way.
- comms updates wrote any column a caller named. The service now enforces its own column allow-list.
- The Docker sandbox fails closed. An egress allowlist or an invalid network name is refused; before, it silently left the network open. Sandboxed agent processes no longer inherit the kernel's environment and secrets.
- Workspace specs reject unsafe input. Paths outside the workspace, and repo URLs or branches that git could read as options, are refused.
/api/comms/unsubscribe/is reachable without the API token, because the people clicking unsubscribe links don't have it.
Changed
- Large internal restructuring with no intended change in behaviour beyond the above: one HTTP route helper, one operation per dashboard action, typed tools and module factories in the extension SDK, shared channel and forge plumbing, the agent executor split into phases, one retry and fallback loop in the LLM layer, one frontend library shared by the dashboard and extension pages, calendar sources contributed by the modules that own them, and generated extension entry points.
Removed
- Issues (page, extension, tools) and the unused contacts analytics page. See Upgrading.