May 15, 2026
Chalie learns to ask permission
A per-action policy layer ships across chat, subagent, and background contexts, right as home automation and full email send widen what Chalie can actually do.
Today was about trust. Chalie’s action surface has been growing fast — rich-media cards, more capabilities, more autonomy — and every new ability is another thing the agent can do without asking first. That’s fine right up until it isn’t. Before Chalie touches your smart lights or sends an email on your behalf, there needs to be a real answer to “wait, did I say it could do that?” Today that answer shipped: a policy enforcement layer with per-action allow/ask/deny control, live everywhere Chalie takes action.
The core of it is a single chokepoint. Every action Chalie’s abilities can take now routes through one dispatch point, checked against a policy table seeded with 147 default rules across 49 distinct action IDs — one row per action, per context (chat, subagent, or background). Three states: allow it silently, ask first, or deny outright. “Ask” in chat or subagent pauses the dispatch thread and pushes a slide-up permission card over the socket; “ask” in the background auto-rejects and logs it, since there’s no one there to click Allow. If the policy check itself fails for any reason, it fails open rather than freezing the agent — a broken policy layer shouldn’t be able to brick the assistant.
The permission card itself needed real design attention, not just plumbing. The first cut showed raw parameters like “UID 44165,” which means nothing to anyone. It now builds a human-readable line — “Sending email — to john@example.com,” “Reading an email” — from the action context instead. I also killed the 30-second auto-deny timer: a prompt that silently expires into a denial while you’ve looked away is worse than no prompt at all, so the backend now polls for up to an hour. And I found a genuinely nasty bug while wiring this up — permission responses were sent over the WebSocket, but that socket’s receive loop is blocked for the entire chat turn inside the pub/sub listener, so every “Allow” click sat unread in the buffer until it timed out and got treated as a denial. Moved the response path to a plain REST POST that writes straight to the message bus, bypassing the blocked socket entirely.
That timing wasn’t incidental. Two of the day’s other features are exactly the kind of thing you want gated. Home automation landed — a capability that talks to Home Assistant over both REST and WebSocket, with six actions from listing devices to triggering automations. And email finally got a real send path: send, reply, and forward over SMTP, with the old awkward “read it first in a separate call” guard removed — reply and forward now read the original message internally and hand its content back so you can see what Chalie acted on. Controlling your lights and sending mail unsupervised are two of the more consequential things an agent can do, and both shipped the same day the policy layer did. The gate needs to exist before the capability does, not the week after.
Underneath all that, a batch of mail sync fixes closed a chain of bugs that had been silently killing CalDAV and CardDAV: a cycle counter that never reset on reconnect, a CardDAV handler calling instance methods that didn’t exist, and address-book discovery that returned nothing on minimal servers until a direct-collection fallback was added. None of it glamorous, but a policy layer is only as trustworthy as the sync underneath it.
Next up: now that permission is enforced, the work is making “ask” feel less like a wall and more like a conversation — smarter defaults, sensible presets, and fewer prompts for things you’ve already told Chalie it’s fine to do.
-
Policy enforcement layer: 147 default allow/ask/deny rules across 49 actions, enforced at a single dispatch chokepoint
-
Home Automation capability — REST + WebSocket to Home Assistant, six actions from device control to automation triggers
-
Email gets real send, reply, and forward over SMTP, with the old read-before-reply guard removed
-
Fixed a bug where permission responses sat unread on a blocked WebSocket, turning every “Allow” into a timeout-denial
-
CalDAV/CardDAV sync repaired: cycle-count reset on reconnect, working instance-method calls, and a direct-collection fallback for minimal servers