An agent here is plain files you can read — localstack gives it working skills, localbrain gives it memory on your disk. Change a line, run it again: that's building. And it only gets the tools you granted — decided when the runtime starts, not promised in a settings page. Watch every step, answer what it cannot decide alone, revoke it mid-run.
What you just watched is the whole pitch. An agent hired from a text-file brief finds real leads with evidence, drafts outreach a human sends, remembers what it already shipped — and answers for it when asked. Below, the same app live with sample data.
This is the control room, and it stays one. Pick a model, grant an agent files, web, browser or shell — or don't — watch what it touches, read the transcript, revoke a chat. Those chips are not labels: they are the tool list the runtime is handed at start, which is the subject of the next section. The skills an agent deploys with are localstack, our open-source suite — every one readable before you grant it anything.
The thing you are actually deciding. An agent with your files, your logged-in browser and a chat app pointed at it is the most access you have ever handed a program that takes instructions from text — text that can arrive from a web page it read or a stranger in a group chat. Below is what stops that turning into a bad afternoon: nine mechanisms in the daemon, five checks you can run yourself in about a minute, and the two things we would rather you heard from us than found later.
The files · web · browser · shell chips are the tool list the runtime is started with. An agent without browser has no browser tool to call — there is nothing at run time left to refuse.
Provider keys are filed under the localoy service, encrypted at rest. The daemon reads one to launch an engine; no HTTP route returns it, so a stolen local token can't be traded for your API keys.
The daemon binds 127.0.0.1 only, behind a bearer token minted at startup into a 0600 file, plus a strict Origin allowlist so no website can drive it.
Long polling, not webhooks: no public URL, no tunnel, no inbound port, nothing to forward from a laptop behind NAT. The connection is always one your machine opened.
The allowlist holds numeric transport ids, never @usernames — a handle can be released and re-registered by someone else. Anyone not on it is dropped, not answered.
Group chats are opt-in at link time, because every other member's text arrives as untrusted content in the agent's view: material to read, never instruction to follow.
What reaches your phone is emitted by the daemon, not by a tool the model can call. “Your agent messaged you” is a fact about the run, not something a prompt can fabricate.
A binding is a capability checked when it is exercised, not when it was issued. Kill it in the app and a message already in flight stops working — no restart, no window where the old link still counts.
The working folder is picked in the app when you link, stored on the binding, and inherited by every session that chat creates. There is no message you can send that moves an agent somewhere else on the disk.
Every one of these runs against the free build, on your own machine. If any of them behaves differently than described here, that is a bug and we want the report.
Two things we would rather you heard from us.
1 · The Keychain ACL is not ours. Items are read through
/usr/bin/security, so the ACL is granted to that binary rather than to
localoy: against a process already running as you it is no stronger than a
0600 file. What it buys is encryption at rest, correct backup semantics,
and no plaintext key in the application directory. Tightening it needs a signed bundle and the
framework API, and it is on the list rather than in the release.
2 · A task-shaped prompt may leave the machine even with a local agent. The planning and
verification calls that wrap a task run on your local model when one is running, and fall through
to the same provider your agent already uses when one is not — your key, your provider, but that
text leaves the machine. Start a local model and it stays on it. This is in our own tracker as a
documentation bug; it is here because it is the kind of thing you would want to have been told.
A chat app is not another surface to build — it is the one already open on your phone. No second app to install, no account to make, no push service of ours in the middle: your bot, your chat, polled by your own machine.
Generate a link code in the app, send /link YOUR-CODE to your bot.
That chat is now bound to one agent, one session, one working folder —
chosen on the Mac, and not changeable from the chat.
Whatever the run costs — thirty-four fetches, a read, two files written — you get three messages: nothing for the first four seconds, one status line that edits itself, then the reply in plain text.
When the agent needs a human, the turn parks and the question appears as buttons in the chat and in the app at the same time. The first answer wins; every other surface shows how it was resolved.
The skills in that run are localstack — the open-source
suite localoy deploys by default: a sales pipeline from
/prospect-brief to /sales-retro that drafts outreach
and never sends it — a human sends every message — plus the SEO stack. The same
skills install into Claude Code, Codex and Hermes, and every one is readable at
github.com/localoy-ai/localstack.
The Mac has to be awake. localoy holds no power assertion — a machine that sleeps pauses its agents, and a lid you closed is a machine asleep. Leave it running and walk away; what you send while it is out is delivered when it polls again.
Telegram is the transport that ships today. Authority — bindings, allowlists, approvals — lives above the transport, so the next one inherits it instead of inventing its own idea of who may speak.
From your phone · in Telegram — an app you already have
A whole run, in four messages. Nothing for the first four seconds, one status line that edits itself however long the work takes, a permission you answer with a button, then the reply in plain text. No streaming, no narration — every line above is a format the daemon actually sends.
Apple Silicon and Intel builds. The tray app plus the daemon, in one dmg. macOS 12+.
One-click NSIS installer, x64. Installs per-user — no admin prompt. Windows 10+.
These builds are not signed. macOS blocks them on first open — “Apple cannot check it for malicious software” is that block, not a broken download. Three steps, once per install:
Windows. More info, then Run anyway.
Or clear the download flag yourself and skip all of it:
Latest build from main → Rebuilt on every change. Replaces a stable install in place.
The desktop app is only a window onto the daemon — everything runs in localoyd, so a VPS gets the exact same runtime the desktop gets. Static Go binaries, no dependencies, no container required. The permission model travels with the agent, which is what makes where it runs — this Mac, a box you rent, a cloud — a capacity decision rather than a safety one.