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,132 @@
|
|
|
1
|
+
"""B61. Content Extraction Pipeline (Trafilatura Pattern).
|
|
2
|
+
|
|
3
|
+
HTML → clean text/markdown with metadata, sitemap/feed discovery, URL dedup.
|
|
4
|
+
Source: trafilatura pattern.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum, auto
|
|
11
|
+
from typing import Iterable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ContentType(Enum):
|
|
15
|
+
HTML = auto()
|
|
16
|
+
MARKDOWN = auto()
|
|
17
|
+
PLAINTEXT = auto()
|
|
18
|
+
PDF = auto()
|
|
19
|
+
JSON = auto()
|
|
20
|
+
UNKNOWN = auto()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ExtractedContent:
|
|
25
|
+
title: str = ""
|
|
26
|
+
text: str = ""
|
|
27
|
+
author: str = ""
|
|
28
|
+
date: str = ""
|
|
29
|
+
url: str = ""
|
|
30
|
+
content_type: ContentType = ContentType.PLAINTEXT
|
|
31
|
+
paragraphs: list[str] = field(default_factory=list)
|
|
32
|
+
links: list[str] = field(default_factory=list)
|
|
33
|
+
metadata: dict[str, str] = field(default_factory=dict)
|
|
34
|
+
word_count: int = 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ContentExtractor:
|
|
38
|
+
"""Extract clean text and metadata from HTML."""
|
|
39
|
+
|
|
40
|
+
# Tags to strip entirely
|
|
41
|
+
STRIP_TAGS = {"script", "style", "nav", "footer", "header", "aside", "noscript", "iframe"}
|
|
42
|
+
# Tags that contain main content
|
|
43
|
+
CONTENT_TAGS = {"article", "main", "section", "div"}
|
|
44
|
+
# Block-level tags
|
|
45
|
+
BLOCK_TAGS = {"p", "h1", "h2", "h3", "h4", "h5", "h6", "li", "blockquote", "pre", "td", "th"}
|
|
46
|
+
|
|
47
|
+
def extract(self, html: str, url: str = "") -> ExtractedContent:
|
|
48
|
+
result = ExtractedContent(url=url, content_type=ContentType.HTML)
|
|
49
|
+
|
|
50
|
+
# Extract title
|
|
51
|
+
title_match = re.search(r"<title[^>]*>(.*?)</title>", html, re.IGNORECASE | re.DOTALL)
|
|
52
|
+
if title_match:
|
|
53
|
+
result.title = title_match.group(1).strip()
|
|
54
|
+
|
|
55
|
+
# Extract metadata
|
|
56
|
+
for match in re.finditer(r'<meta\s+(?:name|property)=["\']([^"\']+)["\']\s+content=["\']([^"\']*)["\']', html, re.IGNORECASE):
|
|
57
|
+
result.metadata[match.group(1)] = match.group(2)
|
|
58
|
+
if match.group(1).lower() == "author":
|
|
59
|
+
result.author = match.group(2)
|
|
60
|
+
if match.group(1).lower() in ("date", "article:published_time", "publish_date"):
|
|
61
|
+
result.date = match.group(2)
|
|
62
|
+
|
|
63
|
+
# Strip unwanted tags
|
|
64
|
+
cleaned = html
|
|
65
|
+
for tag in self.STRIP_TAGS:
|
|
66
|
+
cleaned = re.sub(rf"<{tag}[^>]*>.*?</{tag}>", "", cleaned, flags=re.IGNORECASE | re.DOTALL)
|
|
67
|
+
|
|
68
|
+
# Extract links
|
|
69
|
+
for match in re.finditer(r'href=["\']([^"\']+)["\']', cleaned, re.IGNORECASE):
|
|
70
|
+
result.links.append(match.group(1))
|
|
71
|
+
|
|
72
|
+
# Convert to text
|
|
73
|
+
text = cleaned
|
|
74
|
+
# Replace block tags with newlines
|
|
75
|
+
for tag in self.BLOCK_TAGS:
|
|
76
|
+
text = re.sub(rf"</?{tag}[^>]*>", "\n", text, flags=re.IGNORECASE)
|
|
77
|
+
# Replace <br> with newline
|
|
78
|
+
text = re.sub(r"<br\s*/?>", "\n", text, flags=re.IGNORECASE)
|
|
79
|
+
# Strip all remaining tags
|
|
80
|
+
text = re.sub(r"<[^>]+>", "", text)
|
|
81
|
+
# Decode entities
|
|
82
|
+
text = text.replace("&", "&").replace("<", "<").replace(">", ">").replace(""", '"').replace("'", "'").replace(" ", " ")
|
|
83
|
+
# Normalize whitespace
|
|
84
|
+
text = re.sub(r"\n\s*\n", "\n\n", text).strip()
|
|
85
|
+
|
|
86
|
+
result.text = text
|
|
87
|
+
result.paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
|
|
88
|
+
result.word_count = len(text.split())
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
def to_markdown(self, content: ExtractedContent) -> str:
|
|
92
|
+
"""Convert extracted content to markdown."""
|
|
93
|
+
lines: list[str] = []
|
|
94
|
+
if content.title:
|
|
95
|
+
lines.append(f"# {content.title}")
|
|
96
|
+
lines.append("")
|
|
97
|
+
if content.author:
|
|
98
|
+
lines.append(f"*By {content.author}*")
|
|
99
|
+
if content.date:
|
|
100
|
+
lines.append(f"*{content.date}*")
|
|
101
|
+
if content.url:
|
|
102
|
+
lines.append(f"Source: {content.url}")
|
|
103
|
+
lines.append("")
|
|
104
|
+
for para in content.paragraphs:
|
|
105
|
+
lines.append(para)
|
|
106
|
+
lines.append("")
|
|
107
|
+
return "\n".join(lines).strip()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class URLDeduplicator:
|
|
111
|
+
"""Track seen URLs to avoid re-fetching."""
|
|
112
|
+
|
|
113
|
+
def __init__(self) -> None:
|
|
114
|
+
self._seen: set[str] = set()
|
|
115
|
+
|
|
116
|
+
def is_new(self, url: str) -> bool:
|
|
117
|
+
normalized = self._normalize(url)
|
|
118
|
+
if normalized in self._seen:
|
|
119
|
+
return False
|
|
120
|
+
self._seen.add(normalized)
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
def filter_new(self, urls: Iterable[str]) -> list[str]:
|
|
124
|
+
return [u for u in urls if self.is_new(u)]
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _normalize(url: str) -> str:
|
|
128
|
+
url = url.lower().rstrip("/")
|
|
129
|
+
url = re.sub(r"^https?://", "", url)
|
|
130
|
+
url = re.sub(r"www\.", "", url)
|
|
131
|
+
url = url.split("#")[0]
|
|
132
|
+
return url
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""H21 — Context-Adaptive Parameter Selection.
|
|
2
|
+
|
|
3
|
+
Classify context → select parameters before execution.
|
|
4
|
+
Mined from G0DM0D3 PAPER.md §3.2 AutoTune.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ContextPattern:
|
|
15
|
+
"""A pattern that matches a context and maps to parameters."""
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
keywords: list[str]
|
|
19
|
+
weight: float = 1.0
|
|
20
|
+
parameters: dict[str, Any] = field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ContextClassification:
|
|
25
|
+
"""Result of classifying a context."""
|
|
26
|
+
|
|
27
|
+
matched_patterns: list[str] = field(default_factory=list)
|
|
28
|
+
confidence: float = 0.0
|
|
29
|
+
selected_parameters: dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> dict[str, Any]:
|
|
32
|
+
return {
|
|
33
|
+
"matched_patterns": list(self.matched_patterns),
|
|
34
|
+
"confidence": self.confidence,
|
|
35
|
+
"selected_parameters": dict(self.selected_parameters),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ContextAdaptiveSelector:
|
|
40
|
+
"""Score context against patterns and interpolate parameters."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
self._patterns: list[ContextPattern] = []
|
|
44
|
+
self._defaults: dict[str, Any] = {}
|
|
45
|
+
|
|
46
|
+
def set_defaults(self, params: dict[str, Any]) -> None:
|
|
47
|
+
self._defaults = dict(params)
|
|
48
|
+
|
|
49
|
+
def register_pattern(
|
|
50
|
+
self,
|
|
51
|
+
name: str,
|
|
52
|
+
keywords: list[str],
|
|
53
|
+
parameters: dict[str, Any],
|
|
54
|
+
weight: float = 1.0,
|
|
55
|
+
) -> ContextPattern:
|
|
56
|
+
pattern = ContextPattern(
|
|
57
|
+
name=name, keywords=keywords, weight=weight, parameters=dict(parameters)
|
|
58
|
+
)
|
|
59
|
+
self._patterns.append(pattern)
|
|
60
|
+
return pattern
|
|
61
|
+
|
|
62
|
+
def classify(self, context_text: str, history: list[str] | None = None) -> ContextClassification:
|
|
63
|
+
"""Classify context using weighted pattern scoring (3× current, 1× history)."""
|
|
64
|
+
text_lower = context_text.lower()
|
|
65
|
+
scores: dict[str, float] = {}
|
|
66
|
+
# Current message: 3× weight
|
|
67
|
+
for pattern in self._patterns:
|
|
68
|
+
score = 0.0
|
|
69
|
+
for kw in pattern.keywords:
|
|
70
|
+
if re.search(re.escape(kw.lower()), text_lower):
|
|
71
|
+
score += pattern.weight
|
|
72
|
+
scores[pattern.name] = score * 3.0
|
|
73
|
+
# History: 1× weight
|
|
74
|
+
if history:
|
|
75
|
+
for h in history:
|
|
76
|
+
h_lower = h.lower()
|
|
77
|
+
for pattern in self._patterns:
|
|
78
|
+
for kw in pattern.keywords:
|
|
79
|
+
if re.search(re.escape(kw.lower()), h_lower):
|
|
80
|
+
scores[pattern.name] = scores.get(pattern.name, 0.0) + pattern.weight
|
|
81
|
+
# Select matched patterns (score > 0)
|
|
82
|
+
matched = [name for name, score in scores.items() if score > 0]
|
|
83
|
+
total_score = sum(scores.values())
|
|
84
|
+
confidence = total_score / (total_score + 1.0) if total_score > 0 else 0.0
|
|
85
|
+
# Interpolate parameters
|
|
86
|
+
selected = dict(self._defaults)
|
|
87
|
+
if matched:
|
|
88
|
+
for name in matched:
|
|
89
|
+
pattern = next(p for p in self._patterns if p.name == name)
|
|
90
|
+
for k, v in pattern.parameters.items():
|
|
91
|
+
selected[k] = v
|
|
92
|
+
return ContextClassification(
|
|
93
|
+
matched_patterns=matched,
|
|
94
|
+
confidence=min(confidence, 1.0),
|
|
95
|
+
selected_parameters=selected,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def get_patterns(self) -> list[ContextPattern]:
|
|
99
|
+
return list(self._patterns)
|
|
100
|
+
|
|
101
|
+
def count(self) -> int:
|
|
102
|
+
return len(self._patterns)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""B55. ContextOps: Token Budget Compiler + JIT References.
|
|
2
|
+
|
|
3
|
+
Deterministic context selection with inclusion/exclusion reasons.
|
|
4
|
+
Lazy references: don't load a file unless it fits.
|
|
5
|
+
Source: ctxbudgeter pattern.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum, auto
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InclusionReason(Enum):
|
|
14
|
+
INCLUDED = auto()
|
|
15
|
+
EXCLUDED_BUDGET = auto()
|
|
16
|
+
EXCLUDED_PRIORITY = auto()
|
|
17
|
+
EXCLUDED_DUPLICATE = auto()
|
|
18
|
+
EXCLUDED_STALE = auto()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ContextItem:
|
|
23
|
+
name: str
|
|
24
|
+
content: str
|
|
25
|
+
priority: int = 5 # 1=highest, 10=lowest
|
|
26
|
+
token_estimate: int = 0
|
|
27
|
+
source: str = "unknown"
|
|
28
|
+
inclusion: InclusionReason = InclusionReason.INCLUDED
|
|
29
|
+
exclusion_reason: str = ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ContextBOM:
|
|
34
|
+
"""Bill of Materials for context — auditable record of what entered/was excluded."""
|
|
35
|
+
included: list[ContextItem] = field(default_factory=list)
|
|
36
|
+
excluded: list[ContextItem] = field(default_factory=list)
|
|
37
|
+
total_tokens: int = 0
|
|
38
|
+
budget: int = 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TokenBudgetCompiler:
|
|
42
|
+
"""Compile context deterministically within a token budget."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, budget_tokens: int = 8000):
|
|
45
|
+
self.budget = budget_tokens
|
|
46
|
+
|
|
47
|
+
def _estimate_tokens(self, content: str) -> int:
|
|
48
|
+
return max(1, len(content) // 4)
|
|
49
|
+
|
|
50
|
+
def compile(self, items: list[ContextItem]) -> ContextBOM:
|
|
51
|
+
"""Select items to fit within budget, sorted by priority."""
|
|
52
|
+
# Sort by priority (1=highest), then by token estimate (smaller first)
|
|
53
|
+
sorted_items = sorted(items, key=lambda x: (x.priority, self._estimate_tokens(x.content)))
|
|
54
|
+
|
|
55
|
+
bom = ContextBOM(budget=self.budget)
|
|
56
|
+
used = 0
|
|
57
|
+
|
|
58
|
+
for item in sorted_items:
|
|
59
|
+
tokens = self._estimate_tokens(item.content)
|
|
60
|
+
item.token_estimate = tokens
|
|
61
|
+
|
|
62
|
+
if used + tokens <= self.budget:
|
|
63
|
+
item.inclusion = InclusionReason.INCLUDED
|
|
64
|
+
bom.included.append(item)
|
|
65
|
+
used += tokens
|
|
66
|
+
else:
|
|
67
|
+
item.inclusion = InclusionReason.EXCLUDED_BUDGET
|
|
68
|
+
item.exclusion_reason = f"Would exceed budget ({used + tokens}/{self.budget})"
|
|
69
|
+
bom.excluded.append(item)
|
|
70
|
+
|
|
71
|
+
bom.total_tokens = used
|
|
72
|
+
return bom
|
|
73
|
+
|
|
74
|
+
def jit_reference(self, name: str, loader: callable, current_tokens: int) -> str | None:
|
|
75
|
+
"""Load a reference only if it fits in remaining budget."""
|
|
76
|
+
remaining = self.budget - current_tokens
|
|
77
|
+
if remaining <= 100:
|
|
78
|
+
return None
|
|
79
|
+
content = loader()
|
|
80
|
+
tokens = self._estimate_tokens(content)
|
|
81
|
+
if tokens <= remaining:
|
|
82
|
+
return content
|
|
83
|
+
return None # Doesn't fit — skip
|
|
84
|
+
|
|
85
|
+
def render_bom(self, bom: ContextBOM) -> str:
|
|
86
|
+
"""Render BOM as auditable markdown."""
|
|
87
|
+
lines = [f"# Context BOM ({bom.total_tokens}/{bom.budget} tokens)", ""]
|
|
88
|
+
lines.append("## Included")
|
|
89
|
+
for item in bom.included:
|
|
90
|
+
lines.append(f"- {item.name} ({item.token_estimate}t, P{item.priority})")
|
|
91
|
+
lines.append("")
|
|
92
|
+
lines.append("## Excluded")
|
|
93
|
+
for item in bom.excluded:
|
|
94
|
+
lines.append(f"- {item.name} ({item.token_estimate}t) — {item.exclusion_reason}")
|
|
95
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Count-Min Sketch — sublinear-space frequency estimation.
|
|
2
|
+
|
|
3
|
+
A probabilistic data structure that estimates the frequency of items in a
|
|
4
|
+
data stream using d independent hash functions and a d×w counter array.
|
|
5
|
+
Memory is O(d*w) instead of O(unique_items). Estimates never underestimate
|
|
6
|
+
but may overestimate (false positives only).
|
|
7
|
+
|
|
8
|
+
Harness use:
|
|
9
|
+
- Track tool call frequency ("how often is read_file called?")
|
|
10
|
+
- Track error frequency ("how many times has embed failed?")
|
|
11
|
+
- Detect heavy hitters in the agent loop
|
|
12
|
+
- Feed frequency data to the intuition engine
|
|
13
|
+
|
|
14
|
+
Operations:
|
|
15
|
+
- update(item, count=1): O(d) — increment counters
|
|
16
|
+
- estimate(item): O(d) — return min of d counters
|
|
17
|
+
- merge(other): add two sketches
|
|
18
|
+
- heavy_hitters(threshold): items above threshold (requires tracking)
|
|
19
|
+
|
|
20
|
+
Properties:
|
|
21
|
+
- Never underestimates (true_count <= estimate)
|
|
22
|
+
- Overestimate bounded by: ε·N with probability 1-δ
|
|
23
|
+
where w = ceil(e/ε), d = ceil(ln(1/δ)), N = total count
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import math
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CountMinSketch:
|
|
33
|
+
"""Count-Min Sketch with d×w counter array.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
epsilon: Error bound (overestimate <= epsilon * total_count).
|
|
37
|
+
delta: Failure probability (Pr[overestimate > epsilon*N] < delta).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, epsilon: float = 0.01, delta: float = 0.01) -> None:
|
|
41
|
+
self.epsilon = epsilon
|
|
42
|
+
self.delta = delta
|
|
43
|
+
self.width = max(1, int(math.ceil(math.e / epsilon)))
|
|
44
|
+
self.depth = max(1, int(math.ceil(math.log(1 / delta))))
|
|
45
|
+
self._table: list[list[int]] = [[0] * self.width for _ in range(self.depth)]
|
|
46
|
+
self.total_count = 0
|
|
47
|
+
|
|
48
|
+
def _hash(self, item: Any, row: int) -> int:
|
|
49
|
+
"""Hash item to a column index for a given row."""
|
|
50
|
+
data = f"{row}:{item}".encode("utf-8")
|
|
51
|
+
h = int.from_bytes(hashlib.md5(data).digest()[:8], "little")
|
|
52
|
+
return h % self.width
|
|
53
|
+
|
|
54
|
+
def update(self, item: Any, count: int = 1) -> None:
|
|
55
|
+
"""Increment the count for item by count (default 1)."""
|
|
56
|
+
for row in range(self.depth):
|
|
57
|
+
col = self._hash(item, row)
|
|
58
|
+
self._table[row][col] += count
|
|
59
|
+
self.total_count += count
|
|
60
|
+
|
|
61
|
+
def estimate(self, item: Any) -> int:
|
|
62
|
+
"""Estimate the frequency of item. Never underestimates."""
|
|
63
|
+
return min(
|
|
64
|
+
self._table[row][self._hash(item, row)]
|
|
65
|
+
for row in range(self.depth)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def merge(self, other: CountMinSketch) -> None:
|
|
69
|
+
"""Merge another sketch into this one (element-wise max)."""
|
|
70
|
+
if self.width != other.width or self.depth != other.depth:
|
|
71
|
+
raise ValueError("Cannot merge sketches with different dimensions")
|
|
72
|
+
for row in range(self.depth):
|
|
73
|
+
for col in range(self.width):
|
|
74
|
+
self._table[row][col] += other._table[row][col]
|
|
75
|
+
self.total_count += other.total_count
|
|
76
|
+
|
|
77
|
+
def inner_product(self, other: CountMinSketch) -> int:
|
|
78
|
+
"""Estimate the inner product of two frequency vectors.
|
|
79
|
+
|
|
80
|
+
Useful for computing dot products of item frequency distributions.
|
|
81
|
+
"""
|
|
82
|
+
if self.width != other.width or self.depth != other.depth:
|
|
83
|
+
raise ValueError("Dimensions must match")
|
|
84
|
+
return min(
|
|
85
|
+
sum(self._table[row][col] * other._table[row][col]
|
|
86
|
+
for col in range(self.width))
|
|
87
|
+
for row in range(self.depth)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def stats(self) -> dict[str, Any]:
|
|
91
|
+
return {
|
|
92
|
+
"width": self.width,
|
|
93
|
+
"depth": self.depth,
|
|
94
|
+
"memory_cells": self.width * self.depth,
|
|
95
|
+
"total_count": self.total_count,
|
|
96
|
+
"epsilon": self.epsilon,
|
|
97
|
+
"delta": self.delta,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class HeavyHitters:
|
|
102
|
+
"""Track top-K frequent items using Space-Saving algorithm.
|
|
103
|
+
|
|
104
|
+
Maintains an exact count of the top-K items seen so far, using O(K) memory.
|
|
105
|
+
Based on Metwally et al. (2005) Space-Saving algorithm.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
k: Number of heavy hitters to track.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(self, k: int = 10) -> None:
|
|
112
|
+
self.k = k
|
|
113
|
+
self._counts: dict[str, int] = {}
|
|
114
|
+
self._total = 0
|
|
115
|
+
|
|
116
|
+
def update(self, item: Any, count: int = 1) -> None:
|
|
117
|
+
"""Observe an item."""
|
|
118
|
+
key = str(item)
|
|
119
|
+
self._total += count
|
|
120
|
+
if key in self._counts:
|
|
121
|
+
self._counts[key] += count
|
|
122
|
+
elif len(self._counts) < self.k:
|
|
123
|
+
self._counts[key] = count
|
|
124
|
+
else:
|
|
125
|
+
# Replace the item with minimum count
|
|
126
|
+
min_key = min(self._counts, key=self._counts.get)
|
|
127
|
+
min_count = self._counts[min_key]
|
|
128
|
+
del self._counts[min_key]
|
|
129
|
+
self._counts[key] = min_count + count
|
|
130
|
+
|
|
131
|
+
def top_k(self) -> list[tuple[str, int]]:
|
|
132
|
+
"""Return the top-K items sorted by count (descending)."""
|
|
133
|
+
return sorted(self._counts.items(), key=lambda x: -x[1])
|
|
134
|
+
|
|
135
|
+
def estimate(self, item: Any) -> int:
|
|
136
|
+
"""Estimate the count of an item (lower bound)."""
|
|
137
|
+
return self._counts.get(str(item), 0)
|
|
138
|
+
|
|
139
|
+
def stats(self) -> dict[str, Any]:
|
|
140
|
+
return {
|
|
141
|
+
"k": self.k,
|
|
142
|
+
"tracked_items": len(self._counts),
|
|
143
|
+
"total_observations": self._total,
|
|
144
|
+
"top": self.top_k()[:5],
|
|
145
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""B71. Copy-On-Write State Isolation for Parallel Agents.
|
|
2
|
+
|
|
3
|
+
COW state per parallel agent. Shared read-only history.
|
|
4
|
+
Diff merge back to main context.
|
|
5
|
+
Source: gecko pattern.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class COWState:
|
|
15
|
+
"""Copy-on-write state for a parallel agent."""
|
|
16
|
+
agent_id: str
|
|
17
|
+
parent_state: dict[str, Any] # read-only reference
|
|
18
|
+
local_changes: dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
deleted_keys: set[str] = field(default_factory=set)
|
|
20
|
+
|
|
21
|
+
def get(self, key: str) -> Any:
|
|
22
|
+
if key in self.deleted_keys:
|
|
23
|
+
return None
|
|
24
|
+
if key in self.local_changes:
|
|
25
|
+
return self.local_changes[key]
|
|
26
|
+
return self.parent_state.get(key)
|
|
27
|
+
|
|
28
|
+
def set(self, key: str, value: Any) -> None:
|
|
29
|
+
self.local_changes[key] = value
|
|
30
|
+
self.deleted_keys.discard(key)
|
|
31
|
+
|
|
32
|
+
def delete(self, key: str) -> None:
|
|
33
|
+
self.deleted_keys.add(key)
|
|
34
|
+
self.local_changes.pop(key, None)
|
|
35
|
+
|
|
36
|
+
def diff(self) -> dict[str, Any]:
|
|
37
|
+
"""Return only the changes."""
|
|
38
|
+
return {
|
|
39
|
+
"added_or_modified": dict(self.local_changes),
|
|
40
|
+
"deleted": list(self.deleted_keys),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def snapshot(self) -> dict[str, Any]:
|
|
44
|
+
"""Full snapshot: parent + local changes."""
|
|
45
|
+
result = dict(self.parent_state)
|
|
46
|
+
result.update(self.local_changes)
|
|
47
|
+
for key in self.deleted_keys:
|
|
48
|
+
result.pop(key, None)
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class COWStateManager:
|
|
53
|
+
"""Manage COW states for parallel agents."""
|
|
54
|
+
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
self._main_state: dict[str, Any] = {}
|
|
57
|
+
self._agent_states: dict[str, COWState] = {}
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def main_state(self) -> dict[str, Any]:
|
|
61
|
+
return dict(self._main_state)
|
|
62
|
+
|
|
63
|
+
def update_main(self, key: str, value: Any) -> None:
|
|
64
|
+
self._main_state[key] = value
|
|
65
|
+
|
|
66
|
+
def fork(self, agent_id: str) -> COWState:
|
|
67
|
+
"""Create a COW fork for an agent."""
|
|
68
|
+
state = COWState(agent_id=agent_id, parent_state=self._main_state)
|
|
69
|
+
self._agent_states[agent_id] = state
|
|
70
|
+
return state
|
|
71
|
+
|
|
72
|
+
def get_state(self, agent_id: str) -> COWState | None:
|
|
73
|
+
return self._agent_states.get(agent_id)
|
|
74
|
+
|
|
75
|
+
def merge(self, agent_id: str) -> dict[str, Any]:
|
|
76
|
+
"""Merge agent's changes back to main state."""
|
|
77
|
+
state = self._agent_states.get(agent_id)
|
|
78
|
+
if not state:
|
|
79
|
+
return dict(self._main_state)
|
|
80
|
+
|
|
81
|
+
# Apply changes
|
|
82
|
+
for key, value in state.local_changes.items():
|
|
83
|
+
self._main_state[key] = value
|
|
84
|
+
for key in state.deleted_keys:
|
|
85
|
+
self._main_state.pop(key, None)
|
|
86
|
+
|
|
87
|
+
# Clean up agent state
|
|
88
|
+
del self._agent_states[agent_id]
|
|
89
|
+
return dict(self._main_state)
|
|
90
|
+
|
|
91
|
+
def merge_all(self) -> dict[str, Any]:
|
|
92
|
+
"""Merge all agent states back to main."""
|
|
93
|
+
for agent_id in list(self._agent_states.keys()):
|
|
94
|
+
self.merge(agent_id)
|
|
95
|
+
return dict(self._main_state)
|
|
96
|
+
|
|
97
|
+
def discard(self, agent_id: str) -> None:
|
|
98
|
+
"""Discard an agent's changes without merging."""
|
|
99
|
+
self._agent_states.pop(agent_id, None)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def active_forks(self) -> int:
|
|
103
|
+
return len(self._agent_states)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""B64. Critic Loop + Budget Guard.
|
|
2
|
+
|
|
3
|
+
Quality-gated iteration with cost/iteration kill switch.
|
|
4
|
+
Source: deep-research-agent pattern.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum, auto
|
|
11
|
+
from typing import Any, Callable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CriticDimension(Enum):
|
|
15
|
+
COVERAGE = auto()
|
|
16
|
+
RECENCY = auto()
|
|
17
|
+
DEPTH = auto()
|
|
18
|
+
DIVERSITY = auto()
|
|
19
|
+
ACCURACY = auto()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class CriticScore:
|
|
24
|
+
dimension: CriticDimension
|
|
25
|
+
score: float # 0.0 to 1.0
|
|
26
|
+
notes: str = ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class CriticResult:
|
|
31
|
+
scores: list[CriticScore] = field(default_factory=list)
|
|
32
|
+
overall: float = 0.0
|
|
33
|
+
passed: bool = False
|
|
34
|
+
recommendations: list[str] = field(default_factory=list)
|
|
35
|
+
iteration: int = 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class BudgetGuard:
|
|
40
|
+
max_iterations: int = 5
|
|
41
|
+
max_cost: float = 1.0 # abstract cost units
|
|
42
|
+
max_time_s: float = 120.0
|
|
43
|
+
current_cost: float = 0.0
|
|
44
|
+
current_time: float = 0.0
|
|
45
|
+
current_iterations: int = 0
|
|
46
|
+
_start_time: float = field(default_factory=time.time)
|
|
47
|
+
|
|
48
|
+
def can_proceed(self) -> bool:
|
|
49
|
+
self.current_time = time.time() - self._start_time
|
|
50
|
+
return (
|
|
51
|
+
self.current_iterations < self.max_iterations
|
|
52
|
+
and self.current_cost < self.max_cost
|
|
53
|
+
and self.current_time < self.max_time_s
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def spend(self, cost: float = 0.1) -> None:
|
|
57
|
+
self.current_cost += cost
|
|
58
|
+
self.current_iterations += 1
|
|
59
|
+
|
|
60
|
+
def remaining_budget(self) -> dict[str, float]:
|
|
61
|
+
return {
|
|
62
|
+
"iterations": max(0, self.max_iterations - self.current_iterations),
|
|
63
|
+
"cost": max(0, self.max_cost - self.current_cost),
|
|
64
|
+
"time_s": max(0, self.max_time_s - (time.time() - self._start_time)),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CriticLoop:
|
|
69
|
+
"""Quality-gated iteration loop with budget guard."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, threshold: float = 0.8,
|
|
72
|
+
budget: BudgetGuard | None = None) -> None:
|
|
73
|
+
self._threshold = threshold
|
|
74
|
+
self._budget = budget or BudgetGuard()
|
|
75
|
+
self._scorers: dict[CriticDimension, Callable[[Any], float]] = {}
|
|
76
|
+
|
|
77
|
+
def register_scorer(self, dimension: CriticDimension,
|
|
78
|
+
scorer: Callable[[Any], float]) -> None:
|
|
79
|
+
self._scorers[dimension] = scorer
|
|
80
|
+
|
|
81
|
+
def evaluate(self, artifact: Any, iteration: int) -> CriticResult:
|
|
82
|
+
result = CriticResult(iteration=iteration)
|
|
83
|
+
for dim, scorer in self._scorers.items():
|
|
84
|
+
try:
|
|
85
|
+
score = scorer(artifact)
|
|
86
|
+
result.scores.append(CriticScore(dimension=dim, score=score))
|
|
87
|
+
except Exception:
|
|
88
|
+
result.scores.append(CriticScore(dimension=dim, score=0.0, notes="scorer error"))
|
|
89
|
+
|
|
90
|
+
result.overall = sum(s.score for s in result.scores) / len(result.scores) if result.scores else 0.0
|
|
91
|
+
result.passed = result.overall >= self._threshold
|
|
92
|
+
|
|
93
|
+
if not result.passed:
|
|
94
|
+
for s in result.scores:
|
|
95
|
+
if s.score < self._threshold:
|
|
96
|
+
result.recommendations.append(f"Improve {s.dimension.name.lower()}: {s.score:.0%}")
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
def run(self, produce_fn: Callable[[int], Any],
|
|
101
|
+
improve_fn: Callable[[Any, CriticResult], Any] | None = None,
|
|
102
|
+
cost_per_iteration: float = 0.1) -> tuple[Any, CriticResult]:
|
|
103
|
+
"""Run the critic loop until threshold or budget exhausted."""
|
|
104
|
+
artifact = None
|
|
105
|
+
result = CriticResult()
|
|
106
|
+
|
|
107
|
+
while self._budget.can_proceed():
|
|
108
|
+
self._budget.spend(cost_per_iteration)
|
|
109
|
+
artifact = produce_fn(self._budget.current_iterations)
|
|
110
|
+
result = self.evaluate(artifact, self._budget.current_iterations)
|
|
111
|
+
|
|
112
|
+
if result.passed:
|
|
113
|
+
break
|
|
114
|
+
if improve_fn:
|
|
115
|
+
artifact = improve_fn(artifact, result)
|
|
116
|
+
else:
|
|
117
|
+
result.recommendations.append("Budget exhausted")
|
|
118
|
+
|
|
119
|
+
return artifact, result
|