seisin

seisin (n.) — the legal possession of a piece of land. Not who owns it on paper: who holds it now.

OS-enforced permissions and ownership for AI agents.

Run multiple coding agents without letting them overwrite each other’s work.

Each role gets its own files, keys and network access. The OS enforces the boundary. When one agent needs another role’s territory, seisin tells you who owns it — turning a dead end into a handoff.

npm version test MIT node 18+ one dependency

What you see

The kernel says no. seisin says whose.

Other permission layers answer yes or no, and the good ones say which rule it was. Answering “no, and it belongs to backend” is the part an operating system cannot know.

A terminal: an agent running as the frontend role tries to write a file under src/api, the kernel denies it, and seisin prints that the path belongs to backend and queues a request.
An agent running as frontend reaches into src/api/. The kernel denies it; seisin names the owner and leaves a request behind.
$ seisin run frontend -- sh -c 'echo // fix >> src/api/orders.ts' seisin: frontend · writes src/web/** public/** (+6 scratch) · keys netlify-token.txt · runs no agent seisin knows · env 20 kept, 58 dropped · read all sh: src/api/orders.ts: Operation not permitted seisin: 1 kernel denial(s) recorded #1 frontend wants write on src/api/** (owned by backend) first asked over src/api/orders.ts id frontend:write:src/api this run: 1 new request(s) — seisin requests

The agent can ask before it reaches, too — seisin whose src/api/orders.ts answers from inside the sandbox.

The loop

Denied → whose → a person decides

Nothing is granted by an agent, and nothing is granted through the MCP server. Granting happens in a terminal or in the console, by a person.

agent running as a role writes the kernel Seatbelt · bubblewrap denied seisin reads the denial whose it was belongs to backend the log one hash-chained line a request queued, waiting a person grants or declines, with a reason edits seisin.toml → seisin run → sandbox settings

Measured in real use

See where the policy costs you. Remove grants you do not need.

In one six-day deployment with four teams of agents, the boundary did more than deny writes. It showed what the policy was costing and where the noise came from.

Kernel denials
1,422

Every denied read and write recorded during the window.

One unnecessary lock
75.9%

Were .git/index.lock. The fix was not a wider grant; it was removing an unnecessary write with GIT_OPTIONAL_LOCKS=0.

A person's attention
1 decision

Deduplication collapsed 1,445 recorded asks into one relevant item waiting for a person.

One deployment, not a benchmark. The full method and the numbers that did not reproduce are in the field notes.

Policy review

A policy can get smaller, with evidence.

seisin review turns runtime history into questions you can act on: where a role keeps being denied, which grants were never used, and which paths nobody owns.

$ seisin review 60 entries, 2026-08-23 to 2026-09-11 Denied repeatedly 9× frontend write src/api/checkout — belongs to backend 4× backend write src/web — belongs to frontend Granted, never used frontend public/** backend migrations/** Unowned 4× territory legacy

Trimmed: each section also says why it matters, and an unowned path says what kind of thing it is — territory (ownable), or git's, temporary, build output, a credential — because only the first one wants an owner. This is not automatic policy mutation. It is evidence for a person deciding whether to narrow, transfer or add ownership.

How you use it

Sixty seconds

  1. npm install -g seisinOn Linux, three system packages first — the README lists them.
  2. seisin initProposes a policy from what your repo already says: .claude/agents/, then CODEOWNERS, then one role per top-level folder that holds code. It proposes — a generated policy you did not read is not a policy.
  3. seisin checkThe map first, then one line for each way it does not hold — --verbose says why each matters. Read it before you trust it.
  4. seisin wireLets the agent record what it does, so a denial can be explained afterwards.
  5. seisin run <role> -- claude -p "…"Runs the agent as one of the roles seisin check lists, inside its own territory. Anything not listed is denied — there is no permissive default.

The policy

One file, at the root of your repo

[keys]
dir = ".secrets"

[network]
allow = ["github.com", "*.github.com"]

[roles.frontend]
writes = ["src/web/**", "public/**"]
keys   = ["netlify-token.txt"]

[roles.backend]
writes  = ["src/api/**", "migrations/**"]
keys    = ["database-url.txt"]
network = ["api.stripe.com"]

[roles.ops]
writes        = ["deploy/**"]
control_files = ["ide"]  # its own .vscode/
writes
The role’s territory. A path is in exactly one role’s territory, or in several — and overlap is allowed and reported.
keys
What the role may hold. Either a file under the declared key directory, or a scheme:// reference a provider resolves outside the sandbox — the role gets the value, never the file.
network
Per-role, and it replaces the global list rather than adding to it: a domain in [network] is reachable by every role, including the ones whose job is to reach nothing.
control_files
Files that make something happen outside the sandbox. Git hooks, .claude/ and .mcp.json are closed to every role. Editor settings (.vscode/, .cursor/, .windsurf/) are protected too, unless a role is handed "ide"; instruction files (CLAUDE.md, AGENTS.md…) only when [protect] instructions = true.
HTTP(S) works on both platforms. SSH does not work on macOS. The allow list is enforced by proxies the runtime starts. One of them is a SOCKS5 proxy that could carry SSH, but on macOS the chain breaks at its last link: it requires authentication and the ProxyCommand the runtime wires up cannot send any. On Linux the runtime routes git’s SSH through an authenticated proxy instead (read from its source, not measured here). Use an HTTPS remote for git, and on macOS run SSH deploys outside the confined run.

Three ways in

They all say the same thing

A change to what any of them says lands in all three. That is a rule of the repo, and a test holds one half of it.

CLI

What a person types

And what every agent runs under. run, check, whose, walls, grant, decline.

Console

For deciding

seisin ui — opens on what is waiting on a person. A grant needs a reason, says what it covers before the click, and shows the line it wrote.

MCP server

For asking

An agent asking about its own situation. Read-only by design — granting is never a tool call.

The seisin console: four counters across the top, requests waiting on a person with a required reason, a grant of another role's path that asks for a second click, the line the grant wrote, the denials grouped by path with the kind of each unowned one, the walls each role keeps hitting, the roles, and a control-files change previewed before it is saved.
The console opens on what needs a decision: a grant takes a reason, and a second click when the path is another role’s. Then the denials grouped by path, each unowned one with its kind; the walls a role keeps hitting; the roles, read-only live; and a control-files change, previewed before it is saved.

The honest parts

What the boundary does — and what it does not.

What seisin is for

  • OS-enforced write territories. Seatbelt or bubblewrap applies the boundary.
  • Declared secret isolation. Roles read only the keys assigned to them.
  • Per-role network policy. Egress follows each role’s allow list, enforced by the sandbox.
  • Control files closed. What acts outside the sandbox the moment it changes — git hooks, .claude/, .mcp.json — is denied to every role, and so are editor settings unless the policy hands them to one.
  • A tamper-evident log. Each line carries the hash of the one before, the chain’s start is recorded, and seisin log verify says when either was changed.
  • Ownership, and handoffs to a person. A denial names who owns the path; agents cannot grant themselves anything.

What it does not promise

  • Not a sandbox. It configures one — @anthropic-ai/sandbox-runtime by default, tartaros, or one you declare; the OS does the enforcing.
  • Not hostile-agent containment. An allowed domain is still an allowed exfiltration channel, and while tampering with the log is closed, a hostile agent can still add lines to its own history.
  • Not complete secret discovery. A secret outside declared key directories remains readable — with the default read = "all" that includes ~/.ssh, ~/.aws and ~/.npmrc; read = "territory" closes those homes. seisin scan helps find the rest.
  • Not an orchestrator. It does not schedule agents or decide who does the work. Windows is not yet tested.
Five reviews went looking for ways past the boundary, and what running four teams of agents behind it actually cost — including the numbers that did not survive a re-check — is published too. What it has been put through → Field notes →