Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

How Shiin works

Note

Design document: draft. Authorization, approval, evidence, and early client/integration code exist in source; host, platform, and milestone validation remain incomplete. The specifications are the precise source for everything summarized here.

This page follows one action from the moment an agent proposes it to the moment it is recorded, then walks through four concrete examples.

The path of one action

  1. The agent proposes an action. The agent, running inside a host such as Claude Code, decides to run a command, edit a file, or call a tool.
  2. An adapter intercepts it. Before the host executes the action, the host hands it to a Shiin adapter, for example through a hook.
  3. The adapter normalizes it. The adapter turns the host’s payload into an action request: which agent is asking, what action type it is, what resources it touches, what its effects are, and which task is active. Anything the adapter cannot classify is marked opaque.
  4. The engine evaluates it. The engine takes the action request and a policy snapshot and runs five stages in a fixed order.
  5. A decision comes back. The decision is allow, deny, or pending, with a reason code and a message for the agent.
  6. The adapter enforces it in the host’s own protocol. For a hook, that means returning the host’s allow or deny response. For pending, it means asking an approver and waiting, or using the host’s native prompt, or denying with a ticket when the host cannot wait.
  7. An audit event is written to the audit log before a non-read action is allowed to proceed.

The diagram shows the Unix daemon approval path available in source. Host integration and cross-platform validation remain open.

sequenceDiagram
    participant Host as Host and agent
    participant Adapter
    participant Engine
    participant Approver
    participant Audit as Audit log
    Host->>Adapter: proposed action (host payload)
    Adapter->>Engine: action request
    Engine->>Engine: identity, capability, policy, intent, risk
    Engine->>Audit: request received, decision made
    Engine-->>Adapter: pending
    Adapter->>Approver: approval request
    Approver-->>Adapter: resolution (allow once)
    Adapter->>Audit: approval resolved, grant used
    Adapter-->>Host: allow

The five stages

Stages run in this order. Later stages can only make the result stricter.

StageQuestion it answersCan return
IdentityDo we know which agent is asking, and how sure are we?deny, pending, or pass
CapabilityMay this agent attempt this class of action at all?deny, pending, or pass
PolicyWhat do the rules say about this action on these resources?allow, deny, or pending
IntentDoes the action fit the constraints of the active task?deny, pending, or pass
RiskDoes the action have a risk signal that needs escalation?deny, pending, or pass

Identity, capability, intent, and risk are gates: they can stop or hold an action but never allow it. Only a policy rule, or a grant created by an approver, can produce allow.

The results combine in a fixed way, specified in Evaluation:

  • If any stage returns deny, the decision is deny.
  • Otherwise, if any stage returns pending, the decision is pending, unless a matching grant already covers it.
  • Otherwise, if a policy rule allows the action, the decision is allow.
  • Otherwise, the decision is the default decision, which is deny.

Four examples

The examples use this policy, written in the TOML format specified in Policy syntax. The syntax shown here is illustrative until that specification is accepted.

# .shiin/policy.toml
version = 1

[capabilities.workspace]
id = "workspace"
allow = true
action_types = ["fs.*", "shell.exec"]

[defaults]
decision = "deny"

[[rule]]
id = "edit-source"
effect = "allow"
action_types = ["fs.read", "fs.write", "fs.create"]
resources = ["./src/**", "./tests/**"]

[[rule]]
id = "no-secrets"
effect = "deny"
action_types = ["fs.read"]
resources = ["**/.env", "**/.env.*", "~/.ssh/**"]
reason = "Secret files are not readable by agents."

[[rule]]
id = "run-tests"
effect = "allow"
action_types = ["shell.exec"]
commands = ["cargo test", "cargo nextest run"]

[risk]
opaque_command = "pending"

1. An allowed edit

The agent edits src/login.rs inside the workspace.

  • The adapter builds an action request with action type fs.write and one effect: an exact write to /home/dev/project/src/login.rs.
  • Identity and capability pass. The edit-source rule allows the write.
  • No task is active, and no risk signal applies.
  • The decision is allow. An audit event is written, and the host proceeds.

2. A denied read of a secret

The agent tries to read .env to “check the configuration”.

  • The action request has action type fs.read on /home/dev/project/.env.
  • The no-secrets rule matches and returns deny. The edit-source rule does not matter, because deny wins.
  • The decision is deny with reason code deny.policy.
  • The agent receives a fixed denial message; the operator can inspect the reason code and trace.

3. A command whose effects are unknown

The agent runs python scripts/cleanup.py.

  • The classifier recognizes a Python interpreter running a script. It cannot know what the script will do, so the action request carries an opaque effect.
  • No rule allows the command, and the opaque effect raises the opaque_command risk signal.
  • The decision is pending with reason code pending.opaque.
  • In v0.1, the adapter uses Claude Code’s native permission prompt. From v0.3, the request goes through the approval protocol, and an approver can allow it once.

This example also shows a limit: once the script is allowed, Shiin does not see what it does. Limitations explains why, and Using Shiin with sandboxes explains how to cover that gap.

4. An action outside the task

The developer declares an experimental task; the following is illustrative task data, not a command-line input format:

# task declaration (illustrative)
description = "Fix the login bug"
scope.paths = ["./src/auth/**", "./tests/auth/**"]
forbid.action_types = ["db.schema_change"]
expires_at = "2026-09-14T18:00:00Z"

The agent then tries to create migrations/0042_add_login_attempts.sql.

  • The edit-source rule would not allow this path anyway, but suppose the operator’s policy allowed writes anywhere in the workspace.
  • The intent stage checks the task’s scope.paths constraint. The path is outside it.
  • The decision is deny with reason code deny.intent, even though standing policy allowed the write.

If the agent instead ran psql -c "ALTER TABLE users ADD COLUMN login_attempts int", the experimental SQL classifier can identify a db.schema_change effect, and the forbid.action_types constraint can deny it the same way. The classifier recognizes a conservative subset; unsupported or ambiguous SQL remains opaque, and host adapters do not yet use this classifier.

Where Shiin sits relative to a sandbox

Shiin and a sandbox answer different questions, and they work best together. Shiin decides before an action whether it should happen at all. A sandbox limits what can happen if an allowed action, or code it runs, misbehaves.

flowchart LR
    agent["Agent proposes action"] --> shiin{"Shiin decision"}
    shiin -->|deny| stop["Action refused"]
    shiin -->|allow| sandbox["Action runs inside a sandbox"]
    sandbox --> system["Files, network, services"]

What exists today

The engine, policy, hooks, audit log, experimental task constraints, Unix approvals, grants, receipts, replay, a Rust client, and an early MCP proxy exist in source. The identity gate currently passes through; capabilities require explicit action/effect-class grants. SQL classification remains experimental and is not wired into host adapters. The roadmap lists validation still required for each part.

PartMilestone
Claude Code adapter, engine, policy, audit logv0.1 (source; host validation open)
Task declarations and intent bindingv0.2 (experimental source; measurements open)
Approval protocol and grantsv0.3 (Unix source; platform validation open)
Receipts and replayv0.4 (source; external review open)
MCP proxy, Rust client, and SQL classifiersv0.5 (early source; conformance/integration validation open)