algo-cli-runtime 0.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. algo_cli/__init__.py +3 -0
  2. algo_cli/__main__.py +7 -0
  3. algo_cli/_internal/__init__.py +12 -0
  4. algo_cli/_internal/policy_chain.py +259 -0
  5. algo_cli/action_registry.py +1047 -0
  6. algo_cli/agent_blocks.py +550 -0
  7. algo_cli/agent_pipeline.py +1457 -0
  8. algo_cli/agent_threads.py +308 -0
  9. algo_cli/animations.py +316 -0
  10. algo_cli/cache_admission.py +209 -0
  11. algo_cli/capability_mask.py +66 -0
  12. algo_cli/chat_protocol.py +116 -0
  13. algo_cli/chatgpt_auth.py +510 -0
  14. algo_cli/chatgpt_client.py +657 -0
  15. algo_cli/code_rag.py +479 -0
  16. algo_cli/config.py +651 -0
  17. algo_cli/context_budget.py +679 -0
  18. algo_cli/credential_helpers.py +315 -0
  19. algo_cli/deliberation.py +29 -0
  20. algo_cli/display.py +1470 -0
  21. algo_cli/evals/__init__.py +21 -0
  22. algo_cli/evals/algorithm_effectiveness.py +560 -0
  23. algo_cli/evals/competitive_harness_rating.py +702 -0
  24. algo_cli/evals/cot_quality.py +220 -0
  25. algo_cli/evals/harness_retrieval_benchmark.py +401 -0
  26. algo_cli/evals/performance_regression.py +136 -0
  27. algo_cli/evals/scorecard_grading.py +308 -0
  28. algo_cli/evals/session_distribution.py +84 -0
  29. algo_cli/execution_guardrails.py +806 -0
  30. algo_cli/extensions_manifest.py +84 -0
  31. algo_cli/git_evidence.py +227 -0
  32. algo_cli/google_workspace.py +407 -0
  33. algo_cli/google_workspace_auth.py +523 -0
  34. algo_cli/harness.py +2587 -0
  35. algo_cli/identity.py +557 -0
  36. algo_cli/index_compute_lab.py +228 -0
  37. algo_cli/inference_harness.py +70 -0
  38. algo_cli/intelligence/__init__.py +1103 -0
  39. algo_cli/intelligence/acrobat_config.py +307 -0
  40. algo_cli/intelligence/acrobat_manifests.py +338 -0
  41. algo_cli/intelligence/acrobat_models.py +195 -0
  42. algo_cli/intelligence/acrobat_pipeline.py +295 -0
  43. algo_cli/intelligence/acrobat_runtime.py +302 -0
  44. algo_cli/intelligence/acrobat_security.py +261 -0
  45. algo_cli/intelligence/acrobat_workflows.py +226 -0
  46. algo_cli/intelligence/actionability.py +165 -0
  47. algo_cli/intelligence/adversarial_audit.py +136 -0
  48. algo_cli/intelligence/agent_arena.py +92 -0
  49. algo_cli/intelligence/agent_benchmark.py +236 -0
  50. algo_cli/intelligence/agent_runtime.py +171 -0
  51. algo_cli/intelligence/agents_as_tools.py +70 -0
  52. algo_cli/intelligence/artifact_binding.py +80 -0
  53. algo_cli/intelligence/autonomous_engineer.py +1976 -0
  54. algo_cli/intelligence/backpressure.py +99 -0
  55. algo_cli/intelligence/bloom_filter.py +186 -0
  56. algo_cli/intelligence/bonferroni.py +66 -0
  57. algo_cli/intelligence/boundary_compaction.py +98 -0
  58. algo_cli/intelligence/catalog_verifier.py +172 -0
  59. algo_cli/intelligence/cavecrew.py +118 -0
  60. algo_cli/intelligence/changelog.py +176 -0
  61. algo_cli/intelligence/checkpoint_resume.py +92 -0
  62. algo_cli/intelligence/circuit_breaker.py +88 -0
  63. algo_cli/intelligence/clarification_gate.py +101 -0
  64. algo_cli/intelligence/code_graph.py +180 -0
  65. algo_cli/intelligence/coderank.py +97 -0
  66. algo_cli/intelligence/consistent_hash.py +150 -0
  67. algo_cli/intelligence/consortium_synthesis.py +139 -0
  68. algo_cli/intelligence/construction/__init__.py +241 -0
  69. algo_cli/intelligence/construction/common.py +273 -0
  70. algo_cli/intelligence/construction/documents.py +496 -0
  71. algo_cli/intelligence/construction/labor_units.py +1395 -0
  72. algo_cli/intelligence/construction/payments.py +470 -0
  73. algo_cli/intelligence/construction/risk.py +784 -0
  74. algo_cli/intelligence/content_extractor.py +132 -0
  75. algo_cli/intelligence/context_adaptive.py +102 -0
  76. algo_cli/intelligence/context_ops.py +95 -0
  77. algo_cli/intelligence/count_min.py +145 -0
  78. algo_cli/intelligence/cow_state.py +103 -0
  79. algo_cli/intelligence/critic_loop.py +119 -0
  80. algo_cli/intelligence/cross_source.py +113 -0
  81. algo_cli/intelligence/daemon_mode.py +99 -0
  82. algo_cli/intelligence/dag_orchestration.py +151 -0
  83. algo_cli/intelligence/deep_research.py +155 -0
  84. algo_cli/intelligence/degenerate_detector.py +78 -0
  85. algo_cli/intelligence/delta_report.py +92 -0
  86. algo_cli/intelligence/discovery_event_log.py +92 -0
  87. algo_cli/intelligence/document_ingest.py +298 -0
  88. algo_cli/intelligence/dual_layer_validate.py +151 -0
  89. algo_cli/intelligence/echo_fidelity.py +73 -0
  90. algo_cli/intelligence/ema_tuning.py +104 -0
  91. algo_cli/intelligence/event_log.py +92 -0
  92. algo_cli/intelligence/evidence_graph.py +114 -0
  93. algo_cli/intelligence/extension_host.py +162 -0
  94. algo_cli/intelligence/extension_manifest.py +115 -0
  95. algo_cli/intelligence/falsification_suite.py +178 -0
  96. algo_cli/intelligence/finance/__init__.py +169 -0
  97. algo_cli/intelligence/finance/anomalies.py +135 -0
  98. algo_cli/intelligence/finance/ap_ar.py +351 -0
  99. algo_cli/intelligence/finance/cash.py +162 -0
  100. algo_cli/intelligence/finance/close.py +332 -0
  101. algo_cli/intelligence/finance/common.py +244 -0
  102. algo_cli/intelligence/finance/construction.py +135 -0
  103. algo_cli/intelligence/finance/controls.py +172 -0
  104. algo_cli/intelligence/finance/evidence.py +119 -0
  105. algo_cli/intelligence/finance/exceptions.py +157 -0
  106. algo_cli/intelligence/finance/reconciliations.py +254 -0
  107. algo_cli/intelligence/finance/revenue.py +109 -0
  108. algo_cli/intelligence/finance/tax.py +74 -0
  109. algo_cli/intelligence/finance/workpapers.py +111 -0
  110. algo_cli/intelligence/finding_record.py +120 -0
  111. algo_cli/intelligence/flow_dag.py +267 -0
  112. algo_cli/intelligence/gatherer.py +223 -0
  113. algo_cli/intelligence/golden_master.py +98 -0
  114. algo_cli/intelligence/graph_rag.py +195 -0
  115. algo_cli/intelligence/group_chat.py +143 -0
  116. algo_cli/intelligence/hash_dedup.py +145 -0
  117. algo_cli/intelligence/hyperloglog.py +128 -0
  118. algo_cli/intelligence/incremental_index.py +316 -0
  119. algo_cli/intelligence/index_store.py +16 -0
  120. algo_cli/intelligence/iteration_plan.py +133 -0
  121. algo_cli/intelligence/kernel_plugins.py +167 -0
  122. algo_cli/intelligence/lesson_catalog.py +135 -0
  123. algo_cli/intelligence/llm_fallback.py +169 -0
  124. algo_cli/intelligence/log2_histogram.py +267 -0
  125. algo_cli/intelligence/lsp_integration.py +147 -0
  126. algo_cli/intelligence/memory_evolution.py +117 -0
  127. algo_cli/intelligence/minhash_lsh.py +182 -0
  128. algo_cli/intelligence/multi_model_score.py +174 -0
  129. algo_cli/intelligence/multi_tier_grade.py +211 -0
  130. algo_cli/intelligence/negative_controls.py +113 -0
  131. algo_cli/intelligence/numeric_clamp.py +63 -0
  132. algo_cli/intelligence/occ_editor.py +66 -0
  133. algo_cli/intelligence/output_normalize.py +112 -0
  134. algo_cli/intelligence/parallel_delegation.py +98 -0
  135. algo_cli/intelligence/parallel_fanout.py +104 -0
  136. algo_cli/intelligence/permission_modes.py +105 -0
  137. algo_cli/intelligence/pre_push_gate.py +68 -0
  138. algo_cli/intelligence/prefetch.py +171 -0
  139. algo_cli/intelligence/process_framework.py +217 -0
  140. algo_cli/intelligence/project_graph.py +387 -0
  141. algo_cli/intelligence/query_expansion.py +146 -0
  142. algo_cli/intelligence/ralph_loop.py +117 -0
  143. algo_cli/intelligence/rate_limiter.py +153 -0
  144. algo_cli/intelligence/refactor_transaction.py +94 -0
  145. algo_cli/intelligence/research_workspace.py +108 -0
  146. algo_cli/intelligence/retraction_ledger.py +72 -0
  147. algo_cli/intelligence/saga_pattern.py +88 -0
  148. algo_cli/intelligence/session_fork.py +100 -0
  149. algo_cli/intelligence/shadow_editor.py +67 -0
  150. algo_cli/intelligence/shell_session.py +213 -0
  151. algo_cli/intelligence/source_registry.py +143 -0
  152. algo_cli/intelligence/spawn_scales.py +99 -0
  153. algo_cli/intelligence/stat_stability.py +104 -0
  154. algo_cli/intelligence/structural_validator.py +148 -0
  155. algo_cli/intelligence/subagent_spawner.py +111 -0
  156. algo_cli/intelligence/symmetric_verify.py +70 -0
  157. algo_cli/intelligence/task_classifier.py +129 -0
  158. algo_cli/intelligence/team_execution.py +122 -0
  159. algo_cli/intelligence/tiered_access.py +121 -0
  160. algo_cli/intelligence/utility_registry.py +159 -0
  161. algo_cli/intuition_engine.py +560 -0
  162. algo_cli/intuition_injector.py +82 -0
  163. algo_cli/kernels/__init__.py +5 -0
  164. algo_cli/kernels/manifest.py +763 -0
  165. algo_cli/main.py +3903 -0
  166. algo_cli/memory_candidates.py +541 -0
  167. algo_cli/memory_echo_veil.py +394 -0
  168. algo_cli/memory_runtime.py +112 -0
  169. algo_cli/model_info.py +548 -0
  170. algo_cli/model_profile.py +160 -0
  171. algo_cli/model_routing.py +74 -0
  172. algo_cli/oneshot.py +331 -0
  173. algo_cli/perf_telemetry.py +389 -0
  174. algo_cli/plugins.py +245 -0
  175. algo_cli/private_event_store.py +654 -0
  176. algo_cli/quantization/__init__.py +24 -0
  177. algo_cli/quantization/lloyd_max.py +98 -0
  178. algo_cli/quantization/turbo_quant.py +308 -0
  179. algo_cli/reasoning/__init__.py +46 -0
  180. algo_cli/reasoning/combinatorial.py +356 -0
  181. algo_cli/reasoning/graph_of_thought.py +297 -0
  182. algo_cli/reasoning/mcts.py +220 -0
  183. algo_cli/reasoning/neuro_symbolic.py +250 -0
  184. algo_cli/reasoning/react.py +246 -0
  185. algo_cli/reasoning/reflexion.py +225 -0
  186. algo_cli/reasoning/tree_of_thought.py +241 -0
  187. algo_cli/reasoning_bridge.py +150 -0
  188. algo_cli/reconciliation.py +284 -0
  189. algo_cli/reflex.py +385 -0
  190. algo_cli/resources/docs/ALGO.md +13958 -0
  191. algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
  192. algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
  193. algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
  194. algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
  195. algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
  196. algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
  197. algo_cli/resources/docs/main-split-map.md +35 -0
  198. algo_cli/resources/docs/privacy-and-context.md +48 -0
  199. algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
  200. algo_cli/resources/skills/README.md +26 -0
  201. algo_cli/resources/skills/algo-cli.md +59 -0
  202. algo_cli/resources/skills/edit-file-precision.md +49 -0
  203. algo_cli/resources/skills/harness-search-first.md +47 -0
  204. algo_cli/resources/skills/memory-recall-ritual.md +51 -0
  205. algo_cli/resources/skills/qol-algorithms.md +224 -0
  206. algo_cli/resources/skills/smart-error-recovery.md +56 -0
  207. algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
  208. algo_cli/retrieval_algorithms.py +127 -0
  209. algo_cli/runtime_qos.py +236 -0
  210. algo_cli/runtime_services.py +320 -0
  211. algo_cli/session_commands.py +95 -0
  212. algo_cli/session_mode.py +113 -0
  213. algo_cli/skills.py +430 -0
  214. algo_cli/slash_dispatch.py +1265 -0
  215. algo_cli/small_context.py +206 -0
  216. algo_cli/spawn_budget.py +89 -0
  217. algo_cli/task_ledger.py +84 -0
  218. algo_cli/task_router.py +197 -0
  219. algo_cli/tool_context.py +94 -0
  220. algo_cli/tool_contract.py +99 -0
  221. algo_cli/tool_policy.py +357 -0
  222. algo_cli/tool_runtime.py +647 -0
  223. algo_cli/tools.py +3056 -0
  224. algo_cli/url_scheme.py +174 -0
  225. algo_cli/verify.py +154 -0
  226. algo_cli/version_manifest.py +178 -0
  227. algo_cli/vision_screenshot_verify.py +76 -0
  228. algo_cli/workspace_resolver.py +68 -0
  229. algo_cli/x_account.py +209 -0
  230. algo_cli/xai_auth.py +374 -0
  231. algo_cli/xai_client.py +600 -0
  232. algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
  233. algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
  234. algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
  235. algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
  236. algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
  237. ollama_cli/__init__.py +67 -0
@@ -0,0 +1,220 @@
1
+ """Monte Carlo Tree Search (MCTS) for Reasoning.
2
+
3
+ Implements UCT (Upper Confidence bounds applied to Trees) for reasoning:
4
+ - Selection: Pick the most promising node using UCB1
5
+ - Expansion: Generate child thoughts from the selected node
6
+ - Simulation: Roll out to estimate value (lightweight or via LLM)
7
+ - Backpropagation: Update statistics up the tree
8
+
9
+ Effective for deep reasoning tasks where exploration-exploitation
10
+ tradeoffs matter. Integrates with ToT for structured tree search.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import math
17
+ from dataclasses import dataclass, field
18
+ from typing import Any
19
+
20
+ from ..chat_protocol import get_attr
21
+
22
+
23
+ @dataclass
24
+ class MCTSNode:
25
+ """A node in the MCTS search tree."""
26
+ thought: str
27
+ state: str # Accumulated reasoning state
28
+ parent: "MCTSNode | None" = field(default=None, repr=False)
29
+ children: list["MCTSNode"] = field(default_factory=list)
30
+ visits: int = 0
31
+ total_value: float = 0.0
32
+ depth: int = 0
33
+ fully_expanded: bool = False
34
+ terminal: bool = False
35
+
36
+ @property
37
+ def avg_value(self) -> float:
38
+ return self.total_value / max(1, self.visits)
39
+
40
+ @property
41
+ def ucb1(self) -> float:
42
+ """UCT score with exploration constant sqrt(2)."""
43
+ if self.visits == 0:
44
+ return float("inf")
45
+ exploitation = self.avg_value
46
+ exploration = math.sqrt(2.0 * math.log(max(1, self.parent.visits if self.parent else 1)) / self.visits)
47
+ return exploitation + exploration
48
+
49
+ def best_child(self) -> "MCTSNode | None":
50
+ """Select the child with the highest UCB1 score."""
51
+ if not self.children:
52
+ return None
53
+ return max(self.children, key=lambda c: c.ucb1)
54
+
55
+ def best_leaf(self) -> "MCTSNode":
56
+ """Find the most-visited leaf (most robust conclusion)."""
57
+ if not self.children:
58
+ return self
59
+ return max(self.children, key=lambda c: c.visits).best_leaf()
60
+
61
+ def reasoning_chain(self) -> str:
62
+ """Human-readable chain from root to this node."""
63
+ chain = []
64
+ node: MCTSNode | None = self
65
+ while node is not None:
66
+ chain.append(node)
67
+ node = node.parent
68
+ chain.reverse()
69
+ return "\n".join(
70
+ f"Step {i+1} (visits={n.visits}, val={n.avg_value:.2f}): {n.thought}"
71
+ for i, n in enumerate(chain)
72
+ )
73
+
74
+
75
+ @dataclass
76
+ class MCTSReasoner:
77
+ """MCTS-based reasoning engine."""
78
+ max_iterations: int = 50
79
+ max_depth: int = 6
80
+ branch_factor: int = 3
81
+ rollout_depth: int = 2
82
+ exploration_constant: float = math.sqrt(2.0)
83
+
84
+ root: MCTSNode | None = None
85
+ nodes_created: int = 0
86
+
87
+ def _select(self) -> MCTSNode:
88
+ """Selection: traverse tree following UCB1 to find a node to expand."""
89
+ node = self.root
90
+ while node and node.children and not node.terminal:
91
+ if not node.fully_expanded:
92
+ return node
93
+ selected = node.best_child()
94
+ if selected is None:
95
+ return node
96
+ node = selected
97
+ return node
98
+
99
+ def _expand(self, node: MCTSNode, client: Any, model: str) -> MCTSNode | None:
100
+ """Expansion: generate a new child thought from the selected node."""
101
+ prompt = (
102
+ f"Current reasoning state:\n{node.state[:1500]}\n\n"
103
+ f"Generate one next reasoning step (different from existing children if any).\n"
104
+ f"Existing steps: {[c.thought[:80] for c in node.children]}\n"
105
+ f"Respond with just the reasoning step and updated state as JSON:\n"
106
+ f'{{"thought": "...", "state": "..."}}'
107
+ )
108
+ try:
109
+ response = client.chat(
110
+ model=model,
111
+ messages=[
112
+ {"role": "system", "content": "You are a reasoning step generator."},
113
+ {"role": "user", "content": prompt},
114
+ ],
115
+ stream=False,
116
+ format="json",
117
+ )
118
+ text = get_attr(get_attr(response, "message", {}), "content", "")
119
+ data = json.loads(text)
120
+ thought = str(data.get("thought", ""))
121
+ state = str(data.get("state", node.state))
122
+ except (json.JSONDecodeError, ValueError, TypeError):
123
+ thought = f"(expansion at depth {node.depth + 1})"
124
+ state = node.state
125
+
126
+ child = MCTSNode(
127
+ thought=thought, state=state, parent=node,
128
+ depth=node.depth + 1,
129
+ )
130
+ node.children.append(child)
131
+ self.nodes_created += 1
132
+
133
+ if len(node.children) >= self.branch_factor:
134
+ node.fully_expanded = True
135
+
136
+ if node.depth + 1 >= self.max_depth:
137
+ child.terminal = True
138
+
139
+ return child
140
+
141
+ def _simulate(self, node: MCTSNode, client: Any, model: str) -> float:
142
+ """Simulation: quick rollout to estimate the node's value."""
143
+ prompt = (
144
+ f"Given this reasoning chain, rate the quality of the conclusion (0.0-1.0):\n"
145
+ f"{node.state[:1000]}\n\nRespond with just a number."
146
+ )
147
+ try:
148
+ response = client.chat(
149
+ model=model,
150
+ messages=[
151
+ {"role": "system", "content": "You are a reasoning quality evaluator."},
152
+ {"role": "user", "content": prompt},
153
+ ],
154
+ stream=False,
155
+ )
156
+ text = get_attr(get_attr(response, "message", {}), "content", "").strip()
157
+ import re
158
+ m = re.search(r"([0-9]*\.?[0-9]+)", text)
159
+ if m:
160
+ return max(0.0, min(1.0, float(m.group(1))))
161
+ except Exception:
162
+ pass
163
+ return 0.5
164
+
165
+ def _backpropagate(self, node: MCTSNode, value: float) -> None:
166
+ """Backpropagation: update statistics from leaf to root."""
167
+ current: MCTSNode | None = node
168
+ while current is not None:
169
+ current.visits += 1
170
+ current.total_value += value
171
+ current = current.parent
172
+
173
+
174
+ def run_mcts(
175
+ *,
176
+ task: str,
177
+ client: Any,
178
+ model: str,
179
+ max_iterations: int = 50,
180
+ max_depth: int = 6,
181
+ branch_factor: int = 3,
182
+ ) -> MCTSReasoner:
183
+ """Run MCTS reasoning on a task.
184
+
185
+ Args:
186
+ task: The reasoning task.
187
+ client: Ollama client.
188
+ model: Model name for evaluation and generation.
189
+ max_iterations: Total MCTS iterations (select + expand + simulate + backprop).
190
+ max_depth: Maximum tree depth.
191
+ branch_factor: Max children per node.
192
+
193
+ Returns:
194
+ MCTSReasoner with root tree and best_leaf set.
195
+ """
196
+ reasoner = MCTSReasoner(
197
+ max_iterations=max_iterations,
198
+ max_depth=max_depth,
199
+ branch_factor=branch_factor,
200
+ )
201
+ reasoner.root = MCTSNode(thought="(root)", state=task, visits=1)
202
+
203
+ for _ in range(max_iterations):
204
+ # 1. Select
205
+ node = reasoner._select()
206
+ if node is None or node.terminal:
207
+ break
208
+
209
+ # 2. Expand
210
+ child = reasoner._expand(node, client, model)
211
+ if child is None:
212
+ break
213
+
214
+ # 3. Simulate
215
+ value = reasoner._simulate(child, client, model)
216
+
217
+ # 4. Backpropagate
218
+ reasoner._backpropagate(child, value)
219
+
220
+ return reasoner
@@ -0,0 +1,250 @@
1
+ """Neuro-Symbolic Verifier Loop.
2
+
3
+ Implements a guess-and-check pattern:
4
+ 1. LLM proposes a solution (guess)
5
+ 2. Symbolic/structured verifier checks correctness (check)
6
+ 3. If verification fails, feed back the error and retry
7
+
8
+ Verifiers include:
9
+ - Python AST/expression evaluation for math/logic
10
+ - JSON schema validation for structured outputs
11
+ - Regex/grammar checks for format compliance
12
+ - Unit-test-like assertions for code
13
+
14
+ This dramatically reduces hallucination for formal domains.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import ast
20
+ import json
21
+ import re
22
+ from dataclasses import dataclass, field
23
+ from typing import Any, Callable
24
+
25
+ from ..chat_protocol import get_attr
26
+
27
+
28
+ @dataclass
29
+ class VerificationResult:
30
+ """Result of verifying an LLM output."""
31
+ passed: bool
32
+ checks_total: int
33
+ checks_passed: int
34
+ failures: list[str] = field(default_factory=list)
35
+ suggestions: list[str] = field(default_factory=list)
36
+
37
+ @property
38
+ def score(self) -> float:
39
+ if self.checks_total == 0:
40
+ return 0.0
41
+ return self.checks_passed / self.checks_total
42
+
43
+ def feedback_message(self) -> str:
44
+ if self.passed:
45
+ return "All verification checks passed."
46
+ lines = [f"Verification failed: {self.checks_passed}/{self.checks_total} checks passed."]
47
+ for fail in self.failures:
48
+ lines.append(f" - {fail}")
49
+ if self.suggestions:
50
+ lines.append("Suggestions:")
51
+ for s in self.suggestions:
52
+ lines.append(f" + {s}")
53
+ return "\n".join(lines)
54
+
55
+
56
+ @dataclass
57
+ class NeuroSymbolicVerifier:
58
+ """LLM propose + symbolic verify loop."""
59
+ max_rounds: int = 3
60
+ verifiers: list[Callable[[str], VerificationResult]] = field(default_factory=list)
61
+
62
+ def verify(self, output: str) -> VerificationResult:
63
+ """Run all registered verifiers against the LLM output."""
64
+ if not self.verifiers:
65
+ return VerificationResult(passed=True, checks_total=0, checks_passed=0)
66
+ total = 0
67
+ passed = 0
68
+ failures: list[str] = []
69
+ suggestions: list[str] = []
70
+ for verifier in self.verifiers:
71
+ result = verifier(output)
72
+ total += result.checks_total
73
+ passed += result.checks_passed
74
+ failures.extend(result.failures)
75
+ suggestions.extend(result.suggestions)
76
+ return VerificationResult(
77
+ passed=passed == total and total > 0,
78
+ checks_total=total,
79
+ checks_passed=passed,
80
+ failures=failures,
81
+ suggestions=suggestions,
82
+ )
83
+
84
+ def add_verifier(self, verifier: Callable[[str], VerificationResult]) -> "NeuroSymbolicVerifier":
85
+ self.verifiers.append(verifier)
86
+ return self
87
+
88
+
89
+ # --- Built-in verifiers ---
90
+
91
+ def json_schema_verifier(schema: dict[str, Any]) -> Callable[[str], VerificationResult]:
92
+ """Create a verifier that checks if output is valid JSON matching a schema."""
93
+ def verify(output: str) -> VerificationResult:
94
+ failures: list[str] = []
95
+ try:
96
+ data = json.loads(output)
97
+ except json.JSONDecodeError as e:
98
+ return VerificationResult(
99
+ passed=False, checks_total=1, checks_passed=0,
100
+ failures=[f"Invalid JSON: {e}"],
101
+ suggestions=["Ensure output is valid JSON."],
102
+ )
103
+ # Check required keys
104
+ required = schema.get("required", [])
105
+ for key in required:
106
+ if key not in data:
107
+ failures.append(f"Missing required key: {key}")
108
+ # Check types
109
+ properties = schema.get("properties", {})
110
+ for key, expected_type in properties.items():
111
+ if key in data:
112
+ actual_type = type(data[key]).__name__
113
+ if expected_type != actual_type:
114
+ failures.append(f"Key '{key}' has type {actual_type}, expected {expected_type}")
115
+ passed = len(failures) == 0
116
+ return VerificationResult(
117
+ passed=passed,
118
+ checks_total=len(required) + len(properties),
119
+ checks_passed=len(required) + len(properties) - len(failures),
120
+ failures=failures,
121
+ suggestions=["Fix the schema violations listed above."],
122
+ )
123
+ return verify
124
+
125
+
126
+ def python_syntax_verifier(output: str) -> VerificationResult:
127
+ """Verify that Python code in the output is syntactically valid."""
128
+ # Extract code blocks
129
+ code_blocks = re.findall(r"```python\n(.*?)```", output, re.DOTALL)
130
+ if not code_blocks:
131
+ # Check if entire output looks like code
132
+ if any(kw in output for kw in ["def ", "class ", "import "]):
133
+ code_blocks = [output]
134
+ else:
135
+ return VerificationResult(passed=True, checks_total=0, checks_passed=0)
136
+
137
+ failures: list[str] = []
138
+ for i, code in enumerate(code_blocks):
139
+ try:
140
+ ast.parse(code)
141
+ except SyntaxError as e:
142
+ failures.append(f"Code block {i+1}: SyntaxError at line {e.lineno}: {e.msg}")
143
+
144
+ return VerificationResult(
145
+ passed=len(failures) == 0,
146
+ checks_total=len(code_blocks),
147
+ checks_passed=len(code_blocks) - len(failures),
148
+ failures=failures,
149
+ suggestions=["Fix syntax errors in the code blocks."],
150
+ )
151
+
152
+
153
+ def regex_format_verifier(pattern: str, description: str = "") -> Callable[[str], VerificationResult]:
154
+ """Create a verifier that checks if output matches a regex pattern."""
155
+ desc = description or f"matches pattern: {pattern[:50]}"
156
+ def verify(output: str) -> VerificationResult:
157
+ if re.search(pattern, output):
158
+ return VerificationResult(passed=True, checks_total=1, checks_passed=1)
159
+ return VerificationResult(
160
+ passed=False, checks_total=1, checks_passed=0,
161
+ failures=[f"Output does not {desc}"],
162
+ suggestions=[f"Ensure the output {desc}"],
163
+ )
164
+ return verify
165
+
166
+
167
+ def assertion_verifier(assertions: list[tuple[str, str]]) -> Callable[[str], VerificationResult]:
168
+ """Create a verifier from a list of (pattern, description) assertions.
169
+
170
+ Each assertion is a (regex_pattern, description) tuple.
171
+ The output must match ALL patterns.
172
+ """
173
+ def verify(output: str) -> VerificationResult:
174
+ failures: list[str] = []
175
+ for pattern, desc in assertions:
176
+ if not re.search(pattern, output):
177
+ failures.append(f"Assertion failed: {desc}")
178
+ return VerificationResult(
179
+ passed=len(failures) == 0,
180
+ checks_total=len(assertions),
181
+ checks_passed=len(assertions) - len(failures),
182
+ failures=failures,
183
+ )
184
+ return verify
185
+
186
+
187
+ VERIFY_PROMPT_TEMPLATE = """You previously produced this output:
188
+
189
+ {output}
190
+
191
+ Verification found these issues:
192
+ {feedback}
193
+
194
+ Please revise your output to address all verification failures.
195
+ Produce the corrected output only, without explaining the changes."""
196
+
197
+
198
+ def run_neuro_symbolic(
199
+ *,
200
+ task: str,
201
+ client: Any,
202
+ model: str,
203
+ verifiers: list[Callable[[str], VerificationResult]],
204
+ max_rounds: int = 3,
205
+ system: str = "You are a precise reasoning agent. Produce output that passes all verification checks.",
206
+ ) -> tuple[str, list[VerificationResult]]:
207
+ """Run a neuro-symbolic guess-and-check loop.
208
+
209
+ Args:
210
+ task: The task to solve.
211
+ client: Ollama client.
212
+ model: Model name.
213
+ verifiers: List of verification functions.
214
+ max_rounds: Maximum guess-and-check rounds.
215
+ system: System prompt.
216
+
217
+ Returns:
218
+ (final_output, verification_history)
219
+ """
220
+ ns = NeuroSymbolicVerifier(max_rounds=max_rounds, verifiers=verifiers)
221
+ messages: list[dict[str, Any]] = [
222
+ {"role": "system", "content": system},
223
+ {"role": "user", "content": task},
224
+ ]
225
+ history: list[VerificationResult] = []
226
+ output = ""
227
+
228
+ for round_num in range(max_rounds):
229
+ try:
230
+ response = client.chat(model=model, messages=messages, stream=False)
231
+ output = get_attr(get_attr(response, "message", {}), "content", "").strip()
232
+ except Exception as exc:
233
+ output = f"Error: {exc}"
234
+
235
+ # Verify
236
+ result = ns.verify(output)
237
+ history.append(result)
238
+
239
+ if result.passed:
240
+ break
241
+
242
+ # Feed back failures for retry
243
+ feedback = result.feedback_message()
244
+ messages.append({"role": "assistant", "content": output})
245
+ messages.append({
246
+ "role": "user",
247
+ "content": VERIFY_PROMPT_TEMPLATE.format(output=output[:2000], feedback=feedback),
248
+ })
249
+
250
+ return output, history