Metacognitive Tree-of-Thought Reasoning Engine
Project description
metatotai
metatotai is a small Python package for building a reasoning tree that can watch its own uncertainty while it thinks.
The short version:
- Standard prompting asks a model for an answer.
- Chain-of-thought asks a model to show one line of reasoning.
- Tree-of-Thought asks a model to try several reasoning branches.
- metatotai adds a metacognitive layer: it scores branches using signals like prediction error, uncertainty, expected free energy, precision, and traceable path selection.
That means metatotai is not just asking, "What is the best answer?"
It is asking:
- What branches should we explore?
- Which branches reduce uncertainty?
- Which branches look confident but brittle?
- Which path did we choose, and why?
- Can we store a trace so another system can audit the reasoning later?
metatotai began inside the Dionysus cognitive architecture and was extracted into an independent package so it can be used as a standalone reasoning engine.
Install
pip install metatotai
For the engine quickstart, also make sure numpy is available in your environment:
pip install metatotai numpy
The package requires Python 3.10+.
What problem does metatotai solve?
Large language models are good at producing fluent answers. They are less good at telling you how stable an answer is.
For example, ask a model:
Should we rewrite this service, patch it, or leave it alone?
A normal answer may sound confident even when the choice is fragile. The model may skip hidden tradeoffs. It may choose the familiar path because that path is easy to explain. It may not preserve enough trace data for a later audit.
metatotai gives you a structure for this kind of problem.
Instead of treating reasoning as one answer, it treats reasoning as a search through possible mental actions. A mental action can be something like:
- explore another option
- challenge the current assumption
- evolve a better version of the plan
- integrate what was learned
- step back and observe the reasoning process itself
Each branch gets a state. That state can carry active-inference-style signals:
- prediction error: how far the branch is from what the system expected
- free energy: a compact pressure signal for mismatch and uncertainty
- expected free energy: how costly or useful a branch may be if followed
- precision: how strongly the system should trust a signal
- entropy: how uncertain a belief state is
- confidence: how strongly the selected path should be treated
This does not make the system magically correct. It makes the reasoning more inspectable. That matters when the cost of a bad answer is high.
A 6th-grade explanation
Imagine you are trying to get through a maze.
A simple AI answer is like a friend saying, "Go left."
Chain-of-thought is like the friend saying, "Go left because I think the door is there."
Tree-of-Thought is like the friend checking left, right, and straight before choosing.
metatotai is like giving that friend a notebook and a stress meter.
The notebook records every path the friend considered. The stress meter rises when the friend is guessing, confused, or seeing signals that do not match the plan. The friend can then say:
I checked three paths. This path is not just my favorite. It also has lower uncertainty, fewer bad surprises, and a better trace.
That is the point. metatotai helps a system think in branches, measure uncertainty, and leave a record.
A software-engineer explanation
metatotai exposes a metacognitive Tree-of-Thought engine with Pydantic models, configurable search settings, trace payloads, and optional host-system adapters.
The core engine lives at:
from metatotai.engine.meta_tot_engine import MetaToTEngine, MetaToTConfig
The main models live at:
from metatotai.models.meta_tot import (
ActiveInferenceState,
MetaToTDecision,
MetaToTResult,
MetaToTTracePayload,
)
The standalone-safe path is:
use_llm=Falsepersist_trace=False- injected adapters only if you want host integration
When used this way, metatotai can run without the original Dionysus api.* package on sys.path.
The package also contains optional integration surfaces for systems that provide:
- an LLM provider
- a trace sink
- a world-model provider
- a memory provider
- a thought promotion service
- a decision learner
Those are optional boundaries. The standalone package should import without Dionysus.
Quickstart: run a tiny reasoning tree
Create a file called quickstart.py:
import asyncio
from metatotai.engine.meta_tot_engine import MetaToTConfig, MetaToTEngine
class NoopMemory:
async def route_memory(self, *args, **kwargs):
return None
async def main() -> None:
config = MetaToTConfig(
use_llm=False,
persist_trace=False,
max_depth=2,
branching_factor=2,
random_seed=7,
use_field_based_selection=False,
use_softmax_selection=False,
)
engine = MetaToTEngine(config, memory=NoopMemory())
result, trace = await engine.run(
problem="Choose a caching strategy for a slow API endpoint.",
context={
"goal": "Reduce latency without serving stale user data.",
"constraints": [
"must be safe for user-specific data",
"should be easy to roll back",
"risk: stale cache entries",
],
"unknowns": [
"traffic shape",
"cache invalidation rules",
],
},
)
print("session:", result.session_id)
print("confidence:", round(result.confidence, 3))
print("best path length:", len(result.best_path))
print("metrics:", result.metrics)
if trace is not None:
print("selected action:", trace.selected_action)
print("node count:", len(trace.nodes))
for node in trace.nodes[:5]:
print(node.depth, node.cpa_domain, round(node.score, 3), node.thought[:80])
if __name__ == "__main__":
asyncio.run(main())
Run it:
python quickstart.py
What this does:
- Creates a Meta-ToT engine.
- Turns off LLM calls so the example is local and repeatable.
- Turns off trace persistence so no database is needed.
- Injects a no-op memory adapter so the example does not write to any host memory system.
- Builds a small reasoning tree.
- Scores branches.
- Selects a path.
- Returns both a result and a trace payload.
Quickstart: decide whether Meta-ToT should run
Some tasks do not need a reasoning tree. If the question is simple, you may not want the cost of extra search.
The decision service gives you a threshold gate:
from metatotai.engine.meta_tot_decision import MetaToTDecisionService
service = MetaToTDecisionService()
decision = service.decide(
task="Plan a migration with rollback, data safety, and unclear user impact.",
context={
"goal_alignment": 0.4,
"unknowns": ["schema risk", "traffic timing", "rollback window"],
},
)
print(decision.use_meta_tot)
print(decision.complexity_score)
print(decision.uncertainty_score)
print(decision.rationale)
The service checks signals like:
- task length
- constraint words
- domain words
- explicit unknowns
- goal alignment
- question marks
- optional force/disable flags
You can force or disable Meta-ToT from context:
service.decide("simple task", {"force_meta_tot": True})
service.decide("complex task", {"disable_meta_tot": True})
Why this is different from standard approaches
Standard prompting
Standard prompting asks for one answer.
That is fast. It is also easy to over-trust.
If the model says, "Use Redis," you may not see the branches it skipped: in-process cache, CDN cache, database index, async job, queue, or no cache at all.
Use standard prompting when:
- the task is low risk
- the answer is easy to check
- you do not need an audit trail
Chain-of-thought
Chain-of-thought asks for a visible line of reasoning.
That can help, but it is still usually one path. If the first assumption is wrong, the whole chain can look neat while going in the wrong direction.
Use chain-of-thought-like reasoning when:
- you need a short explanation
- the search space is small
- you do not need branch comparison
Tree-of-Thought
Tree-of-Thought creates multiple branches and chooses between them.
That is closer to how real planning works. But many Tree-of-Thought systems still score branches with simple heuristics, model votes, or final-answer quality.
Use Tree-of-Thought when:
- several options may be valid
- the first answer is likely incomplete
- branch comparison matters
MCTS-style search
Monte Carlo Tree Search and related methods explore a tree by simulating outcomes. They are strong when you have a clear reward signal, a game-like environment, or a simulator.
Many real reasoning tasks do not have that. There may be no clean win/loss reward. The value of a branch may depend on uncertainty, context, identity, goals, and future flexibility.
Use MCTS-style search when:
- you can simulate outcomes
- you have a clear reward function
- repeated rollout is cheap enough
ReAct-style agents
ReAct systems mix reasoning with tool use. They think, act, observe, and continue.
That is useful for web tasks, coding tasks, and agent loops. But ReAct does not automatically give you a strong model of uncertainty or a clean policy-selection trace.
Use ReAct when:
- the system must use tools
- the world can answer back
- observation after action is central
metatotai
metatotai is for problems where the system needs to choose a reasoning path under uncertainty.
It is not only about generating branches. It is about treating branches as possible policies and asking which policy reduces uncertainty or expected cost.
Use metatotai when:
- the task is complex or uncertain
- you need branch-level trace data
- you care about why one path was selected
- you want active-inference-style scoring signals
- you may later plug the engine into a larger cognitive system
Active inference, explained simply
Active inference is a theory of how living systems act and learn.
The simple version:
A system has a model of the world. It expects certain things to happen. When reality does not match the model, the system gets prediction error.
The system can reduce that error in two ways:
- Update its beliefs.
- Act on the world to make observations fit better.
A thermostat is a simple example.
It expects the room to be 70 degrees. If the room is 65 degrees, there is an error. The thermostat can turn on the heat. When the room gets closer to 70, the error goes down.
A mind is more complex, but the same basic idea applies:
- It predicts.
- It notices mismatch.
- It updates beliefs.
- It acts.
- It tries to reduce future mismatch.
In active inference language, this pressure is often called free energy.
Do not let the term scare you. In this package, you can think of free energy as a useful warning light:
This branch may be costly because it leaves too much mismatch, uncertainty, or surprise.
Expected free energy
Expected free energy is about the future.
It asks:
If I follow this action or reasoning path, how much uncertainty or mismatch do I expect later?
A branch with lower expected free energy is often more attractive because it may lead to a clearer, safer, or more useful state.
But there is a balance.
Sometimes a branch is useful because it explores. It may not give the fastest answer, but it may reduce important uncertainty. This is why active inference often cares about both:
- pragmatic value: does this help me get what I want?
- epistemic value: does this help me learn what I need to know?
A good reasoning system needs both.
Precision
Precision means trust or confidence in a signal.
If a smoke alarm is reliable, you treat it as high precision. If it goes off every time someone makes toast, you treat it as lower precision.
In metatotai, precision-like values can shape the search. High precision can make selection sharper. Low precision can make the system explore more broadly.
This matters because not every problem should be searched the same way.
When the system is clear and confident, narrow search may be enough.
When the system is uncertain, wider search may be safer.
POMDPs, explained without the fog
A POMDP is a Partially Observable Markov Decision Process.
That name sounds bigger than the idea.
It means:
- There is a world state.
- You cannot see the whole state directly.
- You only get observations.
- You have beliefs about what is going on.
- You can take actions.
- Actions change what may happen next.
- You choose actions based on beliefs, not perfect knowledge.
A doctor diagnosing a patient is working in a POMDP-like situation. The doctor cannot directly see every hidden process in the body. They see symptoms, labs, history, and response to treatment. They hold beliefs. They choose tests or treatments. Each action changes what they know next.
A software engineer debugging production is also in a POMDP-like situation. You cannot see the whole system state. You see logs, metrics, traces, alerts, and user reports. You choose actions: inspect a service, roll back, add logging, restart a job, or patch code.
metatotai is not a full POMDP solver. It does not require you to define formal transition matrices, observation matrices, or reward tables.
But it borrows the useful framing:
- reasoning branches are possible policies
- each branch has a state
- observations update uncertainty
- selection should care about future uncertainty, not just current score
How metatotai maps these ideas into code
Here is the practical mapping:
| Concept | Plain meaning | metatotai object or field |
|---|---|---|
| Problem | The thing you are trying to reason about | problem passed to engine.run(...) |
| Context | Extra facts, constraints, goals, unknowns | context dict |
| Policy | A possible reasoning path or mental action | branch/node path |
| Belief state | What the system currently thinks | ActiveInferenceState.beliefs |
| Prediction error | How wrong or surprised the system is | prediction_error |
| Free energy | Mismatch/uncertainty pressure | free_energy |
| Expected free energy | Estimated future cost/uncertainty | expected_free_energy |
| Precision | How strongly to trust a signal | precision_*, attentional_precision |
| Trace | Record of the reasoning run | MetaToTTracePayload |
| Result | Final selected path and metrics | MetaToTResult |
What a trace gives you
A trace is the audit trail.
It can show:
- the session id
- the selected action
- the best path
- branch count
- confidence
- processing time
- prediction error
- free energy
- every recorded node
- each node's depth, type, score, and parent
This is useful when you need to debug reasoning, show why an answer was chosen, or feed the trace into a larger system.
Configuration reference
MetaToTConfig controls the search:
| Field | Meaning | Default |
|---|---|---|
max_depth |
How many levels deep the tree can grow | 4 |
simulation_count |
Placeholder budget for simulation-style workflows | 32 |
exploration_constant |
Exploration pressure for search-like policies | 2.0 |
branching_factor |
How many candidates to keep per expansion | 3 |
time_budget_seconds |
Soft time budget recorded in metrics | 5.0 |
use_llm |
Whether to ask an LLM or host model provider for branches | True |
llm_model |
Model name passed to the host LLM provider | gpt-5-nano |
persist_trace |
Whether to store traces through the trace service | True |
random_seed |
Seed for Python random |
None |
selection_temperature |
Softmax temperature | 1.0 |
use_softmax_selection |
Use softmax path selection when field selection is off | True |
use_field_based_selection |
Use field-dynamics-inspired path selection | True |
habitual_bias |
Small boost for routine/default-style branches | 0.2 |
For standalone local examples, start with:
MetaToTConfig(
use_llm=False,
persist_trace=False,
max_depth=2,
branching_factor=2,
)
Then add host adapters only when you need them.
Adapter pattern
metatotai can be used alone, but it was designed to sit inside larger systems.
Instead of hard-binding the package to Dionysus internals, the engine accepts optional adapter-like objects:
engine = MetaToTEngine(
config,
llm=my_llm_provider,
trace_sink=my_trace_sink,
world_model=my_world_model,
memory=my_memory_provider,
thoughtseed_promoter=my_promoter,
decision_learner=my_decision_learner,
)
This lets you decide what the engine can touch.
If you do not provide those adapters, the standalone-safe path uses local fallbacks where possible. Some advanced paths still need host services.
If your Python process also has Dionysus api.* modules on sys.path, metatotai may discover those host adapters. For a strict local tutorial run, inject explicit no-op adapters for any side effect you do not want, especially memory routing.
Example: a small custom trace sink
If you want to keep traces without Graphiti or another database, provide a tiny trace sink:
import asyncio
from metatotai.engine.meta_tot_engine import MetaToTConfig, MetaToTEngine
class InMemoryTraceSink:
def __init__(self) -> None:
self.traces = {}
async def store_trace(self, payload):
self.traces[payload.trace_id] = payload
return payload.trace_id
class NoopMemory:
async def route_memory(self, *args, **kwargs):
return None
async def main() -> None:
sink = InMemoryTraceSink()
engine = MetaToTEngine(
MetaToTConfig(
use_llm=False,
persist_trace=True,
max_depth=1,
use_field_based_selection=False,
use_softmax_selection=False,
),
trace_sink=sink,
memory=NoopMemory(),
)
result, trace = await engine.run(
"Pick a rollout plan for a risky migration.",
{"constraints": ["rollback required", "customer data safety"]},
)
print(result.trace_id)
print(list(sink.traces.keys()))
asyncio.run(main())
Example: API router usage
The package includes a FastAPI router module:
from fastapi import FastAPI
from metatotai.routers.meta_tot import router as meta_tot_router
app = FastAPI()
app.include_router(meta_tot_router)
Endpoints:
POST /api/meta-tot/runGET /api/meta-tot/traces/{trace_id}
The router is meant for host applications. If your host does not provide the needed cognition boundary services, the router may return a service-unavailable style error for those external boundaries.
When to use metatotai
Use it when the reasoning task has at least one of these features:
- high uncertainty
- multiple valid paths
- hidden tradeoffs
- a need for an audit trail
- a need to choose between mental actions
- a need to compare exploration vs exploitation
- a larger system that can use traces later
Good examples:
- architecture tradeoff analysis
- migration planning
- debugging strategy selection
- agent policy selection
- complex product or research decisions
- deciding whether a task needs deeper reasoning
Poor examples:
- formatting a date
- answering a simple fact
- doing arithmetic
- producing a one-off low-risk sentence
- anything where a direct answer is cheaper and safer
Current status
v0.2.2 provides:
- standalone package boundary cleanup
- runtime
metatotai.__version__metadata aligned with the published package version - metacognitive Tree-of-Thought engine
- active-inference state model
- decision gate service
- trace payload models
- optional adapter boundaries
- local no-LLM mode for basic runs
The package is still young. Treat it as an engine layer, not a finished end-user app.
Development
Clone the repo:
git clone https://github.com/bionicbutterfly13/metatotai.git
cd metatotai
Install for local development:
python -m pip install -e '.[dev]'
python -m pip install numpy
Run the standalone boundary tests:
python -m compileall -q src tests
python -m pytest tests/test_standalone_boundary.py -q
Build the package:
python -m build
python -m twine check dist/metatotai-*
Design principle
metatotai is built around one idea:
Reasoning should not only produce an answer. It should preserve enough structure to show how the answer was selected.
That structure is what lets a system improve later.
License
Apache-2.0.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file metatotai-0.2.2.tar.gz.
File metadata
- Download URL: metatotai-0.2.2.tar.gz
- Upload date:
- Size: 40.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f129f15eee4b3b1083375e6569860c996bee7c367a72658ce2f7c6501603879b
|
|
| MD5 |
b03ffbbec0575a0c95a4bd487c61ecf0
|
|
| BLAKE2b-256 |
a62e9c5dad8462271e77cbbc08096886cf467d82d8181fcab530378c873176b2
|
File details
Details for the file metatotai-0.2.2-py3-none-any.whl.
File metadata
- Download URL: metatotai-0.2.2-py3-none-any.whl
- Upload date:
- Size: 34.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ac934b9aa0d2634309ec6bb6b9ec80b88228a3a87f3d8f7fb994562226133c9
|
|
| MD5 |
8a443e6f78ada48be5655affb2f1e540
|
|
| BLAKE2b-256 |
1190f60eefa0316739c0e877cbabe474fb632ff834b6ec9debfdb5ab443f3309
|