Adapt Experta¶
Adapt Experta — mvp.adapt_experta
Cluster: Agents & LLM | Type: component | MCP Tools: 44
Overview¶
Forward-chaining expert system block built on the Experta library (a Python CLIPS port) with a pure-Python fallback that runs without any optional dependency. Maintains a persistent working memory of typed facts across calls and fires production rules in priority order until no more rules match. Supports crisp production rules and Prolog-style clause rules via a Prolog delegate, making it suitable for both symbolic reasoning and logic-program-style inference.
When to use:
- Encoding domain knowledge as IF-THEN production rules for automated reasoning
- Running forward-chaining inference over a fact base assembled by upstream agent steps
- Querying the current working memory for all facts of a given type
- Combining rule-based expert reasoning with LLM-extracted facts in a hybrid pipeline
Launch-readiness caveat: adapt_experta is most useful when the user already has a structured fact schema and explicit rules, or when an upstream template/agent converts domain language into those structures. It provides real value for deterministic diagnostics, compliance checks, configuration decisions, and auditable fallback logic, but it is not yet a standalone non-developer rule-authoring experience. For first-user onboarding, pair it with guided templates, examples, or natural-language fact extraction so a domain specialist does not have to design __type__ facts and rule objects from scratch.
Backend honesty: when the preferred Experta backend is unavailable or fails during execution, results use the pure-Python fallback but are marked degraded with backend metadata. MCP also exposes backend_capabilities and audited, allowlisted backend_native_read; native backend mutation remains disabled by default.
Example:
from mvp.adapt_experta import AdaptExpertaBlock, ExpertaInput, Rule
block = AdaptExpertaBlock(name="experta")
# Add rules then run inference
block.infer(ExpertaInput(operation="add_rules", rules=[
Rule(name="fever_rule", conditions=["Temperature"], actions=["Fever"], priority=10),
]))
block.infer(ExpertaInput(operation="add_facts", facts=[{"__type__": "Temperature", "value": 39.5}]))
result = block.infer(ExpertaInput(operation="run"))
# result.value.rules_fired → ["fever_rule"]; result.value.inferred_facts contains Fever fact
Works well with: adapt_eurisko, formal_methods, goal_engine
Public API¶
ExpertaFactRecommendation¶
Validated natural-language -> facts recommendation decision record.
| Field | Type | Default |
|---|---|---|
facts | tuple[dict, ...] | required |
source | str | required |
domain | str | '' |
rationale | str | '' |
notes | tuple[str, ...] | () |
Methods:
fact_count() -> int¶
type_count() -> int¶
to_metadata() -> dict[str, Any]¶
ExpertaDecisionError(ValueError)¶
The LLM did not produce a usable, validated fact recommendation.
LLMExpertaRuntime¶
Provider-neutral natural-language -> facts recommendation runtime over G6's
Constructor:
| Parameter | Type | Default |
|---|---|---|
llm | LLMCaller \| None | None |
Methods:
synthesize(text: str, domain: str = '') -> Result[list[dict]]¶
ExpertaFactPlanner¶
Runtime-first facade with the deterministic floor as honest fallback.
Constructor:
| Parameter | Type | Default |
|---|---|---|
runtime | ExpertaRuntime \| None | None |
Methods:
recommend_facts(text: str, domain: str = '') -> ExpertaFactRecommendation¶
AuditRecord¶
| Field | Type | Default |
|---|---|---|
id | str | required |
run_id | str | required |
step_type | str | required |
timestamp | str | required |
llm_model | str | required |
prompt_text | str | required |
response_text | str | required |
latency_ms | float | required |
token_in | int | required |
token_out | int | required |
cf_value | float | required |
metadata_json | str | required |
AuditLogger¶
Synchronous SQLite audit logger.
Constructor:
| Parameter | Type | Default |
|---|---|---|
conn | sqlite3.Connection | required |
Methods:
log(step_type: str, run_id: str, llm_model: str = '', prompt_text: str = '', response_text: str = '', latency_ms: float = 0.0, token_in: int = 0, token_out: int = 0, cf_value: float = 0.0, metadata: dict[str, Any] | None = None) -> str¶
Write one audit record. Returns the record id. Raises on write failure.
get_run_audit(run_id: str) -> list[AuditRecord]¶
Return all audit records for a run_id, ordered by timestamp ascending.
list_runs(limit: int = 50) -> list[dict[str, Any]]¶
Return summary rows: run_id, started, ended, step_count.
AdaptExpertaBlock(AIBlock[ExpertaInput, ExpertaOutput, dict])¶
Forward-chaining expert system block.
| Field | Type | Default |
|---|---|---|
name | str | 'adapt_experta' |
resource_bounds | ResourceBounds \| None | None |
Methods:
infer(data: ExpertaInput) -> Result[ExpertaOutput]¶
Rule(BaseModel)¶
A production rule in the expert system.
| Field | Type | Default |
|---|---|---|
name | str | required |
conditions | list[str] | required |
actions | list[str] | required |
priority | int | 0 |
type | Literal['crisp', 'prolog'] | 'crisp' |
clause | str | '' |
ExpertaInput(BaseModel)¶
Input to AdaptExpertaBlock.
| Field | Type | Default |
|---|---|---|
operation | Literal['add_facts', 'add_rules', 'run', 'query', 'reset'] | 'run' |
facts | list[dict[str, object]] | Field(default_factory=list) |
rules | list[Rule] | Field(default_factory=list) |
query_type | str | '' |
max_cycles | int | 20 |
ExpertaOutput(BaseModel)¶
Output from AdaptExpertaBlock.
| Field | Type | Default |
|---|---|---|
inferred_facts | list[dict[str, object]] | required |
rules_fired | list[str] | required |
n_cycles | int | required |
query_results | list[dict[str, object]] | required |
n_facts | int | required |
backend | str | required |
degraded | bool | False |
degradation_reason | str | '' |
capability_status | str | 'available' |
capability_missing | list[str] | Field(default_factory=list) |
backend_metadata | dict[str, object] | Field(default_factory=dict) |
AdaptExpertaMCPBlock(AIBlock[MCPExpertaInput, MCPExpertaOutput, dict])¶
44-op expert system block with SQLite persistence.
| Field | Type | Default |
|---|---|---|
name | str | 'adapt_experta_mcp' |
state | dict \| None | None |
db_path | str | ':memory:' |
resource_bounds | ResourceBounds \| None | None |
Methods:
infer(data: MCPExpertaInput) -> Result[MCPExpertaOutput]¶
MCPExpertaInput(BaseModel)¶
| Field | Type | Default |
|---|---|---|
op | Literal['run', 'add_facts', 'add_rules', 'query', 'reset', 'save_session', 'load_session', 'list_sessions', 'delete_session', 'store_ruleset', 'retrieve_ruleset', 'list_rulesets', 'update_cf', 'get_cf', 'resolve_goal', 'record_run', 'query_runs', 'summarize_runs', 'explain', 'why', 'how', 'submit_job', 'get_job', 'search', 'info', 'extract_facts', 'run_semantic', 'add_prolog_rule', 'get_run_audit', 'list_audit_runs', 'list_approval_queue', 'approve_rule', 'reject_rule', 'get_conflicts', 'prolog_query', 'record_outcome', 'record_goal_outcome', 'get_rule_performance', 'list_weak_rules', 'recommend_facts', 'list_patterns', 'backend_capabilities', 'backend_native_read', 'backend_native_mutate'] | required |
facts | list[dict] | Field(default_factory=list) |
rules | list[dict] | Field(default_factory=list) |
query_type | str | '' |
max_cycles | int | 20 |
resolution_strategy | str | 'log_only' |
name | str | '' |
notes | str | '' |
rules_json | str | '' |
description | str | '' |
domain | str | '' |
tags_csv | str | '' |
fact_type | str | '' |
rule_name | str | '' |
cf_value | float | 1.0 |
goal_type | str | '' |
max_depth | int | 5 |
session_name | str | '' |
rules_fired | list[str] | Field(default_factory=list) |
n_cycles | int | 0 |
n_facts | int | 0 |
limit | int | 50 |
params_json | str | '' |
priority | int | 0 |
job_id | str | '' |
query | str | '' |
top_k | int | 5 |
native_operation | str | '' |
requested_backend | str | 'active' |
cursor | str | '' |
text | str | '' |
domain_hint | str | '' |
semantic_threshold | float | 0.5 |
run_id | str | '' |
candidate_id | str | '' |
reviewer_note | str | '' |
found | bool | False |
min_fires | int | 10 |
max_accuracy | float | 0.2 |
production_mode | bool | False |
ruleset_spec_json | str | '' |
ruleset_name | str | '' |
MCPExpertaOutput(BaseModel)¶
| Field | Type | Default |
|---|---|---|
op | str | '' |
key | str | '' |
value | Any | None |
found | bool | False |
count | int | 0 |
records | list[MCPExpertaRecord] | Field(default_factory=list) |
retrieved | list[Any] | Field(default_factory=list) |
scores | list[float] | Field(default_factory=list) |
summary | str | '' |
message | str | '' |
facts | list[dict] | Field(default_factory=list) |
rules_fired | list[str] | Field(default_factory=list) |
n_cycles | int | 0 |
n_facts | int | 0 |
metadata | dict[str, Any] | Field(default_factory=dict) |
degraded | bool | False |
degradation_reason | str | '' |
capability_status | str | 'ok' |
capability_missing | list[str] | Field(default_factory=list) |
ruleset_spec_missing | list[str] | Field(default_factory=list) |
ExpertaStore¶
Sync SQLite store with 7 tables.
Constructor:
| Parameter | Type | Default |
|---|---|---|
db_path | str | ':memory:' |
Methods:
close() -> None¶
save_session(name: str, facts_json: str, rules_json: str, notes: str) -> str¶
load_session(name: str) -> dict[str, Any] | None¶
list_sessions() -> list[dict[str, Any]]¶
delete_session(name: str) -> bool¶
save_ruleset(name: str, rules_json: str, description: str, domain: str, tags: list[str]) -> str¶
load_ruleset(name: str) -> dict[str, Any] | None¶
increment_ruleset_use(name: str) -> None¶
save_ruleset_spec(ruleset_id: str, name: str, spec_json: str) -> None¶
Persist a RulesetSpec attestation alongside a ruleset.
load_ruleset_spec(name: str) -> str¶
Return the RulesetSpec JSON for a ruleset name, or '' if none.
list_rulesets(domain: str = '') -> list[dict[str, Any]]¶
add_run(session_name: str, rules_fired: list[str], n_cycles: int, n_facts: int, notes: str, tags: list[str]) -> str¶
query_runs(session_name: str = '', limit: int = 50) -> list[dict[str, Any]]¶
summarize_runs(session_name: str = '') -> dict[str, Any]¶
get_cf(fact_type: str) -> float | None¶
add_cf(fact_type: str, rule_name: str, cf_value: float, combined_cf: float) -> str¶
add_job(op: str, params_json: str, priority: int) -> str¶
get_job(job_id: str) -> dict[str, Any] | None¶
update_job(job_id: str, status: str, result_json: str) -> None¶
text_search(query: str, top_k: int = 5) -> list[dict[str, Any]]¶
count_all() -> dict[str, int]¶
add_approval_candidate(run_id: str, rule_json: str, cf_score: float, proposer: str = '') -> str¶
list_approval_queue(status: str = 'pending') -> list[dict]¶
get_approval_candidate(candidate_id: str) -> dict | None¶
Return a single approval-queue row by id (any status), or None.
update_approval_status(candidate_id: str, status: str, reviewer_note: str = '', reviewer_id: str = '') -> None¶
get_pending_count() -> int¶
add_durable_fact(fact_json: str, cf_value: float, run_id: str) -> str¶
list_durable_facts() -> list[dict]¶
get_rule_performance(rule_name: str) -> dict | None¶
upsert_rule_performance(rule_name: str, fires_delta: int = 0, positives_delta: int = 0, negatives_delta: int = 0, cf_adjustment: float | None = None, status: str | None = None) -> None¶
list_weak_rules(min_fires: int = 10, max_accuracy: float = 0.2) -> list[dict]¶
Functions¶
agentic_planner_enabled(default_enabled: bool = True) -> bool¶
Decide whether the agentic experta fact-planner should be used.
validate_fact_recommendation(facts: list[dict]) -> list[dict]¶
Reject any fact recommendation outside the canonical shape / bounds.
recommend_facts_floor(text: str, domain: str = '') -> list[dict]¶
Deterministic, deliberately-brittle NL->facts selector (the honest floor).
MCP Tools¶
| Operation | Source |
|---|---|
run | experta_mcp |
add_facts | experta_mcp |
add_rules | experta_mcp |
query | experta_mcp |
reset | experta_mcp |
save_session | experta_mcp |
load_session | experta_mcp |
list_sessions | experta_mcp |
delete_session | experta_mcp |
store_ruleset | experta_mcp |
retrieve_ruleset | experta_mcp |
list_rulesets | experta_mcp |
update_cf | experta_mcp |
get_cf | experta_mcp |
resolve_goal | experta_mcp |
record_run | experta_mcp |
query_runs | experta_mcp |
summarize_runs | experta_mcp |
explain | experta_mcp |
why | experta_mcp |
how | experta_mcp |
submit_job | experta_mcp |
get_job | experta_mcp |
search | experta_mcp |
info | experta_mcp |
extract_facts | experta_mcp |
run_semantic | experta_mcp |
add_prolog_rule | experta_mcp |
get_run_audit | experta_mcp |
list_audit_runs | experta_mcp |
list_approval_queue | experta_mcp |
approve_rule | experta_mcp |
reject_rule | experta_mcp |
get_conflicts | experta_mcp |
prolog_query | experta_mcp |
record_outcome | experta_mcp |
record_goal_outcome | experta_mcp |
get_rule_performance | experta_mcp |
list_weak_rules | experta_mcp |
recommend_facts | experta_mcp |
list_patterns | experta_mcp |
backend_capabilities | experta_mcp |
backend_native_read | experta_mcp |
backend_native_mutate | experta_mcp |