Using Karsa
Karsa runs as a local daemon (karsa-server) with a web UI at http://karsa.localhost:1688 (Karsa's own name on this machine: its sign-in cookie never goes to another program's port on localhost; Chrome and Firefox find it by themselves, Safari once karsa-enforce-security has run). Everything you do lives in projects, and every project is just plain files on disk — versioned in git, yours to keep, readable without Karsa at all. This page is the lay of the land.
The daemon and $KARSA_HOME
One daemon serves the whole machine. Its state lives under $KARSA_HOME (by default ~/.karsa): the project registry (projects.yaml), machine config (KARSA.md), and this manual (doc/). The daemon is a pure function of its --home flag — there is no hidden environment fallback, so a misconfigured launch fails loudly instead of pointing at the wrong data.
The Karsa System project is a special project whose project_home is $KARSA_HOME. Unlike a normal project it runs uncaged — it can manage, fix, create, and import your other projects, and install the tools Karsa depends on. Its home page is what you see at the root of the UI. It's yours alone (Who reaches what, below).
Projects
A project is a unit of work with its own data, its own agents, and its own cage. A new one has no agent yet: its Home shows a Get started card with three steps, each ticked when it's really done (install OpenCode on Mango Inference; link its Mango Inference key, one you have or a new one; pick a model, or a line's newest such as GLM's, and turn it on), then Chat. When another project already has one, Set up like … does all three in one tap, with a new agent of this project's own. The projects list marks a project with no agent yet. You create one by picking a template, or by describing the system you want and letting Karsa scaffold it — collections, automations, schedules — for your use case.
On disk a project lives under {project_home}/karsa/proj_*/:
artifact/— your collections (typed data: specs, notes, tables, files).sqldb/— application data (SQL tables backing your app).index.md— the project's home page; its body is yours to edit.
Every record is versioned in git per project. Run history (each fn run's exec and its log) stays on disk beside it but isn't versioned: it's runtime state, not the record. If you lose the registry, a project can be re-imported from its folder alone, because the folder is self-describing.
Removing, resetting, deleting and importing a project. When a project leaves (Remove keeps its folder, Reset deletes its Karsa records, Delete deletes the folder), its runs stop, its agents' and apps' OS users are retired and the files they made become yours, their keys are deleted, and its folder is closed to every agent. When one arrives (Import), its agents and apps get OS users and their folders again, as if made here, but no keys come with a folder: which key each app and agent uses is cleared, and you pick again. A folder whose agents are already in another project (a copy of it) is refused.
Who reaches what
Your server is yours, the way an iPhone has one owner: you open Settings, and each app keeps to its own sandbox.
- A server has one owner: you. Whoever installed it, and the one Karsa Cloud account it's linked to. There's no second owner, and no other person signs in to it: other people get a share of a project (below), and agents are users you made. An agent holds everything in its own project (
permission/full), which doesn't make it an owner.
- The System project is yours alone. It belongs to this server: it's never shared, and Karsa refuses to add any other person to it. What only you should reach lives in it or is guarded by it: the project list, every secret, the Karsa Cloud link, hosts and webhooks, sessions, server-wide apps and upgrades, the daemon's log, and the machine's setup (agents' OS users, and what they may reach beyond their project). If you install an agent harness there, its System agent is yours too: what the System project grants it is the whole server, so we don't recommend adding more.
- Server-wide steps go by the System project, whatever project a call comes through: creating, importing, renaming or deleting a project, linking Karsa Cloud, installing a server-wide app, reading the daemon's log. You hold everything there. An agent holds everything in its own project (
permission/full), and that's all it is: it can't change the server or other projects, and can't hand one of your keys to an app. The API reference marks these steps owner-only. - Each project has its own users and permissions. You, its agents and its apps' users each hold what that project grants them, op by op. The project list shows you every project, and an agent only the ones it's in.
- You share a project, never the server. The other person gets the collections you pick (read, or write too) and their events, and mounts them in a project on their own Karsa. They use them there with their own chats and agents; each call runs here, as them, within what you shared. Nobody works inside your server (Sharing a project, below).
Getting around
- A project opens on Home: what's new, then today.
- New: your unread notifications in the project, one row per chat (a task made in a chat counts as its chat) or thing: what it's about, the latest line, N more and when. Tap a chat's row to go to the page it's about with the chat beside it, at the first message you haven't read (a New line marks it). Reading it there settles it, here and in your Inbox. Nothing unread: All caught up.
- Today: today's tasks, as on the calendar: those with no time first, then by time. See tasks for the rest.
- Add task (the chat's New task form) and Get apps are at the top; an app that still needs a key, or a built-in part to settle, shows under Set up; the project's own write-up (its
index.md) is the About card at the end. On or off is in Advanced › Config. - A project's sidebar is five places: Home, Chat, Tasks, Apps, Files. Everything else (agents, runs, subscribers, fns, specs, search, events, config) is under Advanced, below them, closed until you open it.
- The sidebar lists the level above what's on screen, like iPad Settings. On a spec, it lists the specs; on an app's page, the app's pages (Home, Overview, its collections, Config, Function, Subscriber, Permission); on a project's page, the project's pages. The "‹" rows above go back up a level (Projects, the project, the collection), and the header is the level you're in.
- Every list is the same: tap anywhere on a row to open it; a record's details are laid out the same way everywhere.
- You, pinned at the bottom of the sidebar: Settings, the theme, the manual, the server log, and Sign out (on this machine, of this browser; from away, of this server).
- The app bar (☰, the project, the Inbox, Search) sits at the bottom on a phone, in reach of a thumb (Settings › On this device: Top or Bottom). It steps aside while you type, and on a chat, where the message box carries the ☰ instead.
- The project's name in the app bar goes to another project, or a page of this one. On a computer it turns into a search box (⌘K or Ctrl+K from anywhere). On a phone it opens a page of its own: every project is listed (the open one marked) with a box at the bottom like a chat's; type to narrow it to matching projects, this project's pages, and a search inside it, and tap one (or Go for the first). Back returns you to where you were. Switching keeps your place where it can: from a project's Config you land on the other's Config, from a chat on its chat list, from a record on that kind's list (or its home, if it has none).
- The chat button floats at the bottom right of every page (the round one), and asking about what's on screen is the quickest way to get anything done. Each page knows what it is: a record, a search and its words, a folder or file in Browse, a page of the manual, Settings.
- It opens the chat beside the page: on a phone, tap it or swipe in from the right edge; swipe it back to the right (or Back) to see the page again. On a wide screen it sits beside the page.
- Each page's chat is where you left it: the same chat, your half-typed message, how far you'd scrolled, even after a reload. The first time on a page, it's the latest chat started there, or a new one about the page.
- New chat starts another about the page; All chats here lists the page's chats: Topic (started on it, or about what it shows) and Mentioned (they name it).
- The same page, however you got there: what counts is what the page shows, not the extras in its address. A search for "invoice" is one page, whatever else follows in the address; a search for "receipts" is another. All of an app's pages (Home, Config, Permission) are about the app, so a chat started on its Permission page also shows up on its Home.
- Asking about the page: the agent answering knows which page you were on, so "what does this mean?" just works.
- No empty chats: a chat is made only when you send its first message, both here and with + New Chat.
- Outside a project (the manual, Settings, the server log), chats go to the Karsa System project, whose System agent answers questions about Karsa.
- Search (the magnifier in the app bar) opens in the same place as the chat, with Chat · Search to switch. Search Here (this page and its chats; on a chat, that chat), Chats, Files or the whole Project. Your recent searches show while the box is empty, on every device you use. To search somewhere else, go there and tap Search.
- A page's big title turns into a slim bar as you scroll down; scroll back to the top for the full one.
Collections
Collections are your typed data. Pick the shape that fits:
- Tabular data you'll filter, sort, and join → a Karsa SQL DB table, exposed as a collection.
- Files (CSV / PDF / images / datasets) → the Files collection: every file the project keeps (an upload, a file in a chat, what an agent saved), newest first, with a thumbnail for an image or a video. Tap one for its page. The folders on disk are Browse's.
- Notes, specs, memos → a markdown collection (one
.mdper resource, frontmatter + body).
Each markdown resource has YAML frontmatter (its typed fields) and a markdown body. The UI renders both with the shared frontmatter + markdown views; ids cross-link automatically.
Each collection's list has a filter bar: tap ⚲ Filter to filter on any of the collection's fields (a status, a time range, a name, …), then sort and limit; each filter is a pill you tap to change or ✕ to remove. </> shows the same filter as a short query you can type (status = 'failed' and started_at > -1d order by started_at desc). The filter lives in the page's address, so a filtered list can be bookmarked or shared. Execs and each fn's Run History have the same bar.
Chat — talk to your agents
Open Chat in a project and describe what you want. Your agents answer: each is an AI that works for you in this project, on an agent harness (OpenCode, open source; Claude Code; or Codex), on the model provider you pick: OpenCode on Mango Inference and Claude Code on Mango Inference include their harness and set it up for Mango. Installing one from Apps makes a starter agent, @opencode-mango (say), and makes it the default. Agents (doc_agents) walks through it all.
- A message that mentions no one goes to the chat's default agent. @mention agents to bring them in; several can work in one chat.
- The chat is the glue for a piece of work. An agent does what it can right there; work that has to outlive the conversation (later, retried, in the background, by another agent) becomes a task that carries the chat's id. The task queue hands each one to its assignee, or the default worker, and every move posts a one-line status here, with ids that link to the task and its run.
- A new task, through the chat: type
/in an empty chat box and pick New task, tap a time on the Tasks calendar, the round chat button on Tasks (it has a plus there), or Add task on Home. The box becomes a small form: what to do, and a When pill (tap it for the date and time; ✕ clears it: as soon as possible). Your agent makes the task, and the chat follows it from then on. A time already past asks first: Task must be created for a future time, Cancel or Immediate task. - The Tasks calendar on a phone: tap the day's date or the month to jump to a date; swipe the day sideways for the next or the one before; List · Hours shows it as a list or by the hour. In Hours, press and hold a task to drag it to another time (an outline shows its new time), or hold it against the screen's left or right edge to carry it a day at a time.
- While an agent works, one status line,
exec_… · thinking…, updates with each step; the exec id opens the run and its log. - No agent yet? Karsa says so in the chat, and how to add one (or run
karsa doctor).
Files in a chat. 📎 uploads a file and puts its id in your message; any file a message mentions shows under it, an image as a thumbnail, a video with ▶, anything else as a chip. Tap one to see it full screen (‹ › between a message's files). An agent shows you a file the same way: it uploads it to Files and puts the id in its reply.
Your inbox — what nudges you
You › Inbox (the row at the bottom of the sidebar) lists what nudged you, unread first, each with why (reply, mention, keyword: invoice, waits on you). A dot on the menu button and on You says there's something new. Opening a notification takes you to its chat or task, and opening that page settles it; Mark all read settles the rest. Show just the unread ones, your mentions, your keywords, or what waits on you with the chips at the top.
- What notifies you, by default (Replies & mentions): an agent's reply in a chat you're in (one you started or wrote in), a mention of you anywhere, a task for you, and always a task that's blocked on you or one that failed, and an agent asking to reach a folder.
- Change it on the Inbox page, under What notifies you, or for one chat or task with its 🔔 (it shows what's in force there and where that comes from). There are four levels:
- All new messages and updates;
- Replies & mentions;
- Mentions & keywords only;
- Muted.
Set one for everything, one per project, and one per chat or task. The most specific wins. Muted silences even a mention.
- Your keywords (invoice, Budi, urgent) notify you whenever one comes up in a message or a task you can see, in any project, even a chat you're not in. They match whole words, in any case.
- Several in a row from one chat collapse into one ("undefined new messages in “Qundefined invoices”") until you read it.
- Where it shows depends on where you are:
- On that very chat: nothing pops up. The message just arrives. Scrolled up, the arrow says how many are new, and a New line marks where you left off.
- Elsewhere in Karsa: a banner slides in at the top for a few seconds. Tap it to go there, or swipe it up.
- Karsa not open: your phone or computer gets a notification. If you're using Karsa on another device, it waits about a minute and only comes if you haven't read it there. What waits on you comes at once.
- The project's Home lists what's unread in it, one row per chat or thing, those waiting on you first: the same notifications as the Inbox, so reading one settles it in both.
- On your phone or this computer too: Settings › Notifications.
- Your agents can nudge you themselves (
karsa/notification/create): "The nightly import failed".
Apps ask before they use something
An app works inside its own folder freely. Anything beyond that (your project's records, the internet, saving a file to your device) it asks for, like an iPhone app: the first time it needs something, Karsa shows a sheet with what the app wants and the app's reason, and you tap Allow or Don't allow. Karsa remembers your answer. Printing (or saving as a PDF) is asked when you open the app, since it changes how the app's page is loaded. Every app has a Permission page listing what it asks for, with a switch for each, so you can change your mind any time.
Schedule, Browse, Search
- Schedule — a one-off ("run in undefined minutes") or a recurring job, so work happens without you sitting there.
- Browse — every file on disk under the project: text previews in place, images and video show (tap for full screen), audio plays.
- Search — full-text across everything the project holds.
Reaching Karsa from away — link the server to your Karsa Cloud account
Karsa answers two ways, and only two: on this machine (karsa.localhost:1688; programs, the CLI and agents use localhost:1688, with a token or the owner socket), and from anywhere through Karsa Cloud (its own https://…host.karsa.dev address). Not over your home network: another device on your Wi-Fi uses the Karsa Cloud address too, which is https. To get that address, the server joins your Karsa Cloud account.
Joining is interactive — the same shape as gh auth login or signing in to a CLI tool. You never type a credential into the server:
ABCD-1234. On the server, run karsa cloud link (or press Link to Karsa Cloud in the Remote app's page). The daemon proves it is itself using a key that never leaves the machine. cloud.karsa.app/link. It prints a short code and a URL — e.g. ABCD-1234 at cloud.karsa.app/link — and waits (the daemon finishes the join even if you close the terminal; karsa cloud status shows it). karsa cloud status. Open that URL on any device, sign in with your email and the undefined-digit code, then enter the short code. undefined. You are shown which server is asking — its id, name, and when it asked. Approve it. undefined. The server picks up the approval and is now linked; the console lists it, and the server shows the account it belongs to.
The approval step is deliberately on the console side, where you can see what you are admitting: a linked account has owner-level access to that server — every project, remotely. A server is linked to one account at a time: while it's linked, no other account can link it. Only you can unlink it, from either side: the console, signed in as the account it's linked to, or karsa cloud unlink --agree_relink_here_within_30_days on the machine, which tells cloud first so your remote access really stops. Unlinking ends the shares of its projects, and asks you to agree that for undefined days it can be linked again to the same account only (karsa cloud link on the machine). It stays in that account's list as Unlinked for those undefined days, counting toward the account's servers. Then it leaves the list, and linking it again, to any account, takes an identity reset (below). A lost account can never strand a server: on the machine, the karsa CLI is you (karsa cloud unlink --local_only works with cloud gone). See the next section for signing in.
Unlinking, and moving a server to another account
A server belongs to the account it first linked to. Karsa Cloud knows it by the key it first asked to link with, which comes from a secret in this machine's keychain, so only this machine can link it again, and only to that account, within undefined days of an unlink. Someone who gets into your Karsa Cloud account can open your server as you until you sign them out (Sign out everywhere in the console), but can't take it to their account.
Moving a server to another account, or linking it again more than undefined days after an unlink, takes a new identity: unlink it, then on the machine run karsa identity reset. It becomes a new server to Karsa Cloud, with a new id, a new key and a new address, and no shares; your projects and secrets stay (re-encrypted with the new key). Only the person who installed Karsa can run it, at the machine: there's no way to do it remotely. Local browser sessions end (karsa open --local signs you in again). It can't be undone.
If the keychain loses the key (say it was reset), Karsa won't start, rather than quietly making a new key: it says to restore the keychain, or to run karsa identity reset. Then secrets encrypted with the lost key can't be recovered: the reset lists them, to set again, and you unlink the old server in the console.
Karsa Cloud emails you about every sign-in to your account, every unlink of one of your servers, and every share you make, each saying what to do if it wasn't you.
Give the server a name and a note for the console with karsa cloud update --display_name "Studio Mac" --description "the desk machine" (or Edit in the console). Cloud also slows down guessing everywhere it matters. Asking for sign-in codes again and again waits a little longer each time (undefined, undefined, then undefined minutes), never longer than a code lasts, so there's always a code that works in your inbox: use the latest one. Three wrong tries and that code stops working on your network (ask for a new one); join codes back off too.
Signing in
Every request to Karsa needs a token, from this machine too. Who you are comes from your OS user, not a password:
- In a terminal, the
karsaCLI is you: it talks to Karsa over a socket ($KARSA_HOME/.run/karsa.sock) that only the OS user who runs Karsa can open. Nothing to sign in to, and no token on disk. Karsa refuses to start as another OS user; set it up as root, and root it stays. - In a browser, run
karsa open(orkarsa open /project/<id>to land there). Linked to Karsa Cloud with the tunnel on (karsa host up), it opens your server's address, the same way in as from anywhere (below), so a browser that hasn't opened it yet is asked once. Otherwise, or withkarsa open --local(say, without internet), it opens this machine with a one-time link (good once, for undefined minutes) that signs that browser in for undefined days. A page on this machine without a session goes to the sign-in page, which says the same. Sign out on the You menu ends it. On this machine's local address, the Karsa logo wears a small amber open lock. Tap it for Stay here or, depending on where your server stands, Open secured address (the same page at your server's own address), Turn on secured address (linked, but the address isn't set up or its tunnel is off: one tap does both), or Set up secured address (not linked yet: Settings links it to your Karsa Cloud account, then turns the address on). Settings › Karsa Cloud shows the address too. The local address is fine on its own machine; the secured one keeps Karsa apart from the other apps on it, and works from anywhere. - Linked to Karsa Cloud (
karsa cloud link, or Link to Karsa Cloud in Settings), you can also sign in with that account on this machine: on the sign-in page press Sign in with Karsa Cloud, open the link it shows, sign in with your email code, and approve the code (only the linked account can). - From anywhere, open your server's address (the console lists it under Open it). The first time in a browser, Karsa Cloud signs you in (your email and a undefined-digit code) and asks Open “Studio Mac” as you?, naming the account you're signed in with. Tap Open, and that browser opens your server from then on, with no more questions. Each of your servers asks once, and each is signed into on its own.
- Signing out. Sign out on the You menu, from away, signs that browser out of this server only: the next visit asks again, and your other servers stay signed in. Sign out in the Karsa Cloud console signs you out of Karsa Cloud and of every server you opened in that browser. Either way, pages already open there stop within seconds: a chat that was live closes. A Karsa Cloud sign-in lasts undefined days, used or not; then you sign in again, and each server asks again.
- When your server is offline (asleep, no network), its address says Connecting to your server… and keeps trying on its own; when Karsa Cloud itself can't be reached, it says that instead. Only you see either: anyone without your sign-in is just asked to sign in.
- Agents and Karsa itself carry their own tokens: an agent's run has its
$KARSA_API_TOKEN, so thekarsaCLI in an agent's shell acts as that agent, never as you.
Who the karsa CLI is
The CLI has no login. Like an iPhone's Settings, which only you open while each app keeps to its own sandbox, Karsa tells who's calling from how the call arrives:
| Who runs karsa | How it reaches Karsa | Who Karsa takes it for | |---|---|---| | You, the OS user who runs Karsa | the owner socket, $KARSA_HOME/.run/karsa.sock, in a folder only you can open | the owner: everything, owner-only steps included | | An agent, during its run | its run's token ($KARSA_API_TOKEN, also inside $KARSA_API_BASE) | that agent: what it may do, never more | | An agent's or app's OS user outside a run, another account on this Mac, any other program | nothing: it can't open the socket's folder (your home and Karsa's are yours only), and a call without a token is refused (401) | no one |
- Owner-only steps refuse everyone else, an agent's token included: picking which secret an app's key comes from (
karsa app set-key), signing a browser in (karsa open), making a token for someone else. The API reference marks each one owner-only. An agent may pick a key for itself, but only from its own secrets (karsa agent set-key), never one of yours. - A token is its user. A call that claims to be someone else (the
x-karsa-actorheader) is believed only over the owner socket; with a token it's ignored. An agent can't pass as you by saying so. - The socket is the proof. No password and no token on disk: only your OS user (and root, which can do anything on the machine) can open its folder, and Karsa won't start as another OS user.
What tokens don't stop: anything running as you. They keep out other users of this machine, other websites open in your browser, your network, and strangers on the tunnel. A program running as you can still read what you can, Karsa's keychain items included. That's why your agents don't run as you.
Notifications on your phone
Karsa can buzz your phone: an agent replied in one of your chats, a task needs you, an app asks permission. A tap opens that chat or task in Karsa.
- On an iPhone or iPad, notifications come only to Karsa on your Home Screen (iOS undefined.undefined or later). Open your server's address in Safari, tap Share, then Add to Home Screen. Open Karsa from its new icon (it has its own window, and you sign in once there), then turn notifications on in its Settings.
- Turn them on in Settings › Notifications › Notify me on this device, on each device you want them on; your device asks you to allow them.
- Where to reach you (Settings › Notifications) lists each place beyond Karsa's own page. Today that's a device you turned notifications on for; email and the places apps offer (SMS, Telegram, Slack) come later. Each one has:
- On or off;
- What goes there: Everything, Mentions & waiting on you, or Waiting on you only (say, everything to your phone, only what waits on you to your Mac);
- Send a test and Remove.
Your Inbox and the banner in Karsa always get what your rules allow.
- Where it works:
- Your phone, at your server's address (
https://…host.karsa.dev): yes. - This Mac's own browser (
karsa.localhost:1688): yes in Chrome. - A dev server on your phone: only once its certificate authority is installed and fully trusted (Settings › General › About › Certificate Trust Settings). "Proceed anyway" shows the pages, but no notifications.
- Your server sends them itself, through Apple's or Google's push service; Karsa Cloud isn't in the path. Your phone gets them wherever it is, but your Mac has to be awake to send them, and a tap opens Karsa, which loads once your phone can reach your server.
- While you're looking at a chat, it doesn't buzz you about it.
Agents' own OS users
Every agent, and every app running on its own (its warm fns, its service), runs as a hidden OS user of its own, _karsa_<its id>: no login, no password, not in the login window. It can't read your private files, your logins, your keychain or another agent's folder; it reads its project, and an agent writes it. You stay in control of everything they make.
You run one script, once, with sudo. Making OS users takes root, and Karsa never asks for your password. The installer puts karsa-enforce-security beside karsa (in ~/.local/bin): a short shell script that holds every root step Karsa has, and nothing else. Read it, then run it from your own account:
less ~/.local/bin/karsa-enforce-security
sudo ~/.local/bin/karsa-enforce-security
Use its full path: on Linux, sudo doesn't look in ~/.local/bin. It installs two root-owned files, adds Karsa's address to /etc/hosts, and does nothing else:
/usr/local/libexec/karsa-run-as: a short script that only makes, retires, deletes and lists_karsa_*users. Retiring one (its agent or its project went) stops its processes, makes the files it made in its project yours (only root can give a file to someone else), and takes it out of Karsa's group, so nothing runs as it. On a Mac it also starts each agent's and app's processes in that user's own session, like a background service's, not inside yours: your login session lends its window and app services to you only, and an app that needs them (a browser, even a headless one) crashes as another user there. It switches to that user before anything runs: never as root, never as you./etc/sudoers.d/karsa: a rule that lets only your OS user, with no password, run that script and run commands as those users./etc/hosts:127.0.0.1 karsa.localhostand::1 karsa.localhost, so Safari and the OS openhttp://karsa.localhost:1688too (Chrome and Firefox do by themselves). Karsa's sign-in lives on that name, so another program serving on a port oflocalhost(an agent's dev server) never gets it.
You run it again only when a Karsa release changes it, not for each app or agent: what any app runs uses the same two files. The update banner then gives you the exact line, that release's own script (sudo sh ~/.karsa/packages/.staging/<version>/karsa-enforce-security), and Karsa knows when it's done by hashing the installed helper: no button to press (see Upgrading).
It refuses to install if anyone but root could change a folder above that script, because whoever could change it could become root; it says how to fix that. karsa-enforce-security --show prints both files first. --check (no root) says whether they're installed, current and working. sudo ~/.local/bin/karsa-enforce-security --remove deletes every _karsa_* user and both files. Add -v to any of them to see each step and each command before it runs.
karsa start checks it every time, and won't start while it's missing or from another version of Karsa (a downloaded next release's counts too, so a restart between its sudo step and Install still starts): it says why and prints the two lines above. From then on Karsa makes the users as agents and apps come, and retires them as they go. A macOS update can change them (undefined.undefined.karsa status set their group back to staff): Karsa puts each back as it made it at its next start. Retired users are deleted when you run the script again: macOS asks before a user is deleted ("Terminal would like to administer your computer"), so Karsa leaves that to you, at the Terminal; click OK. There's no command that clicks it for you (macOS keeps that answer for the person). Over SSH it doesn't ask when Remote Login allows full disk access for remote users (System Settings → General → Sharing → Remote Login ⓘ): run it from your phone's SSH app and nothing pops up at the Mac. karsa status says how many are waiting. If the files go away while Karsa runs, agents and apps stop running (never as you instead), and karsa status, karsa doctor, Settings, the You menu and each refused turn say so.
Your home and Karsa's home become yours only (undefined). On a Mac your home is open to other OS users by default (undefined, so other accounts can reach your Public folder), and many tools leave files anyone can read: ~/.aws/credentials and ~/.docker/config.json often are. Agents still pass through your home to reach a project in it, so Karsa also closes everything else in the folders they pass through to them (an ACL entry naming Karsa's agents only; it changes nothing for you or anyone else), and whatever you add there later. The programs on your PATH in your home (~/.deno/bin, ~/.opencode/bin, a Homebrew installed there with everything it needs) stay reachable, so a harness still finds them; other toolchains in your home that aren't on your PATH aren't the agent's. Karsa checks this at every start. If you share your home on purpose (your Public folder with another account on this Mac, a web server serving ~/Sites), it can't stay open yet.
Karsa itself is the OS user who first started it on this home; it won't start as anyone else (set it up as root, and root it stays).
What each agent can reach
An agent always reads and writes its project and its own folder. Beyond them, each agent has a file-access list: the paths on this machine it may read, or write. Think of an iPhone: the agent's OS user is its sandbox, its list is Settings → Privacy, and the card it shows you is the "Allow access?" prompt. You change the list on the agent's page (File access) or with karsa file-access. A change applies at once, and Karsa applies the whole list again at every start, so the list is what's real.
Two presets. A preset is only the starting list:
- Auto (the default): it reads your desktop, documents and downloads folders, as this machine names them. On a Mac that's
~/Desktop,~/Documentsand~/Downloads. On Linux it's the folders your desktop set up (~/.config/user-dirs.dirs;~/Schreibtischin German). A missing one is left out, and Settings shows what Auto means on this machine. - Manual: nothing beyond its project until you allow it.
Either way, writing outside its project always asks. Set the preset new agents start with in Settings (New agents read). Switch one agent's on its page: the preset's entries change, and yours and its answers stay.
When it's blocked, it asks you. For example, under Manual: "@analyst wants to read ~/Downloads/qundefined-board.pdf: to summarise it." A card shows on whatever Karsa page you have open, and a line goes in its chat. You answer:
- Once: until its next turn ends.
- Always: the file, or the whole folder holding it (tick it), goes on its list.
- Never: a deny goes on its list, so it stops asking.
Your answer is posted in its chat, which wakes it to carry on.
Hidden files stay closed. In a folder an agent reads, every file or folder whose name starts with a dot (.env, .git) is closed to agents. Karsa does this when you grant the folder and at every start; one made in between stays readable until the next start. Hidden folders aren't the only place secrets live (on a Mac, ~/Library holds browser cookies and app tokens), which is why the presets list folders to open rather than your home minus what's hidden.
Some paths are never asked for from a chat: your keys (~/.ssh, ~/.aws, ~/.gnupg) and Karsa's own home everywhere. On a Mac, all of ~/Library (browser cookies, Mail, Messages, app logins) except your cloud drives in it (iCloud Drive, ~/Library/CloudStorage). On Linux, ~/.config, ~/.local/share and ~/.mozilla. An agent's ask for one is refused. You can still add one yourself on its page, after a warning: anything an agent reads can tell it to ask, and one quick tap would hand over your keys.
You can set it up by asking an agent. Tell it in a chat, for example "give yourself my Code folder and my Dropbox". It can see the names of the folders at the top of your home and your cloud drives, but not what's in them. It asks for all of them in one card: tick the ones you mean, then Always or Once. It can't grant itself anything.
Each entry shows what the machine really allows. Karsa opens the path as the agent's OS user:
- applied: it can.
- blocked by macOS: macOS's privacy protection refuses it, although Karsa's entry is there.
- missing: the path doesn't exist.
- not yours: the file isn't yours, so Karsa can't grant it.
- not set up: agents don't have their own OS users yet.
karsa file-access list --agent analyst
karsa file-access create --agent analyst --path ~/Pictures/receipts --access read
karsa file-access delete <id>
karsa file-access preset --agent analyst --preset manual
karsa file-access default --preset manual # what new agents start with
karsa file-access places --agent analyst # what's there to name: folders and cloud drives, by name
karsa file-access asks # the cards waiting for you
karsa file-access answer --request <fask_…> --answer always --scope folder
Commands aren't limited by name. Any interpreter (python3 -c, sh) runs anything, and an agent can write a program into its project and run it, so a list of allowed commands would be a wall with a door. What a command can do is limited instead: what it can read and write (this list), which keys it holds (a secret you passed it: without a GitHub token, git push fails whatever the command), and, later, its network.
Webhooks — ingress URLs that feed a project
Once the server is linked, it can have ingress URLs: public webhook addresses that services like GitHub, Stripe or Slack post to. Each delivery becomes a karsa/ext/ingress event on one project's bus, where a subscriber picks it up.
karsa ingress create --display_name GitHub --project <id|slug> # prints the URL once
karsa ingress sync # fresh from cloud: name, URL (secret hidden), project
karsa ingress update <id> --project <id|slug> # where it lands (--project null: the System project)
karsa ingress rotate <id> # new secret; the old URL stops within seconds
karsa ingress delete <id>
- An ingress is a Karsa Cloud resource, not a path on your server:
https://ingress.karsa.dev/{uuid}/{secret}/. Its id is its own, so a URL pasted into a third party says nothing about your server. Each points at exactly one server; a personal account has up to undefined. - The secret is optional and shown once. Without one, anyone who has the URL can post to it. Cloud keeps only a hash; lose the URL and rotate.
- Cloud doesn't know about projects. Which project an ingress feeds is set on the server (
assign), so the console shows only the name and note you gave it. An ingress made in the console lands in the System project until you assign it. - Same thing, other surfaces: the Remote app's page (Ingress panel), the API/MCP (
karsa/ingress/create,sync,assign,update,rotate,delete), and the console (create, edit, rotate, delete per server). - What arrives:
{ingress_id, method, path, query, headers, content_type, body, body_encoding}. Cookies andauthorizationare stripped; provider signature headers (x-hub-signature-256,stripe-signature, …) pass through so your subscriber can verify them. A body is at most undefined KiB; a bigger one is refused (413) and nothing arrives.
There is exactly one way to join a server; a paste-a-token variant for headless machines is a possible future addition when servers run as fleets, not something to hunt for today.
Sharing a project
Share a project the way you share a photo album on an iPhone: the other person sees what you picked, in their own Photos, and nothing else of your phone. Here they use what you share from their own Karsa: their chats and agents work with it there, and every call runs on your server, as them, within what you gave. Nobody works inside your server, and the System project is never shared. Both servers need to be linked to Karsa Cloud.
Sharing (the project's Sharing page, Share…): an email, an optional note, and per collection what they may do. Only the project's own collections can be shared (its template's, its apps', yours), never Karsa's (chats, tasks, files, specs), and never an op that runs your code (a collection's api-filter). A new share is read: list, get, and the collection's events. Read & write adds create, update and delete. Karsa Cloud emails them its link, and it's theirs at once: there's nothing to accept. The Sharing page says when they first use it.
- Share your Deals with a partner, read only: their agent finds and summarises your deals in their own project, and can't add one.
- Share Leads with write: their intake form adds leads straight into yours.
Who it is, here. Each share is a person in the project (its Users), named by their email. Whatever they send, by hand or through one of their agents, arrives as that person: you see who wrote a record (_created_by), not which of their agents did. Their writes wake your subscribers like anyone's, so what they write is data for your agents, never instructions (like a webhook's body).
Ending it is yours: End on the Sharing page. Their next call is refused, and their mounts say the share ended. They can only Dismiss it from their list in Karsa Cloud (with a few seconds to undo): it keeps working for them, and you aren't told.
When something is shared with you: the email's link opens Karsa Cloud (cloud.karsa.app/shared/…, also under Shared with you, with who shared it, when, and their note; Dismiss takes one off that list, and its link and your mounts still work). It asks which of your servers to mount it on, your default selected (the first you linked; Make default on a server's page changes it); with one server it goes straight there. On your server, its page lists your projects, the ones that already mount it first: tap one to open it, another to mount it there, or New project… for a blank one.
Mounting (the mount page; also a project's Sharing › Mount…): pick the source (what's shared with you, or another of your projects, from one list), then which of its collections, each with its name here (the source's, or one you type). Each becomes a collection here, listed like your own, whose only file is its index.md: nothing is copied, and every call runs at its source.
- A name you already use here gets four more characters (
deals-x7k2), unless you pick another. - Who here may use it is this project's choice, like any collection's.
- Its events come too (
karsa/<name>/created,updated,deleted), so a subscriber here can react to a shared collection. Your server keeps its place, so after being offline it catches up. - If its server is offline, a call says
unavailable; if the share ended,ended. The rest of your project carries on. Unmount removes it here and changes nothing at the source. - Only you mount: an
index.mdwritten by hand (by an agent, say) reaches nothing.
Updates
Karsa keeps itself up to date. The Updater, an app in the System project, checks the signed release list every day at --download:download your time, downloads a new release, and installs it by itself (or, in mode download, waits for you to tap Install); each project's apps update with it. A banner on every page says where it is, karsa status says it in one line, and karsa upgrade checks, --downloads, --installs and --undoes by hand. Installing restarts Karsa: running agent turns carry over, open browser tabs too unless the Browser app updates with it, and if the new version doesn't come up, the previous one is put back. To change when it checks, or to stop it installing by itself, ask your System agent, or read Upgrading.
Your projects' built-in parts follow along (spec_undefinedMundefinedYARFKZAD). What a project's template made when the project was made (its collections' descriptors, the task queue's fns and subscribers, the repeating tasks' maker) is updated when Karsa starts after an update, so an old project gets what a new one would. Your changes stay:
- a part you didn't change takes the new version;
- one you both changed is merged (a field you set, or your lines in a fn, stay beside the new ones);
- one you changed where the new version changed the same thing is left as yours, and the project's Home says so under Set up: "Karsa couldn't update “task_queue”: you changed it too", with Take Karsa's or Keep mine.
A part the template no longer has goes, unless you changed it. Each project keeps what was stamped in karsa/proj_*/.template/ (the base for the next merge), and the project's history gets a "template update: …" commit. To run it by hand, or see what it would do: karsa project template-update --id <project> --dry_run true (the owner only).
Where to go next
When a task produces data, read Data and storage — it's the rule for where each shape of output lives (assets, sqldb + collections, docdb docs) and the rpc API conventions for putting it there. When the task shifts from "organize data" to "build an app," read Building a Karsa app — the app belongs inside the project, on the Karsa SDK, not in a scratch directory wired to an outside service. If you find yourself reaching outside, read Anti-patterns first.