Restrict agents with path policies

Read-only paths, writable paths, how the two compose by longest match, what agents are told, and what a refusal looks like.

Two flags decide which parts of the notebook one server process may change. --read-only-paths names what it may never change. --writable-paths names what it may change, and makes everything else read-only for that process. Both take a comma-separated list of notebook-relative paths, and both have an environment variable: SLIVINGDOC_READ_ONLY_PATHS and SLIVINGDOC_WRITABLE_PATHS.

An entry protects or opens itself and everything below it, matched on segment boundaries: docs covers a file named docs and every path under docs/.

Warning: This is a guardrail at the MCP tool boundary, not a security boundary against the agent. The serve process holds the S3 credentials, and an agent that can read that environment, or launch its own slivingdoc process, bypasses the setting. It is the same model as an operator’s sftp configuration: the policy lives in the server configuration, never in the data.

Read-only paths

Use --read-only-paths to let a fleet of agents read injected material — FAQ answers, reference documentation — without risking that one of them overwrites it:

export SLIVINGDOC_BUCKET=my-notes
slivingdoc serve --read-only-paths docs,faq.md

The restriction applies to the process configured with it, not to the notebook. A human running without the flag keeps full write access to the same paths:

slivingdoc pull notes
# edit notes/docs/faq.md
slivingdoc commit notes -m "update the FAQ"

The next pull by any agent picks that change up with no conflict.

Writable paths

Use --writable-paths to confine a fleet of agents to a directory each, where listing what they must not touch is not possible — a directory that did not exist at startup, or a file at the notebook root, would otherwise stay writable:

slivingdoc serve --writable-paths agents/scout

Because the writable set is non-empty, everything it does not cover is protected for that process: other agents’ directories, files at the notebook root, and directories that do not exist yet.

How the two compose

The two settings compose, and the longest matching entry wins, so a protected region can hold a writable subdirectory:

slivingdoc serve --read-only-paths docs --writable-paths docs/drafts

That process may write under docs/drafts and nowhere else.

Nesting can go deeper than one level, and the entries you wrote decide there too:

slivingdoc serve \
  --read-only-paths notes,notes/agent-a/locked --writable-paths notes/agent-a

That process may write under notes/agent-a, except under notes/agent-a/locked, which the longer read-only entry protects again. Listing the broader notes beside it changes nothing about the narrower entry: adding an entry to a setting never makes a narrower entry of the same setting stop applying.

A path named by both settings is a configuration error, not a silent precedence rule. Startup refuses, naming the path and both settings.

What agents are told

An agent learns the rule before it edits, and again if it forgets. With a read-only set configured, the entries are named in the server instructions, in both tool descriptions, and in the readOnly array of every pull and commit result.

With both sets configured, the server instructions, both tool descriptions, the result text item, and the report name both sets and end with the rule that decides between them: where the two sets nest, the longest matching entry decides. That replaces the plain “write elsewhere” sentence, which a non-empty writable set would make false, so an agent is never told to write only under an entry and, in the next sentence, that changes under it are refused.

What a refusal looks like

A commit that changes a protected path is refused, the touched files are reset to the last accepted content, and the result names the violated entries:

INVALID_REQUEST · READ_ONLY_PATH
docs is read-only in this server. Your changes there were discarded and the files reset. Write outside the read-only paths, then commit again.
  docs/faq.md  read-only
next: edit the files, then commit
retryable: false
read-only: docs

The MCP structured result an agent decodes carries the same message plus the stable reason: "READ_ONLY_PATH", action: "EDIT_FILES", a READ_ONLY reason on the docs/faq.md file entry, and the readOnly array naming every configured entry.

Under a writable set the refusal names where the process may write instead, because the protected region is then nearly the whole notebook.

A pull restores protected paths from the accepted remote state. That restore applies to a workspace that passes the content rules: an invalid file under a read-only path — a binary, a symbolic link, an invalid name — is refused as INVALID_CONTENT naming that file, on pull and commit alike, and the restore does not run until that file is deleted.

Clearing an inherited value

Like every other shared flag, an explicitly empty flag clears an inherited environment value instead of falling back to it:

slivingdoc serve --writable-paths=

That is how a process asks not to be confined when SLIVINGDOC_WRITABLE_PATHS is set in the environment it inherits. --read-only-paths= does the same for SLIVINGDOC_READ_ONLY_PATHS.

Startup refusals

An invalid entry refuses startup before any native or network dependency loads, so a mistake never reaches the bucket. An entry is invalid when it is an absolute path, when it contains a .. or .git segment, or when it is over the length bound. The same point refuses a path named by both settings, and an entry that sits below another entry of its own setting.

Next

Last updated September 21, 2026

Type to search the documentation.