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,147 @@
|
|
|
1
|
+
"""B60. LSP Integration for Code Intelligence.
|
|
2
|
+
|
|
3
|
+
Language Server Protocol integration for go-to-definition, hover,
|
|
4
|
+
diagnostics, and references. Source: copilot-cli pattern.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
import json
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum, auto
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LSPServerStatus(Enum):
|
|
17
|
+
STOPPED = auto()
|
|
18
|
+
STARTING = auto()
|
|
19
|
+
READY = auto()
|
|
20
|
+
ERROR = auto()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class LSPDiagnostic:
|
|
25
|
+
severity: str # "error", "warning", "info", "hint"
|
|
26
|
+
message: str
|
|
27
|
+
line: int # 0-based
|
|
28
|
+
col: int
|
|
29
|
+
end_line: int = 0
|
|
30
|
+
end_col: int = 0
|
|
31
|
+
source: str = ""
|
|
32
|
+
code: str = ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class LSPDefinition:
|
|
37
|
+
file: str
|
|
38
|
+
line: int
|
|
39
|
+
col: int
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class LSPHover:
|
|
44
|
+
contents: str
|
|
45
|
+
range_line: int = 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class LSPServer:
|
|
50
|
+
name: str
|
|
51
|
+
command: list[str]
|
|
52
|
+
languages: list[str]
|
|
53
|
+
status: LSPServerStatus = LSPServerStatus.STOPPED
|
|
54
|
+
process: Any = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class LSPManager:
|
|
58
|
+
"""Manage LSP servers and provide code intelligence."""
|
|
59
|
+
|
|
60
|
+
# Common LSP server configs
|
|
61
|
+
SERVER_CONFIGS: dict[str, LSPServer] = {
|
|
62
|
+
"pyright": LSPServer(
|
|
63
|
+
name="pyright",
|
|
64
|
+
command=["pyright-langserver", "--stdio"],
|
|
65
|
+
languages=["python"],
|
|
66
|
+
),
|
|
67
|
+
"pylsp": LSPServer(
|
|
68
|
+
name="pylsp",
|
|
69
|
+
command=["pylsp"],
|
|
70
|
+
languages=["python"],
|
|
71
|
+
),
|
|
72
|
+
"typescript": LSPServer(
|
|
73
|
+
name="typescript-language-server",
|
|
74
|
+
command=["typescript-language-server", "--stdio"],
|
|
75
|
+
languages=["typescript", "javascript"],
|
|
76
|
+
),
|
|
77
|
+
"rust-analyzer": LSPServer(
|
|
78
|
+
name="rust-analyzer",
|
|
79
|
+
command=["rust-analyzer"],
|
|
80
|
+
languages=["rust"],
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def __init__(self) -> None:
|
|
85
|
+
self._servers: dict[str, LSPServer] = {}
|
|
86
|
+
self._file_to_server: dict[str, str] = {}
|
|
87
|
+
|
|
88
|
+
def register_server(self, server: LSPServer) -> None:
|
|
89
|
+
self._servers[server.name] = server
|
|
90
|
+
for lang in server.languages:
|
|
91
|
+
self._file_to_server[lang] = server.name
|
|
92
|
+
|
|
93
|
+
def get_server_for_file(self, file_path: str) -> LSPServer | None:
|
|
94
|
+
ext = Path(file_path).suffix.lower()
|
|
95
|
+
lang_map = {
|
|
96
|
+
".py": "python",
|
|
97
|
+
".ts": "typescript",
|
|
98
|
+
".js": "javascript",
|
|
99
|
+
".rs": "rust",
|
|
100
|
+
}
|
|
101
|
+
lang = lang_map.get(ext)
|
|
102
|
+
if not lang:
|
|
103
|
+
return None
|
|
104
|
+
server_name = self._file_to_server.get(lang)
|
|
105
|
+
if not server_name:
|
|
106
|
+
return None
|
|
107
|
+
return self._servers.get(server_name)
|
|
108
|
+
|
|
109
|
+
def parse_diagnostics(self, output: str) -> list[LSPDiagnostic]:
|
|
110
|
+
"""Parse LSP diagnostic output."""
|
|
111
|
+
diags: list[LSPDiagnostic] = []
|
|
112
|
+
try:
|
|
113
|
+
data = json.loads(output)
|
|
114
|
+
for item in data.get("diagnostics", []):
|
|
115
|
+
diags.append(LSPDiagnostic(
|
|
116
|
+
severity=item.get("severity", "info"),
|
|
117
|
+
message=item.get("message", ""),
|
|
118
|
+
line=item.get("range", {}).get("start", {}).get("line", 0),
|
|
119
|
+
col=item.get("range", {}).get("start", {}).get("character", 0),
|
|
120
|
+
end_line=item.get("range", {}).get("end", {}).get("line", 0),
|
|
121
|
+
end_col=item.get("range", {}).get("end", {}).get("character", 0),
|
|
122
|
+
source=item.get("source", ""),
|
|
123
|
+
code=item.get("code", ""),
|
|
124
|
+
))
|
|
125
|
+
except (json.JSONDecodeError, KeyError):
|
|
126
|
+
pass
|
|
127
|
+
return diags
|
|
128
|
+
|
|
129
|
+
def check_available(self) -> dict[str, bool]:
|
|
130
|
+
"""Check which LSP servers are installed."""
|
|
131
|
+
available: dict[str, bool] = {}
|
|
132
|
+
for name, server in self.SERVER_CONFIGS.items():
|
|
133
|
+
try:
|
|
134
|
+
cmd = server.command[0]
|
|
135
|
+
result = subprocess.run(
|
|
136
|
+
["where" if _is_windows() else "which", cmd],
|
|
137
|
+
capture_output=True, timeout=5,
|
|
138
|
+
)
|
|
139
|
+
available[name] = result.returncode == 0
|
|
140
|
+
except Exception:
|
|
141
|
+
available[name] = False
|
|
142
|
+
return available
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _is_windows() -> bool:
|
|
146
|
+
import sys
|
|
147
|
+
return sys.platform == "win32"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""B56. Memory Skill Evolution.
|
|
2
|
+
|
|
3
|
+
Learn reusable memory skills from task feedback. Evolve from hard cases.
|
|
4
|
+
Source: MemSkill pattern.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum, auto
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SkillStatus(Enum):
|
|
14
|
+
CANDIDATE = auto()
|
|
15
|
+
ACTIVE = auto()
|
|
16
|
+
DEPRECATED = auto()
|
|
17
|
+
EVOLVED = auto()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class MemorySkill:
|
|
22
|
+
name: str
|
|
23
|
+
pattern: str # what to remember
|
|
24
|
+
trigger: str # when to apply
|
|
25
|
+
confidence: float = 0.5
|
|
26
|
+
uses: int = 0
|
|
27
|
+
successes: int = 0
|
|
28
|
+
failures: int = 0
|
|
29
|
+
status: SkillStatus = SkillStatus.CANDIDATE
|
|
30
|
+
created_at: float = field(default_factory=time.time)
|
|
31
|
+
evolved_from: str | None = None
|
|
32
|
+
hard_cases: list[str] = field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class TaskFeedback:
|
|
37
|
+
task: str
|
|
38
|
+
memory_used: list[str] # skill names
|
|
39
|
+
outcome: str # "success", "failure", "partial"
|
|
40
|
+
missing_info: str = "" # what was missing?
|
|
41
|
+
wrong_info: str = "" # what was wrong/stale?
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MemorySkillEvolver:
|
|
45
|
+
"""Evolve memory skills from task feedback."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, confidence_threshold: float = 0.7,
|
|
48
|
+
deprecation_threshold: float = 0.3) -> None:
|
|
49
|
+
self._skills: dict[str, MemorySkill] = {}
|
|
50
|
+
self._feedback_history: list[TaskFeedback] = []
|
|
51
|
+
self._confidence_threshold = confidence_threshold
|
|
52
|
+
self._deprecation_threshold = deprecation_threshold
|
|
53
|
+
|
|
54
|
+
def register(self, skill: MemorySkill) -> None:
|
|
55
|
+
self._skills[skill.name] = skill
|
|
56
|
+
|
|
57
|
+
def record_feedback(self, feedback: TaskFeedback) -> None:
|
|
58
|
+
self._feedback_history.append(feedback)
|
|
59
|
+
for skill_name in feedback.memory_used:
|
|
60
|
+
skill = self._skills.get(skill_name)
|
|
61
|
+
if not skill:
|
|
62
|
+
continue
|
|
63
|
+
skill.uses += 1
|
|
64
|
+
if feedback.outcome == "success":
|
|
65
|
+
skill.successes += 1
|
|
66
|
+
elif feedback.outcome == "failure":
|
|
67
|
+
skill.failures += 1
|
|
68
|
+
if feedback.wrong_info:
|
|
69
|
+
skill.hard_cases.append(feedback.wrong_info)
|
|
70
|
+
|
|
71
|
+
def _confidence(self, skill: MemorySkill) -> float:
|
|
72
|
+
if skill.uses == 0:
|
|
73
|
+
return skill.confidence
|
|
74
|
+
return skill.successes / skill.uses
|
|
75
|
+
|
|
76
|
+
def evolve(self) -> list[MemorySkill]:
|
|
77
|
+
"""Promote/deprecate/evolve skills based on feedback."""
|
|
78
|
+
evolved: list[MemorySkill] = []
|
|
79
|
+
for skill in list(self._skills.values()):
|
|
80
|
+
conf = self._confidence(skill)
|
|
81
|
+
if conf >= self._confidence_threshold and skill.status == SkillStatus.CANDIDATE:
|
|
82
|
+
skill.status = SkillStatus.ACTIVE
|
|
83
|
+
evolved.append(skill)
|
|
84
|
+
elif conf < self._deprecation_threshold and skill.status == SkillStatus.ACTIVE:
|
|
85
|
+
skill.status = SkillStatus.DEPRECATED
|
|
86
|
+
evolved.append(skill)
|
|
87
|
+
elif skill.hard_cases and skill.status == SkillStatus.ACTIVE:
|
|
88
|
+
# Evolve: create a refined version
|
|
89
|
+
evolved_name = f"{skill.name}_v2"
|
|
90
|
+
if evolved_name not in self._skills:
|
|
91
|
+
evolved_skill = MemorySkill(
|
|
92
|
+
name=evolved_name,
|
|
93
|
+
pattern=skill.pattern,
|
|
94
|
+
trigger=skill.trigger,
|
|
95
|
+
confidence=0.5,
|
|
96
|
+
status=SkillStatus.CANDIDATE,
|
|
97
|
+
evolved_from=skill.name,
|
|
98
|
+
hard_cases=list(skill.hard_cases),
|
|
99
|
+
)
|
|
100
|
+
self._skills[evolved_name] = evolved_skill
|
|
101
|
+
skill.status = SkillStatus.EVOLVED
|
|
102
|
+
evolved.append(evolved_skill)
|
|
103
|
+
return evolved
|
|
104
|
+
|
|
105
|
+
def mine_hard_cases(self) -> list[dict]:
|
|
106
|
+
"""Find cases where memory was wrong or missing."""
|
|
107
|
+
cases: list[dict] = []
|
|
108
|
+
for fb in self._feedback_history:
|
|
109
|
+
if fb.wrong_info:
|
|
110
|
+
cases.append({"type": "wrong", "info": fb.wrong_info, "task": fb.task})
|
|
111
|
+
if fb.missing_info:
|
|
112
|
+
cases.append({"type": "missing", "info": fb.missing_info, "task": fb.task})
|
|
113
|
+
return cases
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def skills(self) -> dict[str, MemorySkill]:
|
|
117
|
+
return dict(self._skills)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""MinHash + LSH — approximate near-duplicate detection.
|
|
2
|
+
|
|
3
|
+
MinHash estimates Jaccard similarity between sets using k random hash
|
|
4
|
+
functions. LSH (Locality-Sensitive Hashing) buckets similar signatures
|
|
5
|
+
into the same band-slot for O(1) candidate lookup.
|
|
6
|
+
|
|
7
|
+
Harness use:
|
|
8
|
+
- Detect near-duplicate files (rebranded code, copied configs)
|
|
9
|
+
- Find similar conversation contexts for dedup
|
|
10
|
+
- Cluster similar error messages
|
|
11
|
+
- Detect duplicate harness entries
|
|
12
|
+
|
|
13
|
+
Operations:
|
|
14
|
+
- MinHasher.signature(set): compute k-element signature
|
|
15
|
+
- MinHasher.similarity(sig1, sig2): estimated Jaccard similarity
|
|
16
|
+
- LSHIndex.insert(key, signature): add to LSH buckets
|
|
17
|
+
- LSHIndex.query(signature): return candidate near-duplicate keys
|
|
18
|
+
|
|
19
|
+
Properties:
|
|
20
|
+
- Signature computation: O(k * |set|)
|
|
21
|
+
- Similarity estimation: O(k)
|
|
22
|
+
- LSH query: O(bands) expected, where bands = signature_size / rows_per_band
|
|
23
|
+
- Trade-off: more bands = higher recall, more false positives
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MinHasher:
|
|
34
|
+
"""MinHash signature generator.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
num_hashes: Number of hash functions (signature size).
|
|
38
|
+
Higher = more accurate similarity estimation, more memory.
|
|
39
|
+
seed: Random seed for reproducibility.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, num_hashes: int = 128, seed: int = 42) -> None:
|
|
43
|
+
self.num_hashes = num_hashes
|
|
44
|
+
self._seeds = [(seed + i * 2654435761) & 0xFFFFFFFF for i in range(num_hashes)]
|
|
45
|
+
|
|
46
|
+
def _hash(self, item: Any, seed: int) -> int:
|
|
47
|
+
"""Hash an item with a given seed to a 32-bit integer."""
|
|
48
|
+
data = f"{seed}:{item}".encode("utf-8")
|
|
49
|
+
return int.from_bytes(hashlib.md5(data).digest()[:4], "big")
|
|
50
|
+
|
|
51
|
+
def signature(self, items: set[Any] | list[Any]) -> list[int]:
|
|
52
|
+
"""Compute the MinHash signature of a set of items.
|
|
53
|
+
|
|
54
|
+
Returns a list of num_hashes integers, where each is the minimum
|
|
55
|
+
hash value for that hash function across all items.
|
|
56
|
+
"""
|
|
57
|
+
if not items:
|
|
58
|
+
return [0xFFFFFFFF] * self.num_hashes
|
|
59
|
+
items_set = set(items)
|
|
60
|
+
sig = []
|
|
61
|
+
for seed in self._seeds:
|
|
62
|
+
min_val = min(self._hash(item, seed) for item in items_set)
|
|
63
|
+
sig.append(min_val)
|
|
64
|
+
return sig
|
|
65
|
+
|
|
66
|
+
def similarity(self, sig1: list[int], sig2: list[int]) -> float:
|
|
67
|
+
"""Estimate Jaccard similarity from two signatures.
|
|
68
|
+
|
|
69
|
+
Jaccard(A, B) = |A ∩ B| / |A ∪ B|
|
|
70
|
+
MinHash estimates this as the fraction of matching hash positions.
|
|
71
|
+
"""
|
|
72
|
+
if len(sig1) != len(sig2):
|
|
73
|
+
raise ValueError("Signatures must have the same length")
|
|
74
|
+
if not sig1:
|
|
75
|
+
return 0.0
|
|
76
|
+
matches = sum(1 for a, b in zip(sig1, sig2) if a == b)
|
|
77
|
+
return matches / len(sig1)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class LSHIndex:
|
|
82
|
+
"""Locality-Sensitive Hashing index for MinHash signatures.
|
|
83
|
+
|
|
84
|
+
Splits the signature into bands. Two signatures are candidates if
|
|
85
|
+
they share at least one band hash. This provides sublinear query time.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
num_bands: Number of bands (more = higher recall, more false positives).
|
|
89
|
+
rows_per_band: Rows per band (more = higher precision, lower recall).
|
|
90
|
+
num_bands * rows_per_band should equal signature size.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
num_bands: int = 32
|
|
94
|
+
rows_per_band: int = 4
|
|
95
|
+
_buckets: dict[tuple[int, int], set[str]] = field(default_factory=dict)
|
|
96
|
+
_signatures: dict[str, list[int]] = field(default_factory=dict)
|
|
97
|
+
|
|
98
|
+
def __post_init__(self) -> None:
|
|
99
|
+
self._expected_sig_len = self.num_bands * self.rows_per_band
|
|
100
|
+
|
|
101
|
+
def _band_hash(self, signature: list[int], band_idx: int) -> int:
|
|
102
|
+
"""Hash a band of the signature to an integer."""
|
|
103
|
+
start = band_idx * self.rows_per_band
|
|
104
|
+
end = start + self.rows_per_band
|
|
105
|
+
band = tuple(signature[start:end])
|
|
106
|
+
return hash(band)
|
|
107
|
+
|
|
108
|
+
def insert(self, key: str, signature: list[int]) -> None:
|
|
109
|
+
"""Insert a key with its MinHash signature into the LSH index."""
|
|
110
|
+
if len(signature) != self._expected_sig_len:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"Signature length {len(signature)} != expected "
|
|
113
|
+
f"{self._expected_sig_len} (bands={self.num_bands} * "
|
|
114
|
+
f"rows={self.rows_per_band})"
|
|
115
|
+
)
|
|
116
|
+
self._signatures[key] = signature
|
|
117
|
+
for band_idx in range(self.num_bands):
|
|
118
|
+
bh = self._band_hash(signature, band_idx)
|
|
119
|
+
bucket_key = (band_idx, bh)
|
|
120
|
+
if bucket_key not in self._buckets:
|
|
121
|
+
self._buckets[bucket_key] = set()
|
|
122
|
+
self._buckets[bucket_key].add(key)
|
|
123
|
+
|
|
124
|
+
def remove(self, key: str) -> None:
|
|
125
|
+
"""Remove a key from the LSH index."""
|
|
126
|
+
sig = self._signatures.pop(key, None)
|
|
127
|
+
if sig is None:
|
|
128
|
+
return
|
|
129
|
+
for band_idx in range(self.num_bands):
|
|
130
|
+
bh = self._band_hash(sig, band_idx)
|
|
131
|
+
bucket_key = (band_idx, bh)
|
|
132
|
+
if bucket_key in self._buckets:
|
|
133
|
+
self._buckets[bucket_key].discard(key)
|
|
134
|
+
if not self._buckets[bucket_key]:
|
|
135
|
+
del self._buckets[bucket_key]
|
|
136
|
+
|
|
137
|
+
def query(self, signature: list[int]) -> list[str]:
|
|
138
|
+
"""Find candidate near-duplicate keys for a signature.
|
|
139
|
+
|
|
140
|
+
Returns keys that share at least one band hash. These are
|
|
141
|
+
candidates — verify with MinHasher.similarity() to get exact estimate.
|
|
142
|
+
"""
|
|
143
|
+
if len(signature) != self._expected_sig_len:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"Signature length {len(signature)} != expected "
|
|
146
|
+
f"{self._expected_sig_len}"
|
|
147
|
+
)
|
|
148
|
+
candidates: set[str] = set()
|
|
149
|
+
for band_idx in range(self.num_bands):
|
|
150
|
+
bh = self._band_hash(signature, band_idx)
|
|
151
|
+
bucket_key = (band_idx, bh)
|
|
152
|
+
if bucket_key in self._buckets:
|
|
153
|
+
candidates.update(self._buckets[bucket_key])
|
|
154
|
+
return list(candidates)
|
|
155
|
+
|
|
156
|
+
def query_similar(
|
|
157
|
+
self,
|
|
158
|
+
signature: list[int],
|
|
159
|
+
minhasher: MinHasher,
|
|
160
|
+
threshold: float = 0.5,
|
|
161
|
+
) -> list[tuple[str, float]]:
|
|
162
|
+
"""Find near-duplicates above a similarity threshold.
|
|
163
|
+
|
|
164
|
+
Returns list of (key, similarity) sorted by similarity descending.
|
|
165
|
+
"""
|
|
166
|
+
candidates = self.query(signature)
|
|
167
|
+
results: list[tuple[str, float]] = []
|
|
168
|
+
for key in candidates:
|
|
169
|
+
sim = minhasher.similarity(signature, self._signatures[key])
|
|
170
|
+
if sim >= threshold:
|
|
171
|
+
results.append((key, sim))
|
|
172
|
+
results.sort(key=lambda x: -x[1])
|
|
173
|
+
return results
|
|
174
|
+
|
|
175
|
+
def stats(self) -> dict[str, Any]:
|
|
176
|
+
return {
|
|
177
|
+
"num_bands": self.num_bands,
|
|
178
|
+
"rows_per_band": self.rows_per_band,
|
|
179
|
+
"indexed_items": len(self._signatures),
|
|
180
|
+
"num_buckets": len(self._buckets),
|
|
181
|
+
"expected_sig_len": self._expected_sig_len,
|
|
182
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""H12 — Multi-Model Composite Scoring.
|
|
2
|
+
|
|
3
|
+
Score algorithm candidates across a panel of models.
|
|
4
|
+
Mined from G0DM0D3 ULTRAPLINIAN: query N models, score each response,
|
|
5
|
+
pick the winner.
|
|
6
|
+
|
|
7
|
+
LLM integration: requires model clients to query. Falls back to
|
|
8
|
+
rule-based scoring when no models are available.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ModelResponse:
|
|
18
|
+
"""A single model's response to a prompt."""
|
|
19
|
+
|
|
20
|
+
model_name: str
|
|
21
|
+
response: str
|
|
22
|
+
latency_ms: float = 0.0
|
|
23
|
+
error: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ScoredResponse:
|
|
28
|
+
"""A response with its composite score."""
|
|
29
|
+
|
|
30
|
+
model_name: str
|
|
31
|
+
response: str
|
|
32
|
+
score: float
|
|
33
|
+
sub_scores: dict[str, float] = field(default_factory=dict)
|
|
34
|
+
winner: bool = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Scoring dimensions
|
|
38
|
+
SCORE_DIMENSIONS = [
|
|
39
|
+
"directness", # How direct and clear is the response?
|
|
40
|
+
"completeness", # Does it address all parts of the prompt?
|
|
41
|
+
"accuracy", # Is the information correct?
|
|
42
|
+
"conciseness", # Is it appropriately concise?
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _score_dimension(response: str, dimension: str) -> float:
|
|
47
|
+
"""Score a single dimension of a response (rule-based fallback)."""
|
|
48
|
+
if not response:
|
|
49
|
+
return 0.0
|
|
50
|
+
|
|
51
|
+
text = response.lower()
|
|
52
|
+
|
|
53
|
+
if dimension == "directness":
|
|
54
|
+
# Penalize hedging language
|
|
55
|
+
hedge_words = ["maybe", "perhaps", "might", "could", "possibly", "i think"]
|
|
56
|
+
hedge_count = sum(text.count(w) for w in hedge_words)
|
|
57
|
+
return max(0.0, 1.0 - hedge_count * 0.1)
|
|
58
|
+
|
|
59
|
+
if dimension == "completeness":
|
|
60
|
+
# Reward longer responses (up to a point)
|
|
61
|
+
length = len(response)
|
|
62
|
+
if length < 50:
|
|
63
|
+
return length / 50.0
|
|
64
|
+
if length > 500:
|
|
65
|
+
return min(1.0, 500 / length + 0.5)
|
|
66
|
+
return 1.0
|
|
67
|
+
|
|
68
|
+
if dimension == "accuracy":
|
|
69
|
+
# Rule-based: penalize obvious errors (empty, repeated chars)
|
|
70
|
+
if not response.strip():
|
|
71
|
+
return 0.0
|
|
72
|
+
if response.strip() == response[0] * len(response.strip()):
|
|
73
|
+
return 0.1
|
|
74
|
+
return 0.8 # Default neutral-positive
|
|
75
|
+
|
|
76
|
+
if dimension == "conciseness":
|
|
77
|
+
# Reward shorter responses
|
|
78
|
+
length = len(response)
|
|
79
|
+
if length < 100:
|
|
80
|
+
return 1.0
|
|
81
|
+
if length > 2000:
|
|
82
|
+
return 0.3
|
|
83
|
+
return max(0.3, 1.0 - (length - 100) / 1900)
|
|
84
|
+
|
|
85
|
+
return 0.5
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def score_response(
|
|
89
|
+
response: ModelResponse,
|
|
90
|
+
dimensions: list[str] | None = None,
|
|
91
|
+
weights: dict[str, float] | None = None,
|
|
92
|
+
model_client: Any | None = None,
|
|
93
|
+
) -> ScoredResponse:
|
|
94
|
+
"""Score a single model response across dimensions.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
response: The model response to score.
|
|
98
|
+
dimensions: Which dimensions to score. Defaults to all.
|
|
99
|
+
weights: Optional weights per dimension. Defaults to equal.
|
|
100
|
+
model_client: Optional LLM for richer scoring.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
ScoredResponse with composite score.
|
|
104
|
+
"""
|
|
105
|
+
dims = dimensions or SCORE_DIMENSIONS
|
|
106
|
+
w = weights or {d: 1.0 / len(dims) for d in dims}
|
|
107
|
+
|
|
108
|
+
if response.error:
|
|
109
|
+
return ScoredResponse(
|
|
110
|
+
model_name=response.model_name,
|
|
111
|
+
response=response.response,
|
|
112
|
+
score=0.0,
|
|
113
|
+
sub_scores={d: 0.0 for d in dims},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
sub_scores: dict[str, float] = {}
|
|
117
|
+
for dim in dims:
|
|
118
|
+
sub_scores[dim] = _score_dimension(response.response, dim)
|
|
119
|
+
|
|
120
|
+
total_weight = sum(w.get(d, 0.0) for d in dims)
|
|
121
|
+
if total_weight == 0:
|
|
122
|
+
composite = 0.0
|
|
123
|
+
else:
|
|
124
|
+
composite = sum(sub_scores[d] * w.get(d, 0.0) for d in dims) / total_weight
|
|
125
|
+
|
|
126
|
+
return ScoredResponse(
|
|
127
|
+
model_name=response.model_name,
|
|
128
|
+
response=response.response,
|
|
129
|
+
score=composite,
|
|
130
|
+
sub_scores=sub_scores,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def score_panel(
|
|
135
|
+
responses: list[ModelResponse],
|
|
136
|
+
dimensions: list[str] | None = None,
|
|
137
|
+
weights: dict[str, float] | None = None,
|
|
138
|
+
model_client: Any | None = None,
|
|
139
|
+
) -> list[ScoredResponse]:
|
|
140
|
+
"""Score a panel of model responses and pick the winner.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
responses: List of model responses.
|
|
144
|
+
dimensions: Which dimensions to score.
|
|
145
|
+
weights: Optional weights per dimension.
|
|
146
|
+
model_client: Optional LLM for richer scoring.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of ScoredResponses, with the winner marked.
|
|
150
|
+
"""
|
|
151
|
+
scored = [
|
|
152
|
+
score_response(r, dimensions, weights, model_client)
|
|
153
|
+
for r in responses
|
|
154
|
+
]
|
|
155
|
+
if scored:
|
|
156
|
+
best_idx = max(range(len(scored)), key=lambda i: scored[i].score)
|
|
157
|
+
scored[best_idx] = ScoredResponse(
|
|
158
|
+
model_name=scored[best_idx].model_name,
|
|
159
|
+
response=scored[best_idx].response,
|
|
160
|
+
score=scored[best_idx].score,
|
|
161
|
+
sub_scores=scored[best_idx].sub_scores,
|
|
162
|
+
winner=True,
|
|
163
|
+
)
|
|
164
|
+
return scored
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def pick_winner(scored: list[ScoredResponse]) -> ScoredResponse | None:
|
|
168
|
+
"""Get the winning response from a scored panel."""
|
|
169
|
+
for s in scored:
|
|
170
|
+
if s.winner:
|
|
171
|
+
return s
|
|
172
|
+
if not scored:
|
|
173
|
+
return None
|
|
174
|
+
return max(scored, key=lambda s: s.score)
|