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,1976 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Autonomous Engineer v1.0 — Foundation (Phase 1, hardened)
|
|
4
|
+
|
|
5
|
+
Single-file autonomous engineering runtime.
|
|
6
|
+
|
|
7
|
+
Core: Scheduler + Workers + Memory (SQLite) + Execution Sandbox + LLM Router
|
|
8
|
+
|
|
9
|
+
Hardening notes (see CHANGELOG block at bottom of file for the full list):
|
|
10
|
+
- Fixed the shebang.
|
|
11
|
+
- ExecutionSandbox now enforces a real timeout via subprocess isolation,
|
|
12
|
+
with optional POSIX memory limits. This is *isolation*, not a security
|
|
13
|
+
boundary — read the class docstring before running untrusted code.
|
|
14
|
+
- MemoryEngine now applies WAL, enforces foreign keys, is thread-safe
|
|
15
|
+
(lock-guarded writes, check_same_thread=False), and exposes close().
|
|
16
|
+
- Scheduler now creates real session/task rows so the FK chain is valid.
|
|
17
|
+
- Pruned unused imports.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import ast
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import signal
|
|
26
|
+
import sqlite3
|
|
27
|
+
import tempfile
|
|
28
|
+
import statistics
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
import threading
|
|
32
|
+
import time
|
|
33
|
+
import uuid
|
|
34
|
+
from dataclasses import dataclass, asdict
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Any, Dict, Iterator, List, Optional
|
|
37
|
+
|
|
38
|
+
# Optional deps
|
|
39
|
+
try:
|
|
40
|
+
import ollama
|
|
41
|
+
HAS_OLLAMA = True
|
|
42
|
+
except ImportError:
|
|
43
|
+
HAS_OLLAMA = False
|
|
44
|
+
|
|
45
|
+
# POSIX-only resource limits (available on Linux/WSL2, not on native Windows).
|
|
46
|
+
try:
|
|
47
|
+
import resource # type: ignore
|
|
48
|
+
HAS_RESOURCE = True
|
|
49
|
+
except ImportError:
|
|
50
|
+
resource = None # type: ignore
|
|
51
|
+
HAS_RESOURCE = False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# =============================================================================
|
|
55
|
+
# LOGGING
|
|
56
|
+
# =============================================================================
|
|
57
|
+
# Library pattern: attach a NullHandler so importing this module never emits
|
|
58
|
+
# output on its own. Call setup_logging() (or configure the root logger) to see
|
|
59
|
+
# messages. Diagnostics go through this logger; the CLI keeps actual *results*
|
|
60
|
+
# on stdout via print(), so they stay pipeable while chatter goes to stderr.
|
|
61
|
+
logger = logging.getLogger("autonomous_engineer")
|
|
62
|
+
logger.addHandler(logging.NullHandler())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def setup_logging(level: str = "INFO",
|
|
66
|
+
log_file: Optional[str] = None) -> logging.Logger:
|
|
67
|
+
"""Configure the package logger. Idempotent — safe to call repeatedly."""
|
|
68
|
+
lg = logging.getLogger("autonomous_engineer")
|
|
69
|
+
lg.setLevel(getattr(logging, str(level).upper(), logging.INFO))
|
|
70
|
+
# Drop any non-Null handlers from a previous call to avoid duplicates.
|
|
71
|
+
for h in list(lg.handlers):
|
|
72
|
+
if not isinstance(h, logging.NullHandler):
|
|
73
|
+
lg.removeHandler(h)
|
|
74
|
+
fmt = logging.Formatter(
|
|
75
|
+
"%(asctime)s %(levelname)-7s %(name)s: %(message)s",
|
|
76
|
+
datefmt="%H:%M:%S",
|
|
77
|
+
)
|
|
78
|
+
stream = logging.StreamHandler() # stderr
|
|
79
|
+
stream.setFormatter(fmt)
|
|
80
|
+
lg.addHandler(stream)
|
|
81
|
+
if log_file:
|
|
82
|
+
fileh = logging.FileHandler(log_file, encoding="utf-8")
|
|
83
|
+
fileh.setFormatter(fmt)
|
|
84
|
+
lg.addHandler(fileh)
|
|
85
|
+
lg.propagate = False
|
|
86
|
+
return lg
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# CONFIGURATION
|
|
91
|
+
# =============================================================================
|
|
92
|
+
@dataclass
|
|
93
|
+
class Config:
|
|
94
|
+
"""Single source of truth for configuration."""
|
|
95
|
+
db_path: str = "autonomous_engineer.db"
|
|
96
|
+
workspace: str = "./workspace"
|
|
97
|
+
default_model: str = "llama3.1"
|
|
98
|
+
max_workers: int = 4
|
|
99
|
+
default_timeout: float = 30.0
|
|
100
|
+
benchmark_repeats: int = 5
|
|
101
|
+
benchmark_warmup: int = 2
|
|
102
|
+
sqlite_journal_mode: str = "WAL"
|
|
103
|
+
sandbox_memory_limit_mb: Optional[int] = 512 # None = no limit
|
|
104
|
+
require_correctness_test: bool = True
|
|
105
|
+
benchmark_target_sample_time: float = 0.001
|
|
106
|
+
benchmark_max_loops: int = 100_000
|
|
107
|
+
os_sandbox_command: Optional[List[str]] = None
|
|
108
|
+
|
|
109
|
+
def save(self, path: str = "config.json") -> None:
|
|
110
|
+
Path(path).write_text(json.dumps(asdict(self), indent=2),
|
|
111
|
+
encoding="utf-8")
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def load(cls, path: str = "config.json") -> "Config":
|
|
115
|
+
if Path(path).exists():
|
|
116
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
117
|
+
return cls(**data)
|
|
118
|
+
return cls()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# =============================================================================
|
|
122
|
+
# MEMORY ENGINE (SQLite)
|
|
123
|
+
# =============================================================================
|
|
124
|
+
class MemoryEngine:
|
|
125
|
+
"""Persistent memory with SQLite. ACID, indexed, portable.
|
|
126
|
+
|
|
127
|
+
Thread-safety: the connection is opened with check_same_thread=False and
|
|
128
|
+
all writes are serialized through a single lock. This is the simplest
|
|
129
|
+
correct model for the Phase-1 worker pool. If write throughput ever
|
|
130
|
+
becomes the bottleneck, move to a connection-per-thread or a writer queue.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(self, db_path: str = "autonomous_engineer.db",
|
|
134
|
+
journal_mode: str = "WAL"):
|
|
135
|
+
self.db_path = db_path
|
|
136
|
+
self._lock = threading.RLock()
|
|
137
|
+
self._transaction_depth = 0
|
|
138
|
+
self._transaction_rollback_only = False
|
|
139
|
+
self.conn = sqlite3.connect(db_path, check_same_thread=False)
|
|
140
|
+
self.conn.row_factory = sqlite3.Row
|
|
141
|
+
self._configure(journal_mode)
|
|
142
|
+
self._init_schema()
|
|
143
|
+
|
|
144
|
+
def _configure(self, journal_mode: str) -> None:
|
|
145
|
+
"""Apply validated SQLite pragmas."""
|
|
146
|
+
mode = str(journal_mode or "WAL").upper()
|
|
147
|
+
valid_modes = {"DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"}
|
|
148
|
+
if mode not in valid_modes:
|
|
149
|
+
raise ValueError(f"invalid sqlite_journal_mode: {journal_mode!r}")
|
|
150
|
+
|
|
151
|
+
cursor = self.conn.cursor()
|
|
152
|
+
# WAL improves concurrent read/write behavior; honored from Config.
|
|
153
|
+
cursor.execute(f"PRAGMA journal_mode={mode};")
|
|
154
|
+
# Referential integrity is OFF by default in SQLite — turn it on so the
|
|
155
|
+
# session -> task -> attempt chain is actually enforced.
|
|
156
|
+
cursor.execute("PRAGMA foreign_keys=ON;")
|
|
157
|
+
cursor.execute("PRAGMA busy_timeout=5000;")
|
|
158
|
+
if mode == "WAL":
|
|
159
|
+
cursor.execute("PRAGMA synchronous=NORMAL;")
|
|
160
|
+
self.conn.commit()
|
|
161
|
+
|
|
162
|
+
def _init_schema(self) -> None:
|
|
163
|
+
"""Core schema per spec (expandable)."""
|
|
164
|
+
cursor = self.conn.cursor()
|
|
165
|
+
cursor.execute("""
|
|
166
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
167
|
+
id INTEGER PRIMARY KEY,
|
|
168
|
+
started_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
169
|
+
goal TEXT,
|
|
170
|
+
strategy TEXT,
|
|
171
|
+
status TEXT DEFAULT 'running'
|
|
172
|
+
)
|
|
173
|
+
""")
|
|
174
|
+
cursor.execute("""
|
|
175
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
176
|
+
id INTEGER PRIMARY KEY,
|
|
177
|
+
session_id INTEGER,
|
|
178
|
+
name TEXT,
|
|
179
|
+
status TEXT DEFAULT 'pending',
|
|
180
|
+
dependencies TEXT, -- JSON array
|
|
181
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
182
|
+
FOREIGN KEY(session_id) REFERENCES sessions(id)
|
|
183
|
+
)
|
|
184
|
+
""")
|
|
185
|
+
cursor.execute("""
|
|
186
|
+
CREATE TABLE IF NOT EXISTS attempts (
|
|
187
|
+
id INTEGER PRIMARY KEY,
|
|
188
|
+
task_id INTEGER,
|
|
189
|
+
worker_name TEXT,
|
|
190
|
+
code_snippet TEXT,
|
|
191
|
+
success BOOLEAN,
|
|
192
|
+
execution_time REAL,
|
|
193
|
+
score_vector TEXT, -- JSON {perf: 0.95, correctness: 1.0, ...}
|
|
194
|
+
reflection TEXT,
|
|
195
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
196
|
+
FOREIGN KEY(task_id) REFERENCES tasks(id)
|
|
197
|
+
)
|
|
198
|
+
""")
|
|
199
|
+
cursor.execute("""
|
|
200
|
+
CREATE TABLE IF NOT EXISTS benchmarks (
|
|
201
|
+
id INTEGER PRIMARY KEY,
|
|
202
|
+
attempt_id INTEGER,
|
|
203
|
+
median_time REAL,
|
|
204
|
+
std_dev REAL,
|
|
205
|
+
repeats INTEGER,
|
|
206
|
+
confidence REAL,
|
|
207
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
208
|
+
FOREIGN KEY(attempt_id) REFERENCES attempts(id)
|
|
209
|
+
)
|
|
210
|
+
""")
|
|
211
|
+
cursor.execute("""
|
|
212
|
+
CREATE TABLE IF NOT EXISTS reflections (
|
|
213
|
+
id INTEGER PRIMARY KEY,
|
|
214
|
+
attempt_id INTEGER,
|
|
215
|
+
what_happened TEXT,
|
|
216
|
+
why TEXT,
|
|
217
|
+
evidence TEXT,
|
|
218
|
+
lessons TEXT, -- JSON
|
|
219
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
220
|
+
FOREIGN KEY(attempt_id) REFERENCES attempts(id)
|
|
221
|
+
)
|
|
222
|
+
""")
|
|
223
|
+
cursor.execute("""
|
|
224
|
+
CREATE TABLE IF NOT EXISTS repositories (
|
|
225
|
+
id INTEGER PRIMARY KEY,
|
|
226
|
+
path TEXT UNIQUE,
|
|
227
|
+
last_updated TEXT,
|
|
228
|
+
git_commit TEXT
|
|
229
|
+
)
|
|
230
|
+
""")
|
|
231
|
+
cursor.execute("""
|
|
232
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
233
|
+
id INTEGER PRIMARY KEY,
|
|
234
|
+
repo_id INTEGER,
|
|
235
|
+
file_path TEXT,
|
|
236
|
+
name TEXT,
|
|
237
|
+
type TEXT, -- function, class, etc.
|
|
238
|
+
calls TEXT, -- JSON
|
|
239
|
+
hash TEXT,
|
|
240
|
+
FOREIGN KEY(repo_id) REFERENCES repositories(id)
|
|
241
|
+
)
|
|
242
|
+
""")
|
|
243
|
+
cursor.execute("""
|
|
244
|
+
CREATE TABLE IF NOT EXISTS workers (
|
|
245
|
+
name TEXT PRIMARY KEY,
|
|
246
|
+
priority INTEGER DEFAULT 100,
|
|
247
|
+
capabilities TEXT -- JSON
|
|
248
|
+
)
|
|
249
|
+
""")
|
|
250
|
+
# Indexes on foreign keys / common lookup columns. Cheap to create, and
|
|
251
|
+
# they matter as soon as the read path runs over a non-trivial history.
|
|
252
|
+
for stmt in (
|
|
253
|
+
"CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id)",
|
|
254
|
+
"CREATE INDEX IF NOT EXISTS idx_attempts_task ON attempts(task_id)",
|
|
255
|
+
"CREATE INDEX IF NOT EXISTS idx_attempts_worker ON attempts(worker_name)",
|
|
256
|
+
"CREATE INDEX IF NOT EXISTS idx_bench_attempt ON benchmarks(attempt_id)",
|
|
257
|
+
"CREATE INDEX IF NOT EXISTS idx_reflect_attempt ON reflections(attempt_id)",
|
|
258
|
+
"CREATE INDEX IF NOT EXISTS idx_symbols_repo ON symbols(repo_id)",
|
|
259
|
+
"CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)",
|
|
260
|
+
):
|
|
261
|
+
cursor.execute(stmt)
|
|
262
|
+
self.conn.commit()
|
|
263
|
+
logger.info("[OK] MemoryEngine initialized: %s", self.db_path)
|
|
264
|
+
|
|
265
|
+
def _commit_now(self) -> None:
|
|
266
|
+
"""Commit immediately; split out so tests can count real commits."""
|
|
267
|
+
self.conn.commit()
|
|
268
|
+
|
|
269
|
+
def _rollback_now(self) -> None:
|
|
270
|
+
"""Rollback immediately; split out so tests can count rollbacks."""
|
|
271
|
+
self.conn.rollback()
|
|
272
|
+
|
|
273
|
+
def _commit_if_needed(self) -> None:
|
|
274
|
+
"""Commit single writes, but defer inside MemoryEngine.batch()."""
|
|
275
|
+
if self._transaction_depth == 0:
|
|
276
|
+
self._commit_now()
|
|
277
|
+
|
|
278
|
+
def batch(self) -> "_MemoryBatch":
|
|
279
|
+
"""Batch multiple writes into one SQLite transaction.
|
|
280
|
+
|
|
281
|
+
Nested batches are safe: only the outermost context opens/closes the
|
|
282
|
+
transaction. Any exception in a nested batch marks the outer transaction
|
|
283
|
+
rollback-only so caught inner errors cannot accidentally commit partial
|
|
284
|
+
state.
|
|
285
|
+
"""
|
|
286
|
+
return _MemoryBatch(self)
|
|
287
|
+
|
|
288
|
+
# ---- write helpers (all lock-guarded) ----
|
|
289
|
+
|
|
290
|
+
def start_session(self, goal: str, strategy: Optional[str] = None) -> int:
|
|
291
|
+
with self._lock:
|
|
292
|
+
cursor = self.conn.cursor()
|
|
293
|
+
cursor.execute(
|
|
294
|
+
"INSERT INTO sessions (goal, strategy) VALUES (?, ?)",
|
|
295
|
+
(goal, strategy),
|
|
296
|
+
)
|
|
297
|
+
self._commit_if_needed()
|
|
298
|
+
return cursor.lastrowid
|
|
299
|
+
|
|
300
|
+
def _dependencies_satisfied_locked(self, dependencies: List[int]) -> bool:
|
|
301
|
+
"""Return True only when every dependency task exists and completed."""
|
|
302
|
+
if not dependencies:
|
|
303
|
+
return True
|
|
304
|
+
placeholders = ",".join("?" for _ in dependencies)
|
|
305
|
+
cur = self.conn.execute(
|
|
306
|
+
f"SELECT id, status FROM tasks WHERE id IN ({placeholders})",
|
|
307
|
+
tuple(dependencies),
|
|
308
|
+
)
|
|
309
|
+
statuses = {int(row["id"]): row["status"] for row in cur.fetchall()}
|
|
310
|
+
return all(statuses.get(int(dep)) == "completed" for dep in dependencies)
|
|
311
|
+
|
|
312
|
+
def create_task(self, session_id: int, name: str,
|
|
313
|
+
dependencies: Optional[List[int]] = None) -> int:
|
|
314
|
+
deps = [int(dep) for dep in (dependencies or [])]
|
|
315
|
+
with self._lock:
|
|
316
|
+
cursor = self.conn.cursor()
|
|
317
|
+
status = (
|
|
318
|
+
"running" if self._dependencies_satisfied_locked(deps)
|
|
319
|
+
else "blocked"
|
|
320
|
+
)
|
|
321
|
+
cursor.execute(
|
|
322
|
+
"INSERT INTO tasks (session_id, name, status, dependencies) "
|
|
323
|
+
"VALUES (?, ?, ?, ?)",
|
|
324
|
+
(session_id, name, status, json.dumps(deps)),
|
|
325
|
+
)
|
|
326
|
+
self._commit_if_needed()
|
|
327
|
+
return cursor.lastrowid
|
|
328
|
+
|
|
329
|
+
def log_attempt(self, task_id: int, worker: str, data: Dict) -> int:
|
|
330
|
+
with self._lock:
|
|
331
|
+
cursor = self.conn.cursor()
|
|
332
|
+
cursor.execute("""
|
|
333
|
+
INSERT INTO attempts
|
|
334
|
+
(task_id, worker_name, code_snippet, success,
|
|
335
|
+
execution_time, score_vector, reflection)
|
|
336
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
337
|
+
""", (
|
|
338
|
+
task_id, worker, data.get("code"), data.get("success"),
|
|
339
|
+
data.get("time"), json.dumps(data.get("score_vector", {})),
|
|
340
|
+
data.get("reflection"),
|
|
341
|
+
))
|
|
342
|
+
self._commit_if_needed()
|
|
343
|
+
return cursor.lastrowid
|
|
344
|
+
|
|
345
|
+
def log_benchmark(self, attempt_id: int, stats: Dict) -> int:
|
|
346
|
+
with self._lock:
|
|
347
|
+
cursor = self.conn.cursor()
|
|
348
|
+
cursor.execute("""
|
|
349
|
+
INSERT INTO benchmarks
|
|
350
|
+
(attempt_id, median_time, std_dev, repeats, confidence)
|
|
351
|
+
VALUES (?, ?, ?, ?, ?)
|
|
352
|
+
""", (
|
|
353
|
+
attempt_id, stats.get("median"), stats.get("stdev"),
|
|
354
|
+
stats.get("repeats"), stats.get("confidence"),
|
|
355
|
+
))
|
|
356
|
+
self._commit_if_needed()
|
|
357
|
+
return cursor.lastrowid
|
|
358
|
+
|
|
359
|
+
def register_worker(self, name: str, priority: int,
|
|
360
|
+
capabilities: List[str]) -> None:
|
|
361
|
+
with self._lock:
|
|
362
|
+
cursor = self.conn.cursor()
|
|
363
|
+
cursor.execute(
|
|
364
|
+
"INSERT INTO workers (name, priority, capabilities) "
|
|
365
|
+
"VALUES (?, ?, ?) "
|
|
366
|
+
"ON CONFLICT(name) DO UPDATE SET "
|
|
367
|
+
"priority=excluded.priority, "
|
|
368
|
+
"capabilities=excluded.capabilities",
|
|
369
|
+
(name, priority, json.dumps(capabilities)),
|
|
370
|
+
)
|
|
371
|
+
self._commit_if_needed()
|
|
372
|
+
|
|
373
|
+
def complete_task(self, task_id: int) -> None:
|
|
374
|
+
with self._lock:
|
|
375
|
+
cursor = self.conn.cursor()
|
|
376
|
+
cursor.execute(
|
|
377
|
+
"UPDATE tasks SET status='completed' WHERE id = ?",
|
|
378
|
+
(task_id,))
|
|
379
|
+
self._commit_if_needed()
|
|
380
|
+
|
|
381
|
+
def fail_task(self, task_id: int) -> None:
|
|
382
|
+
with self._lock:
|
|
383
|
+
cursor = self.conn.cursor()
|
|
384
|
+
cursor.execute(
|
|
385
|
+
"UPDATE tasks SET status='failed' WHERE id = ?",
|
|
386
|
+
(task_id,))
|
|
387
|
+
self._commit_if_needed()
|
|
388
|
+
|
|
389
|
+
def complete_session(self, session_id: int) -> None:
|
|
390
|
+
with self._lock:
|
|
391
|
+
cursor = self.conn.cursor()
|
|
392
|
+
cursor.execute(
|
|
393
|
+
"UPDATE sessions SET status='completed' WHERE id = ?",
|
|
394
|
+
(session_id,))
|
|
395
|
+
self._commit_if_needed()
|
|
396
|
+
|
|
397
|
+
def fail_session(self, session_id: int) -> None:
|
|
398
|
+
with self._lock:
|
|
399
|
+
cursor = self.conn.cursor()
|
|
400
|
+
cursor.execute(
|
|
401
|
+
"UPDATE sessions SET status='failed' WHERE id = ?",
|
|
402
|
+
(session_id,))
|
|
403
|
+
self._commit_if_needed()
|
|
404
|
+
|
|
405
|
+
def block_session(self, session_id: int) -> None:
|
|
406
|
+
with self._lock:
|
|
407
|
+
cursor = self.conn.cursor()
|
|
408
|
+
cursor.execute(
|
|
409
|
+
"UPDATE sessions SET status='blocked' WHERE id = ?",
|
|
410
|
+
(session_id,))
|
|
411
|
+
self._commit_if_needed()
|
|
412
|
+
|
|
413
|
+
# ---- read helpers (lock-guarded; single shared connection) ----
|
|
414
|
+
# Reads are serialized through the same lock as writes because the
|
|
415
|
+
# connection is shared across threads. WAL's reader/writer concurrency only
|
|
416
|
+
# pays off with separate connections — the upgrade path if reads ever
|
|
417
|
+
# become hot is a connection-per-thread pool.
|
|
418
|
+
|
|
419
|
+
@staticmethod
|
|
420
|
+
def _rows(cursor) -> List[Dict]:
|
|
421
|
+
return [dict(r) for r in cursor.fetchall()]
|
|
422
|
+
|
|
423
|
+
def get_session(self, session_id: int) -> Optional[Dict]:
|
|
424
|
+
with self._lock:
|
|
425
|
+
cur = self.conn.execute(
|
|
426
|
+
"SELECT * FROM sessions WHERE id = ?", (session_id,))
|
|
427
|
+
row = cur.fetchone()
|
|
428
|
+
return dict(row) if row else None
|
|
429
|
+
|
|
430
|
+
def get_task(self, task_id: int) -> Optional[Dict]:
|
|
431
|
+
with self._lock:
|
|
432
|
+
cur = self.conn.execute(
|
|
433
|
+
"SELECT * FROM tasks WHERE id = ?", (task_id,))
|
|
434
|
+
row = cur.fetchone()
|
|
435
|
+
return dict(row) if row else None
|
|
436
|
+
|
|
437
|
+
def get_tasks(self, session_id: int) -> List[Dict]:
|
|
438
|
+
with self._lock:
|
|
439
|
+
cur = self.conn.execute(
|
|
440
|
+
"SELECT * FROM tasks WHERE session_id = ? ORDER BY id",
|
|
441
|
+
(session_id,))
|
|
442
|
+
return self._rows(cur)
|
|
443
|
+
|
|
444
|
+
def get_attempts(self, task_id: int) -> List[Dict]:
|
|
445
|
+
with self._lock:
|
|
446
|
+
cur = self.conn.execute(
|
|
447
|
+
"SELECT * FROM attempts WHERE task_id = ? ORDER BY id",
|
|
448
|
+
(task_id,))
|
|
449
|
+
return self._rows(cur)
|
|
450
|
+
|
|
451
|
+
def best_attempt(self, task_id: int) -> Optional[Dict]:
|
|
452
|
+
"""Fastest SUCCESSFUL attempt for a task — the selection primitive.
|
|
453
|
+
|
|
454
|
+
Orders by measured median time when a benchmark row exists, else by the
|
|
455
|
+
attempt's own execution_time. Incorrect candidates are success=0 and so
|
|
456
|
+
are excluded automatically.
|
|
457
|
+
"""
|
|
458
|
+
with self._lock:
|
|
459
|
+
cur = self.conn.execute("""
|
|
460
|
+
SELECT a.*, b.median_time, b.confidence
|
|
461
|
+
FROM attempts a
|
|
462
|
+
LEFT JOIN benchmarks b ON b.attempt_id = a.id
|
|
463
|
+
WHERE a.task_id = ? AND a.success = 1
|
|
464
|
+
ORDER BY COALESCE(b.median_time, a.execution_time) ASC
|
|
465
|
+
LIMIT 1
|
|
466
|
+
""", (task_id,))
|
|
467
|
+
row = cur.fetchone()
|
|
468
|
+
return dict(row) if row else None
|
|
469
|
+
|
|
470
|
+
def recent_attempts(self, limit: int = 10) -> List[Dict]:
|
|
471
|
+
with self._lock:
|
|
472
|
+
cur = self.conn.execute(
|
|
473
|
+
"SELECT * FROM attempts ORDER BY id DESC LIMIT ?", (limit,))
|
|
474
|
+
return self._rows(cur)
|
|
475
|
+
|
|
476
|
+
def close(self) -> None:
|
|
477
|
+
with self._lock:
|
|
478
|
+
self.conn.close()
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
class _MemoryBatch:
|
|
482
|
+
"""Re-entrant SQLite transaction scope for MemoryEngine writes."""
|
|
483
|
+
|
|
484
|
+
def __init__(self, memory: MemoryEngine):
|
|
485
|
+
self.memory = memory
|
|
486
|
+
|
|
487
|
+
def __enter__(self) -> MemoryEngine:
|
|
488
|
+
memory = self.memory
|
|
489
|
+
memory._lock.acquire()
|
|
490
|
+
if memory._transaction_depth == 0:
|
|
491
|
+
memory.conn.execute("BEGIN")
|
|
492
|
+
memory._transaction_rollback_only = False
|
|
493
|
+
memory._transaction_depth += 1
|
|
494
|
+
return memory
|
|
495
|
+
|
|
496
|
+
def __exit__(self, exc_type, exc, tb) -> bool:
|
|
497
|
+
memory = self.memory
|
|
498
|
+
if exc_type is not None:
|
|
499
|
+
memory._transaction_rollback_only = True
|
|
500
|
+
memory._transaction_depth -= 1
|
|
501
|
+
try:
|
|
502
|
+
if memory._transaction_depth == 0:
|
|
503
|
+
if memory._transaction_rollback_only:
|
|
504
|
+
memory._rollback_now()
|
|
505
|
+
else:
|
|
506
|
+
memory._commit_now()
|
|
507
|
+
memory._transaction_rollback_only = False
|
|
508
|
+
finally:
|
|
509
|
+
memory._lock.release()
|
|
510
|
+
return False
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
# =============================================================================
|
|
514
|
+
# WORKER BASE
|
|
515
|
+
# =============================================================================
|
|
516
|
+
class Worker:
|
|
517
|
+
"""Base class for all pluggable workers."""
|
|
518
|
+
NAME = "base"
|
|
519
|
+
PRIORITY = 100
|
|
520
|
+
CAPABILITIES: List[str] = []
|
|
521
|
+
|
|
522
|
+
def __init__(self, memory: MemoryEngine, config: Config):
|
|
523
|
+
self.memory = memory
|
|
524
|
+
self.config = config
|
|
525
|
+
|
|
526
|
+
def can_run(self, context: Dict) -> bool:
|
|
527
|
+
"""Determine if this worker is relevant."""
|
|
528
|
+
return True
|
|
529
|
+
|
|
530
|
+
def run(self, context: Dict) -> Dict:
|
|
531
|
+
"""Main execution. Must return an evidence-based result."""
|
|
532
|
+
raise NotImplementedError("Worker must implement run()")
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# =============================================================================
|
|
536
|
+
# WORKER: PerformanceWorker
|
|
537
|
+
# =============================================================================
|
|
538
|
+
class PerformanceWorker(Worker):
|
|
539
|
+
"""Benchmark candidate code with strict correctness gates.
|
|
540
|
+
|
|
541
|
+
Safety model: the candidate process is isolated and timeout-limited, but it
|
|
542
|
+
is not a security boundary. Parent-owned harness code controls timing and
|
|
543
|
+
result writing; candidate and baseline code run in separate namespaces.
|
|
544
|
+
"""
|
|
545
|
+
NAME = "performance"
|
|
546
|
+
PRIORITY = 80
|
|
547
|
+
CAPABILITIES = ["optimization", "benchmarking", "loop_transform"]
|
|
548
|
+
|
|
549
|
+
def __init__(self, memory: MemoryEngine, config: Config):
|
|
550
|
+
super().__init__(memory, config)
|
|
551
|
+
self.sandbox = ExecutionSandbox(
|
|
552
|
+
config.workspace,
|
|
553
|
+
config.sandbox_memory_limit_mb,
|
|
554
|
+
config.os_sandbox_command,
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
@staticmethod
|
|
558
|
+
def _indent(text: str, spaces: int = 4) -> str:
|
|
559
|
+
pad = " " * spaces
|
|
560
|
+
return "\n".join(
|
|
561
|
+
(pad + line) if line.strip() else line
|
|
562
|
+
for line in text.splitlines()
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
def _build_harness(self, ctx: Dict) -> "tuple[Optional[str], Optional[str], Optional[str]]":
|
|
566
|
+
"""Build a single-candidate benchmark harness.
|
|
567
|
+
|
|
568
|
+
Returns (script, error, result_file). Candidate code never receives the
|
|
569
|
+
result-file path in its namespace. Candidate and baseline run in fresh
|
|
570
|
+
setup-derived namespaces so baseline definitions cannot clobber the
|
|
571
|
+
candidate under test.
|
|
572
|
+
"""
|
|
573
|
+
call = ctx.get("call")
|
|
574
|
+
if not call:
|
|
575
|
+
return None, "no 'call' expression provided to time", None
|
|
576
|
+
|
|
577
|
+
code = ctx.get("code") or ctx.get("initial_code") or ""
|
|
578
|
+
setup = ctx.get("setup") or ""
|
|
579
|
+
test = ctx.get("test")
|
|
580
|
+
baseline_code = ctx.get("baseline_code") or ""
|
|
581
|
+
baseline_call = ctx.get("baseline_call")
|
|
582
|
+
warmup = max(0, int(self.config.benchmark_warmup))
|
|
583
|
+
repeats = max(1, int(self.config.benchmark_repeats))
|
|
584
|
+
target = max(0.0, float(self.config.benchmark_target_sample_time))
|
|
585
|
+
max_loops = max(1, int(self.config.benchmark_max_loops))
|
|
586
|
+
|
|
587
|
+
fd, result_file = tempfile.mkstemp(prefix="_ae_result_", suffix=".json")
|
|
588
|
+
os.close(fd)
|
|
589
|
+
|
|
590
|
+
p: List[str] = [
|
|
591
|
+
"import builtins as _ae_builtins",
|
|
592
|
+
"import json as _ae_json",
|
|
593
|
+
"import time as _ae_time",
|
|
594
|
+
"_ae_perf_counter = _ae_time.perf_counter",
|
|
595
|
+
"_ae_open = _ae_builtins.open",
|
|
596
|
+
f"_ae_result_file = {result_file!r}",
|
|
597
|
+
f"_ae_setup_source = {setup!r}",
|
|
598
|
+
f"_ae_code_source = {code!r}",
|
|
599
|
+
f"_ae_test_source = {test!r}",
|
|
600
|
+
f"_ae_call = {call!r}",
|
|
601
|
+
f"_ae_baseline_source = {baseline_code!r}",
|
|
602
|
+
f"_ae_baseline_call = {baseline_call!r}",
|
|
603
|
+
f"_ae_warmup = {warmup}",
|
|
604
|
+
f"_ae_repeats = {repeats}",
|
|
605
|
+
f"_ae_target_sample_time = {target!r}",
|
|
606
|
+
f"_ae_max_loops = {max_loops}",
|
|
607
|
+
"",
|
|
608
|
+
"def _ae_indent(_ae_text):",
|
|
609
|
+
" return '\\n'.join((' ' + _ae_line) if _ae_line.strip() else _ae_line for _ae_line in _ae_text.splitlines())",
|
|
610
|
+
"",
|
|
611
|
+
"def _ae_build_ns(_ae_code_source, _ae_label):",
|
|
612
|
+
" _ae_ns = {'__builtins__': __builtins__}",
|
|
613
|
+
" if _ae_setup_source:",
|
|
614
|
+
" exec(compile(_ae_setup_source, '<setup>', 'exec'), _ae_ns)",
|
|
615
|
+
" if _ae_code_source:",
|
|
616
|
+
" exec(compile(_ae_code_source, _ae_label, 'exec'), _ae_ns)",
|
|
617
|
+
" return _ae_ns",
|
|
618
|
+
"",
|
|
619
|
+
"def _ae_make_bench(_ae_ns, _ae_expr, _ae_label):",
|
|
620
|
+
" _ae_src = 'def _ae_bench():\\n' + _ae_indent(_ae_expr)",
|
|
621
|
+
" exec(compile(_ae_src, _ae_label, 'exec'), _ae_ns)",
|
|
622
|
+
" return _ae_ns['_ae_bench']",
|
|
623
|
+
"",
|
|
624
|
+
"def _ae_calibrate(_ae_fn):",
|
|
625
|
+
" for _ in range(_ae_warmup):",
|
|
626
|
+
" _ae_fn()",
|
|
627
|
+
" _ae_loops = 1",
|
|
628
|
+
" while _ae_loops < _ae_max_loops:",
|
|
629
|
+
" _ae_t0 = _ae_perf_counter()",
|
|
630
|
+
" for _ in range(_ae_loops):",
|
|
631
|
+
" _ae_fn()",
|
|
632
|
+
" _ae_elapsed = _ae_perf_counter() - _ae_t0",
|
|
633
|
+
" if _ae_elapsed >= _ae_target_sample_time or _ae_target_sample_time <= 0:",
|
|
634
|
+
" break",
|
|
635
|
+
" _ae_loops = min(_ae_loops * 2, _ae_max_loops)",
|
|
636
|
+
" return max(1, _ae_loops)",
|
|
637
|
+
"",
|
|
638
|
+
"def _ae_measure(_ae_fn):",
|
|
639
|
+
" _ae_loops = _ae_calibrate(_ae_fn)",
|
|
640
|
+
" _ae_times = []",
|
|
641
|
+
" for _ in range(_ae_repeats):",
|
|
642
|
+
" _ae_t0 = _ae_perf_counter()",
|
|
643
|
+
" for _ in range(_ae_loops):",
|
|
644
|
+
" _ae_fn()",
|
|
645
|
+
" _ae_times.append((_ae_perf_counter() - _ae_t0) / _ae_loops)",
|
|
646
|
+
" return _ae_times, _ae_loops",
|
|
647
|
+
"",
|
|
648
|
+
"_ae_payload = {'times': [], 'correct': False, 'base_times': None, 'error': None}",
|
|
649
|
+
"try:",
|
|
650
|
+
" _ae_candidate_ns = _ae_build_ns(_ae_code_source, '<candidate>')",
|
|
651
|
+
" _ae_correct = None",
|
|
652
|
+
" if _ae_test_source:",
|
|
653
|
+
" try:",
|
|
654
|
+
" exec(compile(_ae_test_source, '<test>', 'exec'), _ae_candidate_ns)",
|
|
655
|
+
" _ae_correct = True",
|
|
656
|
+
" except Exception:",
|
|
657
|
+
" _ae_correct = False",
|
|
658
|
+
" _ae_fn = _ae_make_bench(_ae_candidate_ns, _ae_call, '<bench-candidate>')",
|
|
659
|
+
" _ae_times, _ae_loops = _ae_measure(_ae_fn)",
|
|
660
|
+
" _ae_base_times = None",
|
|
661
|
+
" _ae_base_loops = None",
|
|
662
|
+
" if _ae_baseline_call:",
|
|
663
|
+
" _ae_base_ns = _ae_build_ns(_ae_baseline_source, '<baseline>')",
|
|
664
|
+
" _ae_base_fn = _ae_make_bench(_ae_base_ns, _ae_baseline_call, '<bench-baseline>')",
|
|
665
|
+
" _ae_base_times, _ae_base_loops = _ae_measure(_ae_base_fn)",
|
|
666
|
+
" _ae_payload = {",
|
|
667
|
+
" 'times': _ae_times,",
|
|
668
|
+
" 'correct': _ae_correct,",
|
|
669
|
+
" 'base_times': _ae_base_times,",
|
|
670
|
+
" 'loops_per_sample': _ae_loops,",
|
|
671
|
+
" 'base_loops_per_sample': _ae_base_loops,",
|
|
672
|
+
" 'error': None,",
|
|
673
|
+
" }",
|
|
674
|
+
"except BaseException as _ae_exc:",
|
|
675
|
+
" _ae_payload = {'times': [], 'correct': False, 'base_times': None, 'error': repr(_ae_exc)}",
|
|
676
|
+
"with _ae_open(_ae_result_file, 'w', encoding='utf-8') as _ae_f:",
|
|
677
|
+
" _ae_f.write(_ae_json.dumps(_ae_payload))",
|
|
678
|
+
]
|
|
679
|
+
return "\n".join(p), None, result_file
|
|
680
|
+
|
|
681
|
+
def _build_batch_harness(self, ctx: Dict,
|
|
682
|
+
candidates: List[str]) -> "tuple[str, Optional[str], Optional[str]]":
|
|
683
|
+
"""Build a parent-controlled batch benchmark harness."""
|
|
684
|
+
call = ctx.get("call")
|
|
685
|
+
if not call:
|
|
686
|
+
return "", "no 'call' expression provided to time", None
|
|
687
|
+
test = ctx.get("test")
|
|
688
|
+
setup = ctx.get("setup") or ""
|
|
689
|
+
warmup = max(0, int(self.config.benchmark_warmup))
|
|
690
|
+
repeats = max(1, int(self.config.benchmark_repeats))
|
|
691
|
+
target = max(0.0, float(self.config.benchmark_target_sample_time))
|
|
692
|
+
max_loops = max(1, int(self.config.benchmark_max_loops))
|
|
693
|
+
|
|
694
|
+
fd, result_file = tempfile.mkstemp(prefix="_ae_batch_", suffix=".json")
|
|
695
|
+
os.close(fd)
|
|
696
|
+
|
|
697
|
+
p: List[str] = [
|
|
698
|
+
"import builtins as _ae_builtins",
|
|
699
|
+
"import json as _ae_json",
|
|
700
|
+
"import time as _ae_time",
|
|
701
|
+
"_ae_perf_counter = _ae_time.perf_counter",
|
|
702
|
+
"_ae_open = _ae_builtins.open",
|
|
703
|
+
f"_ae_result_file = {result_file!r}",
|
|
704
|
+
f"_ae_setup_source = {setup!r}",
|
|
705
|
+
f"_ae_candidates = {candidates!r}",
|
|
706
|
+
f"_ae_call = {call!r}",
|
|
707
|
+
f"_ae_test_source = {test!r}",
|
|
708
|
+
f"_ae_warmup = {warmup}",
|
|
709
|
+
f"_ae_repeats = {repeats}",
|
|
710
|
+
f"_ae_target_sample_time = {target!r}",
|
|
711
|
+
f"_ae_max_loops = {max_loops}",
|
|
712
|
+
"",
|
|
713
|
+
"def _ae_indent(_ae_text):",
|
|
714
|
+
" return '\\n'.join((' ' + _ae_line) if _ae_line.strip() else _ae_line for _ae_line in _ae_text.splitlines())",
|
|
715
|
+
"",
|
|
716
|
+
"def _ae_build_ns(_ae_code_source, _ae_label):",
|
|
717
|
+
" _ae_ns = {'__builtins__': __builtins__}",
|
|
718
|
+
" if _ae_setup_source:",
|
|
719
|
+
" exec(compile(_ae_setup_source, '<setup>', 'exec'), _ae_ns)",
|
|
720
|
+
" exec(compile(_ae_code_source, _ae_label, 'exec'), _ae_ns)",
|
|
721
|
+
" return _ae_ns",
|
|
722
|
+
"",
|
|
723
|
+
"def _ae_make_bench(_ae_ns):",
|
|
724
|
+
" _ae_src = 'def _ae_bench():\\n' + _ae_indent(_ae_call)",
|
|
725
|
+
" exec(compile(_ae_src, '<bench>', 'exec'), _ae_ns)",
|
|
726
|
+
" return _ae_ns['_ae_bench']",
|
|
727
|
+
"",
|
|
728
|
+
"def _ae_calibrate(_ae_fn):",
|
|
729
|
+
" for _ in range(_ae_warmup):",
|
|
730
|
+
" _ae_fn()",
|
|
731
|
+
" _ae_loops = 1",
|
|
732
|
+
" while _ae_loops < _ae_max_loops:",
|
|
733
|
+
" _ae_t0 = _ae_perf_counter()",
|
|
734
|
+
" for _ in range(_ae_loops):",
|
|
735
|
+
" _ae_fn()",
|
|
736
|
+
" _ae_elapsed = _ae_perf_counter() - _ae_t0",
|
|
737
|
+
" if _ae_elapsed >= _ae_target_sample_time or _ae_target_sample_time <= 0:",
|
|
738
|
+
" break",
|
|
739
|
+
" _ae_loops = min(_ae_loops * 2, _ae_max_loops)",
|
|
740
|
+
" return max(1, _ae_loops)",
|
|
741
|
+
"",
|
|
742
|
+
"def _ae_measure(_ae_fn):",
|
|
743
|
+
" _ae_loops = _ae_calibrate(_ae_fn)",
|
|
744
|
+
" _ae_times = []",
|
|
745
|
+
" for _ in range(_ae_repeats):",
|
|
746
|
+
" _ae_t0 = _ae_perf_counter()",
|
|
747
|
+
" for _ in range(_ae_loops):",
|
|
748
|
+
" _ae_fn()",
|
|
749
|
+
" _ae_times.append((_ae_perf_counter() - _ae_t0) / _ae_loops)",
|
|
750
|
+
" return _ae_times, _ae_loops",
|
|
751
|
+
"",
|
|
752
|
+
"_ae_all_results = {}",
|
|
753
|
+
"for _ae_idx, _ae_code in enumerate(_ae_candidates):",
|
|
754
|
+
" try:",
|
|
755
|
+
" _ae_ns = _ae_build_ns(_ae_code, f'<candidate-{_ae_idx}>')",
|
|
756
|
+
" _ae_correct = None",
|
|
757
|
+
" if _ae_test_source:",
|
|
758
|
+
" try:",
|
|
759
|
+
" exec(compile(_ae_test_source, '<test>', 'exec'), _ae_ns)",
|
|
760
|
+
" _ae_correct = True",
|
|
761
|
+
" except Exception:",
|
|
762
|
+
" _ae_correct = False",
|
|
763
|
+
" _ae_fn = _ae_make_bench(_ae_ns)",
|
|
764
|
+
" _ae_times, _ae_loops = _ae_measure(_ae_fn)",
|
|
765
|
+
" _ae_all_results[_ae_idx] = {'times': _ae_times, 'correct': _ae_correct, 'loops_per_sample': _ae_loops, 'error': None}",
|
|
766
|
+
" except BaseException as _ae_exc:",
|
|
767
|
+
" _ae_all_results[_ae_idx] = {'times': [], 'correct': False, 'loops_per_sample': None, 'error': repr(_ae_exc)}",
|
|
768
|
+
"",
|
|
769
|
+
"with _ae_open(_ae_result_file, 'w', encoding='utf-8') as _ae_f:",
|
|
770
|
+
" _ae_f.write(_ae_json.dumps(_ae_all_results))",
|
|
771
|
+
]
|
|
772
|
+
return "\n".join(p), None, result_file
|
|
773
|
+
|
|
774
|
+
def _parse_bench(self, result_file: str) -> Optional[Dict]:
|
|
775
|
+
"""Read benchmark results from the temp JSON file."""
|
|
776
|
+
try:
|
|
777
|
+
with open(result_file, "r", encoding="utf-8") as f:
|
|
778
|
+
return json.loads(f.read())
|
|
779
|
+
except (json.JSONDecodeError, OSError):
|
|
780
|
+
return None
|
|
781
|
+
|
|
782
|
+
@staticmethod
|
|
783
|
+
def _cleanup_result(result_file: Optional[str]) -> None:
|
|
784
|
+
"""Remove the temp result file if it exists."""
|
|
785
|
+
if result_file:
|
|
786
|
+
try:
|
|
787
|
+
Path(result_file).unlink(missing_ok=True)
|
|
788
|
+
except OSError:
|
|
789
|
+
pass
|
|
790
|
+
|
|
791
|
+
@staticmethod
|
|
792
|
+
def _summarize(times: List[float]) -> Dict[str, float]:
|
|
793
|
+
n = len(times)
|
|
794
|
+
median = statistics.median(times)
|
|
795
|
+
stdev = statistics.stdev(times) if n >= 2 else 0.0
|
|
796
|
+
rsd = (stdev / median) if median > 0 else 0.0
|
|
797
|
+
return {
|
|
798
|
+
"median": median,
|
|
799
|
+
"mean": statistics.fmean(times),
|
|
800
|
+
"stdev": stdev,
|
|
801
|
+
"min": min(times),
|
|
802
|
+
"repeats": n,
|
|
803
|
+
"rsd": rsd,
|
|
804
|
+
"confidence": max(0.0, 1.0 - rsd),
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
def _fail(self, ctx: Dict, reflection: str) -> Dict:
|
|
808
|
+
result = {
|
|
809
|
+
"success": False,
|
|
810
|
+
"code": ctx.get("code") or ctx.get("initial_code"),
|
|
811
|
+
"time": None,
|
|
812
|
+
"score_vector": {},
|
|
813
|
+
"reflection": reflection,
|
|
814
|
+
}
|
|
815
|
+
task_id = ctx.get("task_id")
|
|
816
|
+
if task_id is not None:
|
|
817
|
+
self.memory.log_attempt(task_id, self.NAME, result)
|
|
818
|
+
return result
|
|
819
|
+
|
|
820
|
+
@staticmethod
|
|
821
|
+
def _describe(stats: Dict, correct, speedup) -> str:
|
|
822
|
+
ms = stats["median"] * 1e3
|
|
823
|
+
sd = stats["stdev"] * 1e3
|
|
824
|
+
parts = [f"median {ms:.4f} ms over {stats['repeats']} runs (±{sd:.4f} ms)"]
|
|
825
|
+
parts.append(
|
|
826
|
+
"correctness not checked" if correct is None
|
|
827
|
+
else ("correct" if correct else "INCORRECT (test failed)")
|
|
828
|
+
)
|
|
829
|
+
if speedup:
|
|
830
|
+
parts.append(f"{speedup:.2f}x vs baseline")
|
|
831
|
+
return "; ".join(parts)
|
|
832
|
+
|
|
833
|
+
def _success_from_correctness(self, correct) -> bool:
|
|
834
|
+
"""Strict by default: unknown correctness is not success."""
|
|
835
|
+
if self.config.require_correctness_test:
|
|
836
|
+
return correct is True
|
|
837
|
+
return correct is not False
|
|
838
|
+
|
|
839
|
+
def run(self, context: Dict) -> Dict:
|
|
840
|
+
script, err, result_file = self._build_harness(context)
|
|
841
|
+
if err:
|
|
842
|
+
return self._fail(context, f"harness error: {err}")
|
|
843
|
+
|
|
844
|
+
exec_result = self.sandbox.run_code(
|
|
845
|
+
script, timeout=self.config.default_timeout
|
|
846
|
+
)
|
|
847
|
+
if not exec_result.get("success"):
|
|
848
|
+
reason = (exec_result.get("error")
|
|
849
|
+
or (exec_result.get("stderr") or "").strip()
|
|
850
|
+
or "unknown failure")
|
|
851
|
+
self._cleanup_result(result_file)
|
|
852
|
+
return self._fail(context, f"execution failed: {reason}")
|
|
853
|
+
|
|
854
|
+
payload = self._parse_bench(result_file)
|
|
855
|
+
self._cleanup_result(result_file)
|
|
856
|
+
if not payload or not payload.get("times"):
|
|
857
|
+
detail = payload.get("error") if isinstance(payload, dict) else None
|
|
858
|
+
return self._fail(
|
|
859
|
+
context,
|
|
860
|
+
"no benchmark payload returned" + (f": {detail}" if detail else ""),
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
stats = self._summarize(payload["times"])
|
|
864
|
+
correct = payload.get("correct")
|
|
865
|
+
speedup = None
|
|
866
|
+
base = payload.get("base_times")
|
|
867
|
+
if base:
|
|
868
|
+
base_stats = self._summarize(base)
|
|
869
|
+
if stats["median"] > 0:
|
|
870
|
+
speedup = base_stats["median"] / stats["median"]
|
|
871
|
+
|
|
872
|
+
score_vector = {
|
|
873
|
+
"median_s": round(stats["median"], 9),
|
|
874
|
+
"mean_s": round(stats["mean"], 9),
|
|
875
|
+
"stdev_s": round(stats["stdev"], 9),
|
|
876
|
+
"min_s": round(stats["min"], 9),
|
|
877
|
+
"repeats": stats["repeats"],
|
|
878
|
+
"loops_per_sample": payload.get("loops_per_sample"),
|
|
879
|
+
"correct": correct,
|
|
880
|
+
"speedup_vs_baseline": round(speedup, 4) if speedup else None,
|
|
881
|
+
}
|
|
882
|
+
success = self._success_from_correctness(correct)
|
|
883
|
+
reflection = self._describe(stats, correct, speedup)
|
|
884
|
+
if correct is None and self.config.require_correctness_test:
|
|
885
|
+
reflection += "; correctness not checked; refusing success"
|
|
886
|
+
result = {
|
|
887
|
+
"success": success,
|
|
888
|
+
"code": context.get("code") or context.get("initial_code"),
|
|
889
|
+
"time": stats["median"],
|
|
890
|
+
"score_vector": score_vector,
|
|
891
|
+
"reflection": reflection,
|
|
892
|
+
}
|
|
893
|
+
task_id = context.get("task_id")
|
|
894
|
+
if task_id is not None:
|
|
895
|
+
attempt_id = self.memory.log_attempt(task_id, self.NAME, result)
|
|
896
|
+
if success:
|
|
897
|
+
self.memory.log_benchmark(attempt_id, {
|
|
898
|
+
"median": stats["median"], "stdev": stats["stdev"],
|
|
899
|
+
"repeats": stats["repeats"],
|
|
900
|
+
"confidence": stats["confidence"],
|
|
901
|
+
})
|
|
902
|
+
return result
|
|
903
|
+
|
|
904
|
+
def batch_run(self, context: Dict, candidates: List[str]) -> List[Dict]:
|
|
905
|
+
"""Benchmark multiple candidates in a single subprocess."""
|
|
906
|
+
if not candidates:
|
|
907
|
+
return []
|
|
908
|
+
|
|
909
|
+
script, err, result_file = self._build_batch_harness(context, candidates)
|
|
910
|
+
if err:
|
|
911
|
+
return [self._fail(context, f"harness error: {err}")
|
|
912
|
+
for _ in candidates]
|
|
913
|
+
|
|
914
|
+
exec_result = self.sandbox.run_code(
|
|
915
|
+
script, timeout=self.config.default_timeout
|
|
916
|
+
)
|
|
917
|
+
if not exec_result.get("success"):
|
|
918
|
+
reason = (exec_result.get("error")
|
|
919
|
+
or (exec_result.get("stderr") or "").strip()
|
|
920
|
+
or "unknown failure")
|
|
921
|
+
self._cleanup_result(result_file)
|
|
922
|
+
return [self._fail(context, f"execution failed: {reason}")
|
|
923
|
+
for _ in candidates]
|
|
924
|
+
|
|
925
|
+
payload = self._parse_bench(result_file)
|
|
926
|
+
self._cleanup_result(result_file)
|
|
927
|
+
if not payload:
|
|
928
|
+
return [self._fail(context, "no benchmark payload returned")
|
|
929
|
+
for _ in candidates]
|
|
930
|
+
|
|
931
|
+
results = []
|
|
932
|
+
task_id = context.get("task_id")
|
|
933
|
+
batch_scope = self.memory.batch() if task_id is not None else None
|
|
934
|
+
if batch_scope is not None:
|
|
935
|
+
batch_scope.__enter__()
|
|
936
|
+
try:
|
|
937
|
+
for i, code in enumerate(candidates):
|
|
938
|
+
entry = payload.get(str(i), {})
|
|
939
|
+
times = entry.get("times", [])
|
|
940
|
+
correct = entry.get("correct")
|
|
941
|
+
if not times:
|
|
942
|
+
detail = entry.get("error")
|
|
943
|
+
results.append(self._fail(
|
|
944
|
+
{**context, "code": code},
|
|
945
|
+
f"candidate {i}: no times" + (f": {detail}" if detail else ""),
|
|
946
|
+
))
|
|
947
|
+
continue
|
|
948
|
+
|
|
949
|
+
stats = self._summarize(times)
|
|
950
|
+
score_vector = {
|
|
951
|
+
"median_s": round(stats["median"], 9),
|
|
952
|
+
"mean_s": round(stats["mean"], 9),
|
|
953
|
+
"stdev_s": round(stats["stdev"], 9),
|
|
954
|
+
"min_s": round(stats["min"], 9),
|
|
955
|
+
"repeats": stats["repeats"],
|
|
956
|
+
"loops_per_sample": entry.get("loops_per_sample"),
|
|
957
|
+
"correct": correct,
|
|
958
|
+
"speedup_vs_baseline": None,
|
|
959
|
+
}
|
|
960
|
+
success = self._success_from_correctness(correct)
|
|
961
|
+
reflection = self._describe(stats, correct, None)
|
|
962
|
+
if correct is None and self.config.require_correctness_test:
|
|
963
|
+
reflection += "; correctness not checked; refusing success"
|
|
964
|
+
result = {
|
|
965
|
+
"success": success,
|
|
966
|
+
"code": code,
|
|
967
|
+
"time": stats["median"],
|
|
968
|
+
"score_vector": score_vector,
|
|
969
|
+
"reflection": reflection,
|
|
970
|
+
}
|
|
971
|
+
if task_id is not None:
|
|
972
|
+
attempt_id = self.memory.log_attempt(task_id, self.NAME, result)
|
|
973
|
+
if success:
|
|
974
|
+
self.memory.log_benchmark(attempt_id, {
|
|
975
|
+
"median": stats["median"], "stdev": stats["stdev"],
|
|
976
|
+
"repeats": stats["repeats"],
|
|
977
|
+
"confidence": stats["confidence"],
|
|
978
|
+
})
|
|
979
|
+
results.append(result)
|
|
980
|
+
except Exception:
|
|
981
|
+
if batch_scope is not None:
|
|
982
|
+
batch_scope.__exit__(*sys.exc_info())
|
|
983
|
+
raise
|
|
984
|
+
else:
|
|
985
|
+
if batch_scope is not None:
|
|
986
|
+
batch_scope.__exit__(None, None, None)
|
|
987
|
+
return results
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
# =============================================================================
|
|
991
|
+
# LLM ROUTER
|
|
992
|
+
# =============================================================================
|
|
993
|
+
class LLMRouter:
|
|
994
|
+
"""Provider-agnostic model router.
|
|
995
|
+
|
|
996
|
+
For testability and offline use, an optional non-streaming `engine` and a
|
|
997
|
+
streaming `stream_engine` can be injected. Each is a callable:
|
|
998
|
+
engine(prompt: str, model: str) -> str
|
|
999
|
+
stream_engine(prompt: str, model: str) -> Iterator[str]
|
|
1000
|
+
When neither is given, the ollama provider is used if available, else a
|
|
1001
|
+
stub. This mirrors the pluggable candidate_provider used by optimize_loop.
|
|
1002
|
+
"""
|
|
1003
|
+
|
|
1004
|
+
def __init__(self, config: Config, engine=None, stream_engine=None):
|
|
1005
|
+
self.config = config
|
|
1006
|
+
self._engine = engine
|
|
1007
|
+
self._stream_engine = stream_engine
|
|
1008
|
+
|
|
1009
|
+
def generate(self, prompt: str, model: Optional[str] = None) -> str:
|
|
1010
|
+
model = model or self.config.default_model
|
|
1011
|
+
if self._engine is not None:
|
|
1012
|
+
return self._engine(prompt, model)
|
|
1013
|
+
if HAS_OLLAMA:
|
|
1014
|
+
try:
|
|
1015
|
+
resp = ollama.chat(
|
|
1016
|
+
model=model,
|
|
1017
|
+
messages=[{"role": "user", "content": prompt}],
|
|
1018
|
+
)
|
|
1019
|
+
return resp["message"]["content"]
|
|
1020
|
+
except Exception as e: # noqa: BLE001 - router must not crash caller
|
|
1021
|
+
logger.warning("LLM call failed: %s", e)
|
|
1022
|
+
return f"[LLM Error: {e}]"
|
|
1023
|
+
return "[LLM Stub - install ollama]"
|
|
1024
|
+
|
|
1025
|
+
def stream(self, prompt: str,
|
|
1026
|
+
model: Optional[str] = None) -> Iterator[str]:
|
|
1027
|
+
"""Yield response text in chunks as they arrive.
|
|
1028
|
+
|
|
1029
|
+
This is the path that lets a display update incrementally, so the
|
|
1030
|
+
time-to-first-display becomes the model's time-to-first-token rather
|
|
1031
|
+
than its full generation time.
|
|
1032
|
+
"""
|
|
1033
|
+
model = model or self.config.default_model
|
|
1034
|
+
if self._stream_engine is not None:
|
|
1035
|
+
yield from self._stream_engine(prompt, model)
|
|
1036
|
+
return
|
|
1037
|
+
if HAS_OLLAMA:
|
|
1038
|
+
try:
|
|
1039
|
+
for part in ollama.chat(
|
|
1040
|
+
model=model,
|
|
1041
|
+
messages=[{"role": "user", "content": prompt}],
|
|
1042
|
+
stream=True,
|
|
1043
|
+
):
|
|
1044
|
+
chunk = (part.get("message") or {}).get("content", "")
|
|
1045
|
+
if chunk:
|
|
1046
|
+
yield chunk
|
|
1047
|
+
except Exception as e: # noqa: BLE001
|
|
1048
|
+
logger.warning("LLM stream failed: %s", e)
|
|
1049
|
+
yield f"[LLM Error: {e}]"
|
|
1050
|
+
return
|
|
1051
|
+
yield "[LLM Stub - install ollama]"
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
# =============================================================================
|
|
1055
|
+
# LATENCY PROBE (call -> display)
|
|
1056
|
+
# =============================================================================
|
|
1057
|
+
def make_simulated_stream(n_tokens: int = 30, ttft: float = 0.10,
|
|
1058
|
+
per_token: float = 0.010, token: str = "lorem "):
|
|
1059
|
+
"""Build a fake STREAMING engine that mimics a model: an initial think
|
|
1060
|
+
delay (time-to-first-token) then tokens at a steady rate. Offline demos /
|
|
1061
|
+
tests only -- real numbers come from a real model."""
|
|
1062
|
+
def _engine(prompt: str, model: str) -> Iterator[str]:
|
|
1063
|
+
time.sleep(ttft)
|
|
1064
|
+
for _ in range(n_tokens):
|
|
1065
|
+
time.sleep(per_token)
|
|
1066
|
+
yield token
|
|
1067
|
+
return _engine
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def make_simulated_blocking(n_tokens: int = 30, ttft: float = 0.10,
|
|
1071
|
+
per_token: float = 0.010, token: str = "lorem "):
|
|
1072
|
+
"""Non-streaming counterpart with the SAME total cost as the streaming
|
|
1073
|
+
engine above, but it returns only once fully done. Pairing the two shows
|
|
1074
|
+
that streaming changes *when* output is visible, not total compute."""
|
|
1075
|
+
def _engine(prompt: str, model: str) -> str:
|
|
1076
|
+
time.sleep(ttft + n_tokens * per_token)
|
|
1077
|
+
return token * n_tokens
|
|
1078
|
+
return _engine
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def measure_latency(router: "LLMRouter", prompt: str, *,
|
|
1082
|
+
model: Optional[str] = None, repeats: int = 5,
|
|
1083
|
+
stream: bool = True, sink=None) -> Dict[str, Any]:
|
|
1084
|
+
"""Measure call->display latency over `repeats` runs.
|
|
1085
|
+
|
|
1086
|
+
Reports median/stdev/min for:
|
|
1087
|
+
ttfd - time to first displayed output (the perceived-latency metric).
|
|
1088
|
+
total - call -> fully displayed.
|
|
1089
|
+
In blocking mode nothing is visible until the whole response arrives, so
|
|
1090
|
+
ttfd == total by construction; that contrast is the point of the test.
|
|
1091
|
+
`sink(chunk)` is where display happens; default is a no-op for clean timing.
|
|
1092
|
+
"""
|
|
1093
|
+
sink = sink or (lambda s: None)
|
|
1094
|
+
ttfd: List[float] = []
|
|
1095
|
+
total: List[float] = []
|
|
1096
|
+
for _ in range(max(1, repeats)):
|
|
1097
|
+
t0 = time.perf_counter()
|
|
1098
|
+
first = None
|
|
1099
|
+
if stream:
|
|
1100
|
+
for chunk in router.stream(prompt, model):
|
|
1101
|
+
if first is None:
|
|
1102
|
+
first = time.perf_counter()
|
|
1103
|
+
sink(chunk)
|
|
1104
|
+
else:
|
|
1105
|
+
text = router.generate(prompt, model)
|
|
1106
|
+
first = time.perf_counter() # nothing visible until full text ready
|
|
1107
|
+
sink(text)
|
|
1108
|
+
end = time.perf_counter()
|
|
1109
|
+
if first is None:
|
|
1110
|
+
first = end
|
|
1111
|
+
ttfd.append(first - t0)
|
|
1112
|
+
total.append(end - t0)
|
|
1113
|
+
|
|
1114
|
+
def _stats(xs: List[float]) -> Dict[str, float]:
|
|
1115
|
+
n = len(xs)
|
|
1116
|
+
return {
|
|
1117
|
+
"median": statistics.median(xs),
|
|
1118
|
+
"stdev": statistics.stdev(xs) if n >= 2 else 0.0,
|
|
1119
|
+
"min": min(xs),
|
|
1120
|
+
"repeats": n,
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return {
|
|
1124
|
+
"mode": "stream" if stream else "blocking",
|
|
1125
|
+
"ttfd": _stats(ttfd),
|
|
1126
|
+
"total": _stats(total),
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
# =============================================================================
|
|
1131
|
+
# EXECUTION SANDBOX
|
|
1132
|
+
# =============================================================================
|
|
1133
|
+
class ExecutionSandbox:
|
|
1134
|
+
"""Process-isolated code execution with an enforced timeout.
|
|
1135
|
+
|
|
1136
|
+
IMPORTANT — this is ISOLATION, not a SECURITY BOUNDARY. For model-generated
|
|
1137
|
+
code, configure ``Config.os_sandbox_command`` with an OS-level sandbox
|
|
1138
|
+
wrapper (container/VM/sandbox tool). This class enforces timeout, cleans up
|
|
1139
|
+
process trees, and optionally applies POSIX memory limits; it does not
|
|
1140
|
+
prevent deliberate filesystem/network access by itself.
|
|
1141
|
+
"""
|
|
1142
|
+
|
|
1143
|
+
def __init__(self, workspace: str = "./workspace",
|
|
1144
|
+
memory_limit_mb: Optional[int] = 512,
|
|
1145
|
+
os_sandbox_command: Optional[List[str]] = None):
|
|
1146
|
+
self.workspace = Path(workspace)
|
|
1147
|
+
self.workspace.mkdir(parents=True, exist_ok=True)
|
|
1148
|
+
self.memory_limit_mb = memory_limit_mb
|
|
1149
|
+
if isinstance(os_sandbox_command, str):
|
|
1150
|
+
self.os_sandbox_command = [os_sandbox_command]
|
|
1151
|
+
else:
|
|
1152
|
+
self.os_sandbox_command = list(os_sandbox_command or [])
|
|
1153
|
+
|
|
1154
|
+
def _preexec(self) -> None:
|
|
1155
|
+
"""POSIX-only: cap address space so runaway allocations die fast."""
|
|
1156
|
+
if self.memory_limit_mb and HAS_RESOURCE:
|
|
1157
|
+
limit = self.memory_limit_mb * 1024 * 1024
|
|
1158
|
+
resource.setrlimit(resource.RLIMIT_AS, (limit, limit))
|
|
1159
|
+
|
|
1160
|
+
def _command_for_script(self, script: Path) -> List[str]:
|
|
1161
|
+
python_cmd = [sys.executable, str(script)]
|
|
1162
|
+
if not self.os_sandbox_command:
|
|
1163
|
+
return python_cmd
|
|
1164
|
+
mapping = {
|
|
1165
|
+
"{python}": sys.executable,
|
|
1166
|
+
"{script}": str(script),
|
|
1167
|
+
"{workspace}": str(self.workspace),
|
|
1168
|
+
}
|
|
1169
|
+
expanded = [mapping.get(part, part) for part in self.os_sandbox_command]
|
|
1170
|
+
if any(part in self.os_sandbox_command for part in ("{python}", "{script}")):
|
|
1171
|
+
return expanded
|
|
1172
|
+
return expanded + python_cmd
|
|
1173
|
+
|
|
1174
|
+
@staticmethod
|
|
1175
|
+
def _kill_process_tree(proc: subprocess.Popen) -> None:
|
|
1176
|
+
if proc.poll() is not None:
|
|
1177
|
+
return
|
|
1178
|
+
if os.name == "nt":
|
|
1179
|
+
try:
|
|
1180
|
+
subprocess.run(
|
|
1181
|
+
["taskkill", "/F", "/T", "/PID", str(proc.pid)],
|
|
1182
|
+
capture_output=True, text=True, timeout=5,
|
|
1183
|
+
)
|
|
1184
|
+
except Exception:
|
|
1185
|
+
try:
|
|
1186
|
+
proc.kill()
|
|
1187
|
+
except OSError:
|
|
1188
|
+
pass
|
|
1189
|
+
return
|
|
1190
|
+
try:
|
|
1191
|
+
os.killpg(proc.pid, signal.SIGKILL)
|
|
1192
|
+
except Exception:
|
|
1193
|
+
try:
|
|
1194
|
+
proc.kill()
|
|
1195
|
+
except OSError:
|
|
1196
|
+
pass
|
|
1197
|
+
|
|
1198
|
+
def run_code(self, code: str, timeout: float = 30.0) -> Dict[str, Any]:
|
|
1199
|
+
# Absolute path: subprocess cwd is the workspace, so a relative script
|
|
1200
|
+
# path would be resolved against it twice. resolve() avoids that.
|
|
1201
|
+
script = (self.workspace / f"_exec_{uuid.uuid4().hex}.py").resolve()
|
|
1202
|
+
script.write_text(code, encoding="utf-8")
|
|
1203
|
+
start = time.perf_counter()
|
|
1204
|
+
proc: Optional[subprocess.Popen] = None
|
|
1205
|
+
try:
|
|
1206
|
+
popen_kwargs: Dict[str, Any] = {
|
|
1207
|
+
"args": self._command_for_script(script),
|
|
1208
|
+
"stdout": subprocess.PIPE,
|
|
1209
|
+
"stderr": subprocess.PIPE,
|
|
1210
|
+
"text": True,
|
|
1211
|
+
"cwd": str(self.workspace),
|
|
1212
|
+
}
|
|
1213
|
+
if os.name == "posix":
|
|
1214
|
+
popen_kwargs["preexec_fn"] = self._preexec
|
|
1215
|
+
popen_kwargs["start_new_session"] = True
|
|
1216
|
+
elif os.name == "nt":
|
|
1217
|
+
popen_kwargs["creationflags"] = getattr(
|
|
1218
|
+
subprocess, "CREATE_NEW_PROCESS_GROUP", 0
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
proc = subprocess.Popen(**popen_kwargs)
|
|
1222
|
+
try:
|
|
1223
|
+
stdout, stderr = proc.communicate(timeout=timeout)
|
|
1224
|
+
except subprocess.TimeoutExpired:
|
|
1225
|
+
self._kill_process_tree(proc)
|
|
1226
|
+
try:
|
|
1227
|
+
stdout, stderr = proc.communicate(timeout=2)
|
|
1228
|
+
except subprocess.TimeoutExpired:
|
|
1229
|
+
stdout, stderr = "", ""
|
|
1230
|
+
elapsed = time.perf_counter() - start
|
|
1231
|
+
return {
|
|
1232
|
+
"success": False,
|
|
1233
|
+
"time": elapsed,
|
|
1234
|
+
"returncode": proc.returncode,
|
|
1235
|
+
"stdout": stdout,
|
|
1236
|
+
"stderr": stderr,
|
|
1237
|
+
"error": f"timeout after {timeout}s",
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
elapsed = time.perf_counter() - start
|
|
1241
|
+
return {
|
|
1242
|
+
"success": proc.returncode == 0,
|
|
1243
|
+
"time": elapsed,
|
|
1244
|
+
"returncode": proc.returncode,
|
|
1245
|
+
"stdout": stdout,
|
|
1246
|
+
"stderr": stderr,
|
|
1247
|
+
}
|
|
1248
|
+
except Exception as e: # noqa: BLE001
|
|
1249
|
+
if proc is not None:
|
|
1250
|
+
self._kill_process_tree(proc)
|
|
1251
|
+
return {"success": False, "error": str(e)}
|
|
1252
|
+
finally:
|
|
1253
|
+
try:
|
|
1254
|
+
script.unlink(missing_ok=True)
|
|
1255
|
+
except OSError:
|
|
1256
|
+
pass
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
# =============================================================================
|
|
1260
|
+
# OPTIMIZE SPEC
|
|
1261
|
+
# =============================================================================
|
|
1262
|
+
@dataclass
|
|
1263
|
+
class OptimizeSpec:
|
|
1264
|
+
"""Inputs for an optimization loop.
|
|
1265
|
+
|
|
1266
|
+
Assumptions made explicit:
|
|
1267
|
+
- Every candidate must define the SAME callable that `call` invokes, so a
|
|
1268
|
+
single `call` and `test` apply to the reference and all variants.
|
|
1269
|
+
- `test` is REQUIRED: selecting an unverified candidate is unsafe, so the
|
|
1270
|
+
loop refuses to run without one.
|
|
1271
|
+
"""
|
|
1272
|
+
name: str
|
|
1273
|
+
call: str
|
|
1274
|
+
reference_code: str = ""
|
|
1275
|
+
setup: str = ""
|
|
1276
|
+
test: str = ""
|
|
1277
|
+
n_variants: int = 3
|
|
1278
|
+
model: Optional[str] = None
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
# =============================================================================
|
|
1282
|
+
# SCHEDULER (Core Kernel)
|
|
1283
|
+
# =============================================================================
|
|
1284
|
+
def _optimize_sequential_env_enabled() -> bool:
|
|
1285
|
+
"""Return True when the legacy sequential optimize loop is requested."""
|
|
1286
|
+
raw = os.environ.get("ALGO_CLI_OPTIMIZE_SEQUENTIAL", "")
|
|
1287
|
+
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
class Scheduler:
|
|
1291
|
+
"""Central operating system. Coordinates tasks and workers."""
|
|
1292
|
+
|
|
1293
|
+
def __init__(self, config: Config):
|
|
1294
|
+
self.config = config
|
|
1295
|
+
self.memory = MemoryEngine(config.db_path, config.sqlite_journal_mode)
|
|
1296
|
+
self.router = LLMRouter(config)
|
|
1297
|
+
self.sandbox = ExecutionSandbox(
|
|
1298
|
+
config.workspace, config.sandbox_memory_limit_mb,
|
|
1299
|
+
config.os_sandbox_command,
|
|
1300
|
+
)
|
|
1301
|
+
self.workers: Dict[str, Worker] = {}
|
|
1302
|
+
self._discover_workers()
|
|
1303
|
+
self.active_session: Optional[int] = None
|
|
1304
|
+
|
|
1305
|
+
@staticmethod
|
|
1306
|
+
def _iter_subclasses(cls) -> List[type]:
|
|
1307
|
+
"""Recursively collect all subclasses (not just direct ones)."""
|
|
1308
|
+
found: List[type] = []
|
|
1309
|
+
for sub in cls.__subclasses__():
|
|
1310
|
+
found.append(sub)
|
|
1311
|
+
found.extend(Scheduler._iter_subclasses(sub))
|
|
1312
|
+
return found
|
|
1313
|
+
|
|
1314
|
+
def _discover_workers(self) -> None:
|
|
1315
|
+
"""Auto-register Worker subclasses (deduplicated, recursive).
|
|
1316
|
+
|
|
1317
|
+
Each worker is also persisted to the `workers` table with its priority
|
|
1318
|
+
and capabilities, so routing decisions are introspectable from the DB.
|
|
1319
|
+
"""
|
|
1320
|
+
with self.memory.batch():
|
|
1321
|
+
for worker_cls in dict.fromkeys(self._iter_subclasses(Worker)):
|
|
1322
|
+
w = worker_cls(self.memory, self.config)
|
|
1323
|
+
self.workers[w.NAME] = w
|
|
1324
|
+
self.memory.register_worker(w.NAME, w.PRIORITY, list(w.CAPABILITIES))
|
|
1325
|
+
logger.info("[OK] Registered worker: %s (priority=%d, caps=%s)",
|
|
1326
|
+
w.NAME, w.PRIORITY, ",".join(w.CAPABILITIES) or "-")
|
|
1327
|
+
|
|
1328
|
+
def start_session(self, goal: str, strategy: Optional[str] = None) -> int:
|
|
1329
|
+
self.active_session = self.memory.start_session(goal, strategy)
|
|
1330
|
+
return self.active_session
|
|
1331
|
+
|
|
1332
|
+
def _select_worker(self, context: Dict) -> Optional[Worker]:
|
|
1333
|
+
"""Pick a worker by precedence:
|
|
1334
|
+
|
|
1335
|
+
1. context['worker'] — explicit name override.
|
|
1336
|
+
2. context['capability'] — any worker advertising that capability.
|
|
1337
|
+
3. otherwise — any worker that can_run.
|
|
1338
|
+
|
|
1339
|
+
Among capability/fallback candidates the lowest PRIORITY value wins
|
|
1340
|
+
(more specific workers use lower numbers); ties break by name for
|
|
1341
|
+
determinism. can_run() is always respected.
|
|
1342
|
+
"""
|
|
1343
|
+
name = context.get("worker")
|
|
1344
|
+
if name:
|
|
1345
|
+
w = self.workers.get(name)
|
|
1346
|
+
return w if (w and w.can_run(context)) else None
|
|
1347
|
+
|
|
1348
|
+
candidates = [w for w in self.workers.values() if w.can_run(context)]
|
|
1349
|
+
cap = context.get("capability")
|
|
1350
|
+
if cap:
|
|
1351
|
+
candidates = [w for w in candidates if cap in w.CAPABILITIES]
|
|
1352
|
+
if not candidates:
|
|
1353
|
+
return None
|
|
1354
|
+
candidates.sort(key=lambda w: (w.PRIORITY, w.NAME))
|
|
1355
|
+
return candidates[0]
|
|
1356
|
+
|
|
1357
|
+
def run_task(self, task_name: str, context: Dict) -> Dict:
|
|
1358
|
+
"""Create a task row, route to a worker by capability, and execute."""
|
|
1359
|
+
logger.info("[TASK] Running task: %s", task_name)
|
|
1360
|
+
if self.active_session is None:
|
|
1361
|
+
self.start_session(f"adhoc:{task_name}")
|
|
1362
|
+
|
|
1363
|
+
dependencies = context.get("dependencies") or []
|
|
1364
|
+
task_id = self.memory.create_task(
|
|
1365
|
+
self.active_session, task_name, dependencies=dependencies
|
|
1366
|
+
)
|
|
1367
|
+
task = self.memory.get_task(task_id) or {}
|
|
1368
|
+
if task.get("status") == "blocked":
|
|
1369
|
+
return {
|
|
1370
|
+
"success": False,
|
|
1371
|
+
"status": "blocked",
|
|
1372
|
+
"task_id": task_id,
|
|
1373
|
+
"error": "task dependencies are not completed",
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
worker = self._select_worker(context)
|
|
1377
|
+
if worker is None:
|
|
1378
|
+
requested = (context.get("worker") or context.get("capability")
|
|
1379
|
+
or "any")
|
|
1380
|
+
logger.warning("No suitable worker for '%s'", requested)
|
|
1381
|
+
self.memory.fail_task(task_id)
|
|
1382
|
+
return {"success": False,
|
|
1383
|
+
"error": f"No suitable worker for '{requested}'"}
|
|
1384
|
+
|
|
1385
|
+
logger.info("[ROUTE] routed to worker: %s", worker.NAME)
|
|
1386
|
+
result = worker.run({**context, "task_id": task_id})
|
|
1387
|
+
if result.get("success"):
|
|
1388
|
+
self.memory.complete_task(task_id)
|
|
1389
|
+
else:
|
|
1390
|
+
self.memory.fail_task(task_id)
|
|
1391
|
+
return result
|
|
1392
|
+
|
|
1393
|
+
# ---- optimize loop (candidates -> benchmark -> select fastest correct) --
|
|
1394
|
+
|
|
1395
|
+
def optimize_loop(self, spec: "OptimizeSpec",
|
|
1396
|
+
candidate_provider=None,
|
|
1397
|
+
*,
|
|
1398
|
+
sequential: bool = False) -> Dict:
|
|
1399
|
+
"""Generate variants, benchmark each, return the fastest CORRECT one.
|
|
1400
|
+
|
|
1401
|
+
Flow:
|
|
1402
|
+
0. Benchmark the reference implementation. This guarantees a correct
|
|
1403
|
+
fallback: if no variant wins, the original is returned rather than
|
|
1404
|
+
a regression.
|
|
1405
|
+
1. Ask the provider (LLM by default) for candidate sources.
|
|
1406
|
+
2. Benchmark each valid, de-duplicated candidate against the reference.
|
|
1407
|
+
3. best_attempt() selects the fastest attempt with success=1 — the
|
|
1408
|
+
correctness gate has already excluded wrong-but-fast candidates.
|
|
1409
|
+
|
|
1410
|
+
SECURITY: candidates are model-generated code executed in the sandbox,
|
|
1411
|
+
which is ISOLATION, not a security boundary, and strictly more exposed
|
|
1412
|
+
than running your own code. Do not point this at an untrusted model or
|
|
1413
|
+
host without an OS-level sandbox underneath.
|
|
1414
|
+
"""
|
|
1415
|
+
if not sequential and not _optimize_sequential_env_enabled():
|
|
1416
|
+
return self.optimize_loop_batch(spec, candidate_provider=candidate_provider)
|
|
1417
|
+
|
|
1418
|
+
return self.optimize_loop_sequential(spec, candidate_provider=candidate_provider)
|
|
1419
|
+
|
|
1420
|
+
def optimize_loop_sequential(self, spec: "OptimizeSpec",
|
|
1421
|
+
candidate_provider=None) -> Dict:
|
|
1422
|
+
"""Sequential optimize-loop implementation kept as an explicit fallback."""
|
|
1423
|
+
if not spec.call:
|
|
1424
|
+
return {"success": False, "error": "optimize_loop requires a 'call'"}
|
|
1425
|
+
if not spec.test:
|
|
1426
|
+
return {"success": False,
|
|
1427
|
+
"error": "optimize_loop requires a 'test' (cannot select "
|
|
1428
|
+
"an unverified candidate)"}
|
|
1429
|
+
|
|
1430
|
+
worker = self._select_worker({"capability": "benchmarking"})
|
|
1431
|
+
if worker is None:
|
|
1432
|
+
return {"success": False, "error": "no benchmarking worker available"}
|
|
1433
|
+
|
|
1434
|
+
if self.active_session is None:
|
|
1435
|
+
self.start_session(f"optimize:{spec.name}")
|
|
1436
|
+
task_id = self.memory.create_task(self.active_session, spec.name)
|
|
1437
|
+
|
|
1438
|
+
base_ctx = {
|
|
1439
|
+
"task_id": task_id,
|
|
1440
|
+
"setup": spec.setup,
|
|
1441
|
+
"call": spec.call,
|
|
1442
|
+
"test": spec.test,
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
# 0. reference implementation (correct fallback + baseline timing)
|
|
1446
|
+
logger.info("optimize[%s]: benchmarking reference", spec.name)
|
|
1447
|
+
ref_res = worker.run({**base_ctx, "code": spec.reference_code})
|
|
1448
|
+
attempts0 = self.memory.get_attempts(task_id)
|
|
1449
|
+
original_id = attempts0[-1]["id"] if attempts0 else None
|
|
1450
|
+
original_median = (ref_res.get("score_vector") or {}).get("median_s")
|
|
1451
|
+
if not ref_res.get("success"):
|
|
1452
|
+
logger.warning("optimize[%s]: reference failed its own test (%s)",
|
|
1453
|
+
spec.name, ref_res.get("reflection"))
|
|
1454
|
+
self.memory.fail_task(task_id)
|
|
1455
|
+
return {
|
|
1456
|
+
"success": False,
|
|
1457
|
+
"error": "reference implementation failed its own test",
|
|
1458
|
+
"reflection": ref_res.get("reflection"),
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
# 1. candidate sources
|
|
1462
|
+
provider = candidate_provider or self._llm_candidates
|
|
1463
|
+
try:
|
|
1464
|
+
raw = list(provider(spec))
|
|
1465
|
+
except Exception as e: # noqa: BLE001
|
|
1466
|
+
logger.warning("optimize[%s]: candidate provider error: %s",
|
|
1467
|
+
spec.name, e)
|
|
1468
|
+
raw = []
|
|
1469
|
+
logger.info("optimize[%s]: provider returned %d raw candidate(s)",
|
|
1470
|
+
spec.name, len(raw))
|
|
1471
|
+
if raw:
|
|
1472
|
+
logger.warning("optimize[%s]: executing model-generated code in the "
|
|
1473
|
+
"sandbox (isolation, NOT a security boundary)",
|
|
1474
|
+
spec.name)
|
|
1475
|
+
if candidate_provider is None and raw and not self.config.os_sandbox_command:
|
|
1476
|
+
self.memory.fail_task(task_id)
|
|
1477
|
+
return {
|
|
1478
|
+
"success": False,
|
|
1479
|
+
"task_id": task_id,
|
|
1480
|
+
"error": ("model-generated candidates require "
|
|
1481
|
+
"Config.os_sandbox_command for OS-level sandboxing"),
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
# 2. benchmark each valid, unique candidate against the reference
|
|
1485
|
+
seen = {self._norm(spec.reference_code)} if spec.reference_code else set()
|
|
1486
|
+
accepted = 0
|
|
1487
|
+
for i, item in enumerate(raw, 1):
|
|
1488
|
+
code = self._extract_code(item)
|
|
1489
|
+
if not code:
|
|
1490
|
+
logger.warning("optimize[%s]: candidate %d not valid Python; "
|
|
1491
|
+
"skipped", spec.name, i)
|
|
1492
|
+
continue
|
|
1493
|
+
key = self._norm(code)
|
|
1494
|
+
if key in seen:
|
|
1495
|
+
logger.info("optimize[%s]: candidate %d duplicate; skipped",
|
|
1496
|
+
spec.name, i)
|
|
1497
|
+
continue
|
|
1498
|
+
seen.add(key)
|
|
1499
|
+
accepted += 1
|
|
1500
|
+
# No baseline_code here: candidates are drop-in replacements that
|
|
1501
|
+
# share the reference's function name, so injecting the reference
|
|
1502
|
+
# into the same namespace would clobber the candidate (and silently
|
|
1503
|
+
# re-benchmark the reference). Speedup vs the original is computed by
|
|
1504
|
+
# the loop from stored medians instead.
|
|
1505
|
+
worker.run({**base_ctx, "code": code})
|
|
1506
|
+
|
|
1507
|
+
# 3. select fastest correct attempt (reference + variants)
|
|
1508
|
+
winner = self.memory.best_attempt(task_id)
|
|
1509
|
+
all_attempts = self.memory.get_attempts(task_id)
|
|
1510
|
+
correct = sum(1 for a in all_attempts if a["success"])
|
|
1511
|
+
result: Dict[str, Any] = {
|
|
1512
|
+
"success": winner is not None,
|
|
1513
|
+
"task_id": task_id,
|
|
1514
|
+
"candidates_total": len(all_attempts), # reference + variants
|
|
1515
|
+
"variants_accepted": accepted,
|
|
1516
|
+
"correct": correct,
|
|
1517
|
+
"winner": winner,
|
|
1518
|
+
"winner_is_original": bool(winner and winner["id"] == original_id),
|
|
1519
|
+
"original_median_s": original_median,
|
|
1520
|
+
}
|
|
1521
|
+
if winner is None:
|
|
1522
|
+
result["summary"] = ("no correct candidate (reference and all "
|
|
1523
|
+
"variants failed their test)")
|
|
1524
|
+
logger.warning("optimize[%s]: %s", spec.name, result["summary"])
|
|
1525
|
+
self.memory.fail_task(task_id)
|
|
1526
|
+
return result
|
|
1527
|
+
|
|
1528
|
+
wmed = winner.get("median_time")
|
|
1529
|
+
speedup = (original_median / wmed) if (original_median and wmed) else None
|
|
1530
|
+
result["winner_median_s"] = wmed
|
|
1531
|
+
result["speedup_vs_original"] = round(speedup, 4) if speedup else None
|
|
1532
|
+
which = "original" if result["winner_is_original"] else "a variant"
|
|
1533
|
+
result["summary"] = (
|
|
1534
|
+
f"selected {which}: median {wmed * 1e3:.4f} ms"
|
|
1535
|
+
+ (f"; {speedup:.2f}x vs original" if speedup else "")
|
|
1536
|
+
+ f" ({correct}/{len(all_attempts)} correct)"
|
|
1537
|
+
)
|
|
1538
|
+
logger.info("optimize[%s]: %s", spec.name, result["summary"])
|
|
1539
|
+
self.memory.complete_task(task_id)
|
|
1540
|
+
return result
|
|
1541
|
+
|
|
1542
|
+
# ---- batch optimize: all candidates in one subprocess ----
|
|
1543
|
+
|
|
1544
|
+
def optimize_loop_batch(self, spec: "OptimizeSpec",
|
|
1545
|
+
candidate_provider=None) -> Dict:
|
|
1546
|
+
"""Optimized optimize_loop: benchmarks all candidates in a single
|
|
1547
|
+
subprocess (3x faster on Windows by eliminating N-1 interpreter
|
|
1548
|
+
startups).
|
|
1549
|
+
|
|
1550
|
+
Same interface and return shape as optimize_loop, plus
|
|
1551
|
+
result['batch_mode'] = True.
|
|
1552
|
+
"""
|
|
1553
|
+
if not spec.call:
|
|
1554
|
+
return {"success": False, "error": "optimize_loop requires a 'call'"}
|
|
1555
|
+
if not spec.test:
|
|
1556
|
+
return {"success": False,
|
|
1557
|
+
"error": "optimize_loop requires a 'test'"}
|
|
1558
|
+
|
|
1559
|
+
worker = self._select_worker({"capability": "benchmarking"})
|
|
1560
|
+
if worker is None:
|
|
1561
|
+
return {"success": False, "error": "no benchmarking worker available"}
|
|
1562
|
+
|
|
1563
|
+
if self.active_session is None:
|
|
1564
|
+
self.start_session(f"optimize:{spec.name}")
|
|
1565
|
+
task_id = self.memory.create_task(self.active_session, spec.name)
|
|
1566
|
+
|
|
1567
|
+
base_ctx = {
|
|
1568
|
+
"task_id": task_id,
|
|
1569
|
+
"setup": spec.setup,
|
|
1570
|
+
"call": spec.call,
|
|
1571
|
+
"test": spec.test,
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
# Collect all candidate codes (reference first)
|
|
1575
|
+
all_codes = [spec.reference_code]
|
|
1576
|
+
|
|
1577
|
+
# Get variants from provider
|
|
1578
|
+
provider = candidate_provider or self._llm_candidates
|
|
1579
|
+
try:
|
|
1580
|
+
raw = list(provider(spec))
|
|
1581
|
+
except Exception as e:
|
|
1582
|
+
logger.warning("optimize[%s]: candidate provider error: %s",
|
|
1583
|
+
spec.name, e)
|
|
1584
|
+
raw = []
|
|
1585
|
+
|
|
1586
|
+
if candidate_provider is None and raw and not self.config.os_sandbox_command:
|
|
1587
|
+
self.memory.fail_task(task_id)
|
|
1588
|
+
return {
|
|
1589
|
+
"success": False,
|
|
1590
|
+
"task_id": task_id,
|
|
1591
|
+
"batch_mode": True,
|
|
1592
|
+
"error": ("model-generated candidates require "
|
|
1593
|
+
"Config.os_sandbox_command for OS-level sandboxing"),
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
# Filter valid, unique candidates
|
|
1597
|
+
seen = {self._norm(spec.reference_code)} if spec.reference_code else set()
|
|
1598
|
+
accepted = 0
|
|
1599
|
+
for i, item in enumerate(raw, 1):
|
|
1600
|
+
code = self._extract_code(item)
|
|
1601
|
+
if not code:
|
|
1602
|
+
logger.warning("optimize[%s]: candidate %d not valid Python; skipped",
|
|
1603
|
+
spec.name, i)
|
|
1604
|
+
continue
|
|
1605
|
+
key = self._norm(code)
|
|
1606
|
+
if key in seen:
|
|
1607
|
+
continue
|
|
1608
|
+
seen.add(key)
|
|
1609
|
+
accepted += 1
|
|
1610
|
+
all_codes.append(code)
|
|
1611
|
+
|
|
1612
|
+
logger.info("optimize[%s]: batch benchmarking %d candidates in 1 subprocess",
|
|
1613
|
+
spec.name, len(all_codes))
|
|
1614
|
+
|
|
1615
|
+
# Benchmark ALL candidates in one subprocess
|
|
1616
|
+
results = worker.batch_run(base_ctx, all_codes)
|
|
1617
|
+
|
|
1618
|
+
# Record original (reference) info
|
|
1619
|
+
original_median = ((results[0].get("score_vector") or {}).get("median_s")
|
|
1620
|
+
if results else None)
|
|
1621
|
+
original_attempt_id = None
|
|
1622
|
+
attempts0 = self.memory.get_attempts(task_id)
|
|
1623
|
+
if attempts0:
|
|
1624
|
+
original_attempt_id = attempts0[0]["id"]
|
|
1625
|
+
|
|
1626
|
+
# Check reference correctness
|
|
1627
|
+
if results and not results[0].get("success"):
|
|
1628
|
+
logger.warning("optimize[%s]: reference failed its own test (%s)",
|
|
1629
|
+
spec.name, results[0].get("reflection"))
|
|
1630
|
+
self.memory.fail_task(task_id)
|
|
1631
|
+
return {
|
|
1632
|
+
"success": False,
|
|
1633
|
+
"error": "reference implementation failed its own test",
|
|
1634
|
+
"reflection": results[0].get("reflection"),
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
# Select fastest correct attempt
|
|
1638
|
+
winner = self.memory.best_attempt(task_id)
|
|
1639
|
+
all_attempts = self.memory.get_attempts(task_id)
|
|
1640
|
+
correct = sum(1 for a in all_attempts if a["success"])
|
|
1641
|
+
result: Dict[str, Any] = {
|
|
1642
|
+
"success": winner is not None,
|
|
1643
|
+
"task_id": task_id,
|
|
1644
|
+
"candidates_total": len(all_attempts),
|
|
1645
|
+
"variants_accepted": accepted,
|
|
1646
|
+
"correct": correct,
|
|
1647
|
+
"winner": winner,
|
|
1648
|
+
"winner_is_original": bool(winner and winner["id"] == original_attempt_id),
|
|
1649
|
+
"original_median_s": original_median,
|
|
1650
|
+
"batch_mode": True,
|
|
1651
|
+
}
|
|
1652
|
+
if winner is None:
|
|
1653
|
+
result["summary"] = ("no correct candidate (reference and all "
|
|
1654
|
+
"variants failed their test)")
|
|
1655
|
+
logger.warning("optimize[%s]: %s", spec.name, result["summary"])
|
|
1656
|
+
self.memory.fail_task(task_id)
|
|
1657
|
+
return result
|
|
1658
|
+
|
|
1659
|
+
wmed = winner.get("median_time")
|
|
1660
|
+
speedup = (original_median / wmed) if (original_median and wmed) else None
|
|
1661
|
+
result["winner_median_s"] = wmed
|
|
1662
|
+
result["speedup_vs_original"] = round(speedup, 4) if speedup else None
|
|
1663
|
+
which = "original" if result["winner_is_original"] else "a variant"
|
|
1664
|
+
result["summary"] = (
|
|
1665
|
+
f"selected {which}: median {wmed * 1e3:.4f} ms"
|
|
1666
|
+
+ (f"; {speedup:.2f}x vs original" if speedup else "")
|
|
1667
|
+
+ f" ({correct}/{len(all_attempts)} correct)"
|
|
1668
|
+
)
|
|
1669
|
+
logger.info("optimize[%s]: %s", spec.name, result["summary"])
|
|
1670
|
+
self.memory.complete_task(task_id)
|
|
1671
|
+
return result
|
|
1672
|
+
|
|
1673
|
+
def _llm_candidates(self, spec: "OptimizeSpec") -> List[str]:
|
|
1674
|
+
"""Default provider: ask the LLM for n_variants drop-in replacements."""
|
|
1675
|
+
prompt = (
|
|
1676
|
+
"You are optimizing a Python function for speed.\n"
|
|
1677
|
+
"Return ONLY a single Python code block containing an optimized, "
|
|
1678
|
+
"drop-in replacement. It MUST define the same function name(s) and "
|
|
1679
|
+
"signature and be a correct, faithful replacement. No explanations, "
|
|
1680
|
+
"no tests, no examples — only the implementation.\n\n"
|
|
1681
|
+
"Reference implementation:\n```python\n"
|
|
1682
|
+
f"{spec.reference_code}\n```\n"
|
|
1683
|
+
f"It is invoked as: {spec.call}\n"
|
|
1684
|
+
)
|
|
1685
|
+
return [self.router.generate(prompt, spec.model)
|
|
1686
|
+
for _ in range(max(1, spec.n_variants))]
|
|
1687
|
+
|
|
1688
|
+
@staticmethod
|
|
1689
|
+
def _extract_code(text: str) -> Optional[str]:
|
|
1690
|
+
"""Pull a code block out of an LLM reply; return None if not valid
|
|
1691
|
+
Python (this is what rejects the ollama stub and prose-only replies)."""
|
|
1692
|
+
if not text:
|
|
1693
|
+
return None
|
|
1694
|
+
m = re.search(r"```(?:python)?\s*(.*?)```", text, re.DOTALL)
|
|
1695
|
+
code = (m.group(1) if m else text).strip()
|
|
1696
|
+
if not code:
|
|
1697
|
+
return None
|
|
1698
|
+
try:
|
|
1699
|
+
ast.parse(code)
|
|
1700
|
+
except SyntaxError:
|
|
1701
|
+
return None
|
|
1702
|
+
return code
|
|
1703
|
+
|
|
1704
|
+
@staticmethod
|
|
1705
|
+
def _norm(code: str) -> str:
|
|
1706
|
+
return "\n".join(ln.rstrip()
|
|
1707
|
+
for ln in (code or "").strip().splitlines())
|
|
1708
|
+
|
|
1709
|
+
def shutdown(self) -> None:
|
|
1710
|
+
if self.active_session is not None:
|
|
1711
|
+
tasks = self.memory.get_tasks(self.active_session)
|
|
1712
|
+
if any(t["status"] == "failed" for t in tasks):
|
|
1713
|
+
self.memory.fail_session(self.active_session)
|
|
1714
|
+
elif any(t["status"] == "blocked" for t in tasks):
|
|
1715
|
+
self.memory.block_session(self.active_session)
|
|
1716
|
+
else:
|
|
1717
|
+
self.memory.complete_session(self.active_session)
|
|
1718
|
+
self.memory.close()
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
|
|
1722
|
+
def _demo_candidate_provider(spec: "OptimizeSpec") -> List[str]:
|
|
1723
|
+
"""Built-in stand-in for the LLM, used when ollama is unavailable so the
|
|
1724
|
+
optimize-loop demo is runnable out of the box.
|
|
1725
|
+
|
|
1726
|
+
Returns three variants of the demo `total` function: one faster + correct
|
|
1727
|
+
(builtin sum), one slower + correct (builds an intermediate list), and one
|
|
1728
|
+
INCORRECT (off-by-one) to show the correctness gate excluding it.
|
|
1729
|
+
"""
|
|
1730
|
+
return [
|
|
1731
|
+
"def total(xs):\n return sum(xs)",
|
|
1732
|
+
"def total(xs):\n return sum([x for x in xs])",
|
|
1733
|
+
"def total(xs):\n return sum(xs) + 1", # wrong on purpose
|
|
1734
|
+
]
|
|
1735
|
+
|
|
1736
|
+
|
|
1737
|
+
# =============================================================================
|
|
1738
|
+
# CLI / MAIN
|
|
1739
|
+
# =============================================================================
|
|
1740
|
+
def main() -> None:
|
|
1741
|
+
import argparse
|
|
1742
|
+
|
|
1743
|
+
# Force UTF-8 on Windows consoles that default to cp1252
|
|
1744
|
+
try:
|
|
1745
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
1746
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
1747
|
+
except AttributeError:
|
|
1748
|
+
pass # older Python without reconfigure
|
|
1749
|
+
|
|
1750
|
+
parser = argparse.ArgumentParser(description="Autonomous Engineer v1.0")
|
|
1751
|
+
parser.add_argument(
|
|
1752
|
+
"command", choices=["optimize", "optimize-loop", "latency", "status"],
|
|
1753
|
+
default="optimize", nargs="?",
|
|
1754
|
+
)
|
|
1755
|
+
parser.add_argument("--target", default="matmul_demo")
|
|
1756
|
+
parser.add_argument("--model", default="llama3.1")
|
|
1757
|
+
parser.add_argument("--variants", type=int, default=3,
|
|
1758
|
+
help="candidates to generate in optimize-loop")
|
|
1759
|
+
parser.add_argument("--repeats", type=int, default=5,
|
|
1760
|
+
help="timed runs per mode in latency")
|
|
1761
|
+
parser.add_argument("--log-level", default="INFO",
|
|
1762
|
+
help="DEBUG, INFO, WARNING, ERROR (default: INFO)")
|
|
1763
|
+
parser.add_argument("--log-file", default=None,
|
|
1764
|
+
help="optional path to also write logs to")
|
|
1765
|
+
parser.add_argument("--sequential", action="store_true",
|
|
1766
|
+
help="use legacy sequential optimize-loop instead of batch mode")
|
|
1767
|
+
args = parser.parse_args()
|
|
1768
|
+
|
|
1769
|
+
setup_logging(args.log_level, args.log_file)
|
|
1770
|
+
if args.command == "status":
|
|
1771
|
+
worker_names = sorted(cls.NAME for cls in Scheduler._iter_subclasses(Worker))
|
|
1772
|
+
print("Status: Kernel running. Workers:", worker_names)
|
|
1773
|
+
return
|
|
1774
|
+
|
|
1775
|
+
config = Config(default_model=args.model)
|
|
1776
|
+
scheduler = Scheduler(config)
|
|
1777
|
+
|
|
1778
|
+
try:
|
|
1779
|
+
if args.command == "optimize":
|
|
1780
|
+
scheduler.start_session(f"Optimize {args.target}")
|
|
1781
|
+
# Demo: replace a hand-rolled summation loop (baseline) with the
|
|
1782
|
+
# built-in sum (candidate) and measure the real speedup. Swap these
|
|
1783
|
+
# keys to benchmark your own code.
|
|
1784
|
+
context = {
|
|
1785
|
+
"setup": "data = list(range(10000))",
|
|
1786
|
+
"baseline_code": (
|
|
1787
|
+
"def total(xs):\n"
|
|
1788
|
+
" s = 0\n"
|
|
1789
|
+
" for x in xs:\n"
|
|
1790
|
+
" s += x\n"
|
|
1791
|
+
" return s"
|
|
1792
|
+
),
|
|
1793
|
+
"baseline_call": "total(data)",
|
|
1794
|
+
"call": "sum(data)",
|
|
1795
|
+
"test": "assert sum([1, 2, 3]) == 6",
|
|
1796
|
+
"capability": "optimization", # route by capability, not name
|
|
1797
|
+
"target": args.target,
|
|
1798
|
+
}
|
|
1799
|
+
result = scheduler.run_task("performance_optimize", context)
|
|
1800
|
+
print("[OK] Optimization complete:", result)
|
|
1801
|
+
elif args.command == "optimize-loop":
|
|
1802
|
+
spec = OptimizeSpec(
|
|
1803
|
+
name="optimize_total",
|
|
1804
|
+
setup="data = list(range(10000))",
|
|
1805
|
+
reference_code=(
|
|
1806
|
+
"def total(xs):\n"
|
|
1807
|
+
" s = 0\n"
|
|
1808
|
+
" for x in xs:\n"
|
|
1809
|
+
" s += x\n"
|
|
1810
|
+
" return s"
|
|
1811
|
+
),
|
|
1812
|
+
call="total(data)",
|
|
1813
|
+
test="assert total([1, 2, 3]) == 6",
|
|
1814
|
+
n_variants=args.variants,
|
|
1815
|
+
)
|
|
1816
|
+
# Real LLM when ollama is present; deterministic demo otherwise.
|
|
1817
|
+
provider = None if HAS_OLLAMA else _demo_candidate_provider
|
|
1818
|
+
if provider is not None:
|
|
1819
|
+
logger.info("ollama unavailable; using built-in demo candidate "
|
|
1820
|
+
"provider (stand-in for the LLM)")
|
|
1821
|
+
result = scheduler.optimize_loop(
|
|
1822
|
+
spec, candidate_provider=provider, sequential=args.sequential
|
|
1823
|
+
)
|
|
1824
|
+
print("[OK] Optimize-loop:", result.get("summary"))
|
|
1825
|
+
print(" winner_is_original:", result.get("winner_is_original"),
|
|
1826
|
+
"| speedup_vs_original:", result.get("speedup_vs_original"))
|
|
1827
|
+
elif args.command == "latency":
|
|
1828
|
+
# Real model if ollama is present; a SIMULATED model otherwise so
|
|
1829
|
+
# the test runs offline (numbers then illustrate behavior only).
|
|
1830
|
+
simulated = not HAS_OLLAMA
|
|
1831
|
+
if simulated:
|
|
1832
|
+
logger.info("ollama unavailable; using a SIMULATED model "
|
|
1833
|
+
"(numbers illustrate behavior, not a real model)")
|
|
1834
|
+
router = LLMRouter(
|
|
1835
|
+
config,
|
|
1836
|
+
engine=make_simulated_blocking(),
|
|
1837
|
+
stream_engine=make_simulated_stream(),
|
|
1838
|
+
)
|
|
1839
|
+
else:
|
|
1840
|
+
router = LLMRouter(config)
|
|
1841
|
+
|
|
1842
|
+
prompt = ("Write one short paragraph on why streaming reduces "
|
|
1843
|
+
"perceived latency.")
|
|
1844
|
+
|
|
1845
|
+
# 1) one visible streamed run so the display can be seen updating
|
|
1846
|
+
print("--- streamed display (tokens appear as they arrive) ---")
|
|
1847
|
+
t0 = time.perf_counter()
|
|
1848
|
+
first = None
|
|
1849
|
+
for chunk in router.stream(prompt, args.model):
|
|
1850
|
+
if first is None:
|
|
1851
|
+
first = time.perf_counter()
|
|
1852
|
+
sys.stdout.write(chunk)
|
|
1853
|
+
sys.stdout.flush()
|
|
1854
|
+
print()
|
|
1855
|
+
if first is not None:
|
|
1856
|
+
print("(first token visible after %.1f ms)"
|
|
1857
|
+
% ((first - t0) * 1e3))
|
|
1858
|
+
|
|
1859
|
+
# 2) timed comparison with a no-op sink (clean numbers)
|
|
1860
|
+
s = measure_latency(router, prompt, model=args.model,
|
|
1861
|
+
repeats=args.repeats, stream=True)
|
|
1862
|
+
b = measure_latency(router, prompt, model=args.model,
|
|
1863
|
+
repeats=args.repeats, stream=False)
|
|
1864
|
+
tag = " (simulated)" if simulated else ""
|
|
1865
|
+
print("\n--- call->display latency, %d runs%s ---"
|
|
1866
|
+
% (args.repeats, tag))
|
|
1867
|
+
print(" streaming TTFD median: %8.1f ms | total median: %8.1f ms"
|
|
1868
|
+
% (s["ttfd"]["median"] * 1e3, s["total"]["median"] * 1e3))
|
|
1869
|
+
print(" blocking TTFD median: %8.1f ms | total median: %8.1f ms"
|
|
1870
|
+
% (b["ttfd"]["median"] * 1e3, b["total"]["median"] * 1e3))
|
|
1871
|
+
if s["ttfd"]["median"] > 0:
|
|
1872
|
+
ratio = b["ttfd"]["median"] / s["ttfd"]["median"]
|
|
1873
|
+
print(" -> streaming shows first output %.1fx sooner "
|
|
1874
|
+
"(total compute ~unchanged)" % ratio)
|
|
1875
|
+
else:
|
|
1876
|
+
print("Status: Kernel running. Workers:",
|
|
1877
|
+
list(scheduler.workers.keys()))
|
|
1878
|
+
finally:
|
|
1879
|
+
scheduler.shutdown()
|
|
1880
|
+
|
|
1881
|
+
|
|
1882
|
+
if __name__ == "__main__":
|
|
1883
|
+
main()
|
|
1884
|
+
|
|
1885
|
+
|
|
1886
|
+
# =============================================================================
|
|
1887
|
+
# CHANGELOG (hardening pass)
|
|
1888
|
+
# =============================================================================
|
|
1889
|
+
# 1. Shebang fixed: "!/usr/bin/env python3" -> "#!/usr/bin/env python3".
|
|
1890
|
+
# 2. ExecutionSandbox: enforces timeout via subprocess; optional POSIX memory
|
|
1891
|
+
# cap; captures stdout/stderr/returncode; cleans up temp scripts. Now an
|
|
1892
|
+
# honest isolation boundary (documented as NOT a security boundary).
|
|
1893
|
+
# 3. MemoryEngine: applies PRAGMA journal_mode (WAL) from Config; enables
|
|
1894
|
+
# PRAGMA foreign_keys=ON; check_same_thread=False with a write lock;
|
|
1895
|
+
# added start_session/create_task/close helpers.
|
|
1896
|
+
# 4. Scheduler: creates real session + task rows so the FK chain validates;
|
|
1897
|
+
# wires Config.workspace / timeout / memory limit into the sandbox;
|
|
1898
|
+
# recursive, deduplicated worker discovery; guarded main() with try/finally.
|
|
1899
|
+
# 5. Pruned unused imports (ast, random, traceback, datetime, numpy,
|
|
1900
|
+
# importlib.util, field, Type, Callable, Set).
|
|
1901
|
+
#
|
|
1902
|
+
# Deferred (recommended, not done to limit scope):
|
|
1903
|
+
# - Replace print() with the logging module for level control + file output.
|
|
1904
|
+
# - Add query helpers + indexes to MemoryEngine for the read path.
|
|
1905
|
+
# - Capability-based routing instead of name fallback.
|
|
1906
|
+
#
|
|
1907
|
+
# Phase-2 update — PerformanceWorker now executes + benchmarks for real:
|
|
1908
|
+
# - Builds a single measurement harness, runs it once in the sandbox (so
|
|
1909
|
+
# interpreter startup is paid once, not per repeat), times each call with
|
|
1910
|
+
# perf_counter, and parses raw timings off a sentinel stdout line.
|
|
1911
|
+
# - Computes median/mean/stdev/min; persists a benchmarks row per attempt.
|
|
1912
|
+
# - Optional `test` => real correctness (True/False), else None ("not
|
|
1913
|
+
# checked"). Optional baseline => measured speedup. No fabricated scores.
|
|
1914
|
+
# - MemoryEngine.log_benchmark added; Config gained benchmark_warmup.
|
|
1915
|
+
#
|
|
1916
|
+
# Known limits of the benchmark path (by design, documented for honesty):
|
|
1917
|
+
# - Wall-clock on one machine; default_timeout covers the WHOLE harness, so a
|
|
1918
|
+
# too-low timeout yields no data rather than partial data.
|
|
1919
|
+
# - 'confidence' = 1 - relative stdev (measurement stability), NOT a
|
|
1920
|
+
# statistical CI. Default 5 repeats is low; raise benchmark_repeats.
|
|
1921
|
+
# - Harness injects caller-provided code as text; safe only because the
|
|
1922
|
+
# sandbox is isolation, NOT a security boundary (see ExecutionSandbox).
|
|
1923
|
+
#
|
|
1924
|
+
# Phase-2.1 update — cleared the deferred list:
|
|
1925
|
+
# - Logging: print() replaced by a package logger (NullHandler by default;
|
|
1926
|
+
# setup_logging() wires stderr + optional file). CLI gains --log-level /
|
|
1927
|
+
# --log-file. Diagnostics -> logger (stderr); results stay on stdout.
|
|
1928
|
+
# - Read path: indexes on FK/lookup columns; query helpers get_session,
|
|
1929
|
+
# get_tasks, get_attempts, recent_attempts, and best_attempt() (fastest
|
|
1930
|
+
# SUCCESSFUL attempt — the selection primitive for a future optimize loop).
|
|
1931
|
+
# - Routing: capability-based via Scheduler._select_worker (explicit name >
|
|
1932
|
+
# capability > can_run fallback; lowest PRIORITY wins, name breaks ties).
|
|
1933
|
+
# Workers are now persisted to the `workers` table on discovery, so routing
|
|
1934
|
+
# inputs are introspectable instead of the table being dead.
|
|
1935
|
+
#
|
|
1936
|
+
# Phase-2.2 update — the optimize loop is wired up:
|
|
1937
|
+
# - Scheduler.optimize_loop(spec, candidate_provider=None): benchmarks the
|
|
1938
|
+
# reference (candidate 0, the correct fallback), generates variants via the
|
|
1939
|
+
# provider (LLMRouter by default; pluggable for testing / no-ollama use),
|
|
1940
|
+
# benchmarks each valid+unique one vs the reference, then best_attempt()
|
|
1941
|
+
# selects the fastest CORRECT result. Wrong candidates are excluded by the
|
|
1942
|
+
# correctness gate; if nothing beats the original, the original is returned.
|
|
1943
|
+
# - _extract_code (ast-validated, fence-aware) rejects prose/stub replies;
|
|
1944
|
+
# _norm de-dupes; candidates routed via capability ("benchmarking").
|
|
1945
|
+
# - CLI: `optimize-loop` command (+ --variants); OptimizeSpec dataclass with a
|
|
1946
|
+
# required `test`. _demo_candidate_provider stands in for the LLM offline.
|
|
1947
|
+
# - SECURITY: this runs MODEL-GENERATED code in the sandbox — isolation, not a
|
|
1948
|
+
# security boundary, and more exposed than running your own code.
|
|
1949
|
+
#
|
|
1950
|
+
# Phase-2.3 update — call->display latency probe + streaming:
|
|
1951
|
+
# - LLMRouter gained stream() (ollama stream=True) plus injectable `engine` /
|
|
1952
|
+
# `stream_engine` for offline testing, mirroring the candidate_provider
|
|
1953
|
+
# pattern. generate() is unchanged for existing callers.
|
|
1954
|
+
# - measure_latency() reports TTFD (time to first displayed output) and total
|
|
1955
|
+
# call->display, median/stdev/min, for streaming vs blocking. TTFD is the
|
|
1956
|
+
# perceived-latency metric; streaming collapses it to time-to-first-token
|
|
1957
|
+
# while total compute is unchanged.
|
|
1958
|
+
# - make_simulated_stream / make_simulated_blocking: matched-cost fakes so the
|
|
1959
|
+
# `latency` CLI command runs offline; real numbers come from a real model.
|
|
1960
|
+
# - CLI: `latency` command (+ --repeats). All new output is ASCII to match the
|
|
1961
|
+
# console encoding fix.
|
|
1962
|
+
#
|
|
1963
|
+
# Phase-3 update — harness integration + hardening (algo_cli):
|
|
1964
|
+
# - Copied to algo_cli/intelligence/autonomous_engineer.py (clean module name).
|
|
1965
|
+
# - Exported from algo_cli/intelligence/__init__.py as AEConfig, MemoryEngine,
|
|
1966
|
+
# ExecutionSandbox, PerformanceWorker, LLMRouter, AEScheduler, OptimizeSpec.
|
|
1967
|
+
# - Fixed: emoji -> ASCII tags ([OK], [TASK], [ROUTE]) + UTF-8 reconfigure.
|
|
1968
|
+
# - Fixed: optimize_loop hard-fails when reference fails its own test.
|
|
1969
|
+
# - Fixed: task/session status transitions (complete_task, fail_task,
|
|
1970
|
+
# complete_session) at all exit paths.
|
|
1971
|
+
# - Fixed: benchmark harness uses _ae_ prefixed internals + temp JSON file
|
|
1972
|
+
# instead of stdout sentinel (prevents spoofing by model-generated code).
|
|
1973
|
+
# - Fixed: Config save/load uses encoding="utf-8".
|
|
1974
|
+
# - Fixed: result file path uses .resolve() for subprocess cwd compatibility.
|
|
1975
|
+
# - Added: 17 tests in tests/test_autonomous_engineer.py.
|
|
1976
|
+
# - Added: 10 empirical algorithm benchmarks (B178-B187) in ALGO.md.
|