--- title: Permission Model description: How network and knowledge-base permissions are requested and checked at install time; a gem's own storage, files and folders, and document extraction need no permission. section: dev order: 30 updated: 2026-11-05 translation_of: zh source_sha: 6abd1a9aec85 --- # Permission Model ## Principles A gem lists the permissions it needs in its manifest. Only these need to be requested: network (`network`), the user's models (`llm` under `providers`, which spends the user's money), the knowledge base (`knowledge`), email (`email`), and the user's accounts (`connections`). A gem's own storage, files, and document extraction (`ctx.extract`) are not permissions; every tool of every gem has them. There is no confirmation step at install time (as in a phone app store, the user sees what the gem can do on its store page and installs it with one click). Install records the permissions listed in the manifest, and at run time only this record is consulted; the manifest is not read again. **Anything that needs configuration by the user is off until configured.** Which mailboxes the gem can read, which parts of the knowledge base it can use, which external account it uses: at install time there are none of these, and the gem's requests are rejected until the user turns them on in the gem's page. On upgrade, what the user already turned on is kept; anything new is likewise off by default. There is no confirmation popup at run time. ## Network ```json "permissions": { "network": { "hosts": ["api.open-meteo.com"], "reason": "Look up weather forecasts" } } ``` | Field | Required | Description | | --- | --- | --- | | `hosts` | Yes | External hosts the gem may reach. Host names only (`api.example.com`), with no scheme or path and no wildcards. If the service is not on the default port, include the port (`api.example.com:8443`; a different port is a different host). Internationalized domain names are written in `xn--` form. IP addresses and `localhost` may be listed only when `allowLocalNetwork` is `true`. A request to a host that is not listed is rejected before it is sent. | | `allowLocalNetwork` | No | Whether the gem may reach the user's own machine and local network. Defaults to `false`; see below. When `true`, `hosts` may be empty. | | `reason` | Yes | Why the gem needs network access. Shown on the gem's page. | `ctx.http.fetch` and `ctx.extract.fromUrl` in tools (download a document, then extract its text), and agents that can use the browser, are all checked against this same permission, including every redirect hop. On top of it sit the user's own network rules ("Settings › System › Network access"); a gem's permission can only narrow these, never widen them. ### Local machine and internal network Notochord sorts addresses into two classes with different rules: - **External network**: websites and online services. The user can choose allowlist or blocklist mode; the default is open. - **Local machine and internal network**: the user's computer (`localhost`, `127.0.0.1`, `[::1]`), addresses on the home or company network (`10.x.x.x`, `172.16–31.x.x`, `192.168.x.x`, `169.254.x.x`, `100.64–127.x.x`, and the IPv6 equivalents), and domain names that resolve to those addresses (for example a company-internal `wiki.corp.example.com`). Why they are separate: the local machine and internal network hold the user's own devices and internal services (router and NAS admin pages, printers, the company's OA system and wiki). These often have no password, because normally only people inside can reach them, and they often hold internal data. A web page the AI reads may hide instructions, and a third-party gem may try to probe the user's internal network; if these addresses were freely reachable, that would open a door between the internet and the user's internal network. So by default none of them can be reached, and only addresses the user has listed personally are allowed. For a gem to reach the local machine and internal network: - Write `"allowLocalNetwork": true` in the manifest (in a Gem Studio draft, write `"allow_local_network": true`, or tick "Local machine and internal network" under Permissions in the overview). The gem's page states that it can reach the local-machine and internal-network addresses the user has listed. - The gem can reach only the addresses the user has listed under "Network access › Local machine and internal network"; the gem cannot specify them itself. Local and internal addresses do not go into `hosts`. - A gem without `allowLocalNetwork` cannot reach an address even if the user has listed it. - Example: a gem that helps a developer test a website. Only after the user adds `localhost:5173` to the list can its agent open `http://localhost:5173` in the browser. - Downloads of [`resources`](/en/dev/manifest.md#resources) never reach the local machine or internal network. ## Files are not a permission A gem's code can touch only two kinds of files: **the folder of this one run**, and **the folder of the conversation it was called from**. Nothing outside the conversation is reachable, just like the AI in the conversation (its file tools also work only in the conversation folder). There are no fixed paths to request, and nothing to write in the manifest: every tool has `ctx.files`. For the read and write interface, see the [Tools API](/en/dev/tools.md#ctxfiles). ### Run folder Every run has its own folder: | Who is running | Where the run folder is | | --- | --- | | A SOP or tool called from a chat | In the conversation's working directory, at `runs/<name>-<time>/` | | An exposed agent called from a chat | Same, at `runs/<agent name>-<time>/`; also the working directory of that agent's session | | A scheduled or event-triggered SOP | In the gem's own data folder, at `runs/<name>-<time>/` | | Tools and agents called from a SOP | Shared with that SOP run | - It lives in the conversation that called it, so the AI in the conversation can read and write the results with its own file tools, and the user can open them in the conversation's working directory. - Conventions inside the folder (not enforced): `tmp/` for intermediate files, `output/` for results. Write generated reports as Markdown or HTML. - `tmp/` is deleted after a successful run; on failure or cancellation it is kept. A retry reruns from the start, and steps that already succeeded do not write again. A run folder with nothing left in it is deleted. - When files were left behind, the caller receives `{ "output": <return value>, "folder": "runs/…" }`, where `folder` is relative to the conversation's working directory; when no files were left, the return value is passed through unchanged. An exposed agent returns `{ "reply": …, "folder": … }`. - When a gem is uninstalled, the run folders in its data folder are deleted with it; those already placed in conversations are kept, since they are the user's files. ### Conversation folder Files the user dragged into the conversation, the folder chosen with "Chat about a folder", and results left by other gems all live in the conversation folder. A gem reads and writes it with `workspace:<relative path>` (`workspace:inbox/invoice.pdf`; `workspace:` is the top level). - **Paths are ordinary parameters.** The AI passes a relative path inside the conversation folder as a string (`{ "folder": "inbox/", "note": "notes.txt" }`), and the gem adds the `workspace:` prefix itself. For several files or a whole subfolder, the gem calls `list` and handles them itself; the AI does not have to name them one by one. `inputSchema` needs no special file type, and there is none. - **Files and folders starting with `.` do not exist for a gem** (`.env`, `.git`): they cannot be listed, read, or written. - **The user's files are not lost, and changes can be rolled back.** Every change a gem makes to the conversation folder is recorded in the file history. On overwrite, the old version is saved first (up to 100 MB; larger ones go to the system trash). `delete` moves the file to the user's trash and keeps a copy; `move` records only the path, and a `move` onto an existing path is rejected. When the user retries in chat or edits a previous message, files the gem changed on that branch are restored to their state before the change (files changed elsewhere since are left alone). In environments without a trash (the headless server build), deletion is permanent, but the copy in the history can still be rolled back. - Calls without a conversation, that is calls started from a page, or scheduled SOPs with no working folder chosen on the gem's page, get `E_PERM_DENIED` when they use `workspace:`. - A gem agent's built-in `fs.*` platform tools see the conversation folder as `workspace/`, **read-only**; to change files, go through the gem's own tools. ### Scheduled and event-triggered SOPs There is no conversation. Their `workspace:` is a folder the user chose for **this gem** under "Settings › Working folder" on the gem's page (shown only when the gem has a scheduled or event-triggered SOP); if none is chosen there is no `workspace:`. Folders that cannot be chosen (the filesystem root, the home folder itself and anything containing it, Notochord's data directory, system and other applications' folders, hidden folders) are rejected, the same as for "Chat about a folder". ### What the user sees Files do not appear under "What it can do" on the gem's page, and upgrades do not require fresh approval for files: what a gem can do with files is decided by the conversation, not by the manifest. The user sees folders in only two places: the conversation's working directory, and the working folder chosen on the gem's page for scheduled SOPs. Installing a gem that declares network access amounts to trusting it to send files in the conversations that use it to the sites it declares. ## Storage is not a permission Every gem has its own storage for run state, caches, and records it organizes itself. It is not a permission: nothing to write in the manifest, nothing for the user to turn on, and it is not listed on the gem's page. For the read and write interface, see [Data and Storage](/en/dev/storage.md). - **Every tool has all of the storage**: `ctx.data` (collections, Logs, documents, Blobs, references), `ctx.storage.kv`, and the private SQL database `ctx.storage.sql`. A SOP can also call `storage.kv.*` and `storage.sql.*` directly. The manifest has no storage fields. - **`sideEffect: "read"` is your promise; the platform does not check it.** Plan mode runs tools declared `read` before the user approves the plan, and the platform does not look at what they actually do. So only a tool that writes nothing may declare `read`: no writes to `ctx.data` or `ctx.storage`, none to the knowledge base, no requests other than GET. A false declaration is a quality problem of the gem. - **Each gem has its own folder, which other gems cannot read.** The database and Blobs are encrypted with the gem's own key; documents (`.md` files) and the [run folders](#run-folder) of scheduled and event-triggered SOPs are plain text. When a gem is uninstalled, the whole folder is deleted together with the key. For details see [Encryption and isolation](/en/dev/storage.md#encryption-and-isolation). ## Resource downloads are not a permission [`resources`](/en/dev/manifest.md#resources) in the manifest need no permission: the download URL is fixed in the manifest, Notochord downloads the file and verifies its checksum, and the gem's code can neither change the URL nor handle the download. It is unrelated to the gem's `network` permission, which governs requests the tool makes itself. The user can still see and control it: - On the gem's page, under "What it can do", there is an entry "Download additional files" listing each file's description, size, which host it comes from, and when it is downloaded. - Downloads, like the gem's own requests, are subject to the user's network policy ("Settings › System"). ## Knowledge permissions Permission strings all take the form `data-type.action:scope`: | Permission | Meaning | | --- | --- | | `facts.read:<subject>:<prefix>` | Read a category of facts. The subject is `self` or an entity kind (such as `person`); the prefix is `a.b.*` or a full predicate, at least two levels. | | `facts.write:<subject>:<prefix>` | Write a category of facts; must be covered by some `facts.read`. | | `entities.resolve:<kind>` | Look up an entity by a known email or name; cannot list all of them. | | `entities.read:<kind>` | List all entities of that kind. | | `entities.write:<kind>` | Create and edit entities of that kind (includes resolve). | | `collections.read:<target>` / `collections.write:<target>` | The target is `diary`, `keep`, or `user-selected` (the user ticks it on the gem's page after install). The grant includes all subfolders inside that collection. | | `collections.create` | Create its own collection; after creation it is automatically readable and writable. When created inside another collection, write permission on that collection is also required. | | `changes.manage` | View, undo, and modify records written by other writers. | | `origin.override` | Specify the origin (`origin` / `source`) itself when writing. | Validation rules: - At install: a malformed string, a prefix shorter than two levels (for example `self:*`), a `facts.write` not covered by a `facts.read`, or a knowledge-base id written directly in the manifest all make the install fail. - At run time: every call re-reads the user's current grants. After the user revokes a permission under "Settings › Knowledge" on the gem's page, the next call takes effect immediately. - Query methods simply return nothing for content that is not granted; operations on a single object throw an error when permission is missing. See the [Knowledge API](/en/dev/knowledge.md) for details.
Notochord
简体中文English