---
title: Permissions
description: Declare what a tool needs, grant a set at the composition root, and refuse a tool the permissions it was not given.
url: https://hermesos.thally.app/concepts/permissions
---

# Permissions

Declare what a tool needs, grant a set at the composition root, and refuse a tool the permissions it was not given.

A tool **declares** what it needs (`fs:write`, `net:http`); a host **grants** a
set. The check is a set-membership test with no policy in it.

## What permissions are not

Permissions are not authorization. They do not know who is asking, have no
concept of a user, a role, or a session, and cannot answer "may Ada run this?".

Keeping the two apart matters because they fail differently. A missing grant is
a **configuration fact** known at wiring time; a denied user is a **runtime
decision** about a principal. Fusing them would make every tool call wait on a
policy engine to answer a question a `Set` already knows.

## Grant a set

```ts
import { PermissionSet, toolset, withPermissionsAll } from '@hermes/tools';

const granted = PermissionSet.none().grant('fs:read', 'net:http');

runtime.use(toolset({
  name: 'filesystem',
  tools: [readFile, fetchUrl],
  granted,
}));
```

A `PermissionSet` is **immutable**. A set that could be widened after
construction would make "what is this host allowed to do" a question with a
different answer depending on when you asked — and the answer would be decided
by whichever plugin loaded last.

| Constructor | What it grants |
| --- | --- |
| `PermissionSet.none()` | Nothing. The right default for an untrusted context |
| `PermissionSet.all()` | Everything, for a host that has decided it trusts its tools |
| `new PermissionSet([...])` | Exactly what you list |
| `.grant(...)` | A **copy** with more granted; the original is unchanged |

## Wildcards cover a domain, not an action

`fs:*` grants `fs:read` and `fs:write`. Only one level of wildcard is
supported: `*:read` does **not** grant `fs:read`, and that is deliberate.

"Everything that reads" reads like a safe grant and is not — it spans every
domain, including ones installed later by a plugin nobody reviewed. Domains are
the axis a host reasons about.

## Enforcement is a wrapper

```ts
import { withPermissions, withPermissionsAll } from '@hermes/tools';

const guarded = withPermissions(writeFile, PermissionSet.none());
const allGuarded = withPermissionsAll([writeFile, readFile], granted);
```

Enforcement is a wrapper rather than a check inside `defineTool` because the
grant is a property of the **host**, not of the tool. A tool that checked its
own permissions would need the grant at declaration time, before a host exists
to have decided one.

> **Tip:**
  Prefer `withPermissionsAll` over guarding tools one at a time. Permissions are
  decided once, for a whole toolset, at the composition root — and guarding
  tools individually is how one gets forgotten.

## What a denial looks like

A tool called without a permission it declares throws `PermissionDeniedError`,
carrying the missing permission:

```text
fs.write requires the "fs:write" permission, which was not granted.
Retrying with different arguments will not help
```

The message names **one** missing permission and stops, rather than listing them
all — which is the opposite of what the planner, kernel, and schema layers do.
The audience decides it: an operator grants one at a time and re-runs, and a
model needs "stop" rather than a menu.

## The gate on a tool call

Permissions are the gate a tool call passes through before its effect runs. A
tool whose declared permissions are all granted proceeds directly to `execute`;
one missing a grant is refused before anything happens.

Because the check is pure set membership, it is decided entirely by
configuration — the same call, with the same arguments, always gets the same
answer for a given grant.

## Next steps

#### [Write a tool](/guides/write-a-tool)

    Declare permissions on a tool of your own.

#### [Tool errors](/reference/tool-errors)

    Every error the tool framework throws on purpose.