cd /news/ai-agents/show-hn-open-source-alternative-to-t… · home topics ai-agents article
[ARTICLE · art-133418] src=github.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Show HN: Open-Source Alternative to TypeSafe.ai

A developer released an open-source template for building deterministic models with structured reasoning, positioning it as an alternative to TypeSafe.ai. The template runs JavaScript rule functions through a forward-chaining symbolic inference engine locally on Node.js with no LLM at inference time, and pairs with an agent that orchestrates a feedback loop between LLM fact extraction and deterministic rule evaluation, returning FACT_NEEDED with a fact's schema rather than guessing when facts are ambiguous. The repo includes a model knowledge base template and a symbolic-kb skill in .claude/skills/symbolic-kb/ designed to run as an Agent Skills standard skill across opencode, Claude Code, and Codex.

read6 min views1 publishedSep 18, 2026
Show HN: Open-Source Alternative to TypeSafe.ai
Image: Michielbdejong (auto-discovered)

A self-contained template for building a deterministic model with structured reasoning. Rules are JavaScript functions executed by a forward-chaining symboling inference engine locally with Node.js — no LLM is used at inference time.

To interact with unstructured inputs, an agent can be used to orchestrate a feedback loop between LLM fact extraction and deterministic rule evaluation, refusing to guess when facts are ambiguous.

This repo contains a model knowledge base template (clone it, use the skill to modify .kb/, ship) and the symbolic-kb skill in .claude/skills/symbolic-kb/, which includes instructions for maintaining the knowledge base and the symbolic reasoning engine. This skill is designed to run as an Agent Skills standard skill (opencode, Claude Code, Codex).

A common practical example is where the deterministic code is responsible for making a decision, whereas LLM handles extraction of simple facts from the source unstructured data (a source document, a set of facts/state that need to be extracted from the text etc). The decision-making then runs until an answer can be determined:

  1. The agent picks the rule that answers your question, and extracts the facts it can see in the document/state.
  2. It runs the engine. If a fact is missing, the engine returns FACT_NEEDED with that fact's schema — never a guess.
  3. The agent goes back to the document for that specific fact (using the schema's type and description), adds it, and re-runs.
  4. Repeat until COMPLETED , then the agent answers with the reasoning tree attached.

In an opencode, Claude Code, or Codex session in this repo, ask:

Here's a contract: "Acme LLC and Beta Inc. agree to a 12-month non-compete. Signed by A. Smith for Acme and J. Doe for Beta." Is it valid according to the 'symbolic-kb' skill?

The agent extracts contract.hasNonCompete, contract.signedByPartyA, contract.signedByPartyB from the text, runs the inference, and answers with the reasoning tree — the same one the command-line example below prints. If a fact isn't in the document (e.g., one signature is missing), the engine stops at FACT_NEEDED for that fact and the agent says so rather than guessing:

● Skill(symbolic-kb) Successfully loaded skill

Searched for 1 pattern, ran 3 shell commands

Yes — valid, according to the contract.isValid rule.

Reasoning trace:

  • contract.hasNonCompete = true (12-month non-compete clause is present)
  • contract.signedByPartyA = true (A. Smith signed for Acme)
  • contract.signedByPartyB = true (J. Doe signed for Beta)
  • → contract.isSignedByBothParties = true (both signatures present)
  • → contract.isValid = true (has non-compete AND signed by both parties)

For a document on disk, point the agent at the file (attach it or give its path) and ask the same question.

For structured inputs, the same engine can be called programmatically without an LLM or an agent:

echo '{"fact":"contract.isValid","facts":{"contract.hasNonCompete":true,"contract.signedByPartyA":true}}' \
  | node .claude/skills/symbolic-kb/scripts/run_inference.mjs --kb-dir .kb \
  | node .claude/skills/symbolic-kb/scripts/print_tree.mjs

echo '{"fact":"contract.isValid","facts":{"contract.hasNonCompete":true,"contract.signedByPartyA":true,"contract.signedByPartyB":true}}' \
  | node .claude/skills/symbolic-kb/scripts/run_inference.mjs --kb-dir .kb \
  | node .claude/skills/symbolic-kb/scripts/print_tree.mjs

A FACT_NEEDED response is the engine refusing to guess — supply the missing fact and re-run, or have an agent extract it from your document (see the Quick start above).

To add or change rules, describe them in natural language; the agent translates your description to first-order logic, writes the rule file, updates manifest.json (question, condition, dependencies), and lints the schemas.

Usage example in this repo:

Show me the knowledge base in this project

(Prints explanation of the existing rules in the knowledge base)

Add a rule: a contract is binding if it is valid and has been filed with the county.

The agent updates the knowledge base with the additional rule.

Re-run the query example to see how the updated tree executes.

Modern agents can use the skill to automatically build entire reasoning knowledge base from a set of a few examples, reverse-engineering them into a decision-making model.

For example:

Redo this knowledge base to implement logic behind writing the emails. Use relevant skill to remove all existing rules, analyse the following examples and write rules that would produce all information needed to write a welcome email from the inputs: [a set of emails and when each email was sent]

The agent would then analyze a few email examples provided and reverse-engineer how each was written and how decisions about varying the examples were made based on the input variables, producing a deterministic model capable of making decisions and writing an email brief from those input variables.

.kb/
  manifest.json          # Rule metadata: question, condition, dependencies schema
  rules/
    <rule-name>.mjs      # One async function per file (ESM, default export). Filename IS the rule name.
  query-log.jsonl        # Append-only log of queries (written by the agent)

Override the KB directory with --kb-dir <path> on any script, or set it in AGENTS.md.

Copy templates/rule_template.mjs into .kb/rules/<subject.predicate>.mjs. The default-exported function takes an infer callback and returns a value. Use only infer and standard JS — no external packages.

export default async function(infer) {
  const hasNonCompete = await infer('contract.hasNonCompete');
  const isSignedByBoth = await infer('contract.isSignedByBothParties');
  return hasNonCompete && isSignedByBoth;
}

Then register the rule in manifest.json:

{
  "name": "Contract Validity Checker",
  "rules": {
    "contract.isValid": {
      "question": "Is the contract valid?",
      "condition": "A contract is valid if it has a non-compete clause and is signed by both parties",
      "dependencies": {
        "type": "object",
        "properties": {
          "contract.hasNonCompete": { "type": "boolean", "description": "Whether the contract includes a non-compete clause" },
          "contract.isSignedByBothParties": { "type": "boolean", "description": "Whether the contract is signed by both parties" }
        }
      }
    }
  }
}

question is what the rule answers; condition is the plain-English logic; dependencies is a JSON schema of facts the rule needs (used by the engine to ask for missing facts).

node .claude/skills/symbolic-kb/scripts/lint_schemas.mjs --kb-dir .kb

Rules are auto-discovered at runtime — no build step. Run lint_schemas.mjs after manifest enum changes.

There is no compiled bundle and no build step. The KB is loaded at runtime: load_kb.mjs reads manifest.json, dynamically imports rules/*.mjs, and wires a handler via createHandler from inference.mjs. To deploy, ship .kb/ (rules/*.mjs + manifest.json) alongside inference.mjs and load_kb.mjs:

  • run locally via run_inference.mjs (above),
  • deploy to AWS Lambda — zip .kb/ +inference.mjs +load_kb.mjs and useloadKB(kbDir).handler as the entry point; Lambda reads the files at cold start, no bundler needed,
  • wrap in Express/HTTP for programmatic access, or
  • import directly in Node.js: import { loadKB } from './load_kb.mjs'; const { handler } = await loadKB(kbDir) .

The full agent workflows — natural-language-to-logic rule authoring, the fact-extraction feedback loop, and single-rule evaluation — live in .claude/skills/symbolic-kb/SKILL.md. Read that when you're ready to drive the KB from a conversation rather than the CLI.

── more in #ai-agents 4 stories · sorted by recency
── more on @typesafe.ai 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-open-source-…] indexed:0 read:6min 2026-09-18 ·