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,206 @@
1
+ """Small-context ledger support for models with runtime windows below 75k tokens.
2
+
3
+ The ledger gives compact models a big-model-like recall path: full optional
4
+ context is written to a temporary markdown file while the request only carries a
5
+ short refresh trigger that points the model/tool loop back to that file.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import json
11
+ import tempfile
12
+ import time
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Any, Iterable
16
+
17
+ SMALL_CONTEXT_THRESHOLD = 75_000
18
+ DEFAULT_ROOT = Path(tempfile.gettempdir()) / "algo_cli_small_context"
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class SmallContextLedger:
23
+ """Metadata for a written small-context ledger file."""
24
+
25
+ path: Path
26
+ model: str
27
+ runtime_cap: int
28
+ block_names: tuple[str, ...]
29
+ bytes_written: int
30
+ token_estimate: int
31
+
32
+ def to_dict(self) -> dict[str, Any]:
33
+ return {
34
+ "path": str(self.path),
35
+ "model": self.model,
36
+ "runtime_cap": self.runtime_cap,
37
+ "block_names": list(self.block_names),
38
+ "bytes_written": self.bytes_written,
39
+ "token_estimate": self.token_estimate,
40
+ }
41
+
42
+
43
+ def is_small_context(runtime_cap: int | None, *, threshold: int = SMALL_CONTEXT_THRESHOLD) -> bool:
44
+ """Return True when a model should use the temp context-ledger path."""
45
+ try:
46
+ cap = int(runtime_cap or 0)
47
+ except (TypeError, ValueError):
48
+ return False
49
+ return 0 < cap < threshold
50
+
51
+
52
+ def estimate_text_tokens(text: str) -> int:
53
+ if not text:
54
+ return 0
55
+ return max(1, (len(text) + 3) // 4)
56
+
57
+
58
+ def _safe_name(value: str) -> str:
59
+ cleaned = "".join(ch if ch.isalnum() or ch in "._-" else "-" for ch in value.strip())
60
+ cleaned = cleaned.strip(".-")
61
+ return cleaned[:80] or "context"
62
+
63
+
64
+ def _block_name(block: Any) -> str:
65
+ return str(getattr(block, "name", "") or "context")
66
+
67
+
68
+ def _block_title(block: Any) -> str:
69
+ return str(getattr(block, "title", "") or _block_name(block))
70
+
71
+
72
+ def _block_body(block: Any) -> str:
73
+ return str(getattr(block, "body", "") or "")
74
+
75
+
76
+ def _recent_messages(messages: Iterable[dict[str, Any]], *, limit: int = 8) -> str:
77
+ selected = list(messages)[-limit:]
78
+ lines: list[str] = []
79
+ for msg in selected:
80
+ role = str(msg.get("role") or "message")
81
+ content = str(msg.get("content") or msg.get("thinking") or "")
82
+ if msg.get("tool_name"):
83
+ role += f"[{msg.get('tool_name')}]"
84
+ if len(content) > 1600:
85
+ content = content[:1600].rstrip() + "\n...[truncated in ledger recent-message view]"
86
+ lines.append(f"### {role}\n{content}".rstrip())
87
+ return "\n\n".join(lines)
88
+
89
+
90
+ def build_ledger_text(
91
+ *,
92
+ model: str,
93
+ runtime_cap: int,
94
+ cwd: str,
95
+ base_message: str,
96
+ optional_blocks: Iterable[Any],
97
+ session_summary: str = "",
98
+ messages: Iterable[dict[str, Any]] = (),
99
+ ) -> tuple[str, tuple[str, ...]]:
100
+ """Render the full small-context ledger markdown and block-name list."""
101
+ blocks = list(optional_blocks)
102
+ block_names = tuple(_block_name(block) for block in blocks if _block_body(block).strip())
103
+ generated = time.strftime("%Y-%m-%d %H:%M:%S %Z", time.localtime())
104
+ parts = [
105
+ "# Algo CLI Small-Context Ledger",
106
+ "",
107
+ "This file is an external context window for compact models (<75k runtime context).",
108
+ "When the live prompt feels stale or incomplete, read this file with read_file and use it as context refresh.",
109
+ "",
110
+ "## Metadata",
111
+ f"- Generated: {generated}",
112
+ f"- Model: {model}",
113
+ f"- Runtime context cap: {int(runtime_cap)} tokens",
114
+ f"- Working directory: {cwd}",
115
+ f"- Blocks: {', '.join(block_names) if block_names else 'none'}",
116
+ "",
117
+ "## Current User Request",
118
+ base_message.strip() or "(empty)",
119
+ ]
120
+ if session_summary.strip():
121
+ parts.extend(["", "## Conversation Summary", session_summary.strip()])
122
+ recent = _recent_messages(messages)
123
+ if recent:
124
+ parts.extend(["", "## Recent Messages", recent])
125
+ if blocks:
126
+ parts.extend(["", "## Full Optional Context Blocks"])
127
+ for block in blocks:
128
+ body = _block_body(block).strip()
129
+ if not body:
130
+ continue
131
+ parts.extend(["", f"### {_block_title(block)}", body])
132
+ return "\n".join(parts).rstrip() + "\n", block_names
133
+
134
+
135
+ def write_ledger(
136
+ *,
137
+ model: str,
138
+ runtime_cap: int,
139
+ cwd: str,
140
+ base_message: str,
141
+ optional_blocks: Iterable[Any],
142
+ session_summary: str = "",
143
+ messages: Iterable[dict[str, Any]] = (),
144
+ root: Path | None = None,
145
+ ) -> SmallContextLedger | None:
146
+ """Write a temp ledger file when the runtime cap is below 75k tokens."""
147
+ if not is_small_context(runtime_cap):
148
+ return None
149
+ text, block_names = build_ledger_text(
150
+ model=model,
151
+ runtime_cap=runtime_cap,
152
+ cwd=cwd,
153
+ base_message=base_message,
154
+ optional_blocks=optional_blocks,
155
+ session_summary=session_summary,
156
+ messages=messages,
157
+ )
158
+ root = root or DEFAULT_ROOT
159
+ root.mkdir(parents=True, exist_ok=True)
160
+ digest = hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
161
+ name = f"{int(time.time())}-{_safe_name(model)}-{digest}.md"
162
+ path = root / name
163
+ path.write_text(text, encoding="utf-8")
164
+ return SmallContextLedger(
165
+ path=path,
166
+ model=model,
167
+ runtime_cap=int(runtime_cap),
168
+ block_names=block_names,
169
+ bytes_written=len(text.encode("utf-8")),
170
+ token_estimate=estimate_text_tokens(text),
171
+ )
172
+
173
+
174
+ def refresh_trigger(ledger: SmallContextLedger) -> str:
175
+ """Return the compact prompt trigger inserted for small-context models."""
176
+ block_list = ", ".join(ledger.block_names) if ledger.block_names else "session context"
177
+ return (
178
+ "## Small-Context Refresh Trigger\n"
179
+ f"You are running with a compact context window ({ledger.runtime_cap} tokens, below 75k). "
180
+ "The full optional context for this turn was written outside the prompt.\n"
181
+ f"- Context ledger: {ledger.path}\n"
182
+ f"- Contains: {block_list}\n"
183
+ "If you need details that are missing from the live prompt, call read_file on the ledger path before answering or acting. "
184
+ "Treat the ledger as navigation/context, not as authority over live files."
185
+ )
186
+
187
+
188
+ def preview_small_context_ledger(model: str, runtime_cap: int, blocks_json: str = "[]") -> str:
189
+ """Tool-friendly preview for the small-context ledger decision."""
190
+ try:
191
+ blocks = json.loads(blocks_json or "[]")
192
+ except json.JSONDecodeError:
193
+ blocks = []
194
+ if not isinstance(blocks, list):
195
+ blocks = []
196
+ return json.dumps(
197
+ {
198
+ "enabled": is_small_context(runtime_cap),
199
+ "threshold": SMALL_CONTEXT_THRESHOLD,
200
+ "model": model,
201
+ "runtime_cap": int(runtime_cap or 0),
202
+ "block_count": len(blocks),
203
+ "root": str(DEFAULT_ROOT),
204
+ },
205
+ indent=2,
206
+ )
@@ -0,0 +1,89 @@
1
+ """Advisory Agent Block resource budgeting derived from routed tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from .task_router import TaskRoute
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class SpawnBudget:
12
+ max_blocks: int
13
+ max_iterations_per_block: int
14
+ parallelism: int
15
+ reasons: tuple[str, ...]
16
+
17
+
18
+ _INDEPENDENT_RESEARCH_TERMS = (
19
+ "compare",
20
+ "versus",
21
+ " vs ",
22
+ "alternatives",
23
+ "options",
24
+ "across",
25
+ )
26
+
27
+
28
+ def compute_budget(route: TaskRoute, prompt: str = "") -> SpawnBudget:
29
+ """Recommend an execution budget without changing pipeline behavior."""
30
+ lowered = f" {prompt.lower().strip()} "
31
+
32
+ if route.risk == "high":
33
+ return SpawnBudget(
34
+ max_blocks=0,
35
+ max_iterations_per_block=0,
36
+ parallelism=0,
37
+ reasons=("High-risk work should remain user-directed; no automatic expansion recommended.",),
38
+ )
39
+
40
+ if route.recommended_mode != "agent":
41
+ return SpawnBudget(
42
+ max_blocks=0,
43
+ max_iterations_per_block=0,
44
+ parallelism=0,
45
+ reasons=("Direct chat is sufficient for this task.",),
46
+ )
47
+
48
+ if route.task_type == "review":
49
+ return SpawnBudget(
50
+ max_blocks=2,
51
+ max_iterations_per_block=8,
52
+ parallelism=0,
53
+ reasons=("A reviewer and finalizer provide a bounded review pass.",),
54
+ )
55
+
56
+ if route.task_type == "research":
57
+ independent = any(term in lowered for term in _INDEPENDENT_RESEARCH_TERMS)
58
+ reasons = ["A planner, researcher, and finalizer cover a focused research task."]
59
+ if independent:
60
+ reasons.append("Independent comparisons detected; parallel research may be useful later.")
61
+ return SpawnBudget(
62
+ max_blocks=3,
63
+ max_iterations_per_block=8,
64
+ parallelism=1 if independent else 0,
65
+ reasons=tuple(reasons),
66
+ )
67
+
68
+ if route.task_type == "coding":
69
+ return SpawnBudget(
70
+ max_blocks=4,
71
+ max_iterations_per_block=8,
72
+ parallelism=0,
73
+ reasons=("Coding work benefits from planning, implementation, review, and finalization.",),
74
+ )
75
+
76
+ return SpawnBudget(
77
+ max_blocks=0,
78
+ max_iterations_per_block=0,
79
+ parallelism=0,
80
+ reasons=("No bounded Agent Blocks budget is recommended for this route.",),
81
+ )
82
+
83
+
84
+ def parallelism_label(value: int) -> str:
85
+ if value >= 2:
86
+ return "recommended"
87
+ if value == 1:
88
+ return "optional"
89
+ return "none"
@@ -0,0 +1,84 @@
1
+ """Durable task ledger for /goal and long autonomous runs.
2
+
3
+ `/goal` and agent pipelines previously held all state in RAM: a crash, an
4
+ exit, or hitting the round cap lost the plan entirely. This module persists a
5
+ single active goal's progress to ``CONFIG_DIR/task_ledger.json`` so a run can
6
+ be inspected with ``/goal status`` and continued with ``/goal resume`` across
7
+ process restarts.
8
+
9
+ The ledger holds at most one active goal at a time (the common case for a
10
+ terminal session). Completing, blocking, or starting a new goal overwrites it.
11
+ Writes are atomic (tmp + os.replace) via config._atomic_write_text.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import time
18
+ from dataclasses import asdict, dataclass, field
19
+ from typing import Any
20
+
21
+ from .config import CONFIG_DIR, _atomic_write_text, _load_json_file
22
+
23
+ LEDGER_PATH = CONFIG_DIR / "task_ledger.json"
24
+ LEDGER_SCHEMA_VERSION = 1
25
+
26
+ STATUS_RUNNING = "running"
27
+ STATUS_COMPLETE = "complete"
28
+ STATUS_BLOCKED = "blocked"
29
+ STATUS_STOPPED = "stopped" # user-interrupted or round cap reached
30
+
31
+
32
+ @dataclass
33
+ class GoalRecord:
34
+ goal: str
35
+ status: str = STATUS_RUNNING
36
+ rounds_done: int = 0
37
+ max_rounds: int = 10
38
+ reason: str = "" # blocked/stopped explanation
39
+ cwd: str = ""
40
+ created_at: float = field(default_factory=time.time)
41
+ updated_at: float = field(default_factory=time.time)
42
+ history: list[dict[str, Any]] = field(default_factory=list) # per-round notes
43
+
44
+ def add_round(self, summary: str) -> None:
45
+ self.rounds_done += 1
46
+ self.updated_at = time.time()
47
+ self.history.append(
48
+ {"round": self.rounds_done, "at": self.updated_at, "summary": summary[:500]}
49
+ )
50
+
51
+ @property
52
+ def is_open(self) -> bool:
53
+ return self.status in {STATUS_RUNNING, STATUS_STOPPED}
54
+
55
+
56
+ def save_goal(record: GoalRecord) -> None:
57
+ record.updated_at = time.time()
58
+ payload = {"schema_version": LEDGER_SCHEMA_VERSION, "goal": asdict(record)}
59
+ _atomic_write_text(LEDGER_PATH, json.dumps(payload, indent=2))
60
+
61
+
62
+ def load_goal() -> GoalRecord | None:
63
+ data = _load_json_file(LEDGER_PATH, None)
64
+ if not isinstance(data, dict):
65
+ return None
66
+ goal_data = data.get("goal")
67
+ if not isinstance(goal_data, dict) or not goal_data.get("goal"):
68
+ return None
69
+ known = GoalRecord.__dataclass_fields__.keys()
70
+ filtered = {k: v for k, v in goal_data.items() if k in known}
71
+ try:
72
+ return GoalRecord(**filtered)
73
+ except TypeError:
74
+ return None
75
+
76
+
77
+ def clear_goal() -> bool:
78
+ if not LEDGER_PATH.exists():
79
+ return False
80
+ try:
81
+ LEDGER_PATH.unlink()
82
+ return True
83
+ except OSError:
84
+ return False
@@ -0,0 +1,197 @@
1
+ """Advisory task routing for chat, tools, and Agent Blocks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class TaskRoute:
10
+ task_type: str
11
+ complexity: str
12
+ recommended_mode: str
13
+ suggested_pipeline: str
14
+ allowed_tool_groups: tuple[str, ...]
15
+ risk: str
16
+ reason: str
17
+
18
+
19
+ _HIGH_RISK_TERMS = (
20
+ "delete",
21
+ "rm -rf",
22
+ "reset --hard",
23
+ "drop table",
24
+ "credential",
25
+ "credentials",
26
+ "password",
27
+ "secret",
28
+ "token",
29
+ "api key",
30
+ "invoice",
31
+ "payment",
32
+ "send email",
33
+ "post this",
34
+ "publish",
35
+ )
36
+
37
+ _REVIEW_TERMS = (
38
+ "review",
39
+ "audit",
40
+ "inspect",
41
+ "critique",
42
+ "check the code",
43
+ "security review",
44
+ "find issues",
45
+ "look for bugs",
46
+ )
47
+
48
+ _RESEARCH_TERMS = (
49
+ "research",
50
+ "investigate",
51
+ "compare",
52
+ "look up",
53
+ "find current",
54
+ "latest",
55
+ "summarize docs",
56
+ "read the docs",
57
+ )
58
+
59
+ _READ_ONLY_TERMS = (
60
+ "read-only",
61
+ "read only",
62
+ "read_file only",
63
+ "do not submit",
64
+ "do not write",
65
+ "no writes",
66
+ "reproduce sections",
67
+ "analyze permit",
68
+ "permit pilot",
69
+ "deliver:",
70
+ )
71
+
72
+ _CODING_TERMS = (
73
+ "fix",
74
+ "implement",
75
+ "build",
76
+ "add",
77
+ "refactor",
78
+ "failing test",
79
+ "bug",
80
+ "stack trace",
81
+ "feature",
82
+ "wire up",
83
+ "remove unused",
84
+ "unused import",
85
+ )
86
+
87
+ _QUESTION_PREFIXES = (
88
+ "how ",
89
+ "what ",
90
+ "why ",
91
+ "where ",
92
+ "when ",
93
+ "explain ",
94
+ "tell me ",
95
+ )
96
+
97
+
98
+ def _has_any(text: str, terms: tuple[str, ...]) -> bool:
99
+ return any(term in text for term in terms)
100
+
101
+
102
+ def route_task(prompt: str) -> TaskRoute:
103
+ """Classify a prompt with transparent first-match rules."""
104
+ text = (prompt or "").strip()
105
+ lowered = text.lower()
106
+ high_risk = _has_any(lowered, _HIGH_RISK_TERMS)
107
+
108
+ if not text:
109
+ return TaskRoute(
110
+ task_type="empty",
111
+ complexity="low",
112
+ recommended_mode="chat",
113
+ suggested_pipeline="default",
114
+ allowed_tool_groups=(),
115
+ risk="low",
116
+ reason="No task text was provided.",
117
+ )
118
+
119
+ if _has_any(lowered, _REVIEW_TERMS):
120
+ return TaskRoute(
121
+ task_type="review",
122
+ complexity="medium",
123
+ recommended_mode="agent",
124
+ suggested_pipeline="review",
125
+ allowed_tool_groups=("read", "shell"),
126
+ risk="high" if high_risk else "medium",
127
+ reason="Review and audit tasks benefit from a bounded reviewer/finalizer pipeline.",
128
+ )
129
+
130
+ if _has_any(lowered, _RESEARCH_TERMS):
131
+ return TaskRoute(
132
+ task_type="research",
133
+ complexity="medium",
134
+ recommended_mode="agent",
135
+ suggested_pipeline="research",
136
+ allowed_tool_groups=("read", "web"),
137
+ risk="high" if high_risk else "low",
138
+ reason="Research tasks benefit from a planner, researcher, and finalizer.",
139
+ )
140
+
141
+ if _has_any(lowered, _READ_ONLY_TERMS):
142
+ return TaskRoute(
143
+ task_type="research",
144
+ complexity="medium",
145
+ recommended_mode="agent",
146
+ suggested_pipeline="research",
147
+ allowed_tool_groups=("read", "web"),
148
+ risk="high" if high_risk else "low",
149
+ reason="Read-only analysis tasks should use the research pipeline, not code-change.",
150
+ )
151
+
152
+ if _has_any(lowered, _CODING_TERMS):
153
+ return TaskRoute(
154
+ task_type="coding",
155
+ complexity="high",
156
+ recommended_mode="agent",
157
+ suggested_pipeline="code-change",
158
+ allowed_tool_groups=("read", "write", "shell"),
159
+ risk="high" if high_risk else "medium",
160
+ reason="Coding tasks usually need planning, implementation, review, and verification.",
161
+ )
162
+
163
+ if high_risk:
164
+ return TaskRoute(
165
+ task_type="sensitive",
166
+ complexity="medium",
167
+ recommended_mode="chat",
168
+ suggested_pipeline="default",
169
+ allowed_tool_groups=("read",),
170
+ risk="high",
171
+ reason="The task mentions destructive, credential, financial, or publishing actions.",
172
+ )
173
+
174
+ if lowered.startswith(_QUESTION_PREFIXES):
175
+ return TaskRoute(
176
+ task_type="question",
177
+ complexity="low",
178
+ recommended_mode="chat",
179
+ suggested_pipeline="default",
180
+ allowed_tool_groups=(),
181
+ risk="low",
182
+ reason="Direct questions are usually best handled in chat mode.",
183
+ )
184
+
185
+ return TaskRoute(
186
+ task_type="general",
187
+ complexity="low",
188
+ recommended_mode="chat",
189
+ suggested_pipeline="default",
190
+ allowed_tool_groups=(),
191
+ risk="low",
192
+ reason="No agent-specific routing rule matched.",
193
+ )
194
+
195
+
196
+ def should_suggest(route: TaskRoute) -> bool:
197
+ return route.recommended_mode == "agent" or route.risk == "high"
@@ -0,0 +1,94 @@
1
+ """Prompt-declared tool-context selection.
2
+
3
+ The runtime policy remains the enforcement layer. This module only reduces the
4
+ function schemas shown to the model when a task explicitly declares its allowed
5
+ tool classes, improving signal and prompt-cache efficiency.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from collections.abc import Callable, Sequence
12
+ from typing import Any
13
+
14
+
15
+ _DECLARATION_RE = re.compile(
16
+ r"(?im)^\s*allowed\s+tool(?:\s+classes)?\s*:\s*([^\r\n]+)"
17
+ )
18
+ _CLASS_TO_TOOLS: dict[str, frozenset[str]] = {
19
+ "filesystem": frozenset(
20
+ {
21
+ "read_file",
22
+ "edit_file",
23
+ "read_pdf",
24
+ "render_pdf_pages",
25
+ "write_file",
26
+ "list_directory",
27
+ "search_files",
28
+ "find_unique_anchor",
29
+ "batch_edit",
30
+ "git_status",
31
+ "git_diff",
32
+ }
33
+ ),
34
+ "shell": frozenset({"run_shell"}),
35
+ "web": frozenset({"web_search", "web_fetch"}),
36
+ "network": frozenset({"web_search", "web_fetch", "x_search"}),
37
+ "memory": frozenset({"remember", "append_lesson", "update_user_profile"}),
38
+ "model": frozenset(
39
+ {"embed_text", "vision_describe", "model_pull", "model_delete", "model_create", "model_copy", "model_show"}
40
+ ),
41
+ "harness": frozenset(
42
+ {"harness_refresh", "harness_stats", "harness_scorecard", "harness_competitive_rating", "harness_search", "harness_read"}
43
+ ),
44
+ "knowledge_graph": frozenset(
45
+ {"query_knowledge_graph", "reindex_knowledge_graph", "write_knowledge_graph_note"}
46
+ ),
47
+ "session": frozenset({"available_actions", "session_slash", "session_command"}),
48
+ "plugins": frozenset({"plugins_discover", "plugins_load"}),
49
+ "credentials": frozenset({"credential_helpers_get", "credential_helpers_store"}),
50
+ "social": frozenset(
51
+ {"x_search", "x_account_status", "x_account_draft_post", "x_account_draft_reply", "x_account_post", "x_account_reply", "x_account_post_action"}
52
+ ),
53
+ }
54
+
55
+
56
+ def _normalize_class(value: str) -> str:
57
+ return re.sub(r"[^a-z0-9]+", "_", value.strip().casefold()).strip("_")
58
+
59
+
60
+ def declared_tool_classes(prompt: str) -> tuple[str, ...] | None:
61
+ """Return normalized explicit tool classes, or ``None`` when undeclared."""
62
+
63
+ match = _DECLARATION_RE.search(prompt or "")
64
+ if match is None:
65
+ return None
66
+ values = tuple(
67
+ normalized
68
+ for raw in match.group(1).split(",")
69
+ if (normalized := _normalize_class(raw))
70
+ )
71
+ return values
72
+
73
+
74
+ def select_tools_for_prompt(
75
+ prompt: str,
76
+ all_tools: Sequence[Callable[..., Any]],
77
+ ) -> list[Callable[..., Any]]:
78
+ """Select only explicitly declared known tool classes.
79
+
80
+ Unknown classes contribute no tools. This fails closed instead of
81
+ accidentally exposing the full runtime catalog when a declaration has a
82
+ typo or names a class implemented by another harness.
83
+ """
84
+
85
+ declared = declared_tool_classes(prompt)
86
+ if declared is None:
87
+ return list(all_tools)
88
+ allowed_names: set[str] = set()
89
+ for tool_class in declared:
90
+ allowed_names.update(_CLASS_TO_TOOLS.get(tool_class, ()))
91
+ return [tool for tool in all_tools if getattr(tool, "__name__", "") in allowed_names]
92
+
93
+
94
+ __all__ = ["declared_tool_classes", "select_tools_for_prompt"]