Rule Cascade
ReferenceExample READMEs

Agent tools

A dispatcher that runs the three tools of bindings/llm-tools.json against the rule server, so an AI agent checks an operation before it calls the API that performs it.

A dispatcher that runs the three tools of bindings/llm-tools.json (bindings/llm-tools.json) against the rule server, so an AI agent checks an operation before it calls the API that performs it.

ToolRule server call
list_rulesGET /rulesets/{ruleset}/manifest?channel=…, filtered here by target.entity and operations. The server has no per-entity listing
evaluate_rulesPOST /evaluations. A dry run: nothing is changed
explain_ruleGET /rulesets/{ruleset}/rules/{rule_id}, including origin and overriddenBy: which level defined or changed the rule
import OpenAI from 'openai';
import { createDispatcher, tools } from './src/dispatch.mjs';

const rules = createDispatcher({
  baseUrl: 'http://rules.internal:8080',
  token: process.env.RULE_SERVER_TOKEN,
  actor: { id: session.userId, roles: session.roles },   // from your session, never from the model
});

const response = await new OpenAI().responses.create({ model, input, tools });
for (const item of response.output) {
  if (item.type === 'function_call') input.push(item, await rules.handleToolCall(item));
}

(The OpenAI client is not a dependency of this example; handleToolCall takes and returns the plain Responses API items.)

What the dispatcher decides for the model

  • The actor is not a tool argument. It comes from the session the dispatcher was created for, so a model cannot claim a role. Resolutions (acknowledgements, risk acceptances) are tool arguments because the user gives them, and the tool description tells the model never to invent them; a risk acceptance still only counts when the actor holds a listed role.
  • The token decides what can be listed. With RULE_SERVER_TOKEN the dispatcher reads the server manifest, which includes server-only rules; without it, the client manifest. POST /evaluations always needs the token: the rule server refuses to start without RULE_SERVER_TOKEN unless RULE_SERVER_ALLOW_OPEN=1 is set.
  • Nulls are not forwarded. Strict mode makes the model send null for every absent value. The dispatcher drops the null members of view (none left: every place is evaluated) and a null justification, as bindings/README.md asks of every host.
  • Failures are answers. Bad data_json, an unknown rule, a 401 or an unreachable server come back as { "error": "…" }, which the model can read and act on. Malformed JSON never reaches the server.

As an MCP server

This is not an MCP server, but the mapping is one to one: each entry of tools becomes an MCP tool with name, description and inputSchema = parameters, and a tools/call handler returns dispatch(name, arguments) as the text content (with isError: true when it holds error). Create the dispatcher per MCP session so the actor and token are the connected user's.

Test

npm ci && npm run build                          # from the repository root
npm test -w rule-cascade-example-agent-tools

The test starts a real rule server in process (createRuleServer over examples/contracts, with a token) and drives every tool through it, including a full Responses API function-call round trip. A second set of tests replaces fetch with a recorder and checks the exact route, method, headers and body each tool produces, so the mapping above is pinned without a server.

bindings/README.md describes the same three tools against a runtime in process, in Python; this example is the host for the HTTP rule server.

On this page