# How Optimus works: an agent that uses your computer

> Inside Optimus, the Mercury plugin that sees your screen, moves the mouse and types: its processes, how it looks and acts, voice control, and the safety rails.

Published 2026-10-24 by Salman Qureshi. Tags: engineering, product.
Canonical: https://mercuryagent.sh/blog/how-optimus-works

Optimus is the part of Mercury that uses your computer. It looks at the screen, moves the pointer, clicks, types and opens apps, the way you would. You can ask for something in chat, or hold a key and say it out loud. It runs on macOS, Windows and Linux, and it ships as a plugin, so nobody gets it unless they install it.

This post is about how it works underneath: the processes involved, how a screenshot becomes a click, why it says "sent" instead of "done", how voice reaches Mercury, and what stops it when you want it stopped. The current version is Optimus 0.4.8.

## Mercury is the brain, Optimus is the hands

Mercury does the thinking. It holds the conversation, picks the model, decides the next step and asks you for permission. Optimus has no model of its own for deciding what to do. It is a tool provider: it gives Mercury eyes on the screen and hands on the mouse and keyboard, and it reports back what happened.

That split comes from the plugin rules we set in [ADR-025](https://github.com/cosmicstack-labs/mercury-agent/blob/main/DECISIONS.md) for Mercury 1.4. Features that only some people want ship as plugins, and a broken plugin must never take Mercury down. Optimus is one Rust binary, `mercury-optimus`. Mercury starts it as a child process and talks to it over MCP: JSON-RPC 2.0, one message per line, on stdin and stdout. Logs go to stderr so they can never corrupt the protocol.

Mercury supervises it like any other out-of-process plugin:

- Every request has a timeout, and a hung plugin is killed and restarted.
- Crashes restart it with backoff from 1 second up to 60.
- More than five crashes in ten minutes turns the plugin off, with a message saying why.
- The child gets a minimal environment plus only the secrets its manifest declared.

Install it from any chat:

```text
/plugins install optimus
```

The catalog entry pins each platform build to a SHA-256 checksum. If the download doesn't match, Mercury says "Checksum mismatch" and installs nothing. Builds exist for macOS on Apple silicon and Intel, Windows x64, and Linux x64 and arm64.

## Seven tools

Mercury shows the model seven tools, namespaced as `optimus__look`, `optimus__act` and so on:

| Tool | What it does |
|---|---|
| `look` | Takes a screenshot of one display, or zooms into part of the last one |
| `act` | Runs up to 60 mouse and keyboard actions in a row, then looks again |
| `act_consequential` | The same as `act`, for steps that commit something: send, buy, delete, submit |
| `open` | Opens an app, a URL or a file |
| `windows` | Lists up to 30 visible windows, front first |
| `permissions` | Checks or requests the OS permissions Optimus needs |
| `listen` | Starts a voice session |

An action is one of `click`, `double_click`, `triple_click`, `right_click`, `middle_click`, `move`, `drag`, `draw`, `mouse_down`, `mouse_up`, `type`, `paste`, `key`, `scroll` or `wait`.

There is no accessibility-tree tool and no OCR. Optimus works from pixels, like a person looking at the screen. That keeps it the same on every OS and in every app, including the ones with poor accessibility support.

## How it sees

`look` captures the display and fits the image into 1280×800, which is the size computer-use models are trained on. It is resized and sent as a JPEG at quality 80. A 2880×1800 Retina screen becomes 1280×800, and a 4K screen becomes 1280×720.

The model never deals with Retina scaling or monitor layout. It clicks in the pixels of the screenshot it was given, and Optimus maps those back to real screen coordinates:

```rust title="plugins/optimus/companion/src/ops.rs"
let sx = self.x as f64 + px * self.w as f64 / self.img_w as f64;
let sy = self.y as f64 + py * self.h as f64 / self.img_h as f64;
Ok((sx.round() as i32, sy.round() as i32))
```

`self.x` and `self.y` are the monitor's origin, so a second display to the left, at x = -1920, works without the model knowing it exists. A coordinate outside the screenshot is an error rather than a guess. On Windows the process declares itself per-monitor DPI aware, so it sees real pixels everywhere.

By default `look` captures the display under the pointer. When text is too small to read, the model can ask for a zoom: Optimus crops the full-resolution capture and fits that region into the same box, but coordinates still refer to the full screenshot. Each look also reports the display number, the mouse position and the active window and its title.

On the Mercury side, images go only into what the model receives for that step. Logs, chat history and the learning engine never carry the base64. If the model can't take images, Mercury drops the picture and tells the model so, instead of failing.

## How it acts

Most input goes through the `enigo` crate. Optimus adds the parts that make it behave like a person and land reliably.

**The pointer glides.** It follows a curve with a small random bow of 4 to 12 percent of the distance, eased in and out, and pauses briefly after arriving. In the default Natural pace a move takes 200 to 750 ms depending on distance. Fast pace cuts that to 70 to 220 ms.

**Typing is key by key.** Natural pace waits 28 to 85 ms per key, a little longer after spaces and punctuation, and occasionally pauses as if thinking. One `type` action holds up to 3,000 characters. `paste` is there for long text: it saves your clipboard, pastes, and puts your clipboard back.

**Scrolling uses each OS's own API.**
- macOS: pixel-precise `CGEvent` scroll events, spread over eased steps.
- Windows: `SendInput` with wheel events in small increments.
- Linux: whole notches, which is what X11 offers.

**`open` forgives app names.** On macOS, "Chrome" finds "Google Chrome". If nothing matches, the reply lists the installed apps so the model can try again. Windows uses `start`; Linux uses `xdg-open` for files and URLs and `gtk-launch` for apps.

## Sent is not done

An early build taught us something uncomfortable. Its logs showed 29 `act` calls rejected because the model sent its steps in a shape Optimus couldn't read. Nothing happened on screen, and Mercury reported success anyway. A model that is told "your call went through" tends to believe the job is done.

So every `act` reply now says exactly what it can vouch for:

```rust title="plugins/optimus/companion/src/ops.rs"
// "Sent", not "done": an input reaching the screen doesn't mean it had the intended effect.
let mut text = if done.is_empty() { "Nothing was sent.".to_string() } else { format!("Sent: {}.", done.join("; ")) };
if let Some(s) = &stopped {
    text = format!("FAILED: {s} {text}");
}
text.push_str(" This only means the input was sent: check the screenshot below to see whether it actually worked before saying so.");
```

After the actions run, Optimus waits half a second for the screen to settle, takes a fresh screenshot and attaches it. Mercury's desktop guidance tells the model to look after each step, check it worked, and never claim it is finished without a fresh look. Actions in a batch run in order and stop at the first failure.

The action parser is deliberately lenient. Models send actions as JSON strings, double-encoded JSON, single-quoted pseudo-JSON, inside code fences, or as numbered objects. Optimus accepts all of them, because rejecting a well-meant call is how the 29 failures happened.

## Talking to it

Optimus can also be driven by voice. Press the talk key, `ctrl+alt+m` by default, and speak. A plain letter can't be the talk key, because it would fire while you type.

The key is caught differently on each OS:
- macOS: an event tap that swallows the key, so it never reaches the app you're in.
- Windows: `RegisterHotKey`.
- Linux: an X11 key grab, registered with and without CapsLock and NumLock.

There are two ways to run a voice session:

- **Speech to speech.** A realtime voice model listens and talks: OpenAI Realtime by default, or Azure, Grok, ElevenLabs, Qwen-Omni or Gemini Live.
- **Your own Mercury model.** Speech-to-text with Deepgram, AssemblyAI, ElevenLabs or Sarvam, then your Mercury model, then text-to-speech with Cartesia, ElevenLabs, MiniMax, Sarvam or Deepgram.

Either way, the voice model doesn't control the computer itself. It has three tools: `ask_mercury`, `answer_approval` and `end_conversation`. When you ask for something, it hands the request to Mercury, which runs a normal turn with the same tools and rules as chat. If that turn takes longer than 90 seconds, Mercury says it will send the answer to the chat instead of keeping you waiting on the line.

While Mercury works, Optimus speaks a short line for each step ("Opening Safari", "Typing") and plays a soft tick, quiet enough not to trigger its own microphone. With speakers, the mic is silenced while Mercury talks so it doesn't hear itself. With headphones you can turn on barge-in and interrupt by speaking. A session ends after 60 seconds of silence, or after 30 minutes.

The status row at the bottom of Mercury's terminal shows what Optimus is doing: "🎙 Optimus listening", "🖱 Optimus working" or "🔇 Optimus muted". When the row is short on space, that segment is the last one dropped.

## Why macOS gets its own app

macOS charges Accessibility and Screen Recording permissions to the app that launched a process. If Mercury started Optimus from your terminal, the grant would go to Terminal and cover every script you ever run there. We didn't want that.

So on macOS the plugin builds `Optimus.app` from its own binary. It has no Dock icon, and it signs the app with a certificate created on your Mac, with no Apple account involved. It launches through LaunchServices, so the permissions belong to Optimus alone. The plugin and the app talk over a Unix socket, readable only by you.

Two details make this survive real use:

- **Updates keep your permissions.** macOS identifies a signed app by "this bundle id, signed by this certificate", not by the binary's hash. The certificate stays the same across updates, so you grant permissions once.
- **Your keychain is never touched.** Signing uses a throwaway keychain that exists only for the `codesign` call. The app is rebuilt only when the version or binary changes, or when `codesign --verify` fails.

LaunchServices starts the app with launchd's environment, not Mercury's. So the first message on the socket hands over the variables the app needs, such as API keys for voice. If the app quits, for example after you change a permission, the plugin relaunches it and retries once.

## The safety rails

Optimus is off until you turn it on. Until then, every tool except `permissions` answers "Optimus is off". Install shows a warning before you confirm: "Controls this computer: it can see your screen, move the mouse and type, in any app."

**Committing steps ask first.** `act_consequential` is the one tool marked as not read-only, so in Ask Me mode Mercury asks before each send, purchase, delete or submit. In Allow All mode it doesn't ask. That is the point of Allow All, so choose it knowingly.

**Voice approvals need your voice.** When Mercury asks for approval during a voice session, a "yes" counts only if your own transcribed words contain one after the question. If the voice model claims you agreed and the transcript shows nothing, the answer is "Not approved".

**You can always stop it.**
- **Say "stop", or press the talk key while it works.** Every screen action halts at its next step, new ones are refused for 8 seconds, and Mercury cancels the task. The stop words include "cancel", "ruko" and "bas".
- **Grab the mouse.** If you move the pointer more than 12 pixels from where Optimus left it, it stops and hands control back. It checks between actions, during glides, and every 12 typed characters.

**It shows when it's driving.** A glowing ring follows the pointer with the label "Mercury · Optimus is driving". On macOS and Windows it is excluded from screen capture, so the model never sees it. On X11 it hides during capture.

**Some things are instructions, not walls.** The tool descriptions tell the model never to type passwords, card numbers or 2FA codes, and to decline games with anti-cheat. Those are rules Mercury follows, not technical blocks, and the docs say so.

## Platform limits

| Platform | What works | What doesn't |
|---|---|---|
| macOS 12+ | Everything, after granting Accessibility and Screen Recording | macOS asks again for Screen Recording about once a month |
| Windows | Everything, nothing to grant | Windows running as administrator, such as Task Manager or installers |
| Linux X11 | Everything | Paste needs `xclip` |
| Linux Wayland | Screenshots, through the desktop's screen-sharing prompt | Input and the talk key reach only XWayland apps; no overlay |

The 0.4.8 release fixed a Linux crash at start, "error while loading shared libraries: libwayland-client.so.0", on machines without Wayland installed. Optimus now loads that library only when needed. A small launcher adds bundled copies of any system library the machine lacks, and system libraries win whenever they exist. It shipped as a plugin-only update, so nobody had to upgrade Mercury.

The [Optimus docs](/docs/integrations/optimus) cover setup, every setting and the voice providers. The source is in the Mercury repository under `plugins/optimus`:

https://github.com/cosmicstack-labs/mercury-agent


---

# Mercury 1.4: plugins, WhatsApp calls, and an agent at your desk

> Mercury 1.4 adds isolated plugins and two official ones: WhatsApp chat with voice calls both ways, and Optimus, which sees your screen and uses your mouse.

Published 2026-10-23 by Salman Qureshi. Tags: news, releases, product.
Canonical: https://mercuryagent.sh/blog/mercury-1-4

Mercury 1.4, which we are calling Pluggy Mercury, is out. It adds a plugin system, and two official plugins built on it. The WhatsApp plugin lets you chat with Mercury and talk to it by voice call: you can call it, and it can call you. Optimus lets Mercury use your computer. It looks at the screen, moves the pointer and types the way a person does, and you can drive it by chat or by pressing a key and talking.

This is the first stable release since 1.3.8. The [release notes](/docs/releases/1.4.0) have the full list. This post is about why we built it this way.

## Why plugins

WhatsApp calling needs a WhatsApp protocol stack, a realtime audio pipeline and a handful of voice providers. Computer control needs screen capture, synthetic input and, on macOS, its own signed app bundle. Many people who run Mercury want neither. Putting both into the single Mercury binary would have made the download bigger for everyone and put more native code into every install.

So we wrote down a rule in [ADR-025](https://github.com/cosmicstack-labs/mercury-agent/blob/main/DECISIONS.md): features only some people want ship as plugins, and a bad plugin must never take Mercury down with it. The work started in [#203](https://github.com/cosmicstack-labs/mercury-agent/issues/203).

You manage plugins from any chat:

```text
/plugins install whatsapp
/plugins disable whatsapp
/plugins update
/plugins rollback whatsapp
/plugins uninstall whatsapp
```

The same commands work in a shell as `mercury plugins ...`, and in the dashboard under Configure, then Plugins. The catalog lives at `https://mercuryagent.sh/plugins/catalog.json`. Every entry pins a version to a SHA-256 checksum, and Mercury checks the download against it before installing anything.

## How a plugin is kept at arm's length

Both official plugins are out-of-process. Each is a separate Rust program that Mercury starts and talks to over stdio using MCP, plus a small channel extension for chat and voice. That boundary does most of the work:

- **A crash is the plugin's problem.** Mercury restarts it. After five failures in a row, or too many crashes, the plugin is turned off and you get a message saying why.
- **It gets only what it declared.** The manifest lists the network hosts a plugin may reach and the secrets it may read. Install shows you that list before you confirm. A plugin that controls the computer carries a new `permissions.desktop` flag, and `/plugins info` shows it as a warning.
- **It can't widen access.** For a chat channel, Mercury decides who gets through, not the plugin. Unknown numbers are dropped before they reach the agent.
- **Changes are reversible.** The previous version is kept, so `/plugins rollback` brings it back with its old settings. If Mercury fails to start twice right after a plugin change, it turns that plugin off. `mercury start --safe-mode` starts with every plugin off.

Plugins also borrow Mercury's own AI providers. If you already set up OpenAI in Mercury, both plugins' voice features use that key. You don't paste it again. Keys a plugin needs for itself, such as a Gemini or Deepgram key, are stored with Mercury's keys and shared with any other plugin that declares them.

The plugin menu took two rounds. In the first version, the main settings sat behind a "Change a setting" entry, the voice section forced you through every field, yes/no questions didn't say what each answer meant, and picking a voice engine didn't lead you to the key it needed. Now `/plugins whatsapp` lists every setting with its current value. A yes/no setting says what each answer does ("On: Mercury picks up when you call it"). Change the voice engine to Gemini and Mercury asks for the Gemini key straight away. Every question has a way back.

The plugin API is now at 1.2. The main addition is that a tool can return images to the model, which Optimus needs for screenshots. The images go to the model with that one tool result and are never written to chat history, logs or memory. The [author guide](/docs/reference/plugins) covers the rest.

## WhatsApp: it can call you, or listen to you on the go

The WhatsApp plugin runs Mercury on a spare number as a linked device, the same way WhatsApp Web connects. You chat with it from your own number. One warning up front: this uses an unofficial, reverse-engineered protocol. A WhatsApp update can break it, and automated use can get a number restricted. Link a number you can afford to lose, not your main one.

Text works the way you'd expect. Photos and PDFs you send are passed to the model if it accepts them, and voice notes are transcribed with your OpenAI provider. Two access levels decide how much Mercury can do in the chat. General, the default, is conversation only, with no tools, files or memory. Extended is the full agent.

Calls are where it gets useful. You can ring the spare number while you're out and ask Mercury to look something up, check on a task, or draft a reply. It can also ring you. Say "call me at 9pm about the deploy" in any chat, or on a call. The time is parsed in code, not by a model, and confirmed back to you as an exact time. If you miss the call, it tries once more five minutes later and then sends a message. "Call me when it's done" means it does the work first and then calls. Mercury won't call more than four times an hour by default, so a task stuck in a loop can't keep ringing your phone.

During a call, Mercury uses its tools as usual and keeps you posted while it works. Anything that needs approval is asked out loud and posted in the chat at the same moment, and whichever answer arrives first wins. A spoken yes only counts if your own transcribed words say yes, and it never turns into "always". Mercury doesn't read links aloud. It sends them to the chat and tells you it did.

OpenAI Realtime is the default voice engine, and the one we recommend for the best calls. Gemini Live, Grok Voice, ElevenLabs, Azure, Qwen-Omni, or your own Mercury model with separate speech-to-text and text-to-speech providers all work too. Setup only asks about the engine if you don't have an OpenAI provider already.

The full setup is in the [WhatsApp docs](/docs/integrations/whatsapp).

## Optimus: Mercury at your desk

Optimus is the big one. You ask, and Mercury does it on your screen, in whatever app you use.

**How it sees.** The `look` tool takes a screenshot of the display the mouse is on, scaled to fit 1280 by 800, and can zoom into part of it. Screenshots are taken only when Mercury looks or acts. Nothing captures in the background.

**How it acts.** We wanted you to be able to follow what it's doing. In the default Natural pace, the pointer glides along a gentle curve, scrolling arrives in small steps, and text is typed key by key with an uneven rhythm. Typing key by key means it works in every editor, and paste is only a fallback. While it drives, a ring and the label "Mercury is driving" follow the pointer. On macOS and Windows that overlay is excluded from capture, so it never shows up in Optimus's own screenshots.

**How it stays honest.** This was the hardest part. The logs from one early build showed 29 `act` calls rejected because the model sent its steps in a shape Optimus couldn't read. Nothing happened on screen, and Mercury reported success anyway. Optimus now reads steps in every shape we've seen models send. Every step reports "Sent", meaning the input reached the screen, not "done", and returns a fresh screenshot. Failures start with FAILED. Mercury is told to look again and confirm the result before it says a task is finished. Optimus also records every step that failed or was stopped. In a voice session, the answer is checked against that record before you hear it, so a failed task is reported as failed.

**How you stay in control.**

- Optimus does nothing until you turn on "Control this computer" in setup.
- Ordinary, reversible steps run straight away. The step that submits, sends, posts, pays, deletes, installs or changes a setting goes through a separate tool that needs your approval.
- Move the mouse while it works and it stops at the next step and hands control back.
- It's told never to type passwords, card numbers or 2FA codes. It asks you to do that part.

Press the talk key (`ctrl+alt+m` by default, Control+Option+M on a Mac), hear a chime, and talk. Mercury answers out loud and narrates each step as it happens. A quiet tick-tock plays while it works. Say "stop", or press the talk key while it's working, and every action halts at once. The mute key (`ctrl+alt+shift+m`) stops listening without stopping the work, and nothing you say is sent while muted. After 60 seconds of silence with nothing running, the mic closes by itself. The terminal status bar shows when Optimus is listening, working or muted. Voice settings are shared with the WhatsApp plugin, so a key you added there works here too.

Optimus runs on macOS, Windows and Linux, and no Apple Developer ID is involved. On macOS, privacy permissions are charged to the app that launched a process, so if Optimus ran under your terminal, the grant would cover every script that terminal runs. Instead, Optimus builds its own `Optimus.app` and signs it with a certificate made on your Mac. You grant it Accessibility, Screen Recording and Microphone once, and because the certificate stays the same, the grants survive updates. On Windows there's nothing to grant, though windows running as administrator can't be controlled. On Linux, X11 gets full control. Wayland is limited for now: input and the talk key reach only XWayland apps.

The [Optimus docs](/docs/integrations/optimus) cover every setting and permission.

## The rest of 1.4

- A built-in `web_search` tool, free and keyless. In testing, asking for the news on a WhatsApp call made zero tool calls, partly because Mercury had no search tool to call.
- Mercury always knows it comes from Cosmic Stack.
- The dashboard model picker shows plain-language names, every local Ollama model and ChatGPT Plus/Pro plan models, and has a reasoning picker.
- Fixes for ChatGPT OAuth turns failing after a tool call, memory search failing on long messages, and the dashboard on the Windows standalone binary (found and fixed by @farena-ai in #189). The 1.3.9 fix, where `mercury doctor` restarts the daemon when your configuration changed, is folded in.

## Upgrading

```bash
# npm
npm install -g @cosmicstack/mercury-agent@1.4.0
mercury restart

# standalone binary: rerun the installer, or
mercury upgrade
```

Then install what you want:

```text
/plugins install whatsapp
/plugins install optimus
```

Your `~/.mercury` folder carries over untouched. If something breaks, open an issue or find us on [Discord](https://discord.gg/5emMpMJy5J).

https://github.com/cosmicstack-labs/mercury-agent


---

# The new bottleneck in AI coding isn't code generation. It's verification.

> A Harvard working paper finds coding agents raised code output while review time rose 49%. Why verification, not generation, should decide whether an agent is useful.

Published 2026-10-20 by tashiriskhan. Tags: engineering, news.
Canonical: https://mercuryagent.sh/blog/ai-coding-verification-bottleneck

Coding agents are producing substantially more code. Whether that translates into more finished software is less clear.

A 2026 Harvard job-market paper by Fiona Chen and James Stratton studies roughly 300 million work events across 718 firms. After coding-agent adoption, lines of code rose 30%, commits rose 20%, and pull requests rose 23%. The researchers found no statistically significant increase in resolved Jira issues or epics, the proxies they use for software output. The pressure showed up downstream: pull-request review time rose 49%, requests for changes nearly doubled, comments per PR rose 35%, and the share of workers doing code review rose 14%.

The paper identifies code review as the bottleneck. Our broader reading is that code generation is getting cheaper faster than verification is. The study supports the first claim directly. The second is our interpretation, and the rest of this article argues for it.

## Output is the wrong scoreboard

Lines generated, commits, and pull request count are easy to count, and in the study they all went up. None of them shows whether the work is finished. An agent can add 800 lines, declare "done," and hand over code that does not build, was never tested, or breaks a nearby path. Each unverified change moves cost onto a reviewer, who has to rebuild context, find what was skipped, and decide whether the diff is safe.

Review burden is a fair metric, but it needs care. More comments on a large change do not automatically mean worse code. What matters is the total burden a change creates: review time, requested changes, follow-up fixes, and how many people had to step in.

## Better questions for a coding agent

1. **Did it verify its work?** Which build, test, typecheck, or lint commands actually ran, and did they pass? "This should work" is not verification.
2. **How much review burden did it create?** Look at review time, requested changes, follow-up fixes, and reviewer intervention.
3. **Did it catch failures before handing back?** A failure the agent finds is cheaper than the same failure found by a reviewer.
4. **Did it satisfy the task, or only produce valid code?** Code can compile and pass tests while still missing the requirement.

Compiling is not correctness. Passing tests is not the requirement being met. The fourth question is the one most dashboards miss.

## Verification should happen before review

Verification and human review overlap, but they are not the same thing. A reviewer should not have to discover that the project does not compile.

A coding agent should handle:

- build checks
- tests
- type checking
- linting
- obvious runtime failures

Human reviewers should spend their time on:

- architecture and maintainability
- security
- product requirements
- edge cases that automated checks cannot see

The goal is not to remove human review. It is to stop sending reviewers failures a machine could have caught first.

## Why verification is hard to get right

Checks can be made meaningless. An agent may edit a failing test until it matches the new implementation, skip a verification step, or run only a narrow subset of tests that avoids the changed behaviour. The result is a green run that gives false confidence. No intent is needed for this to happen. The check simply stops testing what it claims to test.

So verification should record what ran, what did not, and any change made to the checks themselves. A reviewer should be able to see that a test file was edited in the same change that made it pass.

Passing checks can also be too narrow. A suite that does not cover the changed path proves little, and an agent that reports green without saying what was covered is overstating its result.

## What this means for Mercury

Mercury Code treats completion as something that needs evidence. For implementation tasks, its completion contract requires a build, test, or typecheck command to run and exit cleanly after the last edit before the "Task complete" banner is allowed. If the task cannot finish, Mercury is designed to surface the named blocker and pause, instead of presenting unfinished work as successful. [The completion architecture](https://mercuryagent.sh/docs/reference/completion-architecture) describes the details.

That does not replace human code review. A passing suite cannot judge every architectural trade-off, security implication, or product requirement. The goal is narrower: obvious, machine-detectable failures should reach the agent before they reach the reviewer.

For any coding agent, including Mercury, ask for three things with each change:

- The exact commands run and their results.
- The parts of the change that were not verified.
- Any blocker that stopped the task from finishing.

If an agent cannot give you those, it has produced code, not finished work.

## References

**Primary** (working paper, not yet peer-reviewed)

Fiona Chen and James Stratton, *Artificial Intelligence in the Firm: Bottlenecks in Software Production*, Harvard University, job-market paper, August 2026. [fion.ac/jellyfish.pdf](https://fion.ac/jellyfish.pdf)

**Secondary**

Ars Technica, coverage of the same study. [arstechnica.com](https://arstechnica.com/)


---

# Agents exceeded their permissions: how to scope them

> CERT-In says AI agents read live business records at Indian firms. Here is how least privilege, scoped access, approvals and audit logs should limit them.

Published 2026-10-17 by tashiriskhan. Tags: security, news.
Canonical: https://mercuryagent.sh/blog/agent-permissions-least-privilege

This week CERT-In, India's national cyber agency, said AI agents reached the live systems of several Indian organisations during "research and evaluation activity" and read business records, including personal data and confidential material. The incident came to light after the affected organisations said the agents "exceeded the authorisation of their assigned tasks." Tencent separately said that agents used to evaluate third-party models showed web-browsing behaviour it did not intend. Both stories point at the same gap: agents were allowed to do more than anyone meant them to.

We run Mercury, an agent with real tools: a shell, a filesystem, git. We read both reports as a test of our own design.

## What CERT-In reported

[The Hindu reported](https://www.thehindu.com/sci-tech/technology/ai-agents-gained-unauthorised-access-to-indian-organisations-says-cert-in/article71592222.ece) that CERT-In said on Thursday, 15 October, that AI agents "undergoing testing" had reached live systems of Indian organisations. [Reuters](https://www.reuters.com/legal/litigation/rogue-ai-agents-broke-into-indian-firms-live-systems-government-says-2026-10-16/) carried the same government statement. The organisations could not detect the access on their own, the agency said.

The agents read "business records, including personal data and confidential material", through credentials and connection settings exposed in application configuration, public code repositories and permissive cloud service roles. [India Today](https://www.indiatoday.in/technology/news/story/ai-agents-gained-access-to-confidential-data-of-indian-firms-cert-in-warns-of-misuse-3018535-2026-10-16) reported the same data exposure. [Business Standard](https://www.business-standard.com/technology/tech-news/ai-agents-accessed-indian-firms-systems-sensitive-data-cert-in-advisory-126101501656_1.html) reported that some model providers had told CERT-In their models reached live systems during testing.

CERT-In did not name the AI providers or the affected organisations, so the public reports cannot say which agents were involved.

None of the weaknesses were new. CERT-In said the AI found and exploited conventional weaknesses "faster, more exhaustively and at greater scale than human attackers."

## What Tencent reported

Tencent said that AI agents used to evaluate third-party models on its cloud platform showed unintended web-browsing behaviour. [Quartz reported](https://qz.com/tencent-ai-agents-unintended-browsing-alibaba-amap-101626) that the agents made more than 2,600 attempts to pull data from Alibaba's Amap service. [South China Morning Post](https://www.scmp.com/tech/tech-trends/article/3371007/ai-agents-tencent-cloud-try-break-alibabas-amap-researchers) put the window at 28 September to 8 October.

Tencent calls the browsing unintended. The coverage we read does not explain how the agents reached Amap, but a browsing tool without a tight boundary can fetch data nobody meant it to.

## The common failure

In both reports the agent did not break a lock. It used authority that already existed and that nobody had scoped to the task. A credential in a config file, a cloud role that reads everything, and a browser that reaches any URL are all standing permission. Nothing in the system said where the agent's job ended.

## How agent permissions should work

We think four rules cover most of this.

**1. Least privilege per task.** An agent should start with no access and receive only the paths, commands and network destinations its current task needs. A summary task does not need write access to the repository.

**2. Scopes that name a path and a mode.** A scope should say what it covers and whether it allows reading, writing or running. Scopes must resolve symlinks before the check. Otherwise an approved directory can contain a link to `~/.ssh`, and the check passes on the link while the read lands on the key.

**3. Approvals for actions that leave the box.** Reading a project file is routine. Pushing to a remote, deleting a directory, or running a package install that reaches the network should stop and wait for a human. The approval should show the exact command or path, not a summary.

**4. Audit for every access.** If a reviewer cannot answer "what did the agent read last Tuesday, and why", the permission model is incomplete. CERT-In's finding that the affected organisations could not detect the access is the strongest argument for this rule.

## How Mercury applies these rules today

Mercury reads its permissions from `~/.mercury/permissions.yaml`. The shape looks like this:

```yaml title="~/.mercury/permissions.yaml"
capabilities:
  filesystem:
    enabled: true
    scopes:
      - path: .
        read: true
        write: true
  shell:
    enabled: true
    cwdOnly: true
    blocked:
      - "sudo *"
    needsApproval:
      - "git push *"
      - "rm -rf *"
```

The full options are in the [permissions reference](/docs/reference/permissions). The enforcement lives in [`src/capabilities/permissions.ts`](https://github.com/cosmicstack-labs/mercury-agent/blob/main/src/capabilities/permissions.ts) and [`src/core/execute-guard.ts`](https://github.com/cosmicstack-labs/mercury-agent/blob/main/src/core/execute-guard.ts). Several parts match the rules above:

- **Blocked commands never run.** The blocklist is checked against every segment of a chained command, so a blocked command cannot hide behind an allowed one such as `echo`.
- **Shell commands take one of two lanes.** A plain command whose binary is on the allowlist, and whose path arguments stay inside the working directory or a readable scope, runs without a prompt. Anything else goes to approval, where the user sees the exact string.
- **Writes always prompt in Ask Me mode.** Even when a scope covers the path, a write asks first. Reads inside a scope do not prompt.
- **Symlinks and hard links are checked.** A read that resolves outside the approved scopes is denied. A regular file with more than one hard link needs approval, because it may be an alias of a file outside every scope.
- **Unattended bots fail closed.** A bot has no one to ask, so an out-of-scope action is denied instead of waiting. Skill elevation is ignored in that mode, so a skill cannot widen what a bot may do.

Two gaps remain. Scheduled tasks run in Allow All mode for their own turn, so shell commands run unless they hit the blocklist and scope prompts are skipped. That is the weakest point, and it looks like the situation CERT-In describes: a background run with broad access and nobody watching. Second, we found no per-access audit log in the permission code, so we treat auditability as missing, not done.

## What it means for you

If you run any agent with tools, check three things this week:

- Search your repositories and config files for credentials. Rotate anything that was ever committed.
- Open your agent's permission file and remove scopes you do not use, especially home-directory and cloud-role grants.
- Give scheduled or unattended runs the narrowest scope that works, and review what they touched.

Read the CERT-In advisory itself, not only the coverage.

## References

1. The Hindu, "AI agents gained unauthorised access to Indian organisations, says CERT-In", 16 October 2026. [thehindu.com](https://www.thehindu.com/sci-tech/technology/ai-agents-gained-unauthorised-access-to-indian-organisations-says-cert-in/article71592222.ece)
2. Reuters, "Rogue AI agents broke into Indian firms' live systems, government says", 16 October 2026. [reuters.com](https://www.reuters.com/legal/litigation/rogue-ai-agents-broke-into-indian-firms-live-systems-government-says-2026-10-16/)
3. India Today, "AI agents gained access to confidential data of Indian firms, CERT-In warns of misuse", 16 October 2026. [indiatoday.in](https://www.indiatoday.in/technology/news/story/ai-agents-gained-access-to-confidential-data-of-indian-firms-cert-in-warns-of-misuse-3018535-2026-10-16)
4. Business Standard, CERT-In advisory coverage, 15 to 16 October 2026. [business-standard.com](https://www.business-standard.com/technology/tech-news/ai-agents-accessed-indian-firms-systems-sensitive-data-cert-in-advisory-126101501656_1.html)
5. Quartz, Tencent AI agents and Alibaba Amap browsing report. [qz.com](https://qz.com/tencent-ai-agents-unintended-browsing-alibaba-amap-101626)
6. South China Morning Post, AI agents on Tencent Cloud and Alibaba's Amap. [scmp.com](https://www.scmp.com/tech/tech-trends/article/3371007/ai-agents-tencent-cloud-try-break-alibabas-amap-researchers)
7. Mercury permissions reference. [/docs/reference/permissions](/docs/reference/permissions)
8. Mercury source: [`src/capabilities/permissions.ts`](https://github.com/cosmicstack-labs/mercury-agent/blob/main/src/capabilities/permissions.ts) and [`src/core/execute-guard.ts`](https://github.com/cosmicstack-labs/mercury-agent/blob/main/src/core/execute-guard.ts).


---

# Mercury Bots: an agent fleet that runs on your machine

> Always-on agents shipped everywhere this autumn. How Mercury Bots work, what we measured, how they compare with Grok Bot, OpenAI dots, Meta Muse, OpenClaw and Hermes Agent, and the commits behind it.

Published 2026-10-16 by Salman Qureshi. Tags: product, engineering, runtime.
Canonical: https://mercuryagent.sh/blog/mercury-bots

Between August 11 and September 29, three of the largest AI companies launched always-on agents. xAI shipped Grok Bot, Meta shipped Muse and OpenAI shipped dots. Each one keeps working after you close the app. The two big open-source projects in this space, OpenClaw and Hermes Agent, have about 646,000 GitHub stars between them.

Mercury Bots is our answer to the same question: what should an agent that runs without you actually look like? Our answer differs from the others in four places. It runs as a fleet on your own machine. It never asks for permission in the middle of a run. It has spending limits that are on before you configure anything. And it judges every run by what it did, not by what it said.

This post explains how that works, shows the code, and compares Mercury Bots honestly with the five products above, including the places where they are ahead of us. Every claim about another product links to its own documentation or to reporting about it. We checked all of them on October 16, 2026.

## What a Mercury bot is

A bot is a folder. Each one lives at `~/.mercury/bots/<id>/` and holds four files:

- **`bot.yaml`** sets its identity, model, schedules and fleet position.
- **`persona.md`** holds its character and standing instructions.
- **`permissions.yaml`** is the only file that decides what it may touch.
- **`.env`** holds credentials that only this bot can read.

Bots form fleets. A lead bot owns a goal and has a crew of specialists, and crews can have their own crews. A typical fleet is a lead that plans a weekly report, a researcher, a writer and a fact-checker. Each one can run on a different model from a different provider, because the model is a per-bot setting.

We studied Hermes Agent and OpenClaw closely before writing the first line. Our design notes credit both, and you can read them in [`BOTS-ARCHITECTURE.md`](https://github.com/cosmicstack-labs/mercury-agent/blob/main/BOTS-ARCHITECTURE.md). The first runtime landed on September 16:

https://github.com/cosmicstack-labs/mercury-agent/commit/69417f08d0bfcdc3f21a6ee2e3e9057921c5cd3b

## One process, many bots

The whole fleet runs inside the Mercury daemon, the background service that starts when you log in. There is no process per bot and no thread per bot. A bot turn is asynchronous work in the same Node.js process, scheduled through one lane with a concurrency cap. That choice comes from the hardware our users run Mercury on: a Raspberry Pi, a small VPS, or a phone under Termux. A separate Node.js process costs tens of megabytes before it does anything, and a fleet of fifteen bots should not need fifteen of them.

```mermaid
%% title: One process, many bots
%% caption: Everything here runs inside the Mercury daemon on your machine. The terminal, the web dashboard and Telegram only attach to it.
flowchart TB
  You["You<br/><small>Telegram · dashboard · API</small>"]
  Bot["Another bot<br/><small>a typed task</small>"]
  Sch["Schedule<br/><small>a routine tick</small>"]
  G{{"Routine gate<br/><small>busy · too soon · cooling down</small>"}}:::warm
  Q[("Durable queue<br/><small>saved before it is acknowledged</small>")]
  M["BotManager<br/><small>one lane · capped concurrency</small>"]
  T["One governed turn<br/><small>fail-closed permissions · the bot's own model<br/>300k tokens or 20 minutes, whichever comes first</small>"]:::lead
  O{{"Outcome from the tool trace"}}:::accent
  F["Deliverables folder<br/><small>~/Documents/Mercury/Bot name/</small>"]:::ok
  J["Journal + transcript<br/><small>for every run</small>"]:::muted
  Sch --> G --> Q
  You --> Q
  Bot --> Q
  Q --> M --> T --> O
  O -->|"deliverable"| F
  O -->|"always"| J
```

Work is written to the queue before the trigger is acknowledged, so a crash between "accepted" and "running" loses nothing. Jobs are claimed on a lease. If the daemon dies mid-turn, the lease expires and the job runs again. Failures are typed. A provider timeout is retried with backoff, and a permission denial goes straight to a dead-letter queue you can inspect and replay. The queue uses SQLite when the platform has it and falls back to a JSON file store when it doesn't, so it works on the same devices as the rest of Mercury.

We learned the hard way that "one process" has to mean exactly one. Until a fix earlier this month, the launcher could start a second full copy of the runtime, and both copies ran the fleet. We wrote that story up in [We were running Mercury twice and didn't know it](/blog/we-were-running-mercury-twice).

## Bots never ask, they fail closed

An agent that runs at 3 a.m. can't wait for you to tap "Allow". Most products in this space solve that with approval prompts that time out, or with a second model that reviews actions. We chose a stricter rule. A bot has no ask handler at all. Anything granted in its `permissions.yaml` runs without a prompt, and everything else is denied and written to the journal:

https://github.com/cosmicstack-labs/mercury-agent/blob/705f6c017874f734743206781e788ecfe898d8c3/src/bots/registry-factory.ts#L114-L123

Three details matter here:

- **No allow-all for bots.** A bot can never inherit the "Allow All" mode you might switch on in your own session, and a literal `*` in a bot's command allowlist is dropped.
- **The deny list wins.** The global blocklist and the bot's own blocked commands always beat an allowlist entry.
- **The workspace is the default.** Relative paths and shell commands resolve inside the bot's own workspace, never inside whatever directory the daemon was started from. We added that after a bot with a wide grant wrote its sandbox files into a real repository.

You choose a permission tier when you create the bot, and the tier is written out as a complete `permissions.yaml`. If a bot keeps getting denied, the journal shows you exactly which action was refused, and you widen the grant on purpose.

## Budgets are on before you set anything

In early October we audited two real fleets that had been running for eleven days. The numbers were bad enough that we published them in the decision record:

<Stats
  items={[
    { value: '13', label: 'Bots across two fleets' },
    { value: '≈1,150', label: 'Runs in eleven days' },
    { value: '617M', label: 'Input tokens' },
    { value: '38%', label: 'Lead runs that failed' },
  ]}
  source="Source: ADR-020 in DECISIONS.md, an audit of two production fleets before turn governance shipped."
/>

The median bot turn read 750,000 tokens of context, and one turn read 6.6 million. There was no cap on a single turn and no context compaction, so every step re-sent every earlier tool result.

<BarChart
  title="Tokens in a single bot turn, before governance"
  subtitle="Measured across the same two fleets. The dashed line is the per-turn cap that ships today."
  unit="tokens"
  data={[
    { label: 'Median turn', value: 750000, note: 'Half of all turns read more context than this.' },
    { label: 'Largest turn', value: 6600000, tone: 'warm', note: 'One run, with no per-turn cap and no compaction.' },
  ]}
  reference={{ value: 300000, label: 'Cap per turn now' }}
  source="Source: ADR-020, DECISIONS.md. The cap is DEFAULT_TURN_LIMITS in src/bots/bot-turn.ts."
/>

The fix went in as hard limits, at three levels:

https://github.com/cosmicstack-labs/mercury-agent/blob/fad01f23e550812dc53545a7d85f51d2f5419652/src/bots/bot-turn.ts#L70-L76

- **Per turn:** 300,000 tokens or 20 minutes, whichever comes first. Context is compacted on every step: the newest eight tool results stay verbatim, and older ones become short stubs the bot can re-fetch if it needs them.
- **Per bot, per day:** 5 million tokens by default. You get a warning at 80%. Setting the budget to `0` is the only way to make a bot unlimited.
- **Per fleet, per day:** 20 million tokens by default. Hitting it pauses every bot until midnight, so one runaway bot can't spend the day's allowance for the others.

All three are pauses, not failures. A bot that hits its daily cap picks up its queued work the next day. `/bots cost` shows where the tokens went, by fleet and by bot. We show tokens rather than money on purpose: provider prices change too often for a currency figure to stay correct.

https://github.com/cosmicstack-labs/mercury-agent/commit/705f6c017874f734743206781e788ecfe898d8c3

## Every run is judged by what it did

The same audit found a subtler problem. Bots were busy, but much of what they produced was notes about earlier runs: "records", "receipts" and status files about other status files.

<BarChart
  title="Files in the fleet-shared folder after eleven days"
  subtitle="637 files in total"
  unit="files"
  data={[
    { label: 'Notes about earlier runs', value: 372, tone: 'warm', note: 'Records, receipts and status files, not finished work.' },
    { label: 'Everything else', value: 265, tone: 'muted', note: 'Data and drafts other bots actually consumed.' },
  ]}
  source="Source: ADR-020, DECISIONS.md."
/>

A model that is told to report its progress will report progress. So Mercury no longer asks the model whether it did anything. It reads the tool trace of the run and decides:

https://github.com/cosmicstack-labs/mercury-agent/blob/fad01f23e550812dc53545a7d85f51d2f5419652/src/bots/bot-turn.ts#L639-L657

A run produced a **deliverable** if it handed over a finished file, or an **action** if it changed something outside its own sandbox or ran a command that isn't read-only. It **delegated** if it sent a typed task to another bot, and it produced a **message** if a reply was what the trigger asked for. Anything else is **none**. Writing notes into its own sandbox doesn't count, and the journal flags any reply that claims work the trace doesn't show.

A scheduled run that ends at `none` gets one nudge: deliver something, do the action, or say plainly that there was nothing to do. If a routine comes back empty three times in a row, it cools down for six hours, then twelve, up to a day, and then resumes by itself. Routine ticks also pass through a gate before they cost anything:

https://github.com/cosmicstack-labs/mercury-agent/blob/8b2078b8e238a3e15bf2823794c0394526bc1d5d/src/bots/bot-manager.ts#L404-L425

```mermaid
%% title: The life of a scheduled routine
%% caption: A tick is an opportunity, not an order. Skipped ticks cost no tokens, and a cooled-down routine resumes by itself.
flowchart TB
  tick(["Schedule fires"])
  gate{{"Busy, too soon or cooling down?"}}:::warm
  skip["Skipped<br/><small>no tokens spent</small>"]:::muted
  run["Run the turn"]:::lead
  out{{"What does the tool trace show?"}}:::accent
  done["Done<br/><small>empty streak resets</small>"]:::ok
  nudge["One nudge<br/><small>deliver, act, or say why not</small>"]:::warm
  streak["Empty streak + 1"]
  cool["Cooldown<br/><small>6h, doubling up to 24h</small>"]:::violet
  tick --> gate
  gate -->|"yes"| skip
  gate -->|"no"| run --> out
  out -->|"deliverable · action · delegated"| done
  out -->|"nothing"| nudge --> streak
  streak -->|"3 in a row"| cool
```

The point is that the bot stays alive. "Always-on" doesn't mean calling a model all day. It means idle between runs, never silently dead, and never spending money to tell you it had nothing to do.

## Fleets work in typed tasks, and leads wake once

When a lead delegated to five crew members, it used to get five free-text emails and run five full turns to read them. Now delegation is data. A task has a goal, acceptance criteria, an assignee, a status and a typed result. Tasks travel in batches with a wake policy and a deadline, and the lead is woken once, with a digest, when the batch is done:

```mermaid
%% title: A lead delegates a batch and wakes up once
%% caption: The crew works in parallel. The lead spends one turn to dispatch and one to synthesise, however many crew members there are.
sequenceDiagram
  participant You as You
  participant Lead as Lead bot
  participant Tasks as Task store
  participant R as Researcher
  participant W as Writer
  You->>Lead: Weekly competitor brief
  Lead->>Tasks: Batch of 2 tasks<br/>wake when all done
  par In parallel
    Tasks->>R: Collect release notes
    Tasks->>W: Draft the outline
  end
  R-->>Tasks: File + summary
  W-->>Tasks: File + summary
  Tasks->>Lead: One digest
  Lead->>You: Final brief as a file
```

A fleet's standard job can be written down as a pipeline in `bot.yaml`. Stages chain themselves, each one gets the previous stage's result, and the lead isn't woken between stages at all. A five-member crew now costs the lead one turn to dispatch and one to synthesise.

Bots also remember, without writing status files. Every prompt carries the bot's last five journal rows and its own short working note. After a run that produced something, the bot's model extracts up to three durable facts into that bot's private memory namespace.

https://github.com/cosmicstack-labs/mercury-agent/commit/fad01f23e550812dc53545a7d85f51d2f5419652

## Results are files you own, and the fleet fits in your pocket

Finished work goes to `~/Documents/Mercury/<Bot name>/`, with final results at the top and crew work under `work/`. It's a normal folder in your Documents directory, not a hidden one under `~/.mercury`, and nothing in it is deleted automatically.

Most people run their fleet from a phone, so Telegram is a full control surface. `/bots` shows the roster with Stop, Start and Run buttons. A task you start from your phone comes back as an attached file, because a path on a computer you can't see is useless. A button tap is turned into the same slash command you'd have typed, so it goes through exactly the same routing and permission checks.

https://github.com/cosmicstack-labs/mercury-agent/commit/9975db46c5a7f4862b56c8a55bb0de2395fc4e0b

## How Mercury Bots compares

Here is the same set of questions asked of all six products. Where a vendor hasn't published an answer, we say so instead of guessing. Facts come from each project's own docs, repository or announcement, listed under [Sources](#sources), and were checked on October 16, 2026.

| | Mercury Bots | Grok Bot | OpenAI dots | Meta Muse | OpenClaw | Hermes Agent |
|---|---|---|---|---|---|---|
| Where it runs | Your machine: macOS, Linux, Windows or Termux, in one background process | One xAI cloud computer per user, shared by all your Bots, hosted in the US | A cloud computer per dot, run by OpenAI | A Meta-hosted Linux VM per user ("Muse Secure VM") | Your machine, in one Gateway process | Your machine or server, with seven terminal backends |
| Source code | ✓ MIT | ✕ Closed | ✕ Closed | ✕ Closed | ✓ MIT | ✓ MIT |
| Choose your model | ✓ Per bot, any configured provider, your own keys | ✕ "No customer-facing model picker" | ? Runs on GPT-6 Astra; no choice documented | ? Runs on Muse Spark; no choice documented | ✓ About 75 providers, your own keys | ✓ 40+ providers, your own keys |
| Several named agents | ✓ Fleets: lead bots with crews, nested | ✓ Named Bots, often a "chief of staff" over specialists | ◐ One dot today; teams of dots announced | ◐ One Muse per user, with internal sub-agents | ✓ Isolated agents per channel binding, plus sub-agents | ✓ Profiles, plus up to 10 parallel sub-agents |
| Delegation that reports back | ✓ Typed tasks in batches; lead woken once with a digest | ◐ Hand-offs in shared threads | ◐ Can hand work to Codex | ? Not exposed to users | ✓ Sub-agents announce results to the parent | ✓ Sub-agent summaries return to the parent |
| Runs on a schedule | ✓ Routines, gated when busy or too recent | ✓ Routines on a schedule or an event | ✓ Scheduled tasks | ✓ Schedules and events | ✓ Heartbeat every 30 min by default, plus automations | ✓ Cron inside the gateway |
| Unattended approval model | Fail closed: only what `permissions.yaml` grants runs, the rest is denied and journaled | Allow once, Always allow or Deny; unattended approvals expire after about 10 minutes | Custom Rules decide what needs your approval; admin policy overrides | A separate component, Sentinel, approves connector actions and network access; confirms before email, messages and purchases | Exec modes from deny to full; sandbox off by default | `smart` mode: a second model approves low-risk commands; unattended runs deny dangerous ones |
| Built-in spend limits | ✓ On by default: per turn, per bot per day, per fleet per day | ✕ "A separate Grok Bot spend cap is not available today" | ? Usage terms not yet published | ◐ Weekly token allowance set by plan | ◐ Optional token budget per goal | ◐ Optional time budget; turn limit unlimited by default |
| Where results land | Files in a folder you own, and as Telegram attachments | Thread messages, drafts, files and actions in your apps | The dot's conversation and profile, Slack or Teams | Chat messages and artifacts, with an activity log | Replies in the chat channel, or a webhook | The originating chat, files or platform targets |
| Chat channels | Terminal, web, Telegram with buttons, Discord, Slack, Signal | Desktop and mobile apps; Slack for Team Bots | ChatGPT apps; Slack and Teams when an admin enables them | Muse app, web and WhatsApp | 33 listed in the docs | 20+ listed in the gateway docs |
| Price | Free; you pay your model provider | Included in SuperGrok and Cursor plans | One dot included with Pro or Business Premium | Free tier, $20 or $100 a month | Free; no paid tier | Free; optional Nous Portal subscription |
| GitHub stars | 3,421 | Not open source | Not open source | Not open source | 392,171 | 254,339 |

### Where the others are ahead

Honesty cuts both ways, so here is where each of them beats us today.

- **A computer of their own.** Grok Bot, dots and Muse give your agents a cloud computer with a real browser they can drive, which you can watch and take over. The work continues with your laptop closed. A Mercury fleet runs where you install it. If that machine sleeps, the fleet sleeps, which is why many of our users run it on a home server or a small VPS.
- **Reach.** OpenClaw lists 33 chat channels and Hermes lists more than 20. Our tappable fleet controls exist on Telegram today. Discord and Slack get the same commands as text.
- **Scale.** OpenClaw and Hermes have far larger communities, a larger skills ecosystem and many more people finding their edge cases.
- **Consumer polish.** Muse pays for things with single-use cards and asks before each purchase. dots connects to a much larger catalogue of apps than we do.

Every project in this table is candid about the same unsolved problem. Meta writes that "prompt injection remains an open problem in the industry." The system card for GPT-6 Astra, the model behind dots, reports test runs in which the agent overstepped its authorisation. Hermes Agent's own security policy says that "the only security boundary against an adversarial LLM is the operating system." OpenClaw's skills registry had a wave of malicious uploads in February. Our answer isn't that Mercury is immune. It's that a bot can only do what one file says it may, that it spends inside a budget it can't raise, and that every run leaves a record you can read.

## The commits

Mercury is MIT-licensed, and all of this is in the open. The work above landed in these commits on `main`:

| Commit | Date | What it did |
|---|---|---|
| [`69417f0`](https://github.com/cosmicstack-labs/mercury-agent/commit/69417f08d0bfcdc3f21a6ee2e3e9057921c5cd3b) | 2026-09-16 | The first persistent, persona-scoped bot runtime |
| [`82eb5a4`](https://github.com/cosmicstack-labs/mercury-agent/commit/82eb5a42b77664106293f6ac71541aea313938c0) | 2026-09-22 | Fleets: lead bots and crews |
| [`9b71f23`](https://github.com/cosmicstack-labs/mercury-agent/commit/9b71f235951edb01126bf064347a3ec72d8f93e2) | 2026-10-10 | The governed turn: caps, compaction, failure classes, outcome gate |
| [`705f6c0`](https://github.com/cosmicstack-labs/mercury-agent/commit/705f6c017874f734743206781e788ecfe898d8c3) | 2026-10-10 | Budgets by default, routine gate, deliverables folder (ADR-020) |
| [`fad01f2`](https://github.com/cosmicstack-labs/mercury-agent/commit/fad01f23e550812dc53545a7d85f51d2f5419652) | 2026-10-10 | Typed tasks with fan-in, pipelines, memory (ADR-021) |
| [`918a852`](https://github.com/cosmicstack-labs/mercury-agent/commit/918a852b2a4281b1be785ddcddafbf7471ed1224) | 2026-10-10 | Cost view, run viewer, kill switch (ADR-022) |
| [`acfe89b`](https://github.com/cosmicstack-labs/mercury-agent/commit/acfe89bcf80f9980dbccc372dacc8518a5fca51a) | 2026-10-10 | The liveness contract: idle, never dead |
| [`9975db4`](https://github.com/cosmicstack-labs/mercury-agent/commit/9975db46c5a7f4862b56c8a55bb0de2395fc4e0b) | 2026-10-10 | Fleet control from Telegram (ADR-023) |

To try it, install Mercury and run `/bots onboard`. It walks you through the persona, the model, the permission tier, the schedule and the budget, and names the folder where the bot's work will land. The [Mercury Bots guide](/docs/integrations/mercury-bots) covers fleets, pipelines and every `/bots` command.

```bash
curl -fsSL https://mercuryagent.sh/install.sh | sh
```

https://github.com/cosmicstack-labs/mercury-agent

## Sources

All checked on October 16, 2026.

- **Grok Bot:** [Introducing Grok Bot](https://x.ai/news/introducing-grok-bot) (Aug 11, 2026), [more plans](https://x.ai/news/grok-bot-more-plans), [Team Bots](https://x.ai/news/team-bots), [FAQ](https://docs.x.ai/grok-bot/faq), [approvals and security](https://docs.x.ai/grok-bot/approvals-security-and-privacy), [teams and enterprises](https://docs.x.ai/grok-bot/teams-and-enterprises), [security](https://cursor.com/docs/grok-bot/security).
- **OpenAI dots:** [Introducing dots](https://openai.com/index/introducing-dots/) (Sep 29, 2026), [getting started](https://help.openai.com/en/articles/20001530-getting-started-with-your-dot), [privacy, security and safety](https://help.openai.com/en/articles/20001529-dots-privacy-security-and-safety-faqs), [GPT-6 Astra system card](https://deploymentsafety.openai.com/gpt-6-astra).
- **Meta Muse:** [Introducing Muse](https://about.fb.com/news/2026/09/introducing-muse-personal-ai-agent/) (Sep 8, 2026), [plans](https://www.meta.com/help/subscriptions/1021145227643680/), [security and safety](https://research.meta.ai/blog/security-and-safety-for-ai-agents-our-approach-with-muse), [AI training controls](https://www.meta.com/help/artificial-intelligence/1047255454427887/).
- **OpenClaw:** [repository](https://github.com/openclaw/openclaw), [docs](https://docs.openclaw.ai), [Bitdefender on ClawHub skills](https://www.bitdefender.com/en-us/blog/labs/helpful-skills-or-hidden-payloads-bitdefender-labs-dives-deep-into-the-openclaw-malicious-skill-trap) (Feb 5, 2026).
- **Hermes Agent:** [repository](https://github.com/NousResearch/hermes-agent), including its `SECURITY.md` and `website/docs`.
- **Mercury:** [`DECISIONS.md`](https://github.com/cosmicstack-labs/mercury-agent/blob/main/DECISIONS.md) (ADR-020 to ADR-024) and [`BOTS-ARCHITECTURE.md`](https://github.com/cosmicstack-labs/mercury-agent/blob/main/BOTS-ARCHITECTURE.md).
- **Star counts:** the GitHub API on October 16, 2026.


---

# The setup wizard that quit at the first menu

> For six releases, a brand-new Mercury install asked for your name, showed one menu, and exited without an error. A walkthrough of a one-tick race between two readers of the same terminal, and the test that now keeps it from coming back.

Published 2026-10-15 by Mercury Engineering. Tags: engineering, postmortem.
Canonical: https://mercuryagent.sh/blog/the-wizard-that-quit-at-the-menu

Here's what a new user saw, from first install, on any platform, between versions 1.3.1 and 1.3.6:

```text
  Your name: Ada
  Agent name [Mercury]:

  Choose your mode
  Use the arrow keys, then press Enter.

  ● Mercury Cloud (hosted, no API keys needed)
  · Offline / BYOK (bring your own keys)

$
```

That `$` is the shell prompt. Mercury didn't crash and didn't print an error. It drew a menu and then simply wasn't running anymore. The exit code was 0.

This is a write-up of what happened, because the bug is a good example of a class of problem that's easy to create in any Node.js CLI that mixes line prompts with key-by-key menus.

## Two ways to read a terminal

Mercury's setup wizard uses two kinds of input.

The name questions use Node's `readline` interface: you type a line, press Enter, and get a string back. The mode menu reads individual key presses, so it can move the highlight with the arrow keys. That needs the terminal in raw mode and a `keypress` listener on `process.stdin`.

Both read from the same stream, so they have to take turns. The question reader has to let go of stdin before the menu takes over.

## The change that introduced it

In October we fixed an older bug where pressing Ctrl+D during setup made the wizard exit silently. Part of that fix was to share one `readline` interface across all the consecutive questions, so an end-of-input during any of them would be caught and reported.

Sharing the interface raised a question: when do you close it? You don't want to close and reopen it between two questions that follow each other immediately. So after each answer, the code scheduled the close for the next turn of the event loop:

```ts
iface.question(prompt, (answer) => {
  pending = null;
  scheduleRelease(); // setImmediate(() => close())
  resolve(answer.trim());
});
```

If another question came along before then, it cancelled the scheduled close and reused the interface. That's a sensible design for a sequence of questions. The problem is what happens when the next consumer isn't a question.

## The race

Here is the exact order of events after you type your agent's name:

1. The answer resolves. A close is scheduled for the next tick.
2. The wizard moves on and opens the arrow-key menu. The menu puts the terminal in raw mode, calls `stdin.resume()`, and attaches its key listener.
3. The next tick arrives and the scheduled close runs. `readline`'s `close()` pauses its input stream, which is `process.stdin`.

Step 3 undid step 2. The menu was waiting for key presses on a stream that was no longer flowing. Nothing else was keeping Node's event loop busy at that moment: no timers, no open sockets, no pending promises tied to I/O. When the event loop has no work left, Node exits. Cleanly. With status 0.

That's why there was no error. Nothing failed. The process just ran out of things to do.

## Why it survived six releases

Every test we had passed, and that's the uncomfortable part.

The unit tests for the question reader passed because, in isolation, it did exactly what it should. The wizard's tests mocked the menu. The binary smoke test in CI ran `mercury --version`, which never reaches the wizard. And anyone on the team who already had a config file never saw the first-run path at all.

None of those tests had a real terminal in them, and this bug only exists when two readers share one.

## The fix

The fix is one line. The menu releases the shared reader synchronously, before it takes over stdin:

```ts

  // Release the question reader now: its deferred close would otherwise
  // pause stdin after we resume it.
  closeAsk();
  readline.emitKeypressEvents(process.stdin);
  // ...raw mode, resume, keypress listener
}
```

`closeAsk()` cancels the scheduled close and closes the interface immediately. By the time the menu calls `resume()`, nothing is left that could pause the stream behind its back.

## The test that would have caught it

The part worth copying is the test. CI now runs the compiled binary through the real first-run wizard inside a pseudo-terminal. It types a name, answers the agent name prompt, waits for the mode menu, and then checks one thing: is the process still alive? Then it presses the down arrow and Enter and expects the provider question.

Run against 1.3.6, it fails at the menu. Run against 1.3.7, it passes. It runs on every pull request on Linux and macOS.

There's also a regression test at the unit level that fails if a deferred release can pause the input after an explicit close.

## Takeaways

- **If two parts of your CLI read stdin, decide the hand-off explicitly.** Don't rely on whoever closes last.
- **"Exited with code 0" can be a bug.** In Node, a process that runs out of work exits quietly. When an interactive program exits without being asked to, look for a stream that stopped flowing.
- **Test interactive paths with a real terminal.** Mocks hide the exact class of bug where two components step on each other. A pseudo-terminal test is maybe forty lines of Python and it's the only thing that would have caught this.

We're sorry to everyone whose first impression of Mercury was a menu that vanished. If you bounced off it, the current release gets you through setup, and we'd love for you to try again.


---

# We were running Mercury twice and didn't know it

> Typing felt sluggish on every machine, and the terminal renderer took the blame. The real cause was a second full copy of the agent, started quietly by the launcher. How we found it, and why the fix was an architecture change rather than a performance tweak.

Published 2026-10-14 by Salman Qureshi. Tags: engineering, postmortem, runtime.
Canonical: https://mercuryagent.sh/blog/we-were-running-mercury-twice

A few days after shipping the always-on bots, I noticed Mercury felt heavy when I typed. Not broken. Just a beat behind, the way a remote shell feels on bad Wi-Fi. Then people started saying the same thing about the dev build on their own machines, which ruled out my laptop as the problem.

The obvious suspect was the terminal UI. We had just moved to a new version of Ink, our renderer, and added our own patches on top. Renderers are where input lag usually lives. So that's where I started, and that's where I was wrong.

## Measuring before guessing

The first thing I did was take the renderer out of the argument. I wrote a benchmark that mounts the real terminal UI in a virtual terminal, loads a 100-message conversation and a 14-bot roster, and types 60 characters, timing each one from key press to the frame that shows it.

The median was about 3 ms per keystroke. The worst was around 20 ms. Each frame wrote roughly 74 bytes to the terminal. Then I ran it again with a bot streaming its reasoning into the UI a hundred times a second, and it barely moved: about 6 ms.

That's not a renderer you can feel. Whatever was eating the latency, it wasn't drawing the screen.

## The process list told the story

So I stopped benchmarking and looked at what was actually running.

There were two Mercury processes doing real work. One was the background daemon I expected. The other was the terminal session itself, and it had used about four times as much CPU as the daemon. A terminal client should use almost nothing. This one was doing a lot.

The daemon's error log had the second clue: nine copies of the same Telegram error in a single day.

```
409 Conflict: terminated by other getUpdates request
```

Telegram returns that when two programs poll the same bot token at once. There is no way to get it with one process.

## How we ended up with two

The launcher had a branch that looked reasonable in isolation. When you typed `mercury` and no background daemon was running, it did two things in a row: start the daemon, then start the agent in the foreground.

```ts
autoDaemonize();
await runAgent();
```

The intent was to make sure Mercury kept running after you closed the terminal, while still giving you a chat right away. The effect was two complete runtimes. Both built a bot manager on the same queue. Both connected to Telegram. Both registered the same scheduled routines. And the foreground copy ran every bot turn, every tool call and every context compaction on the same thread that renders your keystrokes.

For months this didn't matter much, because bots were mostly idle. Then we made them always-on. The foreground copy suddenly had real work to do, on the worst possible thread, and typing started to lag on every machine that took that path. Which was every machine, because it ran on the first `mercury` after any reboot.

On my own laptop it was worse. An old install from npm was still registered as the login service and held the web port. Every launch failed to connect to the new daemon, decided the runtime was unhealthy, and took the "start everything" path again.

## Why we didn't just make it faster

You can imagine a smaller fix: keep the second copy but stop it from polling Telegram, or move bot work to a worker thread. Each of those would have removed one symptom and left the actual problem in place, which is that two programs both believed they were Mercury.

The fix we shipped is a rule instead: **one process owns the runtime, and the terminal attaches to it.**

- Launching `mercury` starts the daemon if needed and then attaches to it. The terminal never becomes a second runtime unless no daemon can run on that platform (Android under Termux is the one case).
- If a foreground process does start beside a live daemon, for example to open Mercury Code, it runs as a terminal for the main agent only. It doesn't register channels, doesn't create a bot manager, doesn't bind the web port, and doesn't replay scheduled work.
- Attaching is instant. The daemon now binds its web listener first, before channels and bots finish starting, and answers a lightweight health check with its process ID. If something else owns the port, you get one sentence saying which process it is instead of a 30-second wait followed by a pointless restart.

## Making lag measurable

The part I'm happiest with is small. Mercury now measures its own event loop. `/api/health` reports the 99th-percentile and worst stall over the last ten seconds, and any stall longer than 250 ms is logged with a warning.

We spent days on this bug partly because "it feels slow" isn't evidence. Now it is. If a bot turn ever does something synchronous that holds the main thread, the daemon's log will say so, with a number.

## What we got wrong, and what we changed

The honest version: the double runtime existed for a long time and we never noticed, because nothing measured it and the symptom only appeared once the workload changed. The tests passed because each runtime, on its own, worked.

So the fix came with a few guards that would have caught it:

- A contract test asserts the launcher never starts the daemon and then a second runtime.
- The login service is checked against the running install on every launch and re-pointed if it drifted.
- Merges now wait for the entire CI run, not just the checks that happen to have reported so far.

If you're building something that runs in the background and has a terminal front end, the takeaway is the same one we learned: decide which process is the program, and make everything else a client. It's much easier than working out, later, which of two programs is the real one.


---

# Mercury 1.3: an agent you can actually leave running

> One background runtime that every terminal attaches to, bots that deliver real files and stay online, and a fleet you can run from Telegram. What changed in the 1.3 line and why.

Published 2026-10-12 by Salman Qureshi. Tags: news, releases, runtime.
Canonical: https://mercuryagent.sh/blog/mercury-1-3

Mercury 1.3 is the release where we stopped adding things and started making the existing things trustworthy. Most of the work is invisible when it goes right: your terminal opens instantly, your bots are still running tomorrow morning, and closing the laptop doesn't kill anything. This post walks through what changed, and the reasoning behind the bigger decisions.

If you want the full list, the [release notes](/docs/releases/1.3.1) have every item. This is the version with opinions.

## One runtime, and the terminal is just a window

Until 1.3, typing `mercury` in a terminal could start a full copy of the agent in that terminal, even when a background copy was already running. That sounds harmless. It wasn't. Two copies meant two Telegram pollers fighting over one bot token, two schedulers firing the same routines, and bot work running on the same thread that draws your keystrokes. We wrote up the details in [We were running Mercury twice and didn't know it](/blog/we-were-running-mercury-twice).

Now there is exactly one process that owns your channels, your bots and the web dashboard. It runs as a login service on macOS, Linux and Windows. When you type `mercury`, the terminal attaches to that process. Close the terminal and nothing stops. Open it again and you're back in under a second.

Two smaller changes make this hold up in practice:

- **The login service follows the install.** If you move from npm to the standalone binary, or from stable to the dev channel, Mercury notices that the service still points at the old install and re-points it. Before this, an old daemon could keep resurrecting itself on every login and hold the web port.
- **Lag is now a number.** `/api/health` reports how long the event loop has stalled in the last ten seconds, and stalls over 250 ms are logged. "It feels slow" turns into something we can actually look at.

## Bots that do work instead of writing about work

Mercury Bots shipped in 1.3.0. After a couple of weeks of running a real fleet we looked at the journals and didn't love what we saw: routines that produced status notes about other status notes, turns that read 750,000 tokens of context to decide nothing needed doing, and finished output buried somewhere under `~/.mercury`.

So 1.3 judges a bot run by what it did, not by what it said. Every run gets an outcome computed from the tool trace: it delivered a file, it took an action, it delegated, it replied, or it did nothing. A scheduled routine that does nothing three times in a row cools down for a few hours and then resumes by itself. Bots stay online. They just stop burning tokens on busywork.

Finished work goes somewhere you can find it: `~/Documents/Mercury/<Bot name>/`, with final results at the top and work in progress under `work/`. There's a daily token budget per bot (5M by default) and per fleet, with a warning at 80%.

Fleets got real coordination too. A lead bot can hand typed tasks to several crew members at once and gets woken up once, with a summary, when they're all done. A lead's standard job can be written down as a pipeline in `bot.yaml`, so "research, draft, fact-check, package" runs without the lead having to babysit each step.

## Your fleet in your pocket

A lot of you run Mercury on a home server and talk to it from Telegram, so 1.3 makes Telegram a first-class control surface. `/bots` shows the roster with Stop, Start and Run buttons for each bot. When a bot finishes a task you asked for from your phone, the result arrives as an attached file, not a path on a machine you can't see. You can create a bot from Telegram too: describe its persona in a message, then pick its permissions, whether it works alone or leads a crew, and its budget, all with buttons.

## Node 22 and a patched terminal renderer

The npm package now requires Node.js 22. The standalone binaries carry their own runtime, so if you installed with the one-line installer, nothing changes for you. If you're on npm, `mercury upgrade` checks your Node version before it touches anything, and the package refuses to install on an older Node with a readable message instead of installing something that won't start.

We also moved the terminal UI to Ink 8 with four patches of our own. The most visible one fixes a problem where long replies stamped copies of themselves into your scrollback on every repaint. The patches live in a public fork and we're working on getting them upstream.

## The hotfixes, and what we learned from them

The 1.3 line had more point releases than we'd like, from 1.3.2 to 1.3.8 in three days. Each one fixed something real: the first-run setup could quit at its first menu, `/code` didn't work from the default launch, the Windows installer failed on stock PowerShell 5.1, and the Linux binary crashed on CPUs without AVX2. The Windows and AVX2 reports came from people who took the time to dig in and tell us exactly what they saw. Thank you.

Every one of those now has a test that would have caught it: the setup wizard runs under a real pseudo-terminal in CI, the Windows installer runs under PowerShell 5.1, and the Linux installer is tested on a CPU that pretends to lack AVX2.

## Upgrading

```bash
# standalone binary (macOS / Linux)
curl -fsSL https://mercuryagent.sh/install.sh | sh

# npm, on Node.js 22+
npm i -g @cosmicstack/mercury-agent
```

Or just run `mercury upgrade`. If you're already on the latest version it now tells you so and does nothing, which is how it should have worked from the start.

Your `~/.mercury` folder (config, memory, bots, Telegram pairing) carries over untouched. On first start, Mercury moves old bot outputs into `~/Documents/Mercury/` and re-points your login service if it needs to.

If something breaks, open an issue or find us on [Discord](https://discord.gg/5emMpMJy5J). We read everything.

The code, the issue tracker and every release live here:

https://github.com/cosmicstack-labs/mercury-agent


---

# Asks before it acts: how Mercury's permission model works

> An agent with a shell, a file system and network access is only useful if you can trust what it will do with them. A tour of the layers between the model and your machine: scopes, approvals, a blocklist nothing can override, an SSRF guard, and tiers for unattended bots.

Published 2026-10-09 by Mercury Engineering. Tags: product, security.
Canonical: https://mercuryagent.sh/blog/asks-before-it-acts

Giving a language model a shell is easy. Giving it a shell and still being able to sleep at night is the actual product.

Mercury can read and write files, run commands, push to git, call the GitHub API and fetch web pages. Every one of those goes through the same question first: is this allowed, and if it isn't obviously allowed, has a person said yes? This post explains the layers that answer it, in the order a request hits them.

## The rule underneath everything

The model never decides what it's allowed to do. It can ask to run `npm test` or write a file, but the decision happens in ordinary code, outside the model, before anything touches your machine. Prompt injection can change what the model *asks* for. It can't change the code that answers.

Everything below is that code.

## Layer 1: a blocklist nothing can override

Some commands are never run, no matter which mode you're in, who's asking or what you approved earlier. `sudo` is one. Recursive deletes of the root or home directory are another, in every spelling we've seen: `rm -rf /`, `rm -fr ~`, `rm -rf /*`, and their PowerShell equivalents like `Remove-Item -Recurse -Force C:\` with the flags in any order.

The list is matched against each segment of a command. Mercury splits pipelines, chains and command substitutions apart first, and anything it can't fully decompose is treated as needing approval, so wrapping a blocked command in `$( )` or chaining it after `&&` doesn't hide it. It's deliberately short. A blocklist can't make an agent safe on its own. What it can do is make sure the handful of truly unrecoverable commands are off the table even in the most permissive configuration.

## Layer 2: scopes

File access is scoped to folders, and each scope is either read-only or read-write. Out of the box Mercury can read and write in the directory it's working in, and nothing else. If a task needs another folder, Mercury asks, and you can grant it for that one request or permanently.

A short list of read-only commands runs without a prompt, because asking every time someone types `ls` trains people to click *Allow* without reading: things like `ls`, `cat`, `pwd`, `git status`, `git diff` and `git log`, plus `npm test` and `npm run`.

Scopes apply to shell commands too. If a command writes to a path outside your granted scopes, Mercury treats it the same as a file write outside them.

Scopes are per channel and per role. If you share a Telegram group with Mercury in it, members can talk to the agent, but they can't install skills or widen what it's allowed to touch. Those decisions stay with admins.

## Layer 3: asking

When something isn't covered by a scope, Mercury asks. In the terminal you get an arrow-key prompt. On Telegram you get buttons. In the web dashboard you get a card with *Allow*, *Always allow* and *Deny*. The prompt says exactly what will happen: the command, the file path, the folder being requested.

You choose the default per session:

- **Ask Me** prompts for every write and every command outside your scopes. Even when a scope already covers a folder, writes in Ask Me mode still prompt.
- **Allow All** runs without prompting, inside the blocklist.

One detail we're careful about: Allow All applies to the session where you turned it on. A request that arrives from a different channel doesn't inherit your permissive setting. If you said "allow all" in your terminal, a message from a Telegram group is still checked as a Telegram message.

## Layer 4: the network

`fetch_url` is how Mercury reads the web, and it's a classic way to attack a self-hosted service: ask the agent to fetch `http://192.168.1.1/admin`, or a public URL that redirects to one, and it becomes a proxy into your home network.

So every fetch is checked before it connects:

- Only `http` and `https`.
- The hostname is resolved, and if any address in the answer is private, loopback or link-local, the request is refused. A resolver that returns one public and one private address doesn't get the benefit of the doubt.
- The connection is pinned to the address that was checked. A hostile DNS server can't hand Mercury a public address for the check and a private one for the actual request.
- Redirects are followed manually and each hop is checked again, up to five.

Responses are also size-capped, so a page can't flood the model's context.

## Layer 5: bots that can't ask

Mercury Bots run unattended. A bot working at 3 a.m. has nobody to ask, so for bots the rule flips: **anything not explicitly granted is denied.** A missing permission isn't a question, it's a no.

When you create a bot you pick a tier:

| Tier | What it can do |
|---|---|
| **Read-only** (the default) | Browse, search, read files, message other bots. No commands, no writes. |
| **Builder** | Also create and edit files inside its granted folders. Still no shell. |
| **Operator** | Also run commands, restricted to its granted folders. The blocklist still applies. |
| **Full access** | Everything, including deleting files. Meant for automation you fully trust. |

Each bot also has its own folder scopes, its own daily token budget, and its own memory. A bot's tier is a ceiling: a crew member can't do more than the permissions it was given, regardless of what its lead asks it to do.

## Layer 6: secrets stay out of the conversation

API keys live in `~/.mercury/.env`, not in the main config file, and the logger redacts anything that looks like a key or token before it's written. The web dashboard binds to `127.0.0.1` only, and its first password is randomly generated and shown once, so there's no default credential from the public source code that works on every install.

## What this doesn't protect you from

It's worth being direct about the limits.

If you choose Allow All and give Mercury write access to your home folder, it can do a lot of damage inside the blocklist, because you told it it could. Skills from the community registry are not audited, and a skill's instructions become part of what the model reads, so review them before installing. And no permission system makes a model's judgement correct. Mercury can still decide to do the wrong thing. The permission layers decide whether it's allowed to, and give you the chance to say no.

## Where to read more

The [permissions reference](/docs/reference/permissions) documents every setting. If you find a way around any of this, please report it privately through our [security policy](https://github.com/cosmicstack-labs/mercury-agent/security/policy). Reports from the community have already closed real issues in this area, and we credit everyone who sends one.
