DEV Community

Tang Haoran
Tang Haoran

Posted on

rulsynor-core v1.0: An AI Agent You Can Audit

rulsynor-core v1.0: An AI Agent You Can Audit

How do you trust an AI Agent in production? Not "trust" as in I think it'll do the right thing. Trust as in cryptographic proof that every decision was correct, traceable, and independently verifiable.

Today: rulsynor-core — open-source Guard engine for AI Agents. MIT license. On npm.

30-Second Demo


bash
npx @openoba/rulsynor-core --tool=exec --cmd="rm -rf /"
🛡️ Decision: DENY
📝 Reason: Destructive command blocked.
🧾 Recorded: sha256:8274b0... (tamper-evident)
🧭 Alternative: Use the read tool to inspect first.
Not just "no." The agent learns why and what to do instead.

What It Does
Guard: Evaluates every tool call before execution — ring-sorted, sub-millisecond. 30 preset rules + write your own.
Audit: Every decision produces a 25-field cryptographic record. JCS + SHA-256. Chain-linked.
Verify: Anyone can verify audit records with zero SDK. Just JCS + SHA-256. No rulsynor needed.
What Makes It Different
Most AI safety tools are prompt-based. "Be careful" isn't governance. rulsynor-core gives you:

7 decision types — ALLOW, DENY, CORRECT, QUARANTINE, REQUEST_HUMAN, NOTIFY, EMERGENCY_HALT
Cryptographic audit chain — not "we logged it," mathematical proof
Third-party verified — 101 cross-implementation vectors, 13/13 passed by Concordia
MIT licensed — zero framework dependencies
Rules in Plain Language
复制
# "If the agent runs rm -rf, block it"
name: block-destructive-rm
when:
  conditions:
    - field: context.tool.name
      operator: eq, value: exec
    - field: context.tool.args.command
      operator: matches, value: rm\s+-rf
then:
  decision: DENY
  instruction: Destructive command blocked.
Any LLM can translate English descriptions into ERDL rules. The compiler validates everything before loading.

5-Minute Integration
import { Evaluator, GuardStateManager,
  loadPresetRules, toCompiledRules }
from '@openoba/rulsynor-core';

const rules = toCompiledRules(loadPresetRules());
const evaluator = new Evaluator(new GuardStateManager());

const result = evaluator.evaluate(
  { toolName, toolArgs, sessionId, agentId },
  rules
);
// result.decision → ALLOW | DENY | CORRECT | ...
Works with LangChain, MCP, custom ReAct loops. Same API everywhere.

Roadmap
 30 preset rules, 20 operators
 JCS+SHA-256 audit trail
 Third-party verified
 LangGraph integration guide
 Community rule marketplace
GitHub · npm

"LLM vendors deliver exceptional intelligence. We deliver accountability."

Built at OpenOBA. MIT.
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
kikashy profile image
Brian Jin •

Really interesting approach. One edge case I’m curious about: how do you represent situations where the engine can’t safely return ALLOW or DENY because required evidence is missing or contradictory? Do you treat that as REQUEST_HUMAN, or model “insufficient evidence” as a distinct outcome?

Collapse
 
haorantang profile image
Tang Haoran •

@brian Jin
Great question — and you've put your finger on a real design tension. Currently ERDL has two mechanisms that partially cover this:

  1. No rule matches → PASS → ALLOW (by default)
    When no rule's when matches, the engine returns PASS, falling through to the file-level metadata.decision (default: ALLOW). This is "I had nothing to say" rather than "the evidence is insufficient."

  2. REQUEST_HUMAN — rule author's deliberate choice
    A rule can be written to emit REQUEST_HUMAN when key evidence is missing (e.g., when: amount >= 500000 AND risk_score == null). But this conflates two semantically distinct situations: "I see a risk, approve?" vs. "I can't even determine whether there's a risk."

  3. DEFER (v1.2) — the gap you've spotted
    The spec already calls out this exact distinction. DEFER means "do not decide now; wait for external signal before evaluating." It differs from REQUEST_HUMAN in that the latter implies a direction (allow/deny) exists but needs human confirmation, while DEFER means the engine genuinely can't form a direction yet.

So to directly answer your question: today the model treats "can't decide" as either PASS→ALLOW (if the rule author didn't anticipate the gap) or REQUEST_HUMAN (if they did, but it's semantically imprecise). DEFER in v1.2 is the deliberate "insufficient evidence" outcome. Good catch — this is worth calling out more explicitly in the current docs as a known design decision rather than an oversight.