Skip to main content

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 can belong to one or more agents (linkedIds), are 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",
"ownerId": "user_456",
"ownerName": "Alice Example",
"organizationId": "org_789",
"sharedWith": [],
"isPublic": false,
"source": "user",
"tags": ["sql", "production"],
"linkedIds": [],
"currentVersion": 1,
"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"],
"always": false
},
"enabled": true,
"enforcementMode": "both",
"violationCount": 0,
"scoreResult": {
"score": 91,
"grade": "A",
"rulesetVersion": "1.0.0",
"failedCriticalCount": 0,
"failedHighCount": 0
},
"lastAssessedAt": "2026-05-10T12:00:00Z",
"createdAt": "2026-04-01T09:00:00Z",
"updatedAt": "2026-05-10T12:00:00Z"
}

linkedIds lists the agents the rule belongs to. lastViolationAt and lastTriggeredAt appear once the rule has been violated or matched. scoreResult (its quality score and grade) and lastAssessedAt are kept current by every change to the rule; assess returns the full report behind them.


GET /api/v1/primitives/rules​

List rules with filters and pagination.

Scope: rules:read

Query Parameters

ParameterTypeRequiredDescription
categorystringNomust / must-not / should
severitystringNocritical / high / medium / low
hierarchyScopestringNosystem / org / user / agent
enabledbooleanNoFilter by enabled state
tagsstringNoComma-separated tag list; a rule must carry all of them
linkedIdsstringNoComma-separated agent IDs; a rule must belong to all of them
qstringNoFull-text search on description, content, and tags
limitintegerNoMax results (default 50, max 200)
cursorstringNometa.nextCursor of the previous page; omit for the first page

Response 200 OK

{
"data": [ /* Rule objects */ ],
"meta": {
"total": 12,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}

POST /api/v1/primitives/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"],
"linkedIds": []
}
FieldTypeRequiredDescription
descriptionstringYesTrigger predicate / one-line summary
contentstringYesRule body (loaded into context on match)
categorystringNomust / must-not / should (default: must-not)
severitystringNocritical / high / medium / low
hierarchyScopestringNosystem / org / user / agent
triggersobjectNo{keywords?, toolName?, embedding?, always?}
enabledbooleanNoDefault true
enforcementModestringNoinject / gate / both (Phase 1: inject only)
tagsstring[]NoTags
sourcestringNoProvenance label (default: user)
linkedIdsstring[]NoAgent IDs the rule belongs to

Response 201 Created

{
"data": { "ruleId": "rule_abc123" },
"meta": { "requestId": "req_abc123" }
}

GET /api/v1/primitives/rules/:id​

Get a single rule by ID.

Scope: rules:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesRule ID

Response 200 OK

Returns the full Rule object.


PATCH /api/v1/primitives/rules/:id​

Update a rule. Content changes append a new version row.

Scope: rules:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesRule ID

Request Body

FieldTypeRequiredDescription
descriptionstringNoOne-line summary
contentstringNoRule body
categorystringNomust / must-not / should
severitystringNocritical / high / medium / low
hierarchyScopestringNosystem / org / user / agent
triggersobjectNoTrigger predicate
enabledbooleanNofalse turns the rule off (it is neither injected nor enforced), true back on
enforcementModestringNoinject / gate / both
tagsstring[]NoTags
linkedIdsstring[]NoAgent IDs the rule belongs to (replaces the list)
changeNotestringNoFree-form note attached to the new version

Response 200 OK​

The rule, as GET /primitives/rules/:id shows it.


DELETE /api/v1/primitives/rules/:id​

Delete a rule and its version history. Irreversible.

Scope: rules:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesRule ID

Response 204 No Content


POST /api/v1/primitives/rules/search​

Return the enabled rules whose triggers match the given turn / tool, 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"
}
FieldTypeRequiredDescription
userTurnstringNoCurrent user turn (matched against triggers.keywords)
toolNamestringNoTool about to be called (matched against triggers.toolName)
linkedIdsstring[]NoAgent IDs; only rules linked to any of them, or linked to no agent, are considered

Response 200 OK

{
"data": {
"rules": [ /* Rule objects */ ],
"count": 2,
"contextBlock": "..."
},
"meta": { "requestId": "req_abc123" }
}

contextBlock is the same rules rendered as a markdown block ready to place in a system prompt.


POST /api/v1/primitives/rules/import​

Import rules from one source: the rows GET /primitives/rules/export emits, or a GitHub repository holding one rule. Send exactly one of rules and githubUrl.

Scope: rules:write

Request Body

FieldTypeRequiredDescription
rulesarrayOne of the twoRule rows, as the export emits them
githubUrlstringOne of the twohttps://github.com/owner/repo, with RULE.md at the repository root (YAML frontmatter; category is required in it)

Response with rules 200 OK

{
"data": { "imported": 11, "unchanged": 1, "skipped": 0, "errors": [] },
"meta": { "requestId": "req_abc123" }
}

Imported rules belong to the caller. imported counts new rules; unchanged counts rows the caller already has (same description and body), which are not created again; skipped counts refused rows. A row is reported in errors (with its index, from 0) and skipped when its category is missing or not must / must-not / should, or when a severity, hierarchyScope or enforcementMode it gives is not one of the allowed values. A row that leaves those out gets the defaults.

Response with githubUrl 201 Created: the new rule. 400 validation-error for an invalid URL or unparseable frontmatter; 404 not-found when the repository has no RULE.md at its root.


GET /api/v1/primitives/rules/violation-summary​

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

ParameterTypeRequiredDescription
daysintegerNoWindow size in days (1–365, default 30)
topRulesintegerNoTop-N rules to surface (default 10)
topToolsintegerNoTop-N tools to surface (default 10)

Response 200 OK

{
"data": {
"windowStart": "2026-04-16T10:00:00Z",
"windowEnd": "2026-05-15T10:00:00Z",
"totalViolations": 142,
"byRule": [
{ "key": "No destructive SQL on prod.", "count": 38 }
],
"byDetectedBy": [
{ "key": "gate", "count": 120 },
{ "key": "llm", "count": 22 }
],
"byDay": [
{ "date": "2026-05-15", "count": 12 }
],
"topOffendingTools": [
{ "key": "sql.execute", "count": 64 }
]
},
"meta": { "requestId": "req_abc123" }
}

byRule is keyed by the rule's description, byDetectedBy by how each violation was detected (llm, gate, user-report), and topOffendingTools by tool name. byDay has an entry for every day in the window, including days with no violations.


GET /api/v1/primitives/rules/export​

Bulk-export every rule the caller can read as JSON.

Scope: rules:read

Query Parameters

ParameterTypeRequiredDescription
enabledbooleanNoFilter to enabled (or disabled) rules

Response 200 OK

{
"data": {
"exportedAt": "2026-05-16T10:00:00Z",
"count": 12,
"rules": [ /* Rule objects */ ]
},
"meta": { "requestId": "req_abc123" }
}

POST /api/v1/primitives/rules/:id/assess​

Run the Rules quality ruleset against a row and cache the score on it.

Scope: rules:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesRule ID

Response 200 OK

{
"data": {
"rulesetVersion": "1.0.0",
"assessedAt": "2026-05-16T10:00:00Z",
"score": 91,
"grade": "A",
"weightAwarded": 50,
"weightTotal": 55,
"failedCriticalCount": 0,
"failedHighCount": 0,
"checks": [
{
"id": "rule-description-nonempty",
"title": "Description is non-empty",
"category": "description",
"severity": "critical",
"weight": 10,
"awarded": 10,
"status": "pass",
"passed": true
}
]
},
"meta": { "requestId": "req_abc123" }
}

Each check's status is pass, fail or skipped. A skipped check does not apply to this row (for example a gate-only check on an inject rule): it earns nothing and its weight is left out of weightTotal, so score is weightAwarded / weightTotal over the checks that apply.


POST /api/v1/primitives/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"
}
FieldTypeRequiredDescription
toolNamestringNoTool the agent is about to invoke
descriptionstringNoHuman description of what the action will do
argsobjectNoTool input arguments
userTurnstringNoCurrent user turn (for keyword-only rules)

Response 200 OK

{
"data": {
"allowed": false,
"blockedBy": [ /* Rule objects */ ],
"reason": "Action blocked by 1 rule:\n• No destructive SQL on prod."
},
"meta": { "requestId": "req_abc123" }
}

When nothing blocks the action, allowed is true, blockedBy is empty and reason is null.


POST /api/v1/primitives/rules/:id/violations​

Record a violation event against a rule.

Scope: rules:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesRule ID

Request Body

{
"attemptedAction": "DROP TABLE users on env=prod",
"detectedBy": "gate",
"agentId": "aBcD1234EfGh5678",
"threadId": "thr_789",
"runId": "run_321",
"evidence": { "query": "DROP TABLE users" }
}
FieldTypeRequiredDescription
attemptedActionstringYesDescription of what was attempted
detectedBystringNogate (a gate rule blocked a tool call), llm (default) or user-report
agentIdstringNoThe agent (its workflow id) that attempted the action
threadIdstringNoOriginating thread
runIdstringNoOriginating run
evidenceobjectNoFree-form evidence payload

Response 201 Created​

The violation, as the rule's violations list shows it.

{
"data": { "_id": "v1", "ruleId": "r1", "attemptedAction": "tool:memory_ingest", "detectedBy": "gate", "detectedAt": "2026-10-11T12:00:00.000Z", "agentId": "wf-1" },
"meta": { "requestId": "..." }
}

GET /api/v1/primitives/rules/:id/violations​

List a rule's violations, newest first. The rule's Violations tab pages through the same list, showing the agent and a link to the conversation.

Scope: rules:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesRule ID

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoRows per page (default 100, max 200)
cursorstringNometa.nextCursor of the previous page; omit for the first page

Response 200 OK

{
"data": [
{
"_id": "viol_abc123",
"ruleId": "rule_xyz",
"attemptedAction": "DROP TABLE users on env=prod",
"detectedBy": "gate",
"detectedAt": "2026-05-15T14:22:00Z",
"agentId": "aBcD1234EfGh5678",
"threadId": "thr_789",
"runId": "run_321",
"evidence": { "query": "DROP TABLE users" }
}
],
"meta": {
"total": 38,
"limit": 100,
"nextCursor": null,
"requestId": "req_abc123"
}
}

Sharing​

Who can reach a rule is the platform's one sharing shape: its owner, its members (role editor can use and change it, user can only use it) and its visibility (public: every user can find and use it; in a multi-tenant deployment, every user of its organization). Changing it stays with its owner and editors.

MethodPathDoesScope
GET/api/v1/primitives/rules/:id/permissionsOwner, members (userId, role) and visibilityrules:read
POST/api/v1/primitives/rules/:id/permissions/membersShare with a user: { "userId", "role": "editor" | "user" }rules:write
DELETE/api/v1/primitives/rules/:ruleId/permissions/members/:userIdStop sharing with a userrules:write
PATCH/api/v1/primitives/rules/:id/permissions{ "visibility": "public" | "private" }rules:write

Each, except a member's removal (204), answers the permissions as they are now:

{
"data": {
"resourceId": "<rule id>",
"owner": "<user id>",
"members": [{ "userId": "<user id>", "role": "user" }],
"visibility": "private"
},
"meta": { "requestId": "req_abc123" }
}

GET /api/v1/primitives/rules/:id/versions​

List every version of a rule.

Scope: rules:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesRule ID

Response 200 OK

{
"data": {
"versions": [
{
"versionNumber": 1,
"content": "Original rule body",
"changeNote": null,
"createdAt": "2026-04-01T09:00:00Z"
}
],
"count": 1
},
"meta": { "requestId": "req_abc123" }
}

GET /api/v1/primitives/rules/:ruleId/versions/:version​

Fetch one version of a rule.

Scope: rules:read

Path Parameters

ParameterTypeRequiredDescription
ruleIdstringYesRule ID
versionintegerYesVersion number (positive integer)

Response 200 OK

{
"data": {
"versionNumber": 1,
"content": "Original rule body",
"changeNote": null,
"createdAt": "2026-04-01T09:00:00Z"
},
"meta": { "requestId": "req_abc123" }
}

POST /api/v1/primitives/rules/:ruleId/versions/:version/restore​

Restore a rule to a prior version. Appends a new version row -- does not mutate history.

Scope: rules:write

Path Parameters

ParameterTypeRequiredDescription
ruleIdstringYesRule ID
versionintegerYesVersion number to restore to

Response 200 OK​

The rule, as GET /primitives/rules/:id shows it: its content is the restored version's, saved as a new version.