Skip to the content.

THEOS

Developer Guide

How to build a THEOS-powered reasoning system for your own domain.


Overview

THEOS is domain-agnostic by design. The core wringer loop makes no assumptions about what you are reasoning about. You supply ten operator functions; THEOS runs the governed I→A→D→I loop and returns a structured output.

This guide walks through building a minimal working domain from scratch.


Step 1 — Understand the Operator Contract

Every THEOS domain implements these ten callables:

encode_observation(query: str) -> Any
    Turn the raw query string into a structured observation object O.

induce_patterns(obs: Any, prev_phi: float, prev_own_deduction: Any | None) -> PatternI
    Extract patterns from the observation.
    prev_phi:            contradiction from the last wringer pass (0.0 on first call)
    prev_own_deduction:  this engine's own prior D (None on first inner pass)
                         Use this to make the engine reflect on what it just concluded.

abduce_left(pattern: PatternI, wisdom: WisdomStore) -> HypothesisA
    Constructive abduction — build toward the strongest coherent answer.

abduce_right(pattern: PatternI, wisdom: WisdomStore) -> HypothesisA
    Adversarial abduction — systematically stress-test and oppose.

deduce(hypothesis: HypothesisA) -> DeductionD
    Shared deduction — derive a conclusion from a hypothesis.

measure_contradiction(d_left: DeductionD, d_right: DeductionD) -> float
    The wringer. Must return a float in [0, 1].
    0.0 = perfect agreement. 1.0 = complete contradiction.

retrieve_wisdom(wisdom: WisdomStore, query: str, threshold: float) -> WisdomStore
    Return the entries from the wisdom store most relevant to this query.

update_wisdom(wisdom: WisdomStore, query: str, deduction: DeductionD, confidence: float) -> WisdomStore
    Add a new lesson (distilled from this wringer run) to the wisdom store.

estimate_entropy(hyp_left: HypothesisA, hyp_right: HypothesisA) -> float
    Estimate entropy of the hypothesis space. Returns float in [0, 1].

estimate_info_gain(phi_prev: float, phi_curr: float) -> float
    How much information was gained this wringer pass.

Step 2 — Build a Minimal Domain

Here is a complete minimal example. The domain reasons about simple text questions using word-overlap as a proxy for contradiction.

import sys, os
sys.path.insert(0, "code")

from theos_core import TheosCore, TheosConfig, AbductionEngines, DeductionEngine

# --- Operator implementations ---

def encode_observation(query: str):
    """Encode query as a dict with key terms."""
    words = set(query.lower().split())
    return {"query": query, "terms": words}

def induce_patterns(obs, prev_phi: float, prev_own_deduction):
    """Extract what the question is really asking."""
    pattern = {"terms": obs["terms"], "phi_feedback": prev_phi}
    if prev_own_deduction is not None:
        # Self-reflection: incorporate own prior conclusion
        pattern["prior_conclusion"] = prev_own_deduction
    return pattern

def abduce_left(pattern, wisdom):
    """Constructive: assert the most affirmative position."""
    terms = pattern["terms"]
    return f"Affirming: {', '.join(sorted(terms))} — building the strongest case."

def abduce_right(pattern, wisdom):
    """Adversarial: assert the most critical position."""
    terms = pattern["terms"]
    return f"Challenging: {', '.join(sorted(terms))} — probing for weaknesses."

def deduce(hypothesis: str) -> str:
    """Derive a conclusion from a hypothesis."""
    return f"Therefore: [{hypothesis}]"

def measure_contradiction(d_left: str, d_right: str) -> float:
    """Word-overlap proxy for contradiction."""
    words_l = set(d_left.lower().split())
    words_r = set(d_right.lower().split())
    if not words_l or not words_r:
        return 1.0
    overlap = len(words_l & words_r) / len(words_l | words_r)
    return 1.0 - overlap   # high overlap → low contradiction

def retrieve_wisdom(wisdom, query: str, threshold: float):
    """Return all wisdom entries for this example."""
    return wisdom

def update_wisdom(wisdom, query: str, deduction, confidence: float):
    """Append a new lesson."""
    wisdom.append({"query": query, "lesson": deduction, "confidence": confidence})
    return wisdom

def estimate_entropy(hyp_left: str, hyp_right: str) -> float:
    """Entropy proxy: proportion of unique words."""
    words_l = set(hyp_left.lower().split())
    words_r = set(hyp_right.lower().split())
    shared = len(words_l & words_r)
    total = len(words_l | words_r)
    return 1.0 - (shared / total) if total > 0 else 0.5

def estimate_info_gain(phi_prev: float, phi_curr: float) -> float:
    """Information gain = reduction in contradiction."""
    return max(0.0, phi_prev - phi_curr)


# --- Wire it together ---

config = TheosConfig(
    max_wringer_passes=3,
    eps_converge=0.3,
    eps_partial=0.6,
    verbose=True,
)

core = TheosCore(
    config=config,
    encode_observation=encode_observation,
    induce_patterns=induce_patterns,
    engines=AbductionEngines(
        abduce_left=abduce_left,
        abduce_right=abduce_right,
    ),
    deduction=DeductionEngine(deduce=deduce),
    measure_contradiction=measure_contradiction,
    retrieve_wisdom=retrieve_wisdom,
    update_wisdom=update_wisdom,
    estimate_entropy=estimate_entropy,
    estimate_info_gain=estimate_info_gain,
)

result = core.run_query("What is the difference between egotism and arrogance?")

print(f"Output type:  {result.output_type}")
print(f"Confidence:   {result.confidence:.2f}")
print(f"Passes used:  {result.wringer_passes_used}")
print(f"Halt reason:  {result.halt_reason.value}")
print(f"Output:       {result.output}")

Step 3 — Handle All Three Output Types

Always branch on result.output_type:

result = core.run_query(query)

if result.output_type == "convergence":
    # Engines agreed. output is the direct conclusion.
    print("Answer:", result.output)

elif result.output_type == "blend":
    # Partial agreement. output may be a structured blend dict.
    blend = result.output
    print("Blended answer:", blend)

elif result.output_type == "disagreement":
    # Irreducible disagreement. Show both perspectives.
    d = result.output
    print("Left perspective:", d["left"])
    print("Right perspective:", d["right"])
    print("The question cannot be resolved without choosing a frame.")

A disagreement result is not a failure — it is the correct answer when the question genuinely depends on unresolved assumptions.


Step 4 — Add Wisdom Persistence

To accumulate reasoning memory across sessions:

from theos_system import TheosSystem

system = TheosSystem(
    core=core,
    persistence_file="my_wisdom.json",
)

result = system.reason("My question")
metrics = system.get_metrics()
print(f"Total queries: {metrics.total_queries}")
print(f"Convergence rate: {metrics.convergence_rate:.0%}")

The wisdom JSON file is written after each query. On the next session, previously accumulated lessons are loaded and surfaced to both engines during abduction — sharpening their hypotheses based on past reasoning.


Step 5 — Tune the Governor

Goal Adjustment
Faster, cheaper reasoning Lower max_wringer_passes (e.g., 2–3)
Deeper reasoning Raise max_wringer_passes (e.g., 10+)
Stricter convergence required Lower eps_converge (e.g., 0.02)
Accept more partial agreement Raise eps_partial (e.g., 0.7)
More per-engine introspection Raise engine_reflection_depth (e.g., 3)
No self-reflection Set engine_reflection_depth=1
Unlimited budget Leave budget=None
Token-cost cap Set budget=10000 (token count)

Existing Domain Examples

The examples/ directory contains three complete domain implementations:

File Domain Key operator pattern
theos_medical_diagnosis.py Medical diagnosis Symptom → differential diagnosis; right engine checks contraindications
theos_financial_analysis.py Financial decisions Left: upside case; right: downside risk
theos_ai_safety.py AI safety evaluation Left: capability maximization; right: safety / alignment

These are the canonical implementation references.


Self-Reflection — The Key Difference

The prev_own_deduction argument to induce_patterns is what makes THEOS architecturally novel. On the second inner pass, each engine receives its own first-pass deduction as feedback. This is not shared — the left engine sees only its own prior, and the right engine sees only its own prior.

def induce_patterns(obs, prev_phi, prev_own_deduction):
    if prev_own_deduction is None:
        # First inner pass: form initial patterns
        return extract_first_pass_patterns(obs)
    else:
        # Second inner pass: reason about your own conclusion
        # This is second-order cognition: thought about thought.
        return refine_patterns_from_own_prior(obs, prev_own_deduction)

Engines that ignore prev_own_deduction still work — they just lose the self-reflection benefit.


API Reference · Integration Guide · Troubleshooting