LLM-in-the-loop rule based expert system
Project description
Theow
LLM-in-the-loop rule-based system for failure recovery.
pip install theow
Working modes
- Resolver - Match failures against known rules, execute fixes deterministically
- Explorer - When no rule matches, LLM investigates and creates new rules
Initialization
from theow import Theow
my_agent = Theow(
theow_dir="./.theow", # Where rules/actions/chroma live
name="my_agent", # Name for logging
llm="gemini/gemini-2.0-flash", # Primary LLM (provider/model)
llm_secondary="anthropic/claude-sonnet-4-20250514", # Optional fallback
session_limit=20, # Max LLM explores per session
max_tool_calls=30, # Max tool calls per conversation
max_tokens=8192, # Max ouput tokens per conversation
)
Supported Providers: gemini/, anthropic/, copilot/
Each provider uses its own API key from environment:
gemini/*:GEMINI_API_KEYanthropic/*:ANTHROPIC_API_KEYcopilot/*:GITHUB_TOKEN
Tools
Tools are what the LLM can do during exploration. Theow provides common tools - import and register what you need:
from theow.tools import read_file, write_file, run_command, list_directory
# Register the ones you want (loose leash)
my_agent.tool()(read_file)
my_agent.tool()(write_file)
my_agent.tool()(run_command)
my_agent.tool()(list_directory)
Or write one with tighter constraints and register:
@my_agent.tool()
def read_workspace_file(workspace: str, relative_path: str) -> str:
"""Read file within workspace only."""
path = Path(workspace) / relative_path
if not path.is_relative_to(workspace):
raise ValueError("Path escapes workspace")
return path.read_text()
@my_agent.tool()
def run_go_mod(workspace: str, args: str) -> dict:
"""Run go mod commands only."""
result = subprocess.run(f"go mod {args}", shell=True, cwd=workspace, capture_output=True, text=True)
return {"returncode": result.returncode, "stderr": result.stderr}
The @mark Decorator
Marks a function for automatic recovery. When the function raises an exception:
- Calls
context_fromwith the function's args and the exception to build context - Tries to find a matching rule (by name > by tags > by vector search)
- If rule found: executes its action, retries the function
- If no rule and
explorable=TrueandTHEOW_EXPLORE=1: runs LLM exploration - If LLM creates a rule: executes action, retries function
- Repeats up to
max_retriestimes with top rules ormax_triesnumber of explorations
@my_agent.mark(
context_from=lambda x, exc: {
"error_type": "build_failure",
"stderr": exc.stderr,
"workspace": str(x.path),
},
max_retries=3,
explorable=True,
)
def build(x):
result = run_build(x)
if result.returncode != 0:
raise BuildFailed(result.stderr)
Theow adds a traceback automatically. That said for better results, the function must raise a an exception with relevent information. If its a custom excpetion with minimal information, there might not be enough context to match rules or explore effectively.
Parameters with their defaults:
@my_agent.mark(
context_from=..., # Callable: (args, kwargs, exception) -> dict. Can include any number of keys.
max_retries=3, # How many rules to try or explorations to do
rules=["rule1"], # Try these rules first (by name)
tags=["go"], # Then try rules with these tags
fallback=True, # Fall back to vector search
explorable=False, # Allow LLM exploration (also requires THEOW_EXPLORE=1 and an API key set)
collection="default", # Chroma collection for rules. Recommended one per tagged function or module.
)
Run with exploration enabled:
THEOW_EXPLORE=1 python my_script.py
Actions
Actions are what rules execute.:
@my_agent.action("patch_config")
def patch_config(workspace: str, key: str, value: str):
"""Patch a config file."""
# ...
Actions live in .theow/actions/*.py (auto-discovered):
from theow import action
@action("fix_module_rename")
def fix_module_rename(workspace: str, old_path: str, new_path: str):
"""Add replace directive for renamed module."""
subprocess.run(
f"go mod edit -replace {old_path}={new_path}@latest",
shell=True, cwd=workspace
)
Rules
Rules live in .theow/rules/*.rule.yaml:
name: module_rename
description: Module path was renamed upstream
tags: [go, dep]
when:
- fact: error_type
equals: dep_resolution
- fact: stderr
regex: 'declares its path as: (?P<new_path>\S+).*required as: (?P<old_path>\S+)'
examples:
- "module declares its path as: dario.cat/mergo but was required as: github.com/imdario/mergo"
then:
- action: fix_module_rename
params:
old_path: "{old_path}"
new_path: "{new_path}"
Matching:
equals: Exact matchcontains: Substring matchregex: Regex with named captures (available as{name}in params)
Examples in facts improve vector search recall.
Directory Structure
.theow/
├── rules/ # Rule YAML files
├── actions/ # Action Python files
├── prompts/ # Prompt templates for probabilistic rules
└── chroma/ # Vector DB (auto-managed)
Probabilistic Rules
Rules can also run a LLM call on match:
name: investigate_unknown
description: Unknown failure, use LLM to investigate
when:
- fact: error_type
equals: unknown
llm_config:
prompt_template: file://prompts/investigate.md # can be a file or a string
tools: [read_file, run_command]
constraints:
max_tool_calls: 20
This would also need an API key set for the LLM provider. This LLM call will not create a new rule or action but directly act on the failure.
Project details
Release history Release notifications | RSS feed
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 theow-0.0.2.tar.gz.
File metadata
- Download URL: theow-0.0.2.tar.gz
- Upload date:
- Size: 137.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e1e6ec4fcecf302de3e8681e13d5dec23df8cd1ce7186f66ed7bc37ff9f904a0
|
|
| MD5 |
8d09d57882e3a8b0fa5bc20ebdd113de
|
|
| BLAKE2b-256 |
8ace2adaca1eaf4ba0acc7bd13df547bc4637c759273a3f45b47a9515bb2722e
|
Provenance
The following attestation bundles were made for theow-0.0.2.tar.gz:
Publisher:
release.yml on adhityaravi/theow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
theow-0.0.2.tar.gz -
Subject digest:
e1e6ec4fcecf302de3e8681e13d5dec23df8cd1ce7186f66ed7bc37ff9f904a0 - Sigstore transparency entry: 943884840
- Sigstore integration time:
-
Permalink:
adhityaravi/theow@225721ad278a5f02c754489e794e37919b752d4c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/adhityaravi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@225721ad278a5f02c754489e794e37919b752d4c -
Trigger Event:
push
-
Statement type:
File details
Details for the file theow-0.0.2-py3-none-any.whl.
File metadata
- Download URL: theow-0.0.2-py3-none-any.whl
- Upload date:
- Size: 46.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
faaa35e9461151e500b7e70862fb11eb19f502f7a60a123d968771bfe13d64ec
|
|
| MD5 |
16beb94a72ed69c406d520ea237167a8
|
|
| BLAKE2b-256 |
0317da1f769aef8b1d34a8ac395458e8a6e3555ddef2ac3a188aada8b79469b9
|
Provenance
The following attestation bundles were made for theow-0.0.2-py3-none-any.whl:
Publisher:
release.yml on adhityaravi/theow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
theow-0.0.2-py3-none-any.whl -
Subject digest:
faaa35e9461151e500b7e70862fb11eb19f502f7a60a123d968771bfe13d64ec - Sigstore transparency entry: 943884844
- Sigstore integration time:
-
Permalink:
adhityaravi/theow@225721ad278a5f02c754489e794e37919b752d4c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/adhityaravi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@225721ad278a5f02c754489e794e37919b752d4c -
Trigger Event:
push
-
Statement type: