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
algo_cli/reflex.py ADDED
@@ -0,0 +1,385 @@
1
+ """Minimal reflex loop v0.1 — session recovery behind ``reflex_enabled`` (default off).
2
+
3
+ Implements a subset of docs/reflex-loop-v0.2.md: detect → one safe ACT → inject
4
+ result. No background tasks, no index mutation, no verification-loop integration.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import re
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from . import tools as tools_module
15
+ from .display import redact_tool_args
16
+
17
+ REFLEX_MAX_CYCLES = 3
18
+ REFLEX_LEDGER_CONTEXT_KEY = "reflex_cycles"
19
+ REFLEX_CAP_NOTIFIED_KEY = "reflex_cap_notified"
20
+
21
+ REFLEX_SAFE_TOOLS = frozenset(
22
+ {
23
+ "harness_search",
24
+ "harness_read",
25
+ "harness_stats",
26
+ "available_actions",
27
+ "session_slash",
28
+ "read_file",
29
+ "search_files",
30
+ "list_directory",
31
+ "git_status",
32
+ "git_diff",
33
+ }
34
+ )
35
+
36
+ _EMPTY_RESULT_MARKERS = (
37
+ "no harness matches",
38
+ "error:",
39
+ "not found",
40
+ "no matches",
41
+ "no such file",
42
+ )
43
+
44
+
45
+ def tool_signature(name: str, args: dict[str, Any]) -> str:
46
+ safe_args = redact_tool_args(name, args)
47
+ try:
48
+ encoded = json.dumps(safe_args, sort_keys=True, ensure_ascii=True, default=str, separators=(",", ":"))
49
+ except TypeError:
50
+ encoded = str(safe_args)
51
+ return f"{name}:{encoded}"
52
+
53
+
54
+ def _reflex_cycles(cfg: Any) -> int:
55
+ state = getattr(cfg, "context_state", None) or {}
56
+ if not isinstance(state, dict):
57
+ return 0
58
+ try:
59
+ return max(0, int(state.get(REFLEX_LEDGER_CONTEXT_KEY, 0)))
60
+ except (TypeError, ValueError):
61
+ return 0
62
+
63
+
64
+ def _increment_reflex_cycles(cfg: Any) -> None:
65
+ state = getattr(cfg, "context_state", None)
66
+ if not isinstance(state, dict):
67
+ cfg.context_state = {}
68
+ state = cfg.context_state
69
+ state[REFLEX_LEDGER_CONTEXT_KEY] = _reflex_cycles(cfg) + 1
70
+
71
+
72
+ def reset_reflex_cycles(cfg: Any) -> None:
73
+ state = getattr(cfg, "context_state", None)
74
+ if isinstance(state, dict):
75
+ state.pop(REFLEX_LEDGER_CONTEXT_KEY, None)
76
+ state.pop(REFLEX_CAP_NOTIFIED_KEY, None)
77
+
78
+
79
+ def begin_agent_pipeline(cfg: Any) -> None:
80
+ """Isolate reflex budget per /agent run (avoids burning cap during exploration)."""
81
+ reset_reflex_cycles(cfg)
82
+
83
+
84
+ def count_signature_in_ledger(cfg: Any, signature: str) -> int:
85
+ ledger = getattr(cfg, "attempt_ledger", None) or []
86
+ return sum(1 for item in ledger if item.get("signature") == signature)
87
+
88
+
89
+ def _result_is_empty_or_failed(result: str, status: str) -> bool:
90
+ if status == "failed":
91
+ return True
92
+ lowered = str(result).strip().lower()
93
+ if not lowered:
94
+ return True
95
+ return any(marker in lowered for marker in _EMPTY_RESULT_MARKERS)
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class ReflexTrigger:
100
+ label: str
101
+ reason: str
102
+
103
+
104
+ def _is_benign_exploration_failure(name: str, args: dict[str, Any], result: str) -> bool:
105
+ """Expected misses while exploring — reflex should not burn cycles here."""
106
+ lowered = str(result).lower()
107
+ if name in {"git_status", "git_diff"}:
108
+ return "not a git repository" in lowered or "fatal: not a git repository" in lowered
109
+ if name == "list_directory":
110
+ return "directory not found" in lowered or "error: directory not found" in lowered
111
+ if name == "read_file":
112
+ if "file not found" not in lowered and "error: file not found" not in lowered:
113
+ return False
114
+ path = str(args.get("path") or "").replace("\\", "/")
115
+ # Wrong relative path from home (e.g. algo_cli/foo vs ollama-cli/algo_cli/foo).
116
+ if path.startswith("algo_cli/") and not path.startswith("ollama-cli/"):
117
+ return True
118
+ return False
119
+
120
+
121
+ def detect_trigger(cfg: Any, name: str, args: dict[str, Any], result: str, status: str) -> ReflexTrigger | None:
122
+ if _is_benign_exploration_failure(name, args, result):
123
+ return None
124
+ signature = tool_signature(name, args)
125
+ prior = count_signature_in_ledger(cfg, signature)
126
+ if prior >= 2:
127
+ return ReflexTrigger("loop_detected", f"same tool path attempted {prior + 1} times")
128
+ if status == "failed":
129
+ return ReflexTrigger("tool_failed", summarize_tool_result(result))
130
+ if (
131
+ name in {"harness_search", "search_files"}
132
+ and prior >= 1
133
+ and _result_is_empty_or_failed(result, status)
134
+ ):
135
+ return ReflexTrigger("search_miss", "search returned no actionable hits")
136
+ if prior >= 1 and _result_is_empty_or_failed(result, status):
137
+ return ReflexTrigger("empty_repeat", "tool returned no useful output on repeat")
138
+ return None
139
+
140
+
141
+ def summarize_tool_result(result: str, limit: int = 140) -> str:
142
+ text = " ".join(str(result).split())
143
+ return text[:limit] + ("..." if len(text) > limit else "")
144
+
145
+
146
+ def _broaden_harness_query(args: dict[str, Any]) -> str:
147
+ raw = str(args.get("query") or args.get("q") or "").strip()
148
+ if not raw:
149
+ return ""
150
+ tokens = [t for t in re.findall(r"[\w.-]+", raw) if len(t) > 2]
151
+ return " ".join(tokens[:6]) if tokens else raw
152
+
153
+
154
+ def _recovery_query_for_tool(name: str, args: dict[str, Any]) -> str:
155
+ if name == "harness_search":
156
+ broadened = _broaden_harness_query(args)
157
+ return broadened or "algo-cli harness"
158
+ if name == "read_file":
159
+ path = str(args.get("path") or "")
160
+ stem = path.replace("\\", "/").split("/")[-1]
161
+ return stem or path
162
+ if name == "search_files":
163
+ return str(args.get("pattern") or args.get("query") or "project")
164
+ return _broaden_harness_query(args) or "workspace context"
165
+
166
+
167
+ def _run_safe_act(cfg: Any, trigger: ReflexTrigger, name: str, args: dict[str, Any]) -> tuple[str, bool]:
168
+ """Return (supplemental text, resolved)."""
169
+ if trigger.label == "loop_detected":
170
+ return (
171
+ "Reflex: loop detected (same tool + args). Stop retrying this path. "
172
+ "Change strategy, ask the user, or use a different read-only tool.",
173
+ False,
174
+ )
175
+
176
+ query = _recovery_query_for_tool(name, args)
177
+ if "harness_search" in REFLEX_SAFE_TOOLS:
178
+ fallback = tools_module.harness_search(query=query, limit=5)
179
+ if fallback and "no harness matches" not in fallback.lower():
180
+ return f"Reflex recovery — harness_search:\n{fallback}", True
181
+
182
+ if name == "read_file" and "search_files" in REFLEX_SAFE_TOOLS:
183
+ pattern = str(args.get("path", "")).split("/")[-1].split("\\")[-1]
184
+ if pattern:
185
+ alt = tools_module.search_files(
186
+ pattern=pattern,
187
+ cwd=str(getattr(cfg, "cwd", ".")),
188
+ limit=5,
189
+ )
190
+ if alt and "error" not in alt.lower()[:20]:
191
+ return f"Reflex recovery — search_files for {pattern}:\n{alt}", True
192
+
193
+ actions = tools_module.available_actions(topic=query[:40])
194
+ return (
195
+ f"Reflex ({trigger.label}): {trigger.reason}\n"
196
+ f"Suggested next reads:\n{actions[:1200]}",
197
+ False,
198
+ )
199
+
200
+
201
+ def maybe_augment_tool_result(
202
+ cfg: Any,
203
+ name: str,
204
+ args: dict[str, Any],
205
+ result: str,
206
+ status: str,
207
+ ) -> tuple[str, str | None]:
208
+ """Append a reflex recovery block when enabled and triggered.
209
+
210
+ Returns (possibly augmented result, user-visible reflex note or None).
211
+ """
212
+ if not getattr(cfg, "reflex_enabled", False):
213
+ return result, None
214
+ if name not in REFLEX_SAFE_TOOLS and status != "failed":
215
+ # Only auto-intervene on unsafe tools when they hard-failed.
216
+ if status != "failed":
217
+ return result, None
218
+ cycles = _reflex_cycles(cfg)
219
+ if cycles >= REFLEX_MAX_CYCLES:
220
+ state = getattr(cfg, "context_state", None)
221
+ if not isinstance(state, dict):
222
+ cfg.context_state = {}
223
+ state = cfg.context_state
224
+ if not state.get(REFLEX_CAP_NOTIFIED_KEY):
225
+ state[REFLEX_CAP_NOTIFIED_KEY] = True
226
+ return result, (
227
+ f"Reflex: cap ({REFLEX_MAX_CYCLES}) reached for this /agent run — "
228
+ "auto-recovery disabled until /reflex reset or next /agent."
229
+ )
230
+ return result, None
231
+
232
+ trigger = detect_trigger(cfg, name, args, result, status)
233
+ if trigger is None:
234
+ return result, None
235
+
236
+ _increment_reflex_cycles(cfg)
237
+ supplement, resolved = _run_safe_act(cfg, trigger, name, args)
238
+ verdict = "resolved" if resolved else "escalate"
239
+ note = f"↻ reflex [{trigger.label}] cycle {_reflex_cycles(cfg)}/{REFLEX_MAX_CYCLES} — {verdict}"
240
+ block = f"\n\n---\n{supplement}"
241
+ return f"{result}{block}", note
242
+
243
+
244
+ def status_line(cfg: Any) -> str:
245
+ enabled = getattr(cfg, "reflex_enabled", False)
246
+ cycles = _reflex_cycles(cfg)
247
+ return f"reflex: {'ON' if enabled else 'OFF'} ({cycles}/{REFLEX_MAX_CYCLES} cycles this session)"
248
+ # ---------------------------------------------------------------------------
249
+ # Reflex Error-Interceptor Loop (v0.2)
250
+ # Catches tool errors, generates a diagnostic prompt, retries with modified
251
+ # arguments up to REFLEX_ERROR_MAX_RETRIES times. Integrates with the existing
252
+ # attempt_ledger and reflex cycle tracker.
253
+ # ---------------------------------------------------------------------------
254
+
255
+ REFLEX_ERROR_MAX_RETRIES = 3
256
+ REFLEX_ERROR_LEDGER_KEY = "reflex_error_retries"
257
+
258
+ # Tools that are safe to auto-retry on failure (read-only or idempotent).
259
+ REFLEX_ERROR_SAFE_RETRY_TOOLS = frozenset({
260
+ "read_file",
261
+ "read_pdf",
262
+ "list_directory",
263
+ "search_files",
264
+ "git_status",
265
+ "git_diff",
266
+ "harness_search",
267
+ "harness_read",
268
+ "harness_stats",
269
+ "harness_refresh",
270
+ "available_actions",
271
+ "web_search",
272
+ "web_fetch",
273
+ "x_search",
274
+ "embed_text",
275
+ "vision_describe",
276
+ "model_show",
277
+ "query_knowledge_graph",
278
+ })
279
+
280
+ # Known error patterns and suggested argument fixes.
281
+ _REFLEX_ERROR_FIXES: dict[str, dict[str, Any]] = {
282
+ "file not found": {"action": "broaden_path", "description": "Try broader search or alternative paths"},
283
+ "not a directory": {"action": "fix_path_type", "description": "Target is a file, not directory"},
284
+ "is a directory": {"action": "fix_path_type", "description": "Target is a directory, not file"},
285
+ "permission denied": {"action": "skip_retry", "description": "Permission issue; retry unlikely to help"},
286
+ "timeout": {"action": "increase_timeout", "description": "Try again with longer timeout"},
287
+ "no harness matches": {"action": "broaden_query", "description": "Broaden or rephrase harness search query"},
288
+ "no matches": {"action": "broaden_query", "description": "Broaden or rephrase search"},
289
+ "tool error": {"action": "diagnose", "description": "Generic tool error; run diagnostic"},
290
+ }
291
+
292
+
293
+ def _reflex_error_retries(cfg: Any, tool_name: str, args: dict[str, Any]) -> int:
294
+ """Return how many error-retries have been used for this specific call signature."""
295
+ state = getattr(cfg, "context_state", None)
296
+ if not isinstance(state, dict):
297
+ return 0
298
+ key = (REFLEX_ERROR_LEDGER_KEY, tool_name, tool_signature(tool_name, args))
299
+ retries = state.get(str(key), 0)
300
+ return int(retries) if isinstance(retries, (int, float)) else 0
301
+
302
+
303
+ def _increment_reflex_error_retry(cfg: Any, tool_name: str, args: dict[str, Any]) -> None:
304
+ """Increment the retry counter for this specific tool call signature."""
305
+ state = getattr(cfg, "context_state", None)
306
+ if not isinstance(state, dict):
307
+ cfg.context_state = {}
308
+ state = cfg.context_state
309
+ key = str((REFLEX_ERROR_LEDGER_KEY, tool_name, tool_signature(tool_name, args)))
310
+ state[key] = _reflex_error_retries(cfg, tool_name, args) + 1
311
+
312
+
313
+ def _suggest_fix(name: str, args: dict[str, Any], result: str) -> dict[str, Any] | None:
314
+ """Based on the error pattern, suggest argument modifications for retry."""
315
+ lowered = str(result).strip().lower()
316
+ for pattern, fix in _REFLEX_ERROR_FIXES.items():
317
+ if pattern in lowered:
318
+ if fix["action"] == "skip_retry":
319
+ return None
320
+ if fix["action"] == "broaden_path" and name == "read_file":
321
+ # Suggest using search_files instead
322
+ return {"action": "search_files", "pattern": str(args.get("path", "")), "description": fix["description"]}
323
+ if fix["action"] == "broaden_query" and name in {"harness_search", "search_files"}:
324
+ return {"action": "broaden_query", "description": fix["description"]}
325
+ if fix["action"] == "increase_timeout" and name == "run_shell":
326
+ return {"action": "increase_timeout", "description": fix["description"]}
327
+ return {"action": fix["action"], "description": fix["description"]}
328
+ return None
329
+
330
+
331
+ def intercept_tool_error(
332
+ cfg: Any,
333
+ name: str,
334
+ args: dict[str, Any],
335
+ result: str,
336
+ ) -> tuple[str, bool]:
337
+ """Error-interceptor: check if a failed tool call should be retried.
338
+
339
+ Returns (diagnostic_message, should_retry).
340
+ The diagnostic_message explains why the error occurred and what to try next.
341
+ should_retry is True only when the tool is retry-safe and retries remain.
342
+ """
343
+ if not getattr(cfg, "reflex_enabled", False):
344
+ return "", False
345
+
346
+ # Only intercept actual errors (status "failed" is handled by the caller)
347
+ lowered = str(result).strip().lower()
348
+ is_error = lowered.startswith("error") or "tool error" in lowered
349
+ if not is_error:
350
+ return "", False
351
+
352
+ # Skip tools that shouldn't be auto-retried
353
+ if name not in REFLEX_ERROR_SAFE_RETRY_TOOLS:
354
+ return "", False
355
+
356
+ retries_so_far = _reflex_error_retries(cfg, name, args)
357
+ if retries_so_far >= REFLEX_ERROR_MAX_RETRIES:
358
+ return (
359
+ f"Reflex: max error-retries ({REFLEX_ERROR_MAX_RETRIES}) reached for {name}. "
360
+ "Escalate to user or change strategy.",
361
+ False,
362
+ )
363
+
364
+ # Generate diagnostic
365
+ fix = _suggest_fix(name, args, result)
366
+ if fix is None:
367
+ return "", False
368
+
369
+ _increment_reflex_error_retry(cfg, name, args)
370
+ suggestion = fix.get("description", "Try again with modified approach")
371
+ return (
372
+ f"Reflex error-interceptor: {name} failed with: {summarize_tool_result(result)}\n"
373
+ f"Diagnostic: {suggestion}\n"
374
+ f"Retry {_reflex_error_retries(cfg, name, args)}/{REFLEX_ERROR_MAX_RETRIES}",
375
+ True,
376
+ )
377
+
378
+
379
+ def reset_error_retries(cfg: Any) -> None:
380
+ """Clear all error-retry counters for a fresh session or /agent run."""
381
+ state = getattr(cfg, "context_state", None)
382
+ if isinstance(state, dict):
383
+ keys_to_remove = [k for k in state if k.startswith(str((REFLEX_ERROR_LEDGER_KEY,)))]
384
+ for key in keys_to_remove:
385
+ state.pop(key, None)