POLICY.md Specification
Version: 0.2.0
Status: Draft
License: Apache-2.0
A POLICY.md file is a portable, Markdown-native negative-capability manifest for AI agents. It declares what an agent must not do, must ask before doing, and is allowed to do.
1. File format
Every POLICY.md file is a Markdown file with YAML frontmatter delimited by ---:
---
name: project-guardrails
version: 1.0.0
scope: project
appliesTo: [all]
rules:
- capability: file.write
action: deny
condition:
field: { path: payload.path, op: includes, value: .env }
reason: Never write .env or secret files
---
## Forbidden
- Never commit `.env` files or any file containing credentials.The frontmatter contains machine-enforceable rules. The Markdown body contains human-readable policy sections.
2. Frontmatter schema
Top-level fields
| Field | Required | Type | Description |
|---|---|---|---|
name | yes | string | Unique policy identifier, kebab-case or snake_case. |
description | no | string | Human-readable summary. |
version | no | string | Semantic version or opaque policy version. |
scope | no | string | One of project, skill, agent, department. Default: project. |
appliesTo | no | string[] | Agent or skill ids this policy applies to. ["all"] means every agent. Default: ["all"]. |
severity | no | string | One of block, warn, require_approval. Default: block. |
rules | no | Rule[] | Machine-enforceable rules. |
Rule schema
| Field | Required | Type | Description |
|---|---|---|---|
id | no | string | Stable rule identifier. |
capability | yes | string | Capability pattern, e.g. file.write, payment.*, *. |
action | yes | string | allow, deny, or require_approval. |
condition | no | Condition | When the rule applies. Omit to match every intent. |
reason | no | string | Explanation returned to the agent/runtime. |
priority | no | number | Higher number wins ties. Default: 0. |
description | no | string | Human-readable rule note. |
Capability patterns
- Exact:
file.writematchesfile.write. - Single-segment wildcard:
payment.*matchespayment.send,payment.refund, etc. - Global wildcard:
*matches every capability.
Condition schema
A condition is exactly one of:
condition:
field: { path: payload.path, op: includes, value: .env }
condition:
and:
- field: { path: payload.amount, op: gt, value: 1000 }
- field: { path: constraints.environment, op: eq, value: production }
condition:
or:
- field: { path: workerId, op: eq, value: payment-bot }
- field: { path: specialist, op: eq, value: payment-bot }
condition:
not:
field: { path: constraints.environment, op: eq, value: development }Field condition
| Field | Required | Type | Description |
|---|---|---|---|
path | yes | string | Dotted path into the ActionIntent object. |
op | yes | string | Operator name. |
value | depends | any | Right-hand value. |
values | depends | any[] | Right-hand array for in/notIn. |
Operators
| Operator | Needs value | Needs values | Description |
|---|---|---|---|
eq | yes | no | Strict equality. |
neq | yes | no | Strict inequality. |
gt | yes (number) | no | Greater than. |
gte | yes (number) | no | Greater than or equal. |
lt | yes (number) | no | Less than. |
lte | yes (number) | no | Less than or equal. |
includes | yes | no | Array includes value, or string contains substring. |
in | no | yes | Field value is in array. |
notIn | no | yes | Field value is not in array. |
exists | no | no | Field is defined and not null. |
notExists | no | no | Field is undefined or null. |
wildcard | no | no | Always true. |
Resolvable paths
payload.<key>- arbitrary payload field supplied by the tool.constraints.<key>- action constraints (environment,resourceId,maxAmount, ...).requester.<key>- requester metadata (type,id,tenantId).risk,actionType,actionClass,reversibility,capability,specialist,workerId,toolName,connectorId.
3. Markdown body
The Markdown body is optional but strongly recommended. It is inserted into the agent system prompt by buildPoliciesContext(). Conventional sections:
## Forbidden- absolute negative boundaries.## Ask first- actions that require human approval.## Allowed- safe actions the agent may take freely.
Runtimes may render the body as-is or summarize it.
4. File discovery
A PolicyManager searches the following locations:
<workingDir>/POLICY.md(project root).<workingDir>/<policyDir>/POLICY.mdfor each configured policy directory.<workingDir>/<policyDir>/<name>/POLICY.mdfor each subdirectory.<workingDir>/<skillDir>/<name>/POLICY.mdwhen aSKILL.mdorskill.mdexists in the same directory.
Default policy directories: .policies, .devin/policies, .sidian/policies.
Default skill directories: .devin/skills, .sidian/skills, .opencode/skills, .claude/skills.
Skill-attached policies inherit scope: skill and appliesTo: [<skillName>]. Their rules are automatically scoped to the matching workerId or specialist at runtime.
5. Runtime semantics
ActionIntent
When a tool is about to run, the runtime builds an ActionIntent:
interface ActionIntent {
id: string;
workRunId: string;
taskId?: string;
workerSessionId?: string;
workerId: string;
specialist: string;
actionType: 'tool' | 'connector' | 'command' | ...;
capability: string;
toolName?: string;
connectorId?: string;
actionClass?: 'read' | 'write' | 'execute' | 'connect';
reversibility?: 'reversible' | 'conditional' | 'irreversible';
riskLevel: 'low' | 'medium' | 'high' | 'critical';
payload: Record<string, unknown>;
constraints?: ActionConstraints;
requester: ActionRequester;
createdAt: number;
}AuthorizationEngine
- Collect all rules whose
capabilitypattern matches the intent'scapability. - Evaluate each rule's
conditionagainst the intent. - Among matching rules, select the highest
priority. - If multiple rules share the top priority, the first one wins.
- Return
allow,deny, orrequire_approvalplus the matched rule id and reason. - If no rule matches, the default is
allow.
Composite engines
Multiple engines can be composed. The most severe decision wins:
deny > require_approval > allow6. Agent context
buildPoliciesContext(manager) renders every loaded policy as Markdown suitable for injection into an agent system prompt. buildAgentPoliciesContext(manager, agentId) filters to policies whose appliesTo includes all or the given agent id.
7. Validation
PolicyValidator checks:
- Required frontmatter fields and valid types.
- Valid
scope,action, andopvalues. - Required
value/valuesfor each operator. - YAML syntax with line numbers.
- Unknown fields when
strict: true.
8. Distribution
Policies can be published as repositories and installed with the CLI:
policy install owner/repoInstalled policies live in .policies/<name>/POLICY.md.
9. Framework adapters
The @sidianlabs/policy/adapters/codex module converts policies into OpenAI Codex-compatible formats:
toCodexExecpolicy(files)→ Starlarkexecpolicyrules.toCodexGuardianPrompt(files)→ Guardian LLM-judge prompt text.
10. Versioning
This specification follows semantic versioning. A policy version is advisory; runtimes should treat the latest loaded copy of a policy as authoritative unless they implement version pinning.