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.