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,560 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import math
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
import time
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
from typing import Any, Dict, Iterator, List, Tuple
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Intelligence Layer tunables
|
|
16
|
+
MIN_QUERY_LEN = 2
|
|
17
|
+
MAX_QUERY_LEN = 2000
|
|
18
|
+
LOW_QUALITY_AVG_SCORE = 0.60
|
|
19
|
+
SHORT_CONTENT_CHARS = 20
|
|
20
|
+
LOW_QUALITY_STREAK_THRESHOLD = 3
|
|
21
|
+
DEFAULT_MAX_BLOCKS = 500
|
|
22
|
+
REINDEX_BATCH_SIZE = 64
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _utc_now() -> datetime:
|
|
26
|
+
return datetime.now(timezone.utc)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _date_slug(dt: datetime | None = None) -> str:
|
|
30
|
+
return (dt or _utc_now()).strftime("%Y%m%d")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _content_hash(content: str) -> str:
|
|
34
|
+
return hashlib.sha256(content.strip().encode("utf-8")).hexdigest()[:8]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@contextmanager
|
|
38
|
+
def _exclusive_index_lock(index_path: str, *, timeout_seconds: float = 30.0) -> Iterator[None]:
|
|
39
|
+
lock_path = f"{index_path}.lock"
|
|
40
|
+
os.makedirs(os.path.dirname(index_path) or ".", exist_ok=True)
|
|
41
|
+
deadline = time.monotonic() + timeout_seconds
|
|
42
|
+
with open(lock_path, "a+b") as lock_file:
|
|
43
|
+
if os.name == "nt":
|
|
44
|
+
import msvcrt
|
|
45
|
+
while True:
|
|
46
|
+
try:
|
|
47
|
+
lock_file.seek(0)
|
|
48
|
+
msvcrt.locking(lock_file.fileno(), msvcrt.LK_NBLCK, 1)
|
|
49
|
+
break
|
|
50
|
+
except OSError:
|
|
51
|
+
if time.monotonic() >= deadline:
|
|
52
|
+
raise TimeoutError(f"Timed out waiting for intuition index lock: {lock_path}")
|
|
53
|
+
time.sleep(0.05)
|
|
54
|
+
try:
|
|
55
|
+
yield
|
|
56
|
+
finally:
|
|
57
|
+
lock_file.seek(0)
|
|
58
|
+
msvcrt.locking(lock_file.fileno(), msvcrt.LK_UNLCK, 1)
|
|
59
|
+
else:
|
|
60
|
+
import fcntl
|
|
61
|
+
while True:
|
|
62
|
+
try:
|
|
63
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
64
|
+
break
|
|
65
|
+
except BlockingIOError:
|
|
66
|
+
if time.monotonic() >= deadline:
|
|
67
|
+
raise TimeoutError(f"Timed out waiting for intuition index lock: {lock_path}")
|
|
68
|
+
time.sleep(0.05)
|
|
69
|
+
try:
|
|
70
|
+
yield
|
|
71
|
+
finally:
|
|
72
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _cosine_similarity(v1: list[float], v2: list[float]) -> float:
|
|
76
|
+
if not v1 or not v2 or len(v1) != len(v2):
|
|
77
|
+
return 0.0
|
|
78
|
+
dot = sum(a * b for a, b in zip(v1, v2))
|
|
79
|
+
norm1 = math.sqrt(sum(a * a for a in v1))
|
|
80
|
+
norm2 = math.sqrt(sum(b * b for b in v2))
|
|
81
|
+
if norm1 == 0.0 or norm2 == 0.0:
|
|
82
|
+
return 0.0
|
|
83
|
+
return dot / (norm1 * norm2)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class IntuitionEngine:
|
|
87
|
+
def __init__(self, index_path: str | None = None, config: dict[str, Any] | None = None):
|
|
88
|
+
if index_path is None:
|
|
89
|
+
try:
|
|
90
|
+
from .config import CONFIG_DIR
|
|
91
|
+
index_path = str(CONFIG_DIR / "intuition_index.json")
|
|
92
|
+
except Exception:
|
|
93
|
+
index_path = os.path.expanduser("~/.algo_cli/intuition_index.json")
|
|
94
|
+
self.index_path = index_path
|
|
95
|
+
self.config = {"recall_enabled": False, "max_blocks": DEFAULT_MAX_BLOCKS}
|
|
96
|
+
if config:
|
|
97
|
+
self.config.update(config)
|
|
98
|
+
self.blocks: Dict[str, Dict] = {}
|
|
99
|
+
self._load_index()
|
|
100
|
+
self._low_quality_count = 0
|
|
101
|
+
self._last_verdict: Dict[str, Any] = {}
|
|
102
|
+
|
|
103
|
+
def _load_index(self):
|
|
104
|
+
if not os.path.exists(self.index_path):
|
|
105
|
+
self.blocks = {}
|
|
106
|
+
return
|
|
107
|
+
try:
|
|
108
|
+
with open(self.index_path, "r", encoding="utf-8") as f:
|
|
109
|
+
data = json.load(f)
|
|
110
|
+
blocks = data.get("blocks", {}) if isinstance(data, dict) else {}
|
|
111
|
+
self.blocks = blocks if isinstance(blocks, dict) else {}
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def _save_index(self):
|
|
116
|
+
os.makedirs(os.path.dirname(self.index_path), exist_ok=True)
|
|
117
|
+
payload = json.dumps({
|
|
118
|
+
"blocks": self.blocks,
|
|
119
|
+
"updated": _utc_now().isoformat()
|
|
120
|
+
}, indent=2)
|
|
121
|
+
fd, tmp_path = tempfile.mkstemp(
|
|
122
|
+
prefix=f".{os.path.basename(self.index_path)}.",
|
|
123
|
+
suffix=".tmp",
|
|
124
|
+
dir=os.path.dirname(self.index_path),
|
|
125
|
+
)
|
|
126
|
+
try:
|
|
127
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
128
|
+
f.write(payload)
|
|
129
|
+
f.flush()
|
|
130
|
+
os.fsync(f.fileno())
|
|
131
|
+
for attempt in range(8):
|
|
132
|
+
try:
|
|
133
|
+
os.replace(tmp_path, self.index_path)
|
|
134
|
+
break
|
|
135
|
+
except PermissionError:
|
|
136
|
+
if attempt >= 7:
|
|
137
|
+
raise
|
|
138
|
+
time.sleep(0.05 * (attempt + 1))
|
|
139
|
+
except Exception:
|
|
140
|
+
try:
|
|
141
|
+
os.unlink(tmp_path)
|
|
142
|
+
except OSError:
|
|
143
|
+
pass
|
|
144
|
+
raise
|
|
145
|
+
|
|
146
|
+
def _duplicate_block_id(self, block_type: str, content_hash: str) -> str | None:
|
|
147
|
+
for block_id, block in self.blocks.items():
|
|
148
|
+
metadata = block.get("metadata")
|
|
149
|
+
if (
|
|
150
|
+
block.get("type") == block_type
|
|
151
|
+
and isinstance(metadata, dict)
|
|
152
|
+
and metadata.get("content_hash") == content_hash
|
|
153
|
+
):
|
|
154
|
+
return block_id
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
def index_blocks(self, blocks: List[Dict[str, Any]]) -> list[str]:
|
|
158
|
+
"""Index multiple blocks under one lock and one atomic write."""
|
|
159
|
+
if not blocks:
|
|
160
|
+
return []
|
|
161
|
+
now = _utc_now()
|
|
162
|
+
records: list[dict[str, Any]] = []
|
|
163
|
+
for block in blocks:
|
|
164
|
+
block_id = block.get("id") or f"{block.get('type', 'general')}:{_date_slug(now)}:{_content_hash(str(block.get('content', '')))}"
|
|
165
|
+
records.append({
|
|
166
|
+
"id": block_id,
|
|
167
|
+
"content": block.get("content", ""),
|
|
168
|
+
"type": block.get("type", "general"),
|
|
169
|
+
"timestamp": block.get("timestamp") or now.isoformat(),
|
|
170
|
+
"metadata": block.get("metadata", {}),
|
|
171
|
+
"embedding": block.get("embedding"),
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
indexed_ids: list[str] = []
|
|
175
|
+
with _exclusive_index_lock(self.index_path):
|
|
176
|
+
self._load_index()
|
|
177
|
+
for record in records:
|
|
178
|
+
metadata = record.get("metadata")
|
|
179
|
+
content_hash = metadata.get("content_hash") if isinstance(metadata, dict) else None
|
|
180
|
+
duplicate_id = (
|
|
181
|
+
self._duplicate_block_id(str(record.get("type", "general")), str(content_hash))
|
|
182
|
+
if content_hash
|
|
183
|
+
else None
|
|
184
|
+
)
|
|
185
|
+
if duplicate_id is not None:
|
|
186
|
+
indexed_ids.append(duplicate_id)
|
|
187
|
+
continue
|
|
188
|
+
block_id = str(record["id"])
|
|
189
|
+
self.blocks[block_id] = record
|
|
190
|
+
indexed_ids.append(block_id)
|
|
191
|
+
self._prune()
|
|
192
|
+
self._save_index()
|
|
193
|
+
return indexed_ids
|
|
194
|
+
|
|
195
|
+
def index_block(self, block: Dict[str, Any]):
|
|
196
|
+
return self.index_blocks([block])[0]
|
|
197
|
+
|
|
198
|
+
def _prune(self) -> None:
|
|
199
|
+
max_blocks = max(1, int(self.config.get("max_blocks", DEFAULT_MAX_BLOCKS)))
|
|
200
|
+
if len(self.blocks) <= max_blocks:
|
|
201
|
+
return
|
|
202
|
+
ordered = sorted(
|
|
203
|
+
self.blocks.items(),
|
|
204
|
+
key=lambda item: str(item[1].get("timestamp", "")),
|
|
205
|
+
reverse=True,
|
|
206
|
+
)
|
|
207
|
+
self.blocks = dict(ordered[:max_blocks])
|
|
208
|
+
|
|
209
|
+
def capture_block(
|
|
210
|
+
self,
|
|
211
|
+
block_type: str,
|
|
212
|
+
content: str,
|
|
213
|
+
*,
|
|
214
|
+
source: str,
|
|
215
|
+
embed_fn: Any | None = None,
|
|
216
|
+
embedding_model: str | None = None,
|
|
217
|
+
metadata: dict[str, Any] | None = None,
|
|
218
|
+
) -> str:
|
|
219
|
+
"""Capture a reusable block, embedding at capture time when possible."""
|
|
220
|
+
clean_type = (block_type or "general").strip().lower() or "general"
|
|
221
|
+
clean_content = (content or "").strip()
|
|
222
|
+
if not clean_content:
|
|
223
|
+
raise ValueError("Intuition block content was empty.")
|
|
224
|
+
|
|
225
|
+
existing_hash = _content_hash(clean_content)
|
|
226
|
+
duplicate_id = self._duplicate_block_id(clean_type, existing_hash)
|
|
227
|
+
if duplicate_id is not None:
|
|
228
|
+
# Re-check under the cross-process lock. Another process may have
|
|
229
|
+
# forgotten the block since this engine last loaded its index.
|
|
230
|
+
with _exclusive_index_lock(self.index_path):
|
|
231
|
+
self._load_index()
|
|
232
|
+
duplicate_id = self._duplicate_block_id(clean_type, existing_hash)
|
|
233
|
+
if duplicate_id is not None:
|
|
234
|
+
return duplicate_id
|
|
235
|
+
|
|
236
|
+
meta = dict(metadata or {})
|
|
237
|
+
meta.update({"source": source, "content_hash": existing_hash})
|
|
238
|
+
embedding: list[float] | None = None
|
|
239
|
+
if embed_fn is not None:
|
|
240
|
+
try:
|
|
241
|
+
vectors = embed_fn([clean_content])
|
|
242
|
+
if vectors:
|
|
243
|
+
embedding = [float(value) for value in vectors[0]]
|
|
244
|
+
meta["embedding_status"] = "ready"
|
|
245
|
+
if embedding_model:
|
|
246
|
+
meta["embedding_model"] = embedding_model
|
|
247
|
+
except Exception as exc:
|
|
248
|
+
logger.debug("Intuition capture embedding failed: %s", exc)
|
|
249
|
+
meta["embedding_status"] = "pending"
|
|
250
|
+
if embedding_model:
|
|
251
|
+
meta["embedding_model"] = embedding_model
|
|
252
|
+
else:
|
|
253
|
+
meta["embedding_status"] = "pending"
|
|
254
|
+
if embedding_model:
|
|
255
|
+
meta["embedding_model"] = embedding_model
|
|
256
|
+
|
|
257
|
+
now = _utc_now()
|
|
258
|
+
block_id = f"{clean_type}:{_date_slug(now)}:{existing_hash}"
|
|
259
|
+
return self.index_block(
|
|
260
|
+
{
|
|
261
|
+
"id": block_id,
|
|
262
|
+
"type": clean_type,
|
|
263
|
+
"content": clean_content,
|
|
264
|
+
"timestamp": now.isoformat(),
|
|
265
|
+
"metadata": meta,
|
|
266
|
+
"embedding": embedding,
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
def status(self) -> dict[str, Any]:
|
|
271
|
+
embedded = 0
|
|
272
|
+
pending = 0
|
|
273
|
+
by_type: dict[str, int] = {}
|
|
274
|
+
for block in self.blocks.values():
|
|
275
|
+
by_type[str(block.get("type", "general"))] = by_type.get(str(block.get("type", "general")), 0) + 1
|
|
276
|
+
if block.get("embedding"):
|
|
277
|
+
embedded += 1
|
|
278
|
+
else:
|
|
279
|
+
pending += 1
|
|
280
|
+
return {
|
|
281
|
+
"index_path": self.index_path,
|
|
282
|
+
"block_count": len(self.blocks),
|
|
283
|
+
"embedded": embedded,
|
|
284
|
+
"pending": pending,
|
|
285
|
+
"by_type": by_type,
|
|
286
|
+
"recall_enabled": bool(self.config.get("recall_enabled", False)),
|
|
287
|
+
"max_blocks": int(self.config.get("max_blocks", DEFAULT_MAX_BLOCKS)),
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
def list_blocks(self) -> list[dict[str, Any]]:
|
|
291
|
+
return sorted(
|
|
292
|
+
(dict(block) for block in self.blocks.values()),
|
|
293
|
+
key=lambda block: str(block.get("timestamp", "")),
|
|
294
|
+
reverse=True,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def forget_block(self, block_id: str) -> dict[str, Any] | None:
|
|
298
|
+
with _exclusive_index_lock(self.index_path):
|
|
299
|
+
self._load_index()
|
|
300
|
+
removed = self.blocks.pop(block_id, None)
|
|
301
|
+
if removed is not None:
|
|
302
|
+
self._save_index()
|
|
303
|
+
return removed
|
|
304
|
+
|
|
305
|
+
def reindex(
|
|
306
|
+
self,
|
|
307
|
+
embed_fn: Any,
|
|
308
|
+
embedding_model: str | None = None,
|
|
309
|
+
*,
|
|
310
|
+
batch_size: int = REINDEX_BATCH_SIZE,
|
|
311
|
+
) -> dict[str, Any]:
|
|
312
|
+
if embed_fn is None:
|
|
313
|
+
return {"ok": False, "reason": "no_embed_fn", "updated": 0, "total": len(self.blocks)}
|
|
314
|
+
with _exclusive_index_lock(self.index_path):
|
|
315
|
+
self._load_index()
|
|
316
|
+
snapshot = {block_id: dict(block) for block_id, block in self.blocks.items()}
|
|
317
|
+
updated_blocks: dict[str, dict[str, Any]] = {}
|
|
318
|
+
updated = 0
|
|
319
|
+
failed = 0
|
|
320
|
+
pending: list[tuple[str, dict[str, Any], dict[str, Any], str]] = []
|
|
321
|
+
for block_id, block in snapshot.items():
|
|
322
|
+
content = str(block.get("content", "")).strip()
|
|
323
|
+
metadata = block.get("metadata")
|
|
324
|
+
if not isinstance(metadata, dict):
|
|
325
|
+
metadata = {}
|
|
326
|
+
else:
|
|
327
|
+
metadata = dict(metadata)
|
|
328
|
+
block["metadata"] = metadata
|
|
329
|
+
if not content:
|
|
330
|
+
failed += 1
|
|
331
|
+
metadata["embedding_status"] = "failed"
|
|
332
|
+
updated_blocks[block_id] = block
|
|
333
|
+
continue
|
|
334
|
+
pending.append((block_id, block, metadata, content))
|
|
335
|
+
|
|
336
|
+
batch_size = max(1, int(batch_size))
|
|
337
|
+
for start in range(0, len(pending), batch_size):
|
|
338
|
+
batch = pending[start:start + batch_size]
|
|
339
|
+
try:
|
|
340
|
+
vectors = embed_fn([content for _block_id, _block, _metadata, content in batch])
|
|
341
|
+
if len(vectors) != len(batch):
|
|
342
|
+
raise ValueError("embedding response count did not match batch")
|
|
343
|
+
except Exception as exc:
|
|
344
|
+
logger.debug("Intuition reindex batch failed: %s", exc)
|
|
345
|
+
for block_id, block, metadata, _content in batch:
|
|
346
|
+
metadata["embedding_status"] = "failed"
|
|
347
|
+
failed += 1
|
|
348
|
+
updated_blocks[block_id] = block
|
|
349
|
+
continue
|
|
350
|
+
for (block_id, block, metadata, _content), vector in zip(batch, vectors):
|
|
351
|
+
try:
|
|
352
|
+
block["embedding"] = [float(value) for value in vector]
|
|
353
|
+
except (TypeError, ValueError) as exc:
|
|
354
|
+
logger.debug("Intuition reindex vector conversion failed for %s: %s", block_id, exc)
|
|
355
|
+
metadata["embedding_status"] = "failed"
|
|
356
|
+
failed += 1
|
|
357
|
+
else:
|
|
358
|
+
metadata["embedding_status"] = "ready"
|
|
359
|
+
if embedding_model:
|
|
360
|
+
metadata["embedding_model"] = embedding_model
|
|
361
|
+
updated += 1
|
|
362
|
+
updated_blocks[block_id] = block
|
|
363
|
+
with _exclusive_index_lock(self.index_path):
|
|
364
|
+
self._load_index()
|
|
365
|
+
applied = 0
|
|
366
|
+
for block_id, block in updated_blocks.items():
|
|
367
|
+
current = self.blocks.get(block_id)
|
|
368
|
+
if current is None:
|
|
369
|
+
continue
|
|
370
|
+
current["embedding"] = block.get("embedding")
|
|
371
|
+
current_meta = current.get("metadata")
|
|
372
|
+
if not isinstance(current_meta, dict):
|
|
373
|
+
current_meta = {}
|
|
374
|
+
current["metadata"] = current_meta
|
|
375
|
+
current_meta.update(block.get("metadata") or {})
|
|
376
|
+
applied += 1
|
|
377
|
+
self._save_index()
|
|
378
|
+
total = len(self.blocks)
|
|
379
|
+
return {
|
|
380
|
+
"ok": failed == 0,
|
|
381
|
+
"updated": updated,
|
|
382
|
+
"failed": failed,
|
|
383
|
+
"applied": applied,
|
|
384
|
+
"total": total,
|
|
385
|
+
"embedding_model": embedding_model,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
def sync_from_squeezer(self, squeeze_engine: Any) -> int:
|
|
389
|
+
if not squeeze_engine:
|
|
390
|
+
return 0
|
|
391
|
+
try:
|
|
392
|
+
get_blocks = getattr(squeeze_engine, "get_new_blocks", None)
|
|
393
|
+
if not get_blocks:
|
|
394
|
+
return 0
|
|
395
|
+
new_blocks = get_blocks() or []
|
|
396
|
+
return len(self.index_blocks(list(new_blocks)))
|
|
397
|
+
except Exception:
|
|
398
|
+
return 0
|
|
399
|
+
|
|
400
|
+
# ---------- Intelligence Layer ----------
|
|
401
|
+
|
|
402
|
+
def _check_guardrails(self, query: str) -> Tuple[bool, str]:
|
|
403
|
+
"""Pre-recall validation. Returns (ok, reason)."""
|
|
404
|
+
if not isinstance(query, str):
|
|
405
|
+
return False, "query_not_string"
|
|
406
|
+
stripped = query.strip()
|
|
407
|
+
if not stripped:
|
|
408
|
+
return False, "query_empty"
|
|
409
|
+
if len(stripped) < MIN_QUERY_LEN:
|
|
410
|
+
return False, "query_too_short"
|
|
411
|
+
if len(stripped) > MAX_QUERY_LEN:
|
|
412
|
+
return False, "query_too_long"
|
|
413
|
+
if not self.blocks:
|
|
414
|
+
return False, "no_blocks_indexed"
|
|
415
|
+
return True, "ok"
|
|
416
|
+
|
|
417
|
+
def _ground_block(self, block_id: str) -> Dict[str, Any] | None:
|
|
418
|
+
"""Confirm a block still exists and has minimum valid metadata."""
|
|
419
|
+
block = self.blocks.get(block_id)
|
|
420
|
+
if not block:
|
|
421
|
+
return None
|
|
422
|
+
content = block.get("content", "")
|
|
423
|
+
if not isinstance(content, str) or not content.strip():
|
|
424
|
+
return None
|
|
425
|
+
if not block.get("timestamp"):
|
|
426
|
+
return None
|
|
427
|
+
return block
|
|
428
|
+
|
|
429
|
+
def _verify_results(self, results: List[Dict]) -> Dict[str, Any]:
|
|
430
|
+
"""Score recall quality and detect failure modes."""
|
|
431
|
+
if not results:
|
|
432
|
+
return {"ok": False, "reason": "no_results", "avg_score": 0.0}
|
|
433
|
+
|
|
434
|
+
avg_score = sum(r["score"] for r in results) / len(results)
|
|
435
|
+
|
|
436
|
+
seen: set[str] = set()
|
|
437
|
+
duplicates = 0
|
|
438
|
+
short = 0
|
|
439
|
+
for r in results:
|
|
440
|
+
content = r.get("content", "")
|
|
441
|
+
digest = hashlib.sha1(content.strip().lower().encode("utf-8")).hexdigest()
|
|
442
|
+
if digest in seen:
|
|
443
|
+
duplicates += 1
|
|
444
|
+
seen.add(digest)
|
|
445
|
+
if len(content.strip()) < SHORT_CONTENT_CHARS:
|
|
446
|
+
short += 1
|
|
447
|
+
|
|
448
|
+
low_quality = (
|
|
449
|
+
avg_score < LOW_QUALITY_AVG_SCORE
|
|
450
|
+
or duplicates > 0
|
|
451
|
+
or short == len(results)
|
|
452
|
+
)
|
|
453
|
+
return {
|
|
454
|
+
"ok": not low_quality,
|
|
455
|
+
"avg_score": round(avg_score, 3),
|
|
456
|
+
"duplicates": duplicates,
|
|
457
|
+
"short_content_hits": short,
|
|
458
|
+
"count": len(results),
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
# ---------- Recall ----------
|
|
462
|
+
|
|
463
|
+
def recall(
|
|
464
|
+
self,
|
|
465
|
+
query: str,
|
|
466
|
+
top_k: int = 3,
|
|
467
|
+
min_score: float = 0.65,
|
|
468
|
+
*,
|
|
469
|
+
enabled: bool | None = None,
|
|
470
|
+
embed_fn: Any | None = None,
|
|
471
|
+
) -> List[Dict]:
|
|
472
|
+
recall_enabled = self.config.get("recall_enabled", False) if enabled is None else enabled
|
|
473
|
+
if not recall_enabled:
|
|
474
|
+
logger.debug("Intuition recall disabled.")
|
|
475
|
+
self._last_verdict = {"ok": False, "reason": "recall_disabled"}
|
|
476
|
+
return []
|
|
477
|
+
if embed_fn is None:
|
|
478
|
+
self._last_verdict = {"ok": False, "reason": "no_embed_fn"}
|
|
479
|
+
return []
|
|
480
|
+
ok, reason = self._check_guardrails(query)
|
|
481
|
+
if not ok:
|
|
482
|
+
self._last_verdict = {"ok": False, "reason": reason}
|
|
483
|
+
return []
|
|
484
|
+
|
|
485
|
+
try:
|
|
486
|
+
query_vectors = embed_fn([query])
|
|
487
|
+
except Exception as exc:
|
|
488
|
+
logger.debug("Intuition query embedding failed: %s", exc)
|
|
489
|
+
self._last_verdict = {"ok": False, "reason": "query_embedding_failed"}
|
|
490
|
+
return []
|
|
491
|
+
if not query_vectors:
|
|
492
|
+
self._last_verdict = {"ok": False, "reason": "query_embedding_missing"}
|
|
493
|
+
return []
|
|
494
|
+
query_vec = [float(value) for value in query_vectors[0]]
|
|
495
|
+
|
|
496
|
+
results: List[Dict] = []
|
|
497
|
+
for block_id, _ in self.blocks.items():
|
|
498
|
+
block = self._ground_block(block_id)
|
|
499
|
+
if not block:
|
|
500
|
+
continue
|
|
501
|
+
embedding = block.get("embedding")
|
|
502
|
+
if not isinstance(embedding, list):
|
|
503
|
+
continue
|
|
504
|
+
|
|
505
|
+
score = round(_cosine_similarity(query_vec, [float(value) for value in embedding]), 3)
|
|
506
|
+
if score < min_score:
|
|
507
|
+
continue
|
|
508
|
+
|
|
509
|
+
results.append({
|
|
510
|
+
"id": block_id,
|
|
511
|
+
"content": block.get("content", ""),
|
|
512
|
+
"type": block.get("type", "general"),
|
|
513
|
+
"score": score,
|
|
514
|
+
"timestamp": block.get("timestamp"),
|
|
515
|
+
"metadata": block.get("metadata", {}),
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
sorted_results = sorted(results, key=lambda x: x["score"], reverse=True)[:top_k]
|
|
519
|
+
|
|
520
|
+
verdict = self._verify_results(sorted_results)
|
|
521
|
+
self._last_verdict = verdict
|
|
522
|
+
if not verdict.get("ok"):
|
|
523
|
+
self._low_quality_count += 1
|
|
524
|
+
else:
|
|
525
|
+
self._low_quality_count = 0
|
|
526
|
+
|
|
527
|
+
return sorted_results
|
|
528
|
+
|
|
529
|
+
def format_for_injection(self, blocks: List[Dict]) -> str:
|
|
530
|
+
lines = ["## Relevant Context (from memory)"]
|
|
531
|
+
for block in blocks:
|
|
532
|
+
block_type = str(block.get("type", "note")).upper()
|
|
533
|
+
block_id = block.get("id", "?")
|
|
534
|
+
content = str(block.get("content", ""))
|
|
535
|
+
score = float(block.get("score", 0.0) or 0.0)
|
|
536
|
+
snippet = content[:300].replace("\n", " ").strip()
|
|
537
|
+
if len(content) > 300:
|
|
538
|
+
snippet += "..."
|
|
539
|
+
if snippet:
|
|
540
|
+
lines.append(f"- [{block_type}] `{block_id}` ({score:.2f}): {snippet}")
|
|
541
|
+
return "\n".join(lines) if len(lines) > 1 else ""
|
|
542
|
+
|
|
543
|
+
def run(self, query: str, *, enabled: bool | None = None, embed_fn: Any | None = None) -> str | None:
|
|
544
|
+
"""Public adapter called by the chat loop.
|
|
545
|
+
"""
|
|
546
|
+
blocks = self.recall(query, enabled=enabled, embed_fn=embed_fn)
|
|
547
|
+
if not blocks:
|
|
548
|
+
return None
|
|
549
|
+
injection = self.format_for_injection(blocks)
|
|
550
|
+
if not injection:
|
|
551
|
+
return None
|
|
552
|
+
return f"{query}\n\n{injection}"
|
|
553
|
+
|
|
554
|
+
def should_introspect(self) -> bool:
|
|
555
|
+
"""Returns True if recall quality has been consistently low."""
|
|
556
|
+
return self._low_quality_count >= LOW_QUALITY_STREAK_THRESHOLD
|
|
557
|
+
|
|
558
|
+
def last_verdict(self) -> Dict[str, Any]:
|
|
559
|
+
"""Return verification details from the most recent recall."""
|
|
560
|
+
return dict(self._last_verdict)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger("intuition_injector")
|
|
6
|
+
|
|
7
|
+
_recent_recalls: dict[str, float] = {}
|
|
8
|
+
_CACHE_TTL = 300
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _format_recall_block(block: dict) -> str:
|
|
12
|
+
"""Produce a clean, evidence-backed recall block."""
|
|
13
|
+
block_id = block.get("id", "unknown")
|
|
14
|
+
score = block.get("score", 0)
|
|
15
|
+
content = block.get("content", "").strip()
|
|
16
|
+
block_type = block.get("type", "general")
|
|
17
|
+
timestamp = block.get("timestamp", "")
|
|
18
|
+
|
|
19
|
+
header = f"**[{block_type.upper()}]** `{block_id}` - Score: {score:.2f}"
|
|
20
|
+
if timestamp:
|
|
21
|
+
header += f" - {timestamp[:10]}"
|
|
22
|
+
|
|
23
|
+
# Keep context small and focused.
|
|
24
|
+
body = content[:450].replace("\n", " ").strip()
|
|
25
|
+
if len(content) > 450:
|
|
26
|
+
body += "..."
|
|
27
|
+
|
|
28
|
+
return f"{header}\n{body}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def inject_intuition(
|
|
32
|
+
system_prompt: str,
|
|
33
|
+
user_input: str,
|
|
34
|
+
intuition_engine: Any,
|
|
35
|
+
top_k: int = 3,
|
|
36
|
+
min_score: float = 0.65,
|
|
37
|
+
max_tokens: int = 1200,
|
|
38
|
+
) -> str:
|
|
39
|
+
"""Inject relevant intuition recalls with structured, polished format."""
|
|
40
|
+
if not intuition_engine:
|
|
41
|
+
return system_prompt
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
recalled = intuition_engine.recall(user_input, top_k=top_k, min_score=min_score)
|
|
45
|
+
if not recalled:
|
|
46
|
+
return system_prompt
|
|
47
|
+
|
|
48
|
+
# Filter recent recalls.
|
|
49
|
+
now = time.time()
|
|
50
|
+
filtered = []
|
|
51
|
+
for block in recalled:
|
|
52
|
+
block_id = block.get("id", "")
|
|
53
|
+
if block_id and _recent_recalls.get(block_id, 0) > now - _CACHE_TTL:
|
|
54
|
+
continue
|
|
55
|
+
filtered.append(block)
|
|
56
|
+
if block_id:
|
|
57
|
+
_recent_recalls[block_id] = now
|
|
58
|
+
|
|
59
|
+
if not filtered:
|
|
60
|
+
return system_prompt
|
|
61
|
+
|
|
62
|
+
# Build structured output within token budget.
|
|
63
|
+
blocks_text = ""
|
|
64
|
+
current_tokens = 0
|
|
65
|
+
for block in filtered:
|
|
66
|
+
block_text = _format_recall_block(block) + "\n\n"
|
|
67
|
+
block_tokens = len(block_text) // 4
|
|
68
|
+
|
|
69
|
+
if current_tokens + block_tokens > max_tokens:
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
blocks_text += block_text
|
|
73
|
+
current_tokens += block_tokens
|
|
74
|
+
|
|
75
|
+
if not blocks_text.strip():
|
|
76
|
+
return system_prompt
|
|
77
|
+
|
|
78
|
+
return f"{system_prompt}\n\n### Relevant Intuition\n{blocks_text.strip()}"
|
|
79
|
+
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
logger.warning("Intuition recall failed: %s", exc)
|
|
82
|
+
return system_prompt
|