{"id":"9074efb0-d44a-4f00-85ed-6dd4c985adff","slug":"socratic-engine","kind":"mcp","name":"socratic-engine","summary":"Externalized epistemic scaffolding for AI agents. Recursive boolean certification trees.","owner":"rm-w3kufe","repoUrl":"https://github.com/rm-w3kufe/socratic-engine","license":"Apache-2.0","licenseTier":"permissive","trust":"community","stars":1,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-08-18T07:32:52.000Z","addedAt":"2026-10-21T21:45:00.937Z","logoUrl":"https://avatars.githubusercontent.com/u/175173985?u=879e1ce2efb93be92d28a0636064f63d94c181e1&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["utilities"],"tags":[],"runtime":"python","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-22T04:08:01.392Z","sample":false,"url":"https://conduits.dev/tools/socratic-engine","language":"Python","transports":["stdio"],"install":{"pypi":"socratic-engine"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# socratic-engine — externalized reasoning for AI agents\n\n[![CI](https://github.com/rm-w3kufe/socratic-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/rm-w3kufe/socratic-engine/actions/workflows/ci.yml)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)\n[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-green.svg)](LICENSE)\n[![PyPI version](https://img.shields.io/pypi/v/socratic-engine.svg)](https://pypi.org/project/socratic-engine/)\n[![MCP Compatible](https://img.shields.io/badge/MCP-compatible-brightgreen.svg)](https://modelcontextprotocol.io/)\n\n> **Don't ask the language model to hold the whole recursion. Give the recursion a substrate.**\n\n`socratic-engine` is a model-agnostic reasoning substrate for AI agents: a recursive evaluator for **trivalent logical trees** with an explicit separation between **truth**, **certification**, and **evidence**.\n\nThe engine is deliberately small. It does not try to make an LLM \"smarter\". It gives the model a formal structure in which complex questioning can be proposed, executed recursively, inspected, and diagnosed **outside the model's token-generation loop**.\n\n**Status:** v0.2.12 — core engine, VSL tree parser (block + inline notation), CLI (`eval-tree`, `decide`, `match-procedure`), MCP bridge, multi-bridge (health tracking + routing observability + duplicate-domain priority), VsmDocProvider, dialectical and **jury** operators, **certified-UNKNOWN doctrine** (indeterminacy vocabulary + gate), **computation nodes** (genome feature-chain evaluation), pragmatic predicates, **context predicates** (ctx_has, ctx_equals, ctx_contains, ctx_not_has), semantic simplification (NOT flattening, contradiction/tautology, dedup, absorption), short-circuit evaluation, tree DoS prevention (depth≤100, nodes≤10K via `_TreeLimitCounter`), **cycle detection**, caching, rate limiting, engine contract protocols (`SocraticEngineProtocol`, `EvaluationProtocol`), **security hardening** (enforce_limits=True by default, predicate error wrapping, arity validation), CI (pytest + coverage gate at 90%), 546-test suite, benchmarks, and the official state-canon bridge ([`bridge_statecanon.py`](./socratic_engine/bridge_statecanon.py)) with end-to-end examples are working. The broader claim — that externalizing recursive structure improves reliability on tasks that exceed a model's implicit recursive reasoning capacity — is an experimental hypothesis, not a proclamation.\n\n---\n\n## The bet\n\nLLMs are excellent at generating hypotheses, decomposing problems, interpreting language, and proposing strategies.\n\nThey are less reliable when they must **implicitly execute a deep recursive reasoning process while simultaneously maintaining all of its premises, branches, dependencies, and intermediate states in context**.\n\nThe bet here is simple:\n\n> **Let the model propose the questions. Let a deterministic substrate execute the recursion.**\n\nThe model does not have to remember the entire reasoning process. The tree does.\n\n```mermaid\nflowchart LR\n    A[\"LLM<br/>semantic proposal\"] --> B[\"Socratic tree<br/>formal IR\"]\n    B --> C[\"SocraticEngine<br/>recursive execution\"]\n    C --> D[\"Evaluation tree<br/>truth + certification + evidence\"]\n    D --> E[\"explain / diagnose\"]\n    E --> A\n```\n\nThis creates a division of labour:\n\n| Component | Responsibility |\n|---|---|\n| **LLM** | formulate hypotheses, propose questions, interpret results |\n| **Tree / IR** | preserve recursive structure explicitly |\n| **SocraticEngine** | execute logic deterministically |\n| **Predicates** | perform domain-specific atomic checks |\n| **Evaluation** | preserve truth, certification, evidence, context |\n| **diagnose()** | identify the branches that prevented certification |\n\nThe central boundary is:\n\n> **The LLM proposes. The engine validates and executes. The LLM does not certify.**\n\n---\n\n## What it is\n\nAt its core, the engine evaluates trees composed of:\n\n- `AND`\n- `OR`\n- `NOT`\n- `XOR`\n- `IMPLIES`\n- `DIALECTICAL_AND`\n- registered predicates\n- boolean literals\n\nThe logical result is **trivalent**:\n\n```text\nTRUE\nFALSE\nUNKNOWN\n```\n\nBut truth is not the same thing as certification.\n\nEvery evaluation can additionally carry:\n\n```text\ntruth\ncertified\nevidence\nsource\ncontext\nmetadata\nchildren\n```\n\nSo the result of a recursive evaluation is itself a **recursive evaluation tree**.\n\n```mermaid\nflowchart TD\n    R[\"op:AND\"] --> A[\"service_running<br/>TRUE ✓\"]\n    R --> B[\"schema_valid<br/>TRUE ✓\"]\n    R --> C[\"llm_judge<br/>TRUE ✗\"]\n\n    R --> T[\"root result<br/>TRUE / not certified\"]\n```\n\nA model may produce a plausible answer. That answer can be represented as `TRUE`.\n\nIt does **not** follow that the system is allowed to certify it.\n\n---\n\n## Why three values?\n\nA certification system must distinguish:\n\n```text\nFALSE\n```\n\nfrom:\n\n```text\nUNKNOWN\n```\n\nIf the system cannot establish a proposition because data is missing, a check timed out, or the subject of the question is absent, returning `FALSE` silently converts **lack of evidence into evidence of falsity**.\n\n`socratic-engine` therefore treats indeterminacy as a first-class result:\n\n```text\nTRUE      → the proposition evaluated true\nFALSE     → the proposition evaluated false\nUNKNOWN   → the proposition could not be decided\n```\n\nThis matters particularly at routing boundaries.\n\nFor example:\n\n```text\nTYPE = \"\"\nquestion: \"Does TYPE start with VSL-?\"\n\n→ UNKNOWN\n```\n\nnot:\n\n```text\n→ FALSE\n```\n\nThe discipline is:\n\n> **No subject, no judgment.**\n\n---\n\n## Truth is not certification\n\nThis is the epistemic boundary at the centre of the engine.\n\n```mermaid\nflowchart LR\n    Q[\"Predicate\"] --> T{\"Truth?\"}\n    T -->|TRUE| C{\"Certified?\"}\n    T -->|FALSE| C\n    T -->|UNKNOWN| U[\"UNKNOWN<br/>insufficient evidence\"]\n    C -->|YES| V[\"Evidence-backed result\"]\n    C -->|NO| P[\"Uncertified claim<br/>or opinion\"]\n```\n\nFor example:\n\n```python\n@engine.register(\"llm_judge\")\ndef llm_judge(question, evidence, **kwargs):\n    return PredicateResult(\n        truth=Truth.TRUE,\n        certified=False,\n        evidence=evidence,\n        source=\"llm\",\n    )\n```\n\nThe LLM can say:\n\n```text\nTRUE\n```\n\nwithout being allowed to say:\n\n```text\nCERTIFIED\n```\n\nA deterministic predicate can instead return:\n\n```python\nPredicateResult(\n    truth=Truth.TRUE,\n    certified=True,\n    evidence={\"pid\": 1234},\n    source=\"pgrep\",\n)\n```\n\nThe distinction is intentional.\n\n### Certified UNKNOWN\n\nCertification admits `UNKNOWN` — but only with proof of indeterminacy,\nnever as a bare claim. A predicate returning `UNKNOWN` + `certified=True`\nwithout an `indeterminacy` evidence block (`{kind, ...proof}`) is degraded\nto uncertified with a warning. Valid kinds: `undecidable-reduction`,\n`quantum-superposition`, `jury-hung`, `exhaustive-empty`,\n`vague-boundary`, `symmetric-tie`.\n\n```python\nPredicateResult(\n    truth=Truth.UNKNOWN,\n    certified=True,  # kept: proof attached\n    evidence={\"indeterminacy\": {\"kind\": \"exhaustive-empty\",\n                                \"scanned\": 4200}},\n)\n```\n\n\"Verified absence\" (exhaustive search, empty result) is expressed as\n`TRUE`-with-evidence, not as `UNKNOWN`. Precedent: `DIALECTICAL_AND`\nalready certifies `UNKNOWN` for proven contradiction.\n\n---\n\n## Recursive questioning\n\nThe tree is not merely a data structure for the final answer.\n\nIt is the **externalized representation of the questioning process**.\n\nA complex question can be decomposed:\n\n```mermaid\nflowchart TD\n    Q[\"Is deployment safe?\"]\n\n    Q --> A[\"Code quality\"]\n    Q --> B[\"Data safety\"]\n    Q --> C[\"Compatibility\"]\n\n    A --> A1[\"Tests exist\"]\n    A --> A2[\"Complexity below threshold\"]\n\n    B --> B1[\"No PII\"]\n    B --> B2[\"Schema valid\"]\n\n    C --> C1[\"LLM contextual judgment\"]\n```\n\nThe model can generate this structure.\n\nThe engine recursively evaluates it.\n\nThe model receives the resulting structure rather than having to simulate the complete recursion itself.\n\nThat is the architectural experiment.\n\n---\n\n## A minimal tree\n\n```python\nfrom socratic_engine import SocraticEngine\n\nengine = SocraticEngine()\n\ntree = {\n    \"op\": \"AND\",\n    \"children\": [\n        {\"predicate\": \"type_prefix\", \"args\": [\"$type\", \"VSL-\"]},\n        {\"predicate\": \"ctx_has\", \"args\": [\"$ctx\", \"status\"]},\n    ],\n}\n\nresult = engine.evaluate(\n    tree,\n    {\n        \"type\": \"VSL-LANG-01\",\n        \"status\": \"validated\",\n    },\n)\n\nprint(result.truth)\nprint(result.certified)\nprint(result.explain())\n```\n\nThe engine can evaluate arbitrary nesting because the same contract is applied at every level.\n\n---\n\n## Computation nodes\n\nA `computation` node evaluates a feature chain natively before deciding —\neach feature can reference raw inputs *and* previously computed derived\nfeatures (`d0 → d1 → d2`), then a genome-style decision tree branches on\nthe resolved values. Zero information loss versus flattening to\npredicates; the resolved features travel in evidence.\n\n```python\ntree = {\"computation\": {\n    \"features\": [\n        {\"op\": \"add\", \"args\": [\"x\", \"y\"], \"output_name\": \"d0\"},\n        {\"op\": \"mul\", \"args\": [\"d0\", \"x\"], \"output_name\": \"d1\"},\n    ],\n    \"tree\": {\"condition\": \"d1\", \"threshold\": 0.3, \"operator\": \"gt\",\n             \"left\": True, \"right\": False},\n}}\nresult = engine.evaluate(tree, {\"x\": 0.8, \"y\": 0.5})\n```\n\nProduced by RSI genome evolution (`genome_to_computation_node`) and\nevaluated without translation loss.\n\n---\n\n## Registering domain predicates\n\nThe engine contains no domain-specific reasoning beyond its small built-in predicate set.\n\nYou add the atomic questions:\n\n```python\nfrom socratic_engine import PredicateResult, Truth\n\n@engine.register(\"service_running\")\ndef service_running(name: str, **kwargs):\n    # Replace with a real structural check.\n    if name == \"cache\":\n        return PredicateResult(\n            truth=Truth.TRUE,\n            certified=True,\n            evidence={\"pid\": 1234},\n            source=\"pgrep\",\n        )\n\n    return PredicateResult(\n        truth=Truth.FALSE,\n        certified=True,\n        evidence={\"service\": name, \"running\": False},\n        source=\"pgrep\",\n    )\n```\n\nThe engine does not need to know what a service is.\n\nIt only knows:\n\n```text\npredicate → PredicateResult\n```\n\nThat keeps the reasoning substrate domain-agnostic.\n\n---\n\n## A complete example\n\n```python\nfrom socratic_engine import SocraticEngine, Truth, PredicateResult\n\nengine = SocraticEngine()\n\n\n@engine.register(\"service_running\")\ndef service_running(name: str, **kwargs):\n    if name == \"cache\":\n        return PredicateResult(\n            truth=Truth.TRUE,\n            certified=True,\n            evidence={\"pid\": 1234, \"uptime\": \"2h 15m\"},\n            source=\"pgrep\",\n        )\n\n    return PredicateResult(\n        truth=Truth.FALSE,\n        certified=True,\n        evidence={\"service\": name},\n        source=\"pgrep\",\n    )\n\n\n@engine.register(\"llm_judge\")\ndef llm_judge(question: str, evidence: str, **kwargs):\n    # In production: call the model here.\n    return PredicateResult(\n        truth=Truth.TRUE,\n        certified=False,\n        evidence=evidence,\n        source=\"llm\",\n        metadata={\"question\": question},\n    )\n\n\ntree = {\n    \"op\": \"AND\",\n    \"children\": [\n        {\n            \"predicate\": \"service_running\",\n            \"args\": [\"cache\"],\n        },\n        {\n            \"predicate\": \"llm_judge\",\n            \"kwargs\": {\n                \"question\": \"Is this deployment safe?\",\n                \"evidence\": \"Traffic is currently below 45%\",\n            },\n        },\n    ],\n}\n\nresult = engine.evaluate(tree)\n\nprint(result.explain())\n```\n\nThe important result is not merely the boolean answer.\n\nThe evaluation preserves the distinction:\n\n```text\nservice_running → TRUE  [✓]\nllm_judge       → TRUE  [✗]\n\nroot            → TRUE  [✗]\n```\n\nThe claim can be true according to the predicate while still being **uncertified**.\n\n---\n\n## Operator semantics\n\nThe engine implements:\n\n| Operator | Logical semantics | Certification |\n|---|---|---|\n| `AND` | all branches must be true | all children relevant to the result must be certified |\n| `OR` | at least one branch must be true | one true child must be certified |\n| `NOT` | invert one proposition | child must be certified |\n| `XOR` | exactly one true branch | children must be certified |\n| `IMPLIES` | antecedent → consequent | antecedent and consequent must be certified when relevant |\n| `DIALECTICAL_AND` | contradiction is not rejection — a certified TRUE/FALSE conflict yields `UNKNOWN` (certified), with thesis/antithesis in metadata | in conflict: all children certified (the contradiction itself is a fact); without conflict: all children certified |\n| `JURY` | verdict by supermajority (default ⅔, configurable `supermajority` in (0.5, 1.0], `quorum` default = all children) — supermajority wins, else hung | all votes certified (the verdict, including a hung one, certifies the *procedure*); no quorum → uncertified |\n\nThe distinction matters for diagnosis.\n\nFor example:\n\n```text\nA OR B\n```\n\ndoes not require both A and B to succeed.\n\nIf A is already certified `TRUE`, B is not a failure merely because it is `FALSE` or `UNKNOWN`.\n\nThe diagnostic engine follows the semantics of the operator rather than simply collecting every non-TRUE leaf.\n\n---\n\n## Diagnosis: reason backwards from failure\n\n`evaluate()` tells you what happened.\n\n`diagnose()` asks:\n\n> **Which branch actually prevented certification?**\n\n```mermaid\nflowchart TD\n    R[\"AND → TRUE ✗\"] --> A[\"service_running → TRUE ✓\"]\n    R --> B[\"schema_valid → TRUE ✓\"]\n    R --> C[\"llm_judge → TRUE ✗\"]\n\n    C --> D[\"diagnostic cause:<br/>truth exists, certification does not\"]\n```\n\nThis is deliberately an **inverse trace**.\n\nInstead of returning only:\n\n```text\nBLOCKED\n```\n\nthe engine can return the structural cause:\n\n```text\nllm_judge\ntruth = TRUE\ncertified = FALSE\nsource = llm\n```\n\nThat makes the result useful to an agent attempting another iteration.\n\n```text\nPROPOSE\n   ↓\nBUILD\n   ↓\nEVALUATE\n   ↓\nDIAGNOSE\n   ↓\nREFINE\n   ↓\nEVALUATE\n   ↓\n...\n```\n\n---\n\n## The agent loop\n\nThe intended architecture is not:\n\n```text\nLLM → answer\n```\n\nbut:\n\n```mermaid\nflowchart LR\n    A[\"Agent / LLM\"] --> B[\"Propose tree\"]\n    B --> C[\"SocraticTreeBuilder\"]\n    C --> D[\"SocraticEngine\"]\n    D --> E{\"Certified?\"}\n    E -->|yes| F[\"Decision / gate\"]\n    E -->|no| G[\"diagnose()\"]\n    G --> A\n```\n\nThe important recursion is therefore not necessarily inside the language model.\n\nIt can occur at the **system level**:\n\n```text\nLLM\n ↓\ntree\n ↓\nengine\n ↓\nevaluation\n ↓\ndiagnosis\n ↓\nLLM\n ↓\nrefined tree\n ↓\nengine\n ↓\n...\n```\n\nThis is the mechanism this project is intended to make explicit and testable.\n\n---\n\n## VSL as a reasoning representation\n\nThe repository includes a small recursive parser for declared:\n\n```text\nsocratic(\"NAME\") = { ... }\n```\n\nblocks.\n\nThat allows the same questioning structure to exist as a language-level artifact rather than only as Python dictionaries.\n\n```mermaid\nflowchart LR\n    A[\"Natural language / VSL\"] --> B[\"Socratic tree\"]\n    B --> C[\"Validated IR\"]\n    C --> D[\"Recursive engine\"]\n    D --> E[\"Evaluation\"]\n```\n\nThis is important for the broader VSL/VSF work:\n\n> the language can describe **how a question is decomposed**, while the engine provides the execution semantics.\n\nThe tree therefore acts as an intermediate representation between semantic generation and deterministic execution.\n\n---\n\n## Builder: the boundary before execution\n\n`SocraticTreeBuilder` exists as a deliberate gate between an external proposer — including an LLM — and the evaluator.\n\nIt recursively validates:\n\n- operators;\n- operator arity;\n- registered predicates;\n- nested children;\n- arguments and keyword arguments.\n\n```text\nLLM proposes\n     │\n     ▼\n┌──────────────────┐\n│ SocraticTreeBuilder │\n│ structural gate  │\n└────────┬─────────┘\n         │ valid\n         ▼\n   SocraticEngine\n```\n\nAn unknown predicate is rejected before execution.\n\nA malformed `NOT` or `IMPLIES` is rejected before execution.\n\nThe model is therefore not trusted to produce executable logic merely because the syntax looks plausible.\n\n---\n\n## Built-in predicates\n\nThe engine currently ships with deterministic predicates for structural classification and pragmatic behaviour:\n\n| Predicate | Question | Missing subject |\n|---|---|---|\n| `type_glob` | does TYPE match a glob? | `UNKNOWN` |\n| `type_prefix` | does TYPE start with a prefix? | `UNKNOWN` |\n| `type_regex` | does TYPE match a regex? | `UNKNOWN` |\n| `type_has` | does TYPE contain a token? | `UNKNOWN` |\n| `doc_has_status` | does a document declare a status? | evaluated from document |\n| `ctx_has` | does context contain a key/value? | `UNKNOWN` |\n| `trend_up` | is a time series growing sustainably (min_delta, noise guard)? | `UNKNOWN` |\n| `trend_down` | is a time series falling sustainably (min_delta, noise guard)? | `UNKNOWN` |\n| `feedback_loop` | does a topology have a closed cycle (length ≥ 2) through target? | `UNKNOWN` |\n\nRegistering custom predicates is the intended extension point.\n\nCostly predicates can be wrapped with `@cached(ttl=...)` — hits are marked `metadata.cached=True` (historical evidence, not a fresh measurement), `UNKNOWN` is never cached, and `engine.cache.clear()` forces re-verification.\n\n---\n\n## MCP bridge\n\nThe engine can be exposed through the **Model Context Protocol**.\n\nInstall the optional dependency:\n\n```bash\npython -m pip install -e \".[mcp]\"\n```\n\nRun the server:\n\n```bash\npython -m socratic_engine.mcp_server\n```\n\nThe current bridge exposes:\n\n| Tool | Purpose |\n|---|---|\n| `socratic_build` | validate a proposed reasoning tree |\n| `socratic_evaluate` | execute a tree against context |\n| `socratic_diagnose` | return inverse failure traces |\n| `socratic_canon_query` | (opt-in) query a data source through the registered bridge |\n| `socratic_canon_matches` | (opt-in, multi-bridge) check if records match expected values |\n| `socratic_canon_field_equals` | (opt-in, multi-bridge) check if a field equals expected value |\n| `socratic_canon_drift` | (opt-in, multi-bridge) detect declared vs observed drift |\n| `socratic_canon_domains` | (opt-in, multi-bridge) list all available domains |\n| `socratic_canon_providers` | (opt-in, multi-bridge) list all registered providers |\n\nThe MCP layer is intentionally thin.\n\nIt includes a per-tool sliding-window rate limiter (`SOCRATIC_MCP_RATE_LIMIT`, default 100 calls / `SOCRATIC_MCP_RATE_WINDOW`, default 60s): a rate-limited call returns error `-32029` with `retry_after_s` — a transient signal, not a rejection.\n\nThe reasoning contract remains in the engine.\n\n---\n\n## Bridge to `state-canon` (official)\n\nThe official integration between the engine and **state-canon** lives in\n[`socratic_engine/bridge_statecanon.py`](./socratic_engine/bridge_statecanon.py).\n\n**Division of labor** (from the \"Relationship to `state-canon`\" section):\n\n> **state-canon grounds the agent in what is observed.**\n> **socratic-engine constrains how claims about that state are evaluated.**\n\nThe bridge registers four predicates on any `SocraticEngine`, all prefixed\n`canon_` — they query the provider (the reconciled ground truth, *observed*)\nand convert the result into certified evidence:\n\n| Predicate | Question it answers |\n|---|---|\n| `canon_query(domain, filter)` | Is there at least one record matching the filter? |\n| `canon_matches(domain, filter, expected)` | Do the records match the expected fields exactly? |\n| `canon_field_equals(domain, filter, field, expected)` | Does a specific field equal the expected value? |\n| `canon_drift(domain, filter, declared_field, observed_field)` | Have declared and observed drifted apart? |\n\n`filter` and `expected` accept either a JSON string or a Python dict.\n\nSemantics follow the engine's epistemology (R9 — no silent concession):\n\n- **TRUE / FALSE** are returned **certified** (structural evidence exists).\n- **UNKNOWN** is returned **uncertified** when there is no evidence — no\n  records, an unknown filter field, a missing field, or an unparseable\n  filter. No inventing, no silent routing to `else_home`.\n\n```python\nfrom socratic_engine import SocraticEngine\nfrom socratic_engine.bridge_statecanon import StateCanonBridge\n\neng = SocraticEngine()\nbridge = StateCanonBridge(eng, provider)   # provider: state_canon.StateProvider\n\nev = eng.evaluate({\"op\": \"DIALECTICAL_AND\", \"children\": [\n    {\"predicate\": \"canon_field_equals\",\n     \"args\": [\"services\", '{\"name\": \"api\"}', \"declared_active\", True]},\n    {\"predicate\": \"canon_field_equals\",\n     \"args\": [\"services\", '{\"name\": \"api\"}', \"observed_active\", True]},\n]})\n# declared TRUE, observed FALSE → certified conflict → UNKNOWN certified,\n# with metadata.thesis / metadata.antithesis — the dialectical operator\n# turns the drift into productive indetermination, not a false binary.\n```\n\nThe MCP server accepts an optional `provider` (opt-in, R6): when set, the\n`canon_*` predicates are registered and `socratic_canon_query` appears in\n`tools/list`.\n\n### Multi-bridge (new)\n\nFor scenarios requiring certification across multiple data sources, the\n`MultiBridge` routes `canon_*` predicates to different providers by domain:\n\n```python\nfrom socratic_engine.multi_bridge import MultiBridge\n\nbridge = MultiBridge()\nbridge.add_provider(\"agent-state\", vsm_provider, [\"tasks\", \"sessions\"])\nbridge.add_provider(\"infra-state\", ssh_provider, [\"services\", \"lxcs\"])\nbridge.add_provider(\"vsm-docs\", doc_provider, [\"boot\", \"s1-operations\"])\nbridge.register(eng)\n\n# Now the engine can certify across all domains:\nev = eng.evaluate({\"op\": \"AND\", \"children\": [\n    {\"predicate\": \"canon_query\", \"args\": [\"services\", '{\"name\": \"api\"}']},\n    {\"predicate\": \"canon_query\", \"args\": [\"boot\", '{\"name\": \"covenant\"}']},\n]})\n```\n\nOr load from config:\n\n```bash\nSOCRATIC_BRIDGE_CONFIG=bridge_config.json python -m socratic_engine.mcp_server\n```\n\n**Provider health tracking**: each provider tracks consecutive failures.\nAfter 3 failures, the provider is marked unhealthy and `canon_providers`\nreturns `UNKNOWN`. Failed queries return `UNKNOWN` (not FALSE) — a\nprovider crash is indetermination, not falsity.\n\n**Routing observability**: every `canon_*` predicate includes routing\ninfo in its evidence: provider name, domain, latency_ms, record_count.\nThis makes provider selection inspectable in the evaluation tree.\n\n**Duplicate-domain priority**: `add_provider(..., priority=N)` and the\n`priority` field in bridge config (default 0, higher wins; tie keeps the\nfirst registered). Introspection (`canon_providers`, `canon_domains`)\nnever mutates health — observing a provider must not heal it.\n\nSee [`socratic_engine/multi_bridge.py`](./socratic_engine/multi_bridge.py) for\nthe full API and config format.\n\n---\n\n## CLI\n\n### Evaluate a tree\n\n```bash\nsocratic-engine eval-tree tree.json --doc-type THEORY-VC-01\n```\n\nA successful response contains the structured decision together with its explanation and diagnostic information.\n\n### Match a procedure\n\n```bash\nsocratic-engine match-procedure --input '{\"instrument\":\"x\",\"hook\":\"session.idle\",\"context\":{},\"repoRoot\":\".\"}'\n```\n\nMatches current context against past procedures in\n`learning_records/procedures/` (instrument/hook/success/recency scoring).\nReturns `reuse` / `adapt` / `skip` with confidence. Thin-dispatcher\ncounterpart to the opencode scenario-memory plugin.\n\n### Inline VSL notation\n\nTrees can also be written inline: `socratic(predicate, arg, ...)` is\nshorthand for `{\"predicate\": ..., \"args\": [...]}`.\n\n```bash\nsocratic-engine eval-tree <(echo 'socratic(type_prefix, \"$type\", \"VSL-\")') --doc-type VSL-X\n```\n\n### Decide: evaluate decisions before execution\n\nThe `decide` command evaluates decisions through the socratic engine, enabling RSI (Recursive Self-Improvement) by logging decision outcomes for learning.\n\n```bash\n# Simple reversible decision\nsocratic-engine decide --decision \"Use JSONL format\" --json\n\n# Decision with alternatives and impact\nsocratic-engine decide \\\n  --decision \"Restructure docs/\" \\\n  --alternatives \"Keep,Reorganize\" \\\n  --impact \"All references\" \\\n  --json\n\n# Irreversible decision (requires approval)\nsocratic-engine decide \\\n  --decision \"Delete production data\" \\\n  --reversible false \\\n  --approved \\\n  --json\n\n# Additional context\nsocratic-engine decide \\\n  --decision \"Deploy v2.0\" \\\n  --context '{\"environment\": \"production\", \"rollback_plan\": true}' \\\n  --json\n```\n\n**Decision logic:**\n- Reversible decisions (`--reversible true`, default) pass automatically\n- Irreversible decisions (`--reversible false`) require `--approved` flag\n- Without approval, irreversible decisions return `UNKNOWN` (uncertified)\n\n**Output (JSON):**\n```json\n{\n  \"truth\": \"TRUE\",\n  \"certified\": true,\n  \"home\": \"pass\",\n  \"explain\": \"op:AND → true [✓]...\",\n  \"decision\": \"Use JSONL format\",\n  \"context\": {\n    \"reversible\": \"true\",\n    \"has_alternatives\": false,\n    \"has_impact\": false,\n    \"approved\": false\n  }\n}\n```\n\nThis makes the same engine usable from:\n\n- agent write-time gates;\n- commit hooks;\n- scripts;\n- CI/CD;\n- MCP clients;\n- other VSM/VSF components;\n- **decision evaluation before execution** (RSI integration).\n\n---\n\n## Install\n\n### From PyPI (v0.2.10)\n\n```bash\npip install socratic-engine\n```\n\n### With MCP support\n\n```bash\npip install \"socratic-engine[mcp]\"\n```\n\n### From source\n\n```bash\ngit clone https://github.com/rm-w3kufe/socratic-engine.git\ncd socratic-engine\n\npython3 -m venv .venv\n.venv/bin/python -m pip install -e .\n```\n\n### Run the tests\n\n```bash\npython3 -m pytest tests/ -q\n```\n\nThe current repository snapshot passes:\n\n```text\n519 passed\n```\n\nat 100% statement coverage (CI gates at 90%; uncovered lines are\ndocumented `# pragma: no cover` defensive branches — see the rationale\ninline in the source).\n\n(includes integration tests with `state-canon`, available when that\npackage is installed or reachable at `~/state-canon-mcp`)\n\n### Releases\n\nReleases are automated via GitHub Actions, triggered by a tag. The workflow verifies version sync across `pyproject.toml`, `__init__.py`, `mcp_server.py`, and tests before publishing to PyPI.\n\n```bash\n# Ship a new version:\ngit tag vX.Y.Z\ngit push origin vX.Y.Z\n```\n\nSee [`.github/workflows/release.yml`](./.github/workflows/release.yml) for details.\n\n---\n\n## Repository layout\n\n```text\nsocratic-engine/\n├── README.md\n├── ROADMAP.md\n├── pyproject.toml\n├── LICENSE\n├── benchmarks/\n│   └── benchmark.py        # deep/wide trees, cache speedup, diagnose\n├── docs/\n│   ├── ONTOLOGY.md         # epistemic model\n│   ├── ARCHITECTURE.md     # how it fits together (code-faithful)\n│   └── EXAMPLES.md         # executable use cases\n├── socratic_engine/\n│   ├── __init__.py\n│   ├── engine.py           # trivalent evaluator + certification + diagnosis\n│   ├── tree.py             # builder + VSL parser + tree routing + TreeExecutor\n│   ├── cli.py              # command-line interface\n│   ├── mcp_server.py       # MCP / JSON-RPC bridge + rate limiter\n│   ├── multi_bridge.py     # multi-provider routing (canon_* by domain)\n│   ├── bridge_statecanon.py # official state-canon bridge (opt-in)\n│   ├── semantics.py        # semantic simplification (NOT flattening, contradiction/tautology, dedup, absorption)\n│   ├── engine_contract.py  # SocraticEngineProtocol + EvaluationProtocol\n│   └── providers/\n│       ├── __init__.py\n│       └── vsm_doc.py      # VSM documentation filesystem provider\n└── tests/\n    ├── test_engine.py\n    ├── test_mcp_server.py\n    ├── test_cli.py\n    ├── test_cli_decide.py\n    ├── test_bridge_statecanon.py\n    ├── test_multi_bridge.py\n    ├── test_vsm_doc.py\n    ├── test_state_canon_integration.py\n    ├── test_enforce_limits.py\n    ├── test_engine_contract.py\n    ├── test_falsification.py\n    ├── test_recursion.py\n    └── test_semantics.py\n```\n\n---\n\n## Relationship to `state-canon`\n\n`socratic-engine` and `state-canon` solve different parts of the same larger problem.\n\n```mermaid\nflowchart LR\n    A[\"Agent / LLM\"]\n\n    A --> B[\"state-canon\"]\n    A --> C[\"socratic-engine\"]\n\n    B --> B1[\"What is the current state?\"]\n    B1 --> B2[\"reconciled ground truth\"]\n\n    C --> C1[\"What must be true?\"]\n    C1 --> C2[\"recursive certification\"]\n\n    B2 --> D[\"Grounded decision\"]\n    C2 --> D\n```\n\nA useful way to state the division is:\n\n> **state-canon grounds the agent in what is observed.  \n> socratic-engine constrains how claims about that state are evaluated.**\n\n`state-canon` answers questions such as:\n\n```text\n\"What is actually running?\"\n\"Has reality drifted from the declared state?\"\n```\n\n`socratic-engine` answers questions such as:\n\n```text\n\"Given these premises, what follows?\"\n\"Which branch prevents certification?\"\n\"Is there enough evidence to pass this gate?\"\n```\n\nTogether they can form a larger agent-governance loop:\n\n```text\n          observed reality\n                 │\n                 ▼\n           state-canon\n                 │\n          grounded context\n                 │\n                 ▼\n        socratic-engine\n                 │\n       certified evaluation\n                 │\n                 ▼\n             decision\n                 │\n                 ▼\n            vsf-rsi\n                 │\n         learned pattern\n                 │\n                 ▼\n          future improvement\n```\n\nThe important boundary remains the same:\n\n> **The thing that produces a claim should not be the sole authority that certifies the claim.**\n\n### Three-tool integration\n\n`socratic-engine` is the reasoning substrate. Combined with `state-canon` and `vsf-rsi`:\n\n| Tool | Role | Question it answers |\n|------|------|---------------------|\n| `state-canon` | Ground truth | \"What is actually true?\" |\n| `socratic-engine` | Reasoning | \"What follows from these premises?\" |\n| `vsf-rsi` | Learning | \"What should we remember for next time?\" |\n\n#### Integration by platform\n\n**OpenCode** — add to `opencode.json`:\n\n```json\n{\n  \"mcp\": {\n    \"socratic-engine\": {\n      \"type\": \"local\",\n      \"command\": [\"python3\", \"/path/to/socratic_mcp_with_bridge.py\"]\n    }\n  }\n}\n```\n\n**Claude Code** — add to `CLAUDE.md`:\n\n```markdown\n## Tools\n- `socratic_engine.evaluate(tree, context)` — evaluate decisions\n```\n\n**Any Python agent**:\n\n```python\nfrom socratic_engine import SocraticEngine\nengine = SocraticEngine()\nresult = engine.evaluate(tree, context)\n```\n\n**Human CLI**:\n\n```bash\npip install socratic-engine\npython -m socratic_engine evaluate tree.json --context ctx.json\n```\n\n#### Full pattern\n\n```python\nfrom socratic_engine import SocraticEngine\nfrom state_canon import StateCanon\nfrom vsf_rsi.scenario_memory import record\n\n# 1. Get ground truth\ncanon = StateCanon()\nstate = canon.query(\"services\", {\"name\": \"api\"})\n\n# 2. Evaluate decision\nengine = SocraticEngine()\nresult = engine.evaluate(tree, {\"state\": state})\n\n# 3. Learn from outcome\nrecord({\n    \"fault_signature\": \"api_timeout\",\n    \"decision\": \"increase_timeout\",\n    \"outcome\": \"success\" if result.certified else \"failure\"\n})\n```\n\n---\n\n## Measured, not proclaimed\n\nThe central research question is empirical:\n\n> **Does externalizing recursive reasoning into an executable tree improve reliability on tasks where the same model performs poorly when recursion is kept implicit?**\n\nThis repository is therefore better treated as an experimental instrument than as a claim that the problem has already been solved.\n\nA useful comparison is:\n\n```text\nCONTROL\nLLM\n ↓\nrecursive problem\n ↓\nanswer\n\n\nEXTERNALIZED\nLLM\n ↓\nreasoning tree\n ↓\nSocraticEngine\n ↓\nevaluation / diagnosis\n ↓\nLLM\n ↓\nanswer\n```\n\nThe measurements that matter include:\n\n- logical correctness;\n- premise retention;\n- contradiction rate;\n- recursive depth successfully handled;\n- recovery after a failed branch;\n- number of model iterations;\n- token cost;\n- deterministic reproducibility.\n\nA result that saves tokens while producing worse decisions is not a win.\n\n---\n\n## Security & robustness (v0.2.10)\n\n### Tree limits (enabled by default)\n\nThe engine prevents pathological trees from crashing the process:\n\n| Limit | Value | Configurable |\n|-------|-------|--------------|\n| Max depth | 100 | No (hardcoded) |\n| Max nodes | 10,000 | No (hardcoded) |\n| Cycle detection | Enabled | Via `_visited` set |\n\n```python\n# Limits are ON by default (v0.2.9+)\nengine.evaluate(tree)  # Safe — limits enforced\n\n# Disable only when you know what you're doing\nengine.evaluate(tree, enforce_limits=False)\n```\n\n### Arity validation\n\nOperators validate their number of children:\n\n| Operator | Required children | Error on mismatch |\n|----------|-------------------|-------------------|\n| NOT | Exactly 1 | `ValueError` |\n| IMPLIES | Exactly 2 | `ValueError` |\n| XOR | Exactly 2 | `ValueError` |\n| AND/OR | ≥0 (warns if 0) | `UserWarning` |\n\n### Predicate safety\n\n- **Error wrapping**: Exceptions from user predicates are wrapped in `RuntimeError` with context (name, args, kwargs)\n- **Overwrite warning**: Registering a predicate with an existing name emits `UserWarning`\n- **No shell injection**: `subprocess` calls use argument lists, not `shell=True`\n\n### Verified by independent audit\n\n- 22 independent logic tests (Kleene trivalence, short-circuit, De Morgan): **22/22 correct**\n- Thread-safety: 16 threads × 200 evaluations on shared engine: **no errors**\n- Stack limits: RecursionError at ~498 nested NOT (without limits), clean ValueError with limits\n- Scaling: O(n) confirmed (100→1600 children, ratio ≤2.04x)\n- Decision evaluation: 16 tests for `decide` command (reversible/irreversible/approval)\n\n---\n\n## Design principles\n\n### R9 — no silent concession\n\nIf a proposition cannot be decided, return `UNKNOWN`.\n\nDo not silently convert missing evidence into a false proposition or route an undecidable branch through an implicit fallback.\n\n### R10 — opinion is not evidence\n\nAn LLM can generate a judgment.\n\nIt cannot make that judgment certified merely by asserting it.\n\n### R10.1 — the LLM proposes, the engine executes\n\nThe external model may propose a reasoning tree.\n\nThe builder validates its structure.\n\nThe engine executes the recursion.\n\n### Domain agnosticism\n\nThe engine does not contain the meaning of the domain.\n\nPredicates do.\n\n### Traceability\n\nEvery recursive evaluation remains inspectable.\n\nThe result is not just a scalar; it is a tree.\n\n### Diagnosis\n\nFailure should produce a useful next question, not merely a blocked state.\n\n---\n\n## What this is not\n\n`socratic-engine` is:\n\n- **not** an agent framework;\n- **not** a prompt-engineering framework;\n- **not** a vector database;\n- **not** a replacement for an LLM;\n- **not** a claim that symbolic logic alone produces intelligence.\n\nIt is a **reasoning substrate**.\n\nIts purpose is narrower:\n\n> **externalize recursive logical structure so that a language model can participate in complex reasoning without being solely responsible for executing the recursion.**\n\n---\n\n## Roadmap\n\nSee **[ROADMAP.md](./ROADMAP.md)** for the full development history and future plans.\n\n### Current status\n\n- **v0.2.11** — `decide` CLI command: evaluate decisions before execution for RSI integration (519 tests)\n- **v0.2.10** — Context predicates: ctx_equals, ctx_contains, ctx_not_has for tree-based evaluation (503 tests)\n- **v0.2.9** — Security hardening: enforce_limits=True by default, cycle detection, XOR arity validation, predicate error wrapping, predicate overwrite warnings (479 tests)\n- **v0.2.8** — published on PyPI (engine contract protocols, `_TreeLimitCounter` bypass fix, 479 tests)\n- **v0.2.7** — `_TreeLimitCounter` bypass fix, 11 regression tests\n- **v0.3.x** — paraconsistent logic, frame semantics, stakeholder graphs\n\nThe roadmap is intentionally open: the next features should be driven by failures observed in the experimental programme, not by feature accumulation.\n\n---\n\n## Going deeper\n\n- **[docs/ONTOLOGY.md](./docs/ONTOLOGY.md)** — the epistemic model: truth, certification, UNKNOWN, operator semantics, and the LLM boundary.\n- **[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)** — how the modules fit together, written against the code.\n- **[docs/EXAMPLES.md](./docs/EXAMPLES.md)** — executable use cases: classification, deploy gates, diagnosis, dialectics, caching, rate limiting.\n- **[ROADMAP.md](./ROADMAP.md)** — current development programme (tracked as [issue #1](https://github.com/rm-w3kufe/socratic-engine/issues/1)).\n- **[benchmarks/benchmark.py](./benchmarks/benchmark.py)** — performance numbers (deep/wide trees, cache speedup, diagnose).\n- **[tests/](./tests/)** — executable specification of the current semantics.\n\n---\n\n## Lineage & philosophy\n\nBuilt on Stafford Beer's **Viable System Model** and the **Cybersyn** project (Chile, 1971) — a system is\nviable when it can be *described*, *governed*, and *audited*. \"Don't trust, verify\" isn't a slogan here;\nit's the reconciler and the verify-at-every-boundary discipline made mechanical. Community-first, and\ndeliberately **from the Global South** — the heir to Cybersyn's bet that good cybernetics serves people.\n\n## License\n\nCode: **Apache-2.0** ([LICENSE](./LICENSE)) · Docs: **CC-BY-4.0**.\n\nThis module is deliberately more permissive than the AGPL core it was extracted from — it is meant to be\nadopted, embedded, and improved by the community. Improvements can flow back; the module stays clean of\nAGPL code by construction.\n"}