Skip to content

Architecture

Jonathan D.A. Jewell edited this page Jul 11, 2026 · 1 revision

Architecture

Statistikles is a Kautz Type‑1 neurosymbolic system: a neural component and a symbolic component operate side by side, separated by a defined, auditable interface. The neural side (an LLM) handles language; the symbolic side (Julia) handles arithmetic. Nothing crosses that line by accident — and a runtime guardrail proves it did not.

This page describes the split, the request pipeline through src/tools/, and a map of the src/ tree. For the guarantee that polices the boundary, see The-No-Fabrication-Guarantee.

The neural / symbolic split

Neural (LLM) Symbolic (Julia)
Job Parse the natural‑language question, choose a tool, interpret and phrase the result Perform every calculation, exactly and deterministically
May produce numbers? No — enforced by the guardrail Yes — the only source of numbers
Lives in src/tools/lmstudio.jl, src/tools/chat.jl src/stats/ (~41 modules), dispatched by src/tools/executor.jl
Backend LM Studio, OpenAI‑compatible chat completions The Julia runtime

The contract is stated at the top of src/Statistikles.jl:

MANDATE: No statistical calculation may be performed by the LLM. Every number in the final report is audited by deterministic code.

Request flow: NL → route → compute → explain

A single turn of statistical_assistant_chat() (in src/tools/chat.jl) moves through four stages:

  1. Route (neural). The user's text plus the tool schemas from get_tools() are sent to the model by call_lm_studio(messages, tools) (src/tools/lmstudio.jl). The model responds either with prose or with one or more tool_calls.
  2. Compute (symbolic). process_tool_calls(response, messages; tools, max_rounds=5) drives the tool loop. For each requested call it parses the JSON arguments and invokes execute_tool(fn_name, args) (src/tools/executor.jl), which dispatches to a verified Julia function. Each result is recorded (native Julia Dicts) as fuel for the guardrail. The loop repeats — feeding results back so the model can chain tools — up to max_rounds rounds.
  3. Explain (neural). With the tool results in the transcript, the model writes a plain‑language interpretation.
  4. Audit (symbolic). Before the reply is printed, enforce_numeric_boundary!(...) (src/tools/chat.jl) runs validate_numeric_provenance() (src/tools/guardrail.jl) over the prose. Any numeric literal that does not trace to a recorded tool result (or a user number, or a small structural integer 0..12) is an orphan. The model gets one retry; residual orphans are flagged with a visible POSSIBLE MOLLOCK warning. The text is never silently rewritten.
flowchart TD
    U["User question<br/>(natural language)"] --> LM["call_lm_studio(messages, tools)<br/>src/tools/lmstudio.jl"]
    LM -->|tool_calls requested| PT["process_tool_calls loop<br/>max_rounds = 5"]
    PT --> EX["execute_tool(name, args)<br/>src/tools/executor.jl"]
    EX --> STATS["Verified Julia functions<br/>src/stats/ (~41 modules)"]
    STATS -->|exact numeric result| PT
    PT -->|results in transcript| LM
    LM -->|interpretive prose| GR["enforce_numeric_boundary!<br/>validate_numeric_provenance()<br/>src/tools/guardrail.jl"]
    GR -->|all numbers trace to a tool result| OUT["Reply printed to user"]
    GR -->|orphan numbers found| RETRY["Single retry, then<br/>append MOLLOCK warning"]
    RETRY --> OUT

    subgraph NEURAL["Neural — no numbers produced"]
        LM
    end
    subgraph SYMBOLIC["Symbolic — the only source of numbers"]
        EX
        STATS
        GR
    end
Loading

The tool pipeline in src/tools/

File Responsibility
definitions.jl get_tools() builds 29 OpenAI‑style function‑calling schemas via the _tool(name, description, properties, required) helper. These are what the model is allowed to call.
lmstudio.jl Transport. call_lm_studio posts chat completions; process_tool_calls runs the bounded tool loop. Config constants BASE_URL, API_KEY, MODEL are overridable via STATISTIKLES_LM_URL, STATISTIKLES_API_KEY, STATISTIKLES_MODEL.
executor.jl execute_tool(tool_name, arguments) — a dispatcher over 55 tool names mapping each to a verified Julia call. Wrapped in try/catch, it returns a clean Dict("error" => …) on failure (full trace only when STATISTIKLES_DEBUG is set).
guardrail.jl validate_numeric_provenance(text, tool_results, user_numbers; rtol=1e-6) and helpers (collect_numbers, extract_numeric_tokens, _token_explained) — the numeric‑provenance audit.
chat.jl The REPL session (statistical_assistant_chat), the system prompt, enforce_numeric_boundary!, run_examples() (offline demo), and main().

Executor safety

execute_tool is the point where neural meets symbolic, so it is defensive:

  • Unknown‑subtype guards. Tools with a type discriminator (e.g. t_test, nonparametric_test, information_theory) fall through to Dict("error" => "Unknown type '…' for …") rather than crashing on an unexpected value.
  • Iteration clamps. Resource‑hungry tools cap their work: bootstrap and PERMANOVA reject n_reps / n_permutations above 100000; component counts (k) for NMF, ICA, and EM are capped at 20.
  • Unknown tools. An unrecognised tool_name returns Dict("error" => "Unknown tool: …").

Map of src/

src/
├── Statistikles.jl          Module root: includes, exports, mandate banner
│
├── stats/                   SYMBOLIC KERNEL — ~41 verified statistics modules
│   ├── descriptive.jl       mean, median, variance, skewness, kurtosis
│   ├── inferential.jl       t-tests, ANOVA, chi-square
│   ├── correlation_regression.jl, nonparametric.jl, effect_sizes.jl
│   ├── power_analysis.jl, bayesian.jl, bayesian_advanced.jl
│   ├── survival.jl, meta_analysis.jl, robust.jl, spatial.jl
│   ├── timeseries.jl, information_theory.jl, machine_learning.jl, nlp.jl
│   ├── causality.jl, sem.jl, multivariate.jl, resampling.jl
│   ├── algebraic.jl, non_classical.jl, dynamic_structured.jl, …
│   └── validation.jl        shared ArgumentError input-validation helpers
│
├── pipeline/                DATA STEWARDSHIP — order matters
│   ├── canonicalization.jl  epochs, precision, precedence (runs first)
│   ├── dimensional_analysis.jl, detection.jl, validation.jl
│   ├── cleansing.jl, normalization.jl
│
├── tools/                   INTERFACE LAYER — the neural / symbolic seam
│   ├── definitions.jl       get_tools() — 29 function-calling schemas
│   ├── executor.jl          execute_tool() — 55-way dispatch to Julia
│   ├── lmstudio.jl          LM Studio transport + tool loop
│   ├── guardrail.jl         numeric-provenance enforcement
│   └── chat.jl              session, system prompt, main(), run_examples()
│
├── bridge/                  CROSS-VERIFICATION & PERSISTENCE
│   ├── aspasia_bridge.jl    Aspasia (Octave) cross-verification
│   ├── verisimdb_schema.jl  VeriSimDB persistence (port 8096)
│   ├── echidna_adapter.jl   ECHIDNA formal-proof dispatch
│   ├── betlang_bridge.jl    BetLang probabilistic programming
│   └── typell_levels.jl     TypeLL levels 1–12 statistical types
│
├── integrations/           JULIA ECOSYSTEM ADAPTERS
│   ├── axiom_integration.jl     property verification
│   ├── smtlib_integration.jl    exact-arithmetic checks
│   ├── causals_integration.jl   causal DAGs + Bradford Hill
│   ├── bowtie_integration.jl    barrier / risk modelling
│   ├── zeroprob_integration.jl  zero-inflated models
│   └── quantum_integration.jl   Bell / CHSH experiments
│
└── output/                 PRESENTATION
    ├── tables.jl            Unicode box-drawing tables
    ├── graphs.jl           ASCII histogram / box / scatter / bar
    └── export.jl           CSV / JSON export with provenance stamps

The include order in src/Statistikles.jl is deliberate: the statistics kernel loads first, then the data pipeline (canonicalization before any logic), then the bridges and integrations, and finally the interface layer that stitches the LLM to the kernel.

Related pages

Clone this wiki locally