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,394 @@
|
|
|
1
|
+
"""Echo Veil integration for algo-cli tiered memory.
|
|
2
|
+
|
|
3
|
+
Wraps Echo Veil's Oracle as a decay-driven memory layer that sits between the
|
|
4
|
+
harness index and retrieval. Memories are managed through active/compressed/
|
|
5
|
+
archived tiers with proximity-based decay, preserving recent context while
|
|
6
|
+
compressing older data.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from algo_cli.memory_echo_veil import EchoVeilMemoryLayer
|
|
10
|
+
|
|
11
|
+
# Initialize with embedding function and capacity
|
|
12
|
+
layer = EchoVeilMemoryLayer(
|
|
13
|
+
embed_fn=embed_fn,
|
|
14
|
+
capacity=400,
|
|
15
|
+
crypto_key=None # or AesGcmCryptoShield key for production
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Before retrieval: enrich context with active memories
|
|
19
|
+
enriched_context = layer.observe(query_embedding)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import os
|
|
27
|
+
import stat
|
|
28
|
+
import sys
|
|
29
|
+
from datetime import datetime, timezone
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any, Callable, Optional
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import echo_veil as _echo_veil_package
|
|
35
|
+
from echo_veil import Oracle, WorkspaceConfig, AesGcmCryptoShield
|
|
36
|
+
ECHO_VEIL_AVAILABLE = True
|
|
37
|
+
ECHO_VEIL_IMPORT_ERROR = ""
|
|
38
|
+
ECHO_VEIL_MODULE_ORIGIN = str(getattr(_echo_veil_package, "__file__", "") or "")
|
|
39
|
+
except ImportError as exc:
|
|
40
|
+
ECHO_VEIL_AVAILABLE = False
|
|
41
|
+
ECHO_VEIL_IMPORT_ERROR = type(exc).__name__
|
|
42
|
+
ECHO_VEIL_MODULE_ORIGIN = ""
|
|
43
|
+
Oracle = None # type: ignore
|
|
44
|
+
WorkspaceConfig = None # type: ignore
|
|
45
|
+
AesGcmCryptoShield = None # type: ignore
|
|
46
|
+
|
|
47
|
+
from .config import CONFIG_DIR, _atomic_write_text
|
|
48
|
+
|
|
49
|
+
logger = logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_echo_veil_readiness(config: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
53
|
+
"""Report the optional integration's current, deliberately narrow wiring.
|
|
54
|
+
|
|
55
|
+
Persisted state currently restores text metadata for diagnostics, not the
|
|
56
|
+
Oracle's memory tiers. Likewise, the runtime does not yet feed memories to
|
|
57
|
+
``sprout`` or inject ``observe`` results into retrieval. Keep those stages
|
|
58
|
+
false until the corresponding end-to-end paths exist.
|
|
59
|
+
"""
|
|
60
|
+
if config is None:
|
|
61
|
+
config_path = CONFIG_DIR / "config.json"
|
|
62
|
+
try:
|
|
63
|
+
loaded = json.loads(config_path.read_text(encoding="utf-8"))
|
|
64
|
+
except (OSError, json.JSONDecodeError):
|
|
65
|
+
loaded = {}
|
|
66
|
+
config = loaded if isinstance(loaded, dict) else {}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
"installed": ECHO_VEIL_AVAILABLE,
|
|
70
|
+
"enabled": bool(config.get("echo_veil_enabled", False)),
|
|
71
|
+
"write_wired": False,
|
|
72
|
+
"retrieval_wired": False,
|
|
73
|
+
"persistence_wired": False,
|
|
74
|
+
"readiness_source": "algo_cli.memory_echo_veil.get_echo_veil_readiness",
|
|
75
|
+
"runtime": f"{sys.implementation.name}-{sys.version_info.major}.{sys.version_info.minor}",
|
|
76
|
+
"module_origin": ECHO_VEIL_MODULE_ORIGIN or None,
|
|
77
|
+
"import_error": ECHO_VEIL_IMPORT_ERROR or None,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _memory_records(raw: Any) -> list[dict[str, str]]:
|
|
82
|
+
if raw is None or isinstance(raw, (str, bytes, int, float, bool)):
|
|
83
|
+
return []
|
|
84
|
+
try:
|
|
85
|
+
iterator = iter(raw)
|
|
86
|
+
except TypeError:
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
records: list[dict[str, str]] = []
|
|
90
|
+
for item in iterator:
|
|
91
|
+
topic = item.get("topic") if isinstance(item, dict) else getattr(item, "topic", None)
|
|
92
|
+
if topic is None:
|
|
93
|
+
topic = item.get("title") if isinstance(item, dict) else getattr(item, "title", None)
|
|
94
|
+
if topic is not None:
|
|
95
|
+
records.append({"topic": str(topic)})
|
|
96
|
+
return records
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _tier_count(raw: Any, records: list[dict[str, str]]) -> int:
|
|
100
|
+
if isinstance(raw, int) and not isinstance(raw, bool):
|
|
101
|
+
return raw
|
|
102
|
+
if raw is None or isinstance(raw, (str, bytes, float, bool)):
|
|
103
|
+
return len(records)
|
|
104
|
+
try:
|
|
105
|
+
return len(raw)
|
|
106
|
+
except TypeError:
|
|
107
|
+
return len(records)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class EchoVeilMemoryLayer:
|
|
111
|
+
"""Tiered memory layer using Echo Veil's decay-driven architecture.
|
|
112
|
+
|
|
113
|
+
Manages memories through active/compressed/archived tiers with proximity-
|
|
114
|
+
based decay. Recent context stays active; older data is compressed and
|
|
115
|
+
eventually archived while preserving structured tension for contradictions.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
embed_fn: Callable[[list[str]], list[list[float]]],
|
|
121
|
+
capacity: int = 400,
|
|
122
|
+
crypto_key: Optional[bytes] = None,
|
|
123
|
+
environment: str = "development",
|
|
124
|
+
persist_path: Optional[Path] = None,
|
|
125
|
+
crypto_key_path: Optional[Path] = None
|
|
126
|
+
):
|
|
127
|
+
"""Initialize Echo Veil memory layer.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
embed_fn: Function to embed text into vectors (e.g., Ollama embed())
|
|
131
|
+
capacity: Maximum active memories before decay kicks in
|
|
132
|
+
crypto_key: AES-256-GCM key for production encryption, or None for dev
|
|
133
|
+
environment: "development" or "production" - production requires valid crypto
|
|
134
|
+
persist_path: Optional path to persist memory state; defaults to ~/.algo_cli/echo_veil_state.json
|
|
135
|
+
crypto_key_path: Path to JSON file containing 'key_hex' field (loads if crypto_key is None)
|
|
136
|
+
"""
|
|
137
|
+
if not ECHO_VEIL_AVAILABLE:
|
|
138
|
+
raise ImportError(
|
|
139
|
+
"Echo Veil not installed. Run: pip install echo-veil"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
self.embed_fn = embed_fn
|
|
143
|
+
self.capacity = capacity
|
|
144
|
+
|
|
145
|
+
# Load crypto key from file if path provided and no key given
|
|
146
|
+
if crypto_key is None and crypto_key_path is not None:
|
|
147
|
+
import json as _json
|
|
148
|
+
with open(crypto_key_path, 'r') as _f:
|
|
149
|
+
_key_data = _json.load(_f)
|
|
150
|
+
crypto_key = bytes.fromhex(_key_data['key_hex'])
|
|
151
|
+
self.environment = environment
|
|
152
|
+
self._loaded_state: dict[str, Any] | None = None
|
|
153
|
+
self._memory_text_by_title: dict[str, str] = {}
|
|
154
|
+
|
|
155
|
+
# Set up crypto shield for production
|
|
156
|
+
if environment == "production":
|
|
157
|
+
if crypto_key is None:
|
|
158
|
+
raise ValueError(
|
|
159
|
+
"Production mode requires a crypto key. "
|
|
160
|
+
"Generate one with AesGcmCryptoShield.generate_key()"
|
|
161
|
+
)
|
|
162
|
+
self.shield = AesGcmCryptoShield(crypto_key)
|
|
163
|
+
else:
|
|
164
|
+
# Development uses null shield (no encryption)
|
|
165
|
+
from echo_veil.crypto_shield import NullCryptoShield
|
|
166
|
+
self.shield = NullCryptoShield()
|
|
167
|
+
|
|
168
|
+
# Initialize Oracle with workspace config
|
|
169
|
+
self.oracle = Oracle(WorkspaceConfig(capacity=capacity), shield=self.shield)
|
|
170
|
+
|
|
171
|
+
# Persistence path
|
|
172
|
+
if persist_path is None:
|
|
173
|
+
self.persist_path = CONFIG_DIR / "echo_veil_state.json"
|
|
174
|
+
else:
|
|
175
|
+
self.persist_path = persist_path
|
|
176
|
+
|
|
177
|
+
# Load state if exists
|
|
178
|
+
self._load_state()
|
|
179
|
+
|
|
180
|
+
def sprout(self, title: str, text: str, embedding: list[float]) -> None:
|
|
181
|
+
"""Add a new memory to the active tier.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
title: Short descriptive title for the memory
|
|
185
|
+
text: Full text content of the memory (stored in metadata)
|
|
186
|
+
embedding: Vector representation from embed_fn
|
|
187
|
+
"""
|
|
188
|
+
# Echo Veil uses topic as the vine identifier. The upstream API only
|
|
189
|
+
# accepts topic + embedding, so retain text locally for diagnostics and
|
|
190
|
+
# persistence metadata rather than silently discarding the argument.
|
|
191
|
+
self._memory_text_by_title[str(title)] = str(text)
|
|
192
|
+
self.oracle.sprout(title, embedding)
|
|
193
|
+
|
|
194
|
+
def observe(self, query_embedding: list[float]) -> dict[str, Any]:
|
|
195
|
+
"""Process current intent through Echo Veil's decay model.
|
|
196
|
+
|
|
197
|
+
Returns context about active/compressed/archived memories relevant to query.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
query_embedding: Vector representation of the current query/intent
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Dict with keys:
|
|
204
|
+
- active: List of currently active memory titles and proximity scores
|
|
205
|
+
- compressed: Summary of recently compressed memories (if any)
|
|
206
|
+
- drift_detected: Boolean indicating if intent has drifted significantly
|
|
207
|
+
"""
|
|
208
|
+
# Run observe cycle which triggers decay
|
|
209
|
+
self.oracle.observe(query_embedding)
|
|
210
|
+
|
|
211
|
+
# Get current state from GardenersReport (counts only)
|
|
212
|
+
gardeners_report = self.oracle.report()
|
|
213
|
+
|
|
214
|
+
# Extract active memories directly from workspace vines
|
|
215
|
+
workspace_vines = getattr(self.oracle.workspace, 'vines', []) or []
|
|
216
|
+
active_memories = []
|
|
217
|
+
for vine in workspace_vines:
|
|
218
|
+
if hasattr(vine, 'topic'):
|
|
219
|
+
title = str(vine.topic)
|
|
220
|
+
active_memories.append({
|
|
221
|
+
"title": title,
|
|
222
|
+
"text": self._memory_text_by_title.get(title, ""),
|
|
223
|
+
"proximity": None,
|
|
224
|
+
"proximity_available": False,
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
# Extract compressed memories (twilight_grove may be a count or records)
|
|
228
|
+
twilight_raw = getattr(gardeners_report, 'twilight_grove', 0)
|
|
229
|
+
twilight_count = _tier_count(twilight_raw, _memory_records(twilight_raw))
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
"active": active_memories,
|
|
233
|
+
"compressed": f"{twilight_count} memories in twilight grove",
|
|
234
|
+
"drift_detected": None,
|
|
235
|
+
"drift_available": False,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
def get_report(self) -> dict[str, Any]:
|
|
239
|
+
"""Get comprehensive memory state report.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Dict with memory statistics and tier breakdown
|
|
243
|
+
"""
|
|
244
|
+
gardeners_report = self.oracle.report()
|
|
245
|
+
active_raw = getattr(gardeners_report, 'thriving_vines', [])
|
|
246
|
+
compressed_raw = getattr(gardeners_report, 'twilight_grove', [])
|
|
247
|
+
active_memories = _memory_records(active_raw)
|
|
248
|
+
compressed_memories = _memory_records(compressed_raw)
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
"active_memories": active_memories,
|
|
252
|
+
"compressed_memories": compressed_memories,
|
|
253
|
+
"archived_memories": [], # Would need archive access
|
|
254
|
+
"active_count": _tier_count(active_raw, active_memories),
|
|
255
|
+
"compressed_count": _tier_count(compressed_raw, compressed_memories),
|
|
256
|
+
"memory_pressure": getattr(gardeners_report, 'memory_pressure', None),
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
def capability_report(self) -> dict[str, Any]:
|
|
260
|
+
"""Get defensive readiness and capability report.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Dict covering crypto, storage, vector index, persistence status
|
|
264
|
+
"""
|
|
265
|
+
cap_report = self.oracle.capability_report()
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
"crypto_status": getattr(cap_report, 'crypto_status', {}),
|
|
269
|
+
"storage_status": getattr(cap_report, 'storage_status', {}),
|
|
270
|
+
"vector_index_status": getattr(cap_report, 'vector_index_status', {})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
def _save_state(self) -> None:
|
|
274
|
+
"""Persist memory state to disk."""
|
|
275
|
+
try:
|
|
276
|
+
# Extract serializable state from Oracle
|
|
277
|
+
report = self.get_report()
|
|
278
|
+
state = {
|
|
279
|
+
"capacity": self.capacity,
|
|
280
|
+
"environment": self.environment,
|
|
281
|
+
"active_count": report.get("active_count", len(report.get("active_memories", []))),
|
|
282
|
+
"compressed_count": report.get("compressed_count", len(report.get("compressed_memories", []))),
|
|
283
|
+
"archived_count": len(report.get("archived_memories", [])),
|
|
284
|
+
"memory_text_by_title": self._memory_text_by_title,
|
|
285
|
+
"last_updated": datetime.now(timezone.utc).isoformat()
|
|
286
|
+
}
|
|
287
|
+
self.persist_path.parent.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
if os.name == "posix":
|
|
289
|
+
os.chmod(self.persist_path.parent, 0o700)
|
|
290
|
+
_atomic_write_text(self.persist_path, json.dumps(state, indent=2))
|
|
291
|
+
if os.name == "posix":
|
|
292
|
+
os.chmod(self.persist_path, 0o600)
|
|
293
|
+
except Exception as exc:
|
|
294
|
+
# Don't fail on persistence errors - memory layer should be resilient.
|
|
295
|
+
logger.debug("Echo Veil state save failed: %s", exc)
|
|
296
|
+
|
|
297
|
+
def _load_state(self) -> None:
|
|
298
|
+
"""Load persisted state from disk if available."""
|
|
299
|
+
try:
|
|
300
|
+
if self.persist_path.exists():
|
|
301
|
+
with open(self.persist_path, 'r') as f:
|
|
302
|
+
state = json.load(f)
|
|
303
|
+
if isinstance(state, dict):
|
|
304
|
+
self._loaded_state = state
|
|
305
|
+
texts = state.get("memory_text_by_title")
|
|
306
|
+
if isinstance(texts, dict):
|
|
307
|
+
self._memory_text_by_title = {str(k): str(v) for k, v in texts.items()}
|
|
308
|
+
# State is informational - Oracle manages actual memory tiers.
|
|
309
|
+
return None
|
|
310
|
+
except Exception as exc:
|
|
311
|
+
logger.debug("Echo Veil state load failed: %s", exc)
|
|
312
|
+
|
|
313
|
+
def __del__(self):
|
|
314
|
+
"""Cleanup - save state on deletion."""
|
|
315
|
+
self._save_state()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def create_echo_veil_layer(
|
|
319
|
+
embed_fn: Callable[[list[str]], list[list[float]]],
|
|
320
|
+
config: dict[str, Any] | None = None,
|
|
321
|
+
crypto_key_path: Optional[str] = None,
|
|
322
|
+
) -> Optional[EchoVeilMemoryLayer]:
|
|
323
|
+
"""Factory function to create Echo Veil layer from algo-cli config.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
embed_fn: Embedding function (e.g., from harness.py)
|
|
327
|
+
config: algo-cli config dict; if None, reads from ~/.algo_cli/config.json
|
|
328
|
+
crypto_key_path: explicit path to key file (overrides config); the file
|
|
329
|
+
may be either a JSON envelope containing a ``key_hex`` field or raw
|
|
330
|
+
32-byte key material.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
EchoVeilMemoryLayer instance or None if not enabled/available
|
|
334
|
+
"""
|
|
335
|
+
if not ECHO_VEIL_AVAILABLE:
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
if config is None:
|
|
339
|
+
config_path = CONFIG_DIR / "config.json"
|
|
340
|
+
if config_path.exists():
|
|
341
|
+
with open(config_path, 'r') as f:
|
|
342
|
+
config = json.load(f)
|
|
343
|
+
else:
|
|
344
|
+
return None
|
|
345
|
+
|
|
346
|
+
# Check if Echo Veil is enabled in config
|
|
347
|
+
if not config.get("echo_veil_enabled", False):
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
capacity = config.get("echo_veil_capacity", 400)
|
|
351
|
+
environment = "production" if config.get("echo_veil_production", False) else "development"
|
|
352
|
+
|
|
353
|
+
# Resolve key path: explicit param wins, else fall back to config
|
|
354
|
+
resolved_key_path = crypto_key_path or config.get("echo_veil_crypto_key_path")
|
|
355
|
+
|
|
356
|
+
# Get crypto key (production mode only)
|
|
357
|
+
crypto_key = None
|
|
358
|
+
if environment == "production" and resolved_key_path:
|
|
359
|
+
key_file = Path(resolved_key_path)
|
|
360
|
+
if not key_file.exists():
|
|
361
|
+
raise FileNotFoundError(f"Echo Veil crypto key not found: {resolved_key_path}")
|
|
362
|
+
if key_file.is_symlink() or not key_file.is_file():
|
|
363
|
+
raise PermissionError("Echo Veil crypto key must be a regular non-symlink file")
|
|
364
|
+
if os.name == "posix" and stat.S_IMODE(key_file.stat().st_mode) & 0o077:
|
|
365
|
+
raise PermissionError(
|
|
366
|
+
"Echo Veil production key permissions are too broad; require mode 0600 or stricter"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Try to parse as JSON envelope first, fall back to raw bytes
|
|
370
|
+
try:
|
|
371
|
+
with open(key_file, 'r') as f:
|
|
372
|
+
key_data = json.load(f)
|
|
373
|
+
key_hex = key_data.get("key_hex")
|
|
374
|
+
if not key_hex:
|
|
375
|
+
raise ValueError(f"Key file missing 'key_hex': {resolved_key_path}")
|
|
376
|
+
crypto_key = bytes.fromhex(key_hex)
|
|
377
|
+
except (json.JSONDecodeError, ValueError) as exc:
|
|
378
|
+
# Not JSON or missing field; try raw bytes
|
|
379
|
+
with open(key_file, 'rb') as f:
|
|
380
|
+
raw = f.read()
|
|
381
|
+
if len(raw) == 32:
|
|
382
|
+
crypto_key = raw
|
|
383
|
+
else:
|
|
384
|
+
raise ValueError(
|
|
385
|
+
f"Key file at {resolved_key_path} is not a valid JSON envelope "
|
|
386
|
+
f"with 'key_hex' and is not exactly 32 raw bytes (got {len(raw)} bytes)"
|
|
387
|
+
) from exc
|
|
388
|
+
|
|
389
|
+
return EchoVeilMemoryLayer(
|
|
390
|
+
embed_fn=embed_fn,
|
|
391
|
+
capacity=capacity,
|
|
392
|
+
crypto_key=crypto_key,
|
|
393
|
+
environment=environment,
|
|
394
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Runtime seam for bounded automatic durable-memory capture."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from . import config as config_module
|
|
10
|
+
from . import memory_candidates
|
|
11
|
+
from .config import Config
|
|
12
|
+
from .perf_telemetry import record_perf_event
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
_EXPLICIT_MEMORY_TOOLS = frozenset({"remember", "append_lesson"})
|
|
17
|
+
_SUCCESSFUL_TOOL_STATUSES = frozenset({"worked"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _successful_explicit_memory_write(tool_calls: Sequence[Mapping[str, Any]]) -> bool:
|
|
21
|
+
return any(
|
|
22
|
+
str(call.get("name") or "") in _EXPLICIT_MEMORY_TOOLS
|
|
23
|
+
and str(call.get("status") or "") in _SUCCESSFUL_TOOL_STATUSES
|
|
24
|
+
for call in tool_calls
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _record_result(result: Mapping[str, Any], *, source: str) -> None:
|
|
29
|
+
counts_value = result.get("counts")
|
|
30
|
+
state_value = result.get("state")
|
|
31
|
+
reasons_value = result.get("reason_counts")
|
|
32
|
+
counts = counts_value if isinstance(counts_value, Mapping) else {}
|
|
33
|
+
state = state_value if isinstance(state_value, Mapping) else {}
|
|
34
|
+
reasons = reasons_value if isinstance(reasons_value, Mapping) else {}
|
|
35
|
+
record_perf_event(
|
|
36
|
+
"memory_candidate",
|
|
37
|
+
source="agent" if source == "agent" else "chat",
|
|
38
|
+
status=str(result.get("status") or "unknown"),
|
|
39
|
+
reason=str(result.get("reason") or ""),
|
|
40
|
+
extracted=int(counts.get("extracted") or 0),
|
|
41
|
+
evaluated=int(counts.get("evaluated") or 0),
|
|
42
|
+
eligible=int(counts.get("eligible") or 0),
|
|
43
|
+
stored=int(counts.get("stored") or 0),
|
|
44
|
+
rejected=int(counts.get("rejected") or 0),
|
|
45
|
+
daily_writes=int(state.get("daily_writes") or 0),
|
|
46
|
+
auto_fingerprints=int(state.get("auto_fingerprints") or 0),
|
|
47
|
+
reason_counts={str(key): int(value) for key, value in reasons.items()},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def capture_completed_user_turn(
|
|
52
|
+
cfg: Config,
|
|
53
|
+
original_user_text: str,
|
|
54
|
+
*,
|
|
55
|
+
completed: bool,
|
|
56
|
+
tool_calls: Sequence[Mapping[str, Any]] = (),
|
|
57
|
+
source: str = "chat",
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
"""Capture at most one memory after a verified completion boundary.
|
|
60
|
+
|
|
61
|
+
Only ``original_user_text`` reaches the deterministic candidate processor.
|
|
62
|
+
Assistant, tool, retrieval, and specialist output are never inspected.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
safe_source = "agent" if source == "agent" else "chat"
|
|
66
|
+
if not completed:
|
|
67
|
+
result = {
|
|
68
|
+
"status": "skipped",
|
|
69
|
+
"reason": "incomplete_turn",
|
|
70
|
+
"counts": {},
|
|
71
|
+
"reason_counts": {},
|
|
72
|
+
"state": {},
|
|
73
|
+
}
|
|
74
|
+
_record_result(result, source=safe_source)
|
|
75
|
+
return result
|
|
76
|
+
if _successful_explicit_memory_write(tool_calls):
|
|
77
|
+
result = {
|
|
78
|
+
"status": "skipped",
|
|
79
|
+
"reason": "explicit_memory_write",
|
|
80
|
+
"counts": {},
|
|
81
|
+
"reason_counts": {},
|
|
82
|
+
"state": {},
|
|
83
|
+
}
|
|
84
|
+
_record_result(result, source=safe_source)
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
return memory_candidates.process_memory_candidates(
|
|
89
|
+
original_user_text,
|
|
90
|
+
tuple(str(item) for item in cfg.memories),
|
|
91
|
+
config_module.MEMORY_CANDIDATE_STATE_FILE,
|
|
92
|
+
bool(cfg.memory_auto_capture_enabled),
|
|
93
|
+
cfg.remember_fact,
|
|
94
|
+
telemetry=lambda result: _record_result(result, source=safe_source),
|
|
95
|
+
daily_limit=int(cfg.memory_auto_daily_limit),
|
|
96
|
+
entry_limit=int(cfg.memory_auto_entry_limit),
|
|
97
|
+
char_limit=int(cfg.memory_auto_char_limit),
|
|
98
|
+
)
|
|
99
|
+
except Exception as exc: # Completion must never fail because memory capture did.
|
|
100
|
+
logger.debug("Automatic memory capture failed: %s", exc)
|
|
101
|
+
result = {
|
|
102
|
+
"status": "error",
|
|
103
|
+
"reason": type(exc).__name__,
|
|
104
|
+
"counts": {},
|
|
105
|
+
"reason_counts": {"runtime_error": 1},
|
|
106
|
+
"state": {},
|
|
107
|
+
}
|
|
108
|
+
_record_result(result, source=safe_source)
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
__all__ = ["capture_completed_user_turn"]
|