Rules
Manage rules in the Rules Library — hard constraints (must / must-not) and preferences (should) that agents inject into context (enforcementMode: inject) and/or block tool calls on (enforcementMode: gate). Rules are scoped (user-global, agent, app, or workflow), versioned, and ordered by hierarchy: system → org → user → agent.
All endpoints require authentication via X-API-Key header and the appropriate scope.
Rule Object
{
"id": "rule_abc123",
"description": "Never run destructive SQL on production.",
"content": "When the target database has env=prod, refuse DROP / TRUNCATE / DELETE without WHERE.",
"category": "must-not",
"severity": "critical",
"hierarchyScope": "org",
"enforcementMode": "both",
"triggers": {
"keywords": ["drop table", "truncate", "delete from"],
"toolName": "sql.execute",
"embedding": null,
"always": false
},
"enabled": true,
"tags": ["sql", "production"],
"source": "manual",
"scope": {
"agentId": null,
"appId": null,
"workflowId": null
},
"violationCount": 0,
"qualityScore": 0.91,
"isPublic": false,
"ownerId": "user_456",
"createdAt": "2026-04-01T09:00:00Z",
"updatedAt": "2026-05-10T12:00:00Z"
}
GET /api/v1/rules
List rules with filters and pagination.
Scope: rules:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
category | string | No | must / must-not / should |
severity | string | No | critical / high / medium / low |
hierarchyScope | string | No | system / org / user / agent |
enabled | boolean | No | Filter by enabled state |
tags | string | No | Comma-separated tag list |
search | string | No | Full-text search |
limit | integer | No | Max results (default 50, max 200) |
offset | integer | No | Pagination offset |
agentId | string | No | Narrow to rules scoped to this agent |
appId | string | No | Narrow to rules scoped to this app instance |
workflowId | string | No | Narrow to rules scoped to this workflow |
Response 200 OK
{
"count": 12,
"limit": 50,
"offset": 0,
"rules": [ /* Rule objects */ ]
}
POST /api/v1/rules
Create a new rule.
Scope: rules:write
Request Body
{
"description": "Never run destructive SQL on production.",
"content": "When the target database has env=prod, refuse DROP / TRUNCATE / DELETE without WHERE.",
"category": "must-not",
"severity": "critical",
"hierarchyScope": "org",
"triggers": {
"keywords": ["drop table", "truncate", "delete from"],
"toolName": "sql.execute"
},
"enabled": true,
"enforcementMode": "both",
"tags": ["sql", "production"],
"source": "manual",
"scope": { "agentId": null, "appId": null, "workflowId": null }
}
| Field | Type | Required | Description |
|---|---|---|---|
description | string | Yes | Trigger predicate / one-line summary |
content | string | Yes | Rule body (loaded into context on match) |
category | string | Yes | must / must-not / should |
severity | string | No | critical / high / medium / low |
hierarchyScope | string | No | system / org / user / agent |
triggers | object | No | {keywords?, toolName?, embedding?, always?} |
enabled | boolean | No | Default true |
enforcementMode | string | No | inject / gate / both (Phase 1: inject only) |
tags | string[] | No | Tags |
source | string | No | Provenance label |
scope | object | No | {agentId?, appId?, workflowId?} |
Response 201 Created
{ "ruleId": "rule_abc123" }
GET /api/v1/rules/:id
Get a single rule by ID.
Scope: rules:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Response 200 OK
Returns the full Rule object.
PUT /api/v1/rules/:id
Update a rule. Content changes append a new version row.
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
description | string | No | One-line summary |
content | string | No | Rule body |
category | string | No | must / must-not / should |
severity | string | No | critical / high / medium / low |
hierarchyScope | string | No | system / org / user / agent |
triggers | object | No | Trigger predicate |
enabled | boolean | No | Toggle enabled |
enforcementMode | string | No | inject / gate / both |
tags | string[] | No | Tags |
changeNote | string | No | Free-form note attached to the new version |
Response 200 OK
Returns the updated Rule object.
DELETE /api/v1/rules/:id
Delete a rule and its version history. Irreversible.
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Response 204 No Content
POST /api/v1/rules/applicable
Return the enabled rules whose triggers match the given turn / tool / scope, sorted by hierarchy priority (system → org → user → agent). This is the progressive-disclosure read used by agents at turn start.
Scope: rules:read
Request Body
{
"userTurn": "Drop the users table on prod.",
"toolName": "sql.execute",
"scope": { "agentId": null, "appId": null, "workflowId": null }
}
| Field | Type | Required | Description |
|---|---|---|---|
userTurn | string | No | Current user turn (matched against triggers.keywords) |
toolName | string | No | Tool about to be called (matched against triggers.toolName) |
scope | object | No | {agentId?, appId?, workflowId?} |
Response 200 OK
{
"rules": [ /* Rule objects */ ],
"count": 2
}
POST /api/v1/rules/import
Bulk-import rules from the JSON shape /rules/export emits.
Scope: rules:write
Request Body
{
"rules": [ /* Rule rows, or pass the full export wrapper */ ],
"scope": { "agentId": null, "appId": null, "workflowId": null }
}
| Field | Type | Required | Description |
|---|---|---|---|
rules | array | No | Array of rule rows. If absent, the entire body is treated as the export wrapper. |
scope | object | No | Override scope for all imported rows |
Response 200 OK
{ "imported": 12, "skipped": 0, "errors": [] }
GET /api/v1/rules/violations/aggregate
Aggregate cross-rule violation stats — totals, per-rule top-N, per-day, top offending tools — windowed to the last N days.
Scope: rules:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
days | integer | No | Window size in days (1–365, default 30) |
topRules | integer | No | Top-N rules to surface (default 10) |
topTools | integer | No | Top-N tools to surface (default 10) |
agentId | string | No | Narrow to scope |
appId | string | No | Narrow to scope |
workflowId | string | No | Narrow to scope |
Response 200 OK
{
"totals": { "violations": 142, "rulesTriggered": 17 },
"topRules": [
{ "ruleId": "rule_abc123", "description": "No destructive SQL on prod.", "count": 38 }
],
"topTools": [
{ "toolName": "sql.execute", "count": 64 }
],
"perDay": [
{ "date": "2026-05-15", "count": 12 }
]
}
GET /api/v1/rules/export
Bulk-export every rule the caller can read as JSON.
Scope: rules:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
enabled | boolean | No | Filter to enabled (or disabled) rules |
agentId | string | No | Narrow to scope |
appId | string | No | Narrow to scope |
workflowId | string | No | Narrow to scope |
Response 200 OK
{
"rules": [ /* Rule objects */ ],
"exportedAt": "2026-05-16T10:00:00Z"
}
POST /api/v1/rules/import-github
Import a rule from a GitHub repo. Expects RULE.md at the repo root with YAML frontmatter; category is required in the frontmatter.
Scope: rules:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
githubUrl | string | Yes | https://github.com/owner/repo |
Response 201 Created
{ "ruleId": "rule_abc123" }
Returns 400 validation-error for invalid URLs or unparseable frontmatter; 404 fetch-failed if RULE.md is not found at the repo root.
POST /api/v1/rules/:id/assess
Run the Rules quality ruleset against a row and cache the score on it.
Scope: rules:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Response 200 OK
{
"score": 0.91,
"checks": [
{ "id": "has-triggers", "passed": true, "weight": 0.3 }
]
}
POST /api/v1/rules/check
Pre-call gate (Phase 3 substrate). Returns { allowed, blockedBy, reason } for a proposed tool call or action. Pattern-based: blocks only must-not rules with enforcementMode in {gate, both} whose triggers match. Agents call this before executing tools.
Scope: rules:read
Request Body
{
"toolName": "sql.execute",
"description": "Drop the users table on prod",
"args": { "query": "DROP TABLE users" },
"userTurn": "delete everything",
"scope": { "agentId": null, "appId": null, "workflowId": null }
}
| Field | Type | Required | Description |
|---|---|---|---|
toolName | string | No | Tool the agent is about to invoke |
description | string | No | Human description of what the action will do |
args | object | No | Tool input arguments |
userTurn | string | No | Current user turn (for keyword-only rules) |
scope | object | No | {agentId?, appId?, workflowId?} |
Response 200 OK
{
"allowed": false,
"blockedBy": [
{ "ruleId": "rule_abc123", "description": "No destructive SQL on prod." }
],
"reason": "Pattern matched must-not rule"
}
POST /api/v1/rules/:id/violation
Record a violation event against a rule.
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Request Body
{
"attemptedAction": "DROP TABLE users on env=prod",
"detectedBy": "gate",
"threadId": "thr_789",
"runId": "run_321",
"scope": { "agentId": null, "appId": null, "workflowId": null },
"evidence": { "query": "DROP TABLE users" }
}
| Field | Type | Required | Description |
|---|---|---|---|
attemptedAction | string | Yes | Description of what was attempted |
detectedBy | string | No | Detector label (e.g. gate, inject, manual) |
threadId | string | No | Originating thread |
runId | string | No | Originating run |
scope | object | No | {agentId?, appId?, workflowId?} |
evidence | object | No | Free-form evidence payload |
Response 201 Created
{
"violationId": "viol_abc123",
"ruleId": "rule_xyz"
}
GET /api/v1/rules/:id/violations
List violations for a rule.
Scope: rules:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Max results (default 100, max 500) |
offset | integer | No | Pagination offset |
Response 200 OK
{
"count": 38,
"limit": 100,
"offset": 0,
"violations": [
{
"_id": "viol_abc123",
"ruleId": "rule_xyz",
"attemptedAction": "DROP TABLE users on env=prod",
"detectedBy": "gate",
"createdAt": "2026-05-15T14:22:00Z"
}
]
}
POST /api/v1/rules/:id/toggle-enabled
Toggle whether a rule is enabled.
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Response 200 OK
{ "enabled": true }
POST /api/v1/rules/:id/toggle-public
Toggle whether a rule is readable across the organization.
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Response 200 OK
{ "isPublic": true }
PATCH /api/v1/rules/:id/scope
Re-scope an existing rule. Owner-only. Same semantics as
PATCH /api/v1/memory/:id/scope — empty / whitespace values collapse
to null, {"scope": {}} is the explicit user-global opt-out, and
cross-app rows surface as 404 (existence-leak prevention).
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Request Body
{ "scope": { "agentId": "agent-123" } }
Response 200 OK
{
"success": true,
"scope": { "agentId": "agent-123", "appId": null, "workflowId": null }
}
POST /api/v1/rules/:id/share
Share a rule with another user.
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User to share with |
Response 200 OK
{ "success": true }
POST /api/v1/rules/:id/unshare
Remove sharing with a user.
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User to unshare from |
Response 200 OK
{ "success": true }
GET /api/v1/rules/:id/versions
List every version of a rule.
Scope: rules:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
Response 200 OK
{
"versions": [
{
"versionNumber": 1,
"content": "Original rule body",
"changeNote": null,
"createdAt": "2026-04-01T09:00:00Z"
}
],
"count": 1
}
GET /api/v1/rules/:id/versions/:n
Fetch one version of a rule.
Scope: rules:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
n | integer | Yes | Version number (positive integer) |
Response 200 OK
{
"versionNumber": 1,
"content": "Original rule body",
"changeNote": null,
"createdAt": "2026-04-01T09:00:00Z"
}
POST /api/v1/rules/:id/versions/:n/restore
Restore a rule to a prior version. Appends a new version row — does not mutate history.
Scope: rules:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Rule ID |
n | integer | Yes | Version number to restore to |
Response 200 OK
{
"ruleId": "rule_abc123",
"restoredFrom": 1,
"newVersionNumber": 3
}