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.
- algo_cli/__init__.py +3 -0
- algo_cli/__main__.py +7 -0
- algo_cli/_internal/__init__.py +12 -0
- algo_cli/_internal/policy_chain.py +259 -0
- algo_cli/action_registry.py +1047 -0
- algo_cli/agent_blocks.py +550 -0
- algo_cli/agent_pipeline.py +1457 -0
- algo_cli/agent_threads.py +308 -0
- algo_cli/animations.py +316 -0
- algo_cli/cache_admission.py +209 -0
- algo_cli/capability_mask.py +66 -0
- algo_cli/chat_protocol.py +116 -0
- algo_cli/chatgpt_auth.py +510 -0
- algo_cli/chatgpt_client.py +657 -0
- algo_cli/code_rag.py +479 -0
- algo_cli/config.py +651 -0
- algo_cli/context_budget.py +679 -0
- algo_cli/credential_helpers.py +315 -0
- algo_cli/deliberation.py +29 -0
- algo_cli/display.py +1470 -0
- algo_cli/evals/__init__.py +21 -0
- algo_cli/evals/algorithm_effectiveness.py +560 -0
- algo_cli/evals/competitive_harness_rating.py +702 -0
- algo_cli/evals/cot_quality.py +220 -0
- algo_cli/evals/harness_retrieval_benchmark.py +401 -0
- algo_cli/evals/performance_regression.py +136 -0
- algo_cli/evals/scorecard_grading.py +308 -0
- algo_cli/evals/session_distribution.py +84 -0
- algo_cli/execution_guardrails.py +806 -0
- algo_cli/extensions_manifest.py +84 -0
- algo_cli/git_evidence.py +227 -0
- algo_cli/google_workspace.py +407 -0
- algo_cli/google_workspace_auth.py +523 -0
- algo_cli/harness.py +2587 -0
- algo_cli/identity.py +557 -0
- algo_cli/index_compute_lab.py +228 -0
- algo_cli/inference_harness.py +70 -0
- algo_cli/intelligence/__init__.py +1103 -0
- algo_cli/intelligence/acrobat_config.py +307 -0
- algo_cli/intelligence/acrobat_manifests.py +338 -0
- algo_cli/intelligence/acrobat_models.py +195 -0
- algo_cli/intelligence/acrobat_pipeline.py +295 -0
- algo_cli/intelligence/acrobat_runtime.py +302 -0
- algo_cli/intelligence/acrobat_security.py +261 -0
- algo_cli/intelligence/acrobat_workflows.py +226 -0
- algo_cli/intelligence/actionability.py +165 -0
- algo_cli/intelligence/adversarial_audit.py +136 -0
- algo_cli/intelligence/agent_arena.py +92 -0
- algo_cli/intelligence/agent_benchmark.py +236 -0
- algo_cli/intelligence/agent_runtime.py +171 -0
- algo_cli/intelligence/agents_as_tools.py +70 -0
- algo_cli/intelligence/artifact_binding.py +80 -0
- algo_cli/intelligence/autonomous_engineer.py +1976 -0
- algo_cli/intelligence/backpressure.py +99 -0
- algo_cli/intelligence/bloom_filter.py +186 -0
- algo_cli/intelligence/bonferroni.py +66 -0
- algo_cli/intelligence/boundary_compaction.py +98 -0
- algo_cli/intelligence/catalog_verifier.py +172 -0
- algo_cli/intelligence/cavecrew.py +118 -0
- algo_cli/intelligence/changelog.py +176 -0
- algo_cli/intelligence/checkpoint_resume.py +92 -0
- algo_cli/intelligence/circuit_breaker.py +88 -0
- algo_cli/intelligence/clarification_gate.py +101 -0
- algo_cli/intelligence/code_graph.py +180 -0
- algo_cli/intelligence/coderank.py +97 -0
- algo_cli/intelligence/consistent_hash.py +150 -0
- algo_cli/intelligence/consortium_synthesis.py +139 -0
- algo_cli/intelligence/construction/__init__.py +241 -0
- algo_cli/intelligence/construction/common.py +273 -0
- algo_cli/intelligence/construction/documents.py +496 -0
- algo_cli/intelligence/construction/labor_units.py +1395 -0
- algo_cli/intelligence/construction/payments.py +470 -0
- algo_cli/intelligence/construction/risk.py +784 -0
- algo_cli/intelligence/content_extractor.py +132 -0
- algo_cli/intelligence/context_adaptive.py +102 -0
- algo_cli/intelligence/context_ops.py +95 -0
- algo_cli/intelligence/count_min.py +145 -0
- algo_cli/intelligence/cow_state.py +103 -0
- algo_cli/intelligence/critic_loop.py +119 -0
- algo_cli/intelligence/cross_source.py +113 -0
- algo_cli/intelligence/daemon_mode.py +99 -0
- algo_cli/intelligence/dag_orchestration.py +151 -0
- algo_cli/intelligence/deep_research.py +155 -0
- algo_cli/intelligence/degenerate_detector.py +78 -0
- algo_cli/intelligence/delta_report.py +92 -0
- algo_cli/intelligence/discovery_event_log.py +92 -0
- algo_cli/intelligence/document_ingest.py +298 -0
- algo_cli/intelligence/dual_layer_validate.py +151 -0
- algo_cli/intelligence/echo_fidelity.py +73 -0
- algo_cli/intelligence/ema_tuning.py +104 -0
- algo_cli/intelligence/event_log.py +92 -0
- algo_cli/intelligence/evidence_graph.py +114 -0
- algo_cli/intelligence/extension_host.py +162 -0
- algo_cli/intelligence/extension_manifest.py +115 -0
- algo_cli/intelligence/falsification_suite.py +178 -0
- algo_cli/intelligence/finance/__init__.py +169 -0
- algo_cli/intelligence/finance/anomalies.py +135 -0
- algo_cli/intelligence/finance/ap_ar.py +351 -0
- algo_cli/intelligence/finance/cash.py +162 -0
- algo_cli/intelligence/finance/close.py +332 -0
- algo_cli/intelligence/finance/common.py +244 -0
- algo_cli/intelligence/finance/construction.py +135 -0
- algo_cli/intelligence/finance/controls.py +172 -0
- algo_cli/intelligence/finance/evidence.py +119 -0
- algo_cli/intelligence/finance/exceptions.py +157 -0
- algo_cli/intelligence/finance/reconciliations.py +254 -0
- algo_cli/intelligence/finance/revenue.py +109 -0
- algo_cli/intelligence/finance/tax.py +74 -0
- algo_cli/intelligence/finance/workpapers.py +111 -0
- algo_cli/intelligence/finding_record.py +120 -0
- algo_cli/intelligence/flow_dag.py +267 -0
- algo_cli/intelligence/gatherer.py +223 -0
- algo_cli/intelligence/golden_master.py +98 -0
- algo_cli/intelligence/graph_rag.py +195 -0
- algo_cli/intelligence/group_chat.py +143 -0
- algo_cli/intelligence/hash_dedup.py +145 -0
- algo_cli/intelligence/hyperloglog.py +128 -0
- algo_cli/intelligence/incremental_index.py +316 -0
- algo_cli/intelligence/index_store.py +16 -0
- algo_cli/intelligence/iteration_plan.py +133 -0
- algo_cli/intelligence/kernel_plugins.py +167 -0
- algo_cli/intelligence/lesson_catalog.py +135 -0
- algo_cli/intelligence/llm_fallback.py +169 -0
- algo_cli/intelligence/log2_histogram.py +267 -0
- algo_cli/intelligence/lsp_integration.py +147 -0
- algo_cli/intelligence/memory_evolution.py +117 -0
- algo_cli/intelligence/minhash_lsh.py +182 -0
- algo_cli/intelligence/multi_model_score.py +174 -0
- algo_cli/intelligence/multi_tier_grade.py +211 -0
- algo_cli/intelligence/negative_controls.py +113 -0
- algo_cli/intelligence/numeric_clamp.py +63 -0
- algo_cli/intelligence/occ_editor.py +66 -0
- algo_cli/intelligence/output_normalize.py +112 -0
- algo_cli/intelligence/parallel_delegation.py +98 -0
- algo_cli/intelligence/parallel_fanout.py +104 -0
- algo_cli/intelligence/permission_modes.py +105 -0
- algo_cli/intelligence/pre_push_gate.py +68 -0
- algo_cli/intelligence/prefetch.py +171 -0
- algo_cli/intelligence/process_framework.py +217 -0
- algo_cli/intelligence/project_graph.py +387 -0
- algo_cli/intelligence/query_expansion.py +146 -0
- algo_cli/intelligence/ralph_loop.py +117 -0
- algo_cli/intelligence/rate_limiter.py +153 -0
- algo_cli/intelligence/refactor_transaction.py +94 -0
- algo_cli/intelligence/research_workspace.py +108 -0
- algo_cli/intelligence/retraction_ledger.py +72 -0
- algo_cli/intelligence/saga_pattern.py +88 -0
- algo_cli/intelligence/session_fork.py +100 -0
- algo_cli/intelligence/shadow_editor.py +67 -0
- algo_cli/intelligence/shell_session.py +213 -0
- algo_cli/intelligence/source_registry.py +143 -0
- algo_cli/intelligence/spawn_scales.py +99 -0
- algo_cli/intelligence/stat_stability.py +104 -0
- algo_cli/intelligence/structural_validator.py +148 -0
- algo_cli/intelligence/subagent_spawner.py +111 -0
- algo_cli/intelligence/symmetric_verify.py +70 -0
- algo_cli/intelligence/task_classifier.py +129 -0
- algo_cli/intelligence/team_execution.py +122 -0
- algo_cli/intelligence/tiered_access.py +121 -0
- algo_cli/intelligence/utility_registry.py +159 -0
- algo_cli/intuition_engine.py +560 -0
- algo_cli/intuition_injector.py +82 -0
- algo_cli/kernels/__init__.py +5 -0
- algo_cli/kernels/manifest.py +763 -0
- algo_cli/main.py +3903 -0
- algo_cli/memory_candidates.py +541 -0
- algo_cli/memory_echo_veil.py +394 -0
- algo_cli/memory_runtime.py +112 -0
- algo_cli/model_info.py +548 -0
- algo_cli/model_profile.py +160 -0
- algo_cli/model_routing.py +74 -0
- algo_cli/oneshot.py +331 -0
- algo_cli/perf_telemetry.py +389 -0
- algo_cli/plugins.py +245 -0
- algo_cli/private_event_store.py +654 -0
- algo_cli/quantization/__init__.py +24 -0
- algo_cli/quantization/lloyd_max.py +98 -0
- algo_cli/quantization/turbo_quant.py +308 -0
- algo_cli/reasoning/__init__.py +46 -0
- algo_cli/reasoning/combinatorial.py +356 -0
- algo_cli/reasoning/graph_of_thought.py +297 -0
- algo_cli/reasoning/mcts.py +220 -0
- algo_cli/reasoning/neuro_symbolic.py +250 -0
- algo_cli/reasoning/react.py +246 -0
- algo_cli/reasoning/reflexion.py +225 -0
- algo_cli/reasoning/tree_of_thought.py +241 -0
- algo_cli/reasoning_bridge.py +150 -0
- algo_cli/reconciliation.py +284 -0
- algo_cli/reflex.py +385 -0
- algo_cli/resources/docs/ALGO.md +13958 -0
- algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
- algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
- algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
- algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
- algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
- algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
- algo_cli/resources/docs/main-split-map.md +35 -0
- algo_cli/resources/docs/privacy-and-context.md +48 -0
- algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
- algo_cli/resources/skills/README.md +26 -0
- algo_cli/resources/skills/algo-cli.md +59 -0
- algo_cli/resources/skills/edit-file-precision.md +49 -0
- algo_cli/resources/skills/harness-search-first.md +47 -0
- algo_cli/resources/skills/memory-recall-ritual.md +51 -0
- algo_cli/resources/skills/qol-algorithms.md +224 -0
- algo_cli/resources/skills/smart-error-recovery.md +56 -0
- algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
- algo_cli/retrieval_algorithms.py +127 -0
- algo_cli/runtime_qos.py +236 -0
- algo_cli/runtime_services.py +320 -0
- algo_cli/session_commands.py +95 -0
- algo_cli/session_mode.py +113 -0
- algo_cli/skills.py +430 -0
- algo_cli/slash_dispatch.py +1265 -0
- algo_cli/small_context.py +206 -0
- algo_cli/spawn_budget.py +89 -0
- algo_cli/task_ledger.py +84 -0
- algo_cli/task_router.py +197 -0
- algo_cli/tool_context.py +94 -0
- algo_cli/tool_contract.py +99 -0
- algo_cli/tool_policy.py +357 -0
- algo_cli/tool_runtime.py +647 -0
- algo_cli/tools.py +3056 -0
- algo_cli/url_scheme.py +174 -0
- algo_cli/verify.py +154 -0
- algo_cli/version_manifest.py +178 -0
- algo_cli/vision_screenshot_verify.py +76 -0
- algo_cli/workspace_resolver.py +68 -0
- algo_cli/x_account.py +209 -0
- algo_cli/xai_auth.py +374 -0
- algo_cli/xai_client.py +600 -0
- algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
- algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
- algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
- algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
- algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
)
|
algo_cli/spawn_budget.py
ADDED
|
@@ -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"
|
algo_cli/task_ledger.py
ADDED
|
@@ -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
|
algo_cli/task_router.py
ADDED
|
@@ -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"
|
algo_cli/tool_context.py
ADDED
|
@@ -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"]
|