diff --git a/README.md b/README.md index 534a56ab..db3efb59 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,10 @@ Five layers, wired through small dataclass schemas — full details in [`docs/co Workflows are **full Python programs**, mutated as source code. The **code-as-genotype** is the workflow file; the phenotype is whatever it produces in the workspace. -- **Selection: Quality-Diversity archive** (**MAP-Elites**-style) — max population of 50, `qd_score = (1−w)·quality + w·novelty` (`w=0.4`). **Novelty search** uses k-NN distance (`k=25`) over a **behaviour descriptor** `[n_agents, n_edges, n_branches, prompt_chars]`. Parents drawn by inverse-child-count roulette so the archive spreads. -- **Variation: stagnation-driven scope** — mutation boldness is a continuous function of how much the last 4 prompt gradients repeat themselves. Near-winners stay protected. Scope bands run from "prompt-only tweak" to "complete topology rethink." -- **Crossover** — ~30 % of generations combine two parents, strongest-first. -- **Cold start** — when the archive is empty, a similarity-filtered scan of past runs on disk (MiniLM cosine ≥ 0.5) seeds the search. Useful workflows transfer across tasks. +- **Selection: Quality-Diversity archive** (**MAP-Elites**-style) — max population of 50, `qd_score = (1−w)·quality + w·novelty` (`w = novelty_weight = 0.25`). **Novelty search** uses k-NN distance (`k = 15`) over the **failure-fingerprint** behaviour descriptor — a 6-D vector of centered per-source pass rates from the verifier (sources A–F). Parents drawn by inverse-child-count roulette so the archive spreads (`MAX_CHILDREN_PER_PARENT = 2`). +- **Variation: Rechenberg-1/5 + plateau-driven scope** — mutation boldness blends the success rate of the last 5 scored offspring (Rechenberg 1/5 rule, threshold `0.20`) with an `iters_since_improvement` plateau counter (patience `6`). Near-winners (parent score > 0.95) get a damper. Scope bands run from `EXPLOITATION` (point mutation) to `RE-SPECIATION` (clean-slate redesign), gated by an effective-boldness threshold (`< 0.35 / 0.50 / 0.65 / 0.90 / 1.01`). +- **Crossover** — by default ~10 % of generations combine two parents, strongest-first, with offspring hard-capped at the highest parent agent count. +- **Cold start** — when the archive is empty, a similarity-filtered scan of past runs on disk (MiniLM cosine ≥ 0.8) seeds the search. Useful workflows transfer across tasks. Full mechanics: [`docs/concepts/evolution-engine.md`](./docs/concepts/evolution-engine.md). diff --git a/config.py b/config.py index 63258da2..e89d122a 100644 --- a/config.py +++ b/config.py @@ -38,18 +38,18 @@ class Config: def __init__(self): # workspace configuration - self.workspace_dir = "/Users/cnrs/Documents/repository/Toolomics/workspace" + self.workspace_dir = "/Users/mlg/Documents/CNRS/toolomics/workspace" # MCPs server discovery self.discovery_addresses: list[AddressMCP] = [ - AddressMCP(ip="0.0.0.0", port_min=5000, port_max=5100) + AddressMCP(ip="0.0.0.0", port_min=5000, port_max=5200) ] # LLMs choices self.planner_llm_model: str = "openrouter/z-ai/glm-5.1" self.workflow_llm_model: str = "openrouter/z-ai/glm-5.1" - self.smolagent_model_id: str = "openrouter/deepseek/deepseek-v3.2" - self.judge_model = "openrouter/minimax/minimax-m3" + self.smolagent_model_id: str = "openrouter/qwen/qwen3.5-122b-a10b" + self.judge_model = "openrouter/deepseek/deepseek-v4-flash" self.capsule_namer_model = "openrouter/deepseek/deepseek-v4-flash" self.engine_name: str = "litellm" # for smolagent @@ -69,10 +69,19 @@ def __init__(self): # learning parameters self.learned_score_threshold = 0.94 - self.max_learning_evolve_iterations = 35 + self.max_learning_evolve_iterations = 10 + + # QD novelty + length penalty (open-ended modes) + # "archive_knn" (default) or "k-NN archive" + self.novelty_comparison: str = "archive_knn" + self.novelty_previous_n: int = 5 + # Length penalty: genotype size at which the penalty starts to + # grow; lambda is small so it only breaks near-ties. + self.length_penalty_baseline_chars: int = 8000 + self.length_penalty_lambda: float = 0.05 # evaluation concurrency settings - self.max_concurrent_eval_tasks: int = 2 # Number of concurrent tasks for CSV evaluation mode + self.max_concurrent_eval_tasks: int = 1 # Number of concurrent tasks for CSV evaluation mode # folder paths for workflow pre-defined code self.schema_code_path: str = "sources/modules/state_schema.py" @@ -97,7 +106,7 @@ def __init__(self): self.openrouter_quantizations_by_model: dict[str, list[str] | None] = {} self.default_openrouter_quantizations: list[str] = ["bf16", "fp16", "fp8"] # runner settings - self.runner_default_python_version: str = "3.10" + self.runner_default_python_version: str = "3.12" self.runner_default_timeout: int = 10800 # Per-agent (SmolAgentFactory) execution timeout in seconds. Injected into # the generated workflow as AGENT_EXECUTION_TIMEOUT. 3600 = 1 hour. @@ -215,6 +224,10 @@ def jsonify( "max_tokens": self.max_tokens, "learned_score_threshold": self.learned_score_threshold, "max_learning_evolve_iterations": self.max_learning_evolve_iterations, + "novelty_comparison": self.novelty_comparison, + "novelty_previous_n": self.novelty_previous_n, + "length_penalty_baseline_chars": self.length_penalty_baseline_chars, + "length_penalty_lambda": self.length_penalty_lambda, "schema_code_path": self.schema_code_path, "smolagent_factory_code_path": self.smolagent_factory_code_path, "runs_capsule_dir": self.runs_capsule_dir, @@ -257,6 +270,16 @@ def from_json(self, data: dict[str, Any]) -> None: self.max_learning_evolve_iterations = data.get( "max_learning_evolve_iterations", self.max_learning_evolve_iterations ) + self.novelty_comparison = data.get("novelty_comparison", self.novelty_comparison) + self.novelty_previous_n = int( + data.get("novelty_previous_n", self.novelty_previous_n) + ) + self.length_penalty_baseline_chars = int( + data.get("length_penalty_baseline_chars", self.length_penalty_baseline_chars) + ) + self.length_penalty_lambda = float( + data.get("length_penalty_lambda", self.length_penalty_lambda) + ) self.schema_code_path = data.get("schema_code_path", self.schema_code_path) self.smolagent_factory_code_path = data.get( "smolagent_factory_code_path", self.smolagent_factory_code_path @@ -319,6 +342,10 @@ def __str__(self) -> str: lines.append(f" max_tokens={self.max_tokens}") lines.append(f" learned_score_threshold={self.learned_score_threshold}") lines.append(f" max_learning_evolve_iterations={self.max_learning_evolve_iterations}") + lines.append(f" novelty_comparison={self.novelty_comparison}") + lines.append(f" novelty_previous_n={self.novelty_previous_n}") + lines.append(f" length_penalty_baseline_chars={self.length_penalty_baseline_chars}") + lines.append(f" length_penalty_lambda={self.length_penalty_lambda}") lines.append(f" max_concurrent_eval_tasks={self.max_concurrent_eval_tasks}") lines.append(f" schema_code_path={self.schema_code_path}") lines.append(f" smolagent_factory_code_path={self.smolagent_factory_code_path}") diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 19b26da9..9f30bd89 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -105,7 +105,8 @@ mimosa-ai/ │ │ ├── selection.py # SelectionPressure (greedy/tournament/novelty/QD) │ │ ├── variation_engine.py # Mutation/crossover prompt assembly + annealing │ │ ├── workflow_selection.py # Parent retrieval (archive draw / disk scan) -│ │ ├── code_features.py # AST → behaviour descriptor (4-vector) +│ │ ├── failure_fingerprint.py # Verifier verdicts → QD behaviour descriptor (6-D, centered) +│ │ ├── code_features.py # Legacy structural descriptor (offline analysis only) │ │ ├── lineage.py # parent → child sidecar records │ │ ├── orchestrator.py # Grounding → factory → sandbox pipeline │ │ ├── workflow_factory.py # Multi-agent workflow synthesis @@ -255,9 +256,9 @@ Each recursive step: 6. selects the next parent(s) and chooses mutation vs crossover, 7. recurses. -Termination: `overall_score > learned_score_threshold` (default 0.94) in +Termination: `overall_score >= learned_score_threshold` (default 0.9) in `--learn` mode, or `max_depth` reached -(`max_learning_evolve_iterations`, default 45; single-shot uses +(`max_learning_evolve_iterations`, default 20; single-shot uses `max_depth=1`). ### 2. `SelectionPressure` — [`sources/core/selection.py`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/selection.py) @@ -277,39 +278,45 @@ penalty `÷(1 + n_children_already)` and a hard ### 3. `VariationEngine` — [`sources/core/variation_engine.py`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/variation_engine.py) Prompt assembly for mutation and crossover. Mutation boldness is a -continuous function of two evidence signals — population stagnation -*and* the Rechenberg 1/5 success rate of recent offspring — not a fixed -phase schedule. - -- `_compute_stagnation(window=4)` — mean pairwise MiniLM cosine - similarity over the last 4 non-failure prompt gradients, rescaled so - the unrelated baseline (`≈0.4`) maps to `0` and full repetition - (`≥0.8`) maps to `1`. +continuous function of two evidence signals — an +`iters_since_improvement` plateau counter *and* the Rechenberg 1/5 +success rate of recent offspring — not a fixed phase schedule. + +- `_iters_since_improvement()` — length of the trailing run of scored + offspring that did not strictly beat best-so-far (failures and + unscored entries skipped). Normalised as + `plateau = min(1, iters / _PLATEAU_PATIENCE)` with + `_PLATEAU_PATIENCE = 6`. - `_compute_success_rate(window=5)` — fraction of the last 5 scored offspring that strictly beat the running best at production time. The Rechenberg 1/5 success rule threshold is `0.20`. - `_get_prompt_step_size(parent_score)` — combines the two: - * cold start (no scored history yet) — `effective = raw_stagnation`, - * `success_rate < 0.20` — `effective = max(raw_stagnation, deficit)` + * cold start (fewer than two comparable scored offspring) — + `effective = 0.3 · plateau` (capped ramp), + * `success_rate < 0.20` — `effective = 0.5 · deficit + 0.5 · plateau` with `deficit = (0.20 − success_rate) / 0.20` (escalate), - * `success_rate ≥ 0.20` — `effective = raw_stagnation · (1 − progress)` + * `success_rate ≥ 0.20` — `effective = plateau · (1 − progress)` with `progress = min(1, (success_rate − 0.20) / (0.80 − 0.20))` (damp boldness in proportion to real progress), * near-finish floor: when `parent_score > 0.95`, multiply by `(1 − 0.5 · (parent_score − 0.95) / 0.05)` so a 0.96 parent isn't - gambled away one generation before early-stop. + gambled away one generation before early-stop, + * RE-SPECIATION gate: unless `iters_since_improvement ≥ 8` *and* + `success_rate ∈ {None, 0.0}`, `effective` is clamped to + `_RESPECIATION_CLAMP = 0.89`, just below the EXPLORATION/ + RE-SPECIATION boundary at `0.90`. Then it grows the agent budget from the previous generation's count toward `max_possible_agents = 7` proportionally to `effective`, and samples the actual agent count with a Beta-Binomial biased upward by `effective`. -| Effective boldness | Agent budget | Mutation scope (advisory) | -|--------------------|--------------|-------------------------------------------------------------| -| <0.20 | ≈ current | prompt-only little tweak | -| <0.40 | current+1 | prompt, handoff, tools — improve information flow | -| <0.60 | current+2 | significant redesign while keeping topology | -| <0.80 | current+3 | bold rewire — restructure or grow the agent set | -| ≥0.80 | up to 7 | complete rethink — discard inherited topology / prompts | +| Effective boldness | Mutation scope (advisory) | +|--------------------|-----------------------------------------------------------------------------| +| < 0.35 | `EXPLOITATION` — point mutation: minor phrasing / prompt-adjective tweaks | +| < 0.50 | `ALIGNMENT` — interface optimization: refine handoff prompts, IO contracts | +| < 0.65 | `ADAPTATION` — component overhaul: rewrite lagging agent prompts, swap tools | +| < 0.90 | `EXPLORATION` — macro structural mutation: add/merge agents, change routing | +| ≥ 0.90 | `RE-SPECIATION` — clean-slate redesign of the multi-agent architecture | Scope is an advisory line injected into the mutation prompt; the LLM may still pick any topology. The hard control is the agent-count @@ -322,7 +329,7 @@ Two-mode parent retrieval: - **Steady state**: when `selection_pressure._archive` is populated, draws parents from the live session archive via QD-roulette. - **Cold start**: empty archive → similarity-filtered disk scan - (`cosine ≥ 0.5` on MiniLM embeddings of `original_task`, `score ≥ 0.05`) + (`cosine ≥ 0.8` on MiniLM embeddings of `original_task`, `score ≥ 0.1`) routed through the same `select_parents()` weighting. ### 5. `WorkflowOrchestrator` — [`sources/core/orchestrator.py`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/orchestrator.py) @@ -400,7 +407,7 @@ EvolutionEngine.start_workflow_evolution(goal) ├─ SelectionPressure.validate_survivor() → archive admit? ├─ record_lineage() ├─ select next parent (archive QD-roulette) - ├─ choose crossover (~0.4 rate, once initial_population met) or mutation + ├─ choose crossover (default crossover_rate=0.1, once initial_population met) or mutation └─ recurse → stop on threshold OR max_depth ↓ WorkspaceManager.restore_best(best_uuid) @@ -464,20 +471,16 @@ For each generation: pre-installed (lazy one-shot install per process). Soft claims get a `pass/unsure/fail` LLM verdict against workspace previews + literature grounding (mapped to `1.0 / 0.5 / 0.0`). -3. **Independent cheat detector**: present in the codebase but currently - **disabled** pending a rewrite. The aggregation path still has a slot - for `cheat_penalty`. Behavioral anti-cheat pressure today comes from - Source C's recompute-from-disk verifiers, the inverted-score "Used - fallback" claim type, and the anti-tautology tripwires. +3. **Behavioral pressure against shortcut workflows** comes from Source + C's recompute-from-disk verifiers, the inverted-score "Used fallback" + claim type, and the anti-tautology tripwires. 4. **Aggregation**: ``` - overall = clamp(base_mean + info_bonus, 0, 1) + overall = clamp(base_mean, 0, 1) if any hard claim refuted: overall = min(overall, 0.99) # _HARD_FAIL_CAP (soft, for now) - overall = max(0, overall - cheat_penalty) # cheat_penalty = 0.0 today ``` - where `info_bonus(n_hard_pass) = 0.05 · (1 - exp(-n_hard_pass / 8))` - (saturating reward for thoroughness). + `base_mean` is the importance-weighted mean of per-claim scores. 5. **Prompt gradient** — plain-language single-sentence diagnosis prefixed with a short code name (e.g. `FALLBACK_ECFP_CLASSIFIER`). It is the **only** verifier signal the mutator sees, and recent history diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md index 5d22cb2d..65640657 100644 --- a/docs/concepts/architecture.md +++ b/docs/concepts/architecture.md @@ -45,12 +45,13 @@ recursively evolves workflows, with help from: (improvement over baseline or `qd_score > admit_threshold`); capacity is curated by lowest-`qd_score` eviction. - **VariationEngine** — assembles mutation or crossover prompts, with an - evidence-driven mutation scope: boldness grows as recent prompt - gradients converge (the offspring keep failing the same way) *and* as - the Rechenberg 1/5 success rate of recent scored offspring drops - below 20 %. A near-finish floor only damps boldness once the parent - score is above 0.95, so near-winners aren't gambled away one - generation before early-stop. + evidence-driven mutation scope: boldness grows with an + `iters_since_improvement` plateau counter (patience `6`) *and* as the + Rechenberg 1/5 success rate of the last 5 scored offspring drops below + 20 %. A near-finish floor only damps boldness once the parent score is + above 0.95, so near-winners aren't gambled away one generation before + early-stop. The top `RE-SPECIATION` band is hysteresis-gated and only + opens after `iters_since_improvement ≥ 8` with a zero success rate. - **WorkflowOrchestrator** — wraps "grounding → factory → sandbox" into one callable per generation. diff --git a/docs/concepts/evaluation-pipeline.md b/docs/concepts/evaluation-pipeline.md index 3bee08d9..1f83daa2 100644 --- a/docs/concepts/evaluation-pipeline.md +++ b/docs/concepts/evaluation-pipeline.md @@ -20,6 +20,12 @@ fingerprint). The single signal that flows back to the mutator is a short **prompt gradient** that summarizes failure modes without leaking the verified claims themselves. +The same per-claim verdicts are projected into a 6-dim **failure +fingerprint** that the [evolution engine](evolution-engine.md#behaviour-descriptor-failure-fingerprint) +uses as the behaviour descriptor for QD novelty. The descriptor is +centered so overall quality cannot leak into novelty — see the firewall +section below. + ![Evaluation pipeline](../images/evaluation_pipeline.png){ width="100%" } ## The pipeline @@ -32,8 +38,8 @@ The verifier runs four stages per workflow run: small Python program that recomputes the asserted value from workspace files (the default path), or renders a soft LLM verdict when no deterministic check is possible. -3. **Aggregation** — per-claim scores combine into `overall_score` with a - saturating thoroughness bonus and a hard-fail cap. +3. **Aggregation** — per-claim scores combine into `overall_score` as an + importance-weighted mean with a hard-fail cap. 4. **Prompt gradient** — a plain-language summary of failure modes, the **only** signal that reaches the mutator. It does not name claims, scores, or sources, so the mutator cannot turn the verified-claim @@ -106,22 +112,54 @@ only falls back to an LLM verdict when no executable check is possible. ## Aggregation ```python -base_mean = mean(score for each non-error claim) -bonus = α · (1 − exp(−n_hard_pass / β)) # α=0.05, β=8 -pre_cap = clamp(base_mean + bonus, 0, 1) +base_mean = importance_weighted_mean(score for each non-error claim) +pre_cap = clamp(base_mean, 0, 1) overall_score = min(pre_cap, hard_fail_cap) if any_hard_claim_refuted else pre_cap ``` -- `α = _INFO_BONUS_ALPHA = 0.05`, `β = _INFO_BONUS_BETA = 8.0` — - saturating reward for thoroughness, conditioned on *passing hard* - claims so trivial or failed claims contribute nothing. +- Per-claim weights come from the rater-assigned importance (1–10), so an + importance-10 deliverable claim moves the score ~5× more than a + low-importance hygiene claim. - `hard_fail_cap = _HARD_FAIL_CAP = 0.99` — currently set permissively to keep the evolutionary signal smooth; a refuted hard claim still flags `hard_fail_capped = True`. - The engine separately keeps `overall_score_uncapped` (pre-cap) so QD rank ordering doesn't flatten under hard fails. +## Failure fingerprint (QD behaviour descriptor) + +The verifier doesn't just emit a score — the same per-claim verdicts feed +the QD novelty signal as a **failure fingerprint**: a centered vector +of per-source pass rates that tells the archive *how* a candidate fails, +not *whether* it failed. + +```python +# Per source A..F (six entries, always — absent sources get a neutral value). +pass_rate[s] = passes[s] / total[s] if total[s] > 0 else 0.5 +presence[s] = 1.0 if total[s] > 0 else 0.0 +# Center so the descriptor encodes profile shape, not quality level. +mean_present = mean(pass_rate[s] for s where presence[s] == 1) +vector[s] = pass_rate[s] - mean_present if presence[s] == 1 + = 0 otherwise +``` + +**The quality firewall.** An all-pass run and an all-fail run both yield +the zero profile. This is intended and asserted in the tests +(`test_all_pass_yields_zero_profile`, +`test_all_fail_yields_zero_profile`). The QD score combines quality and +novelty *additively* — `(1 − w)·quality_norm + w·novelty_norm` — so +quality already drives `quality_norm`. If quality also leaked into +novelty, QD would collapse back into greedy search. The centering step +is what keeps these two terms separable. + +The fingerprint is persisted under +`state_result.json` → `evaluation.verifier.failure_fingerprint.vector` +and consumed by +[`SelectionPressure._extract_behaviour_descriptor`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/selection.py). +Full info-flow audit: +[`docs/info-flow/failure_fingerprint.md`](../info-flow/failure_fingerprint.md). + ## Prompt gradient After aggregation the verifier composes a single-sentence diagnosis (the @@ -139,13 +177,10 @@ The intent is informational, not punitive: the mutator learns *what direction to push the workflow next* without being handed a vocabulary it can over-fit against. -## Cheat detector (currently disabled) +## Behavioral pressure against shortcut workflows -A standalone cheat detector pass over the agents' produced source code -existed in earlier versions and is kept in the codebase but is currently -**disabled** (`cheat = None`) pending a rewrite. The aggregation pipeline -still supports a `cheat_penalty` field for when it is re-enabled. The -behavioral anti-cheat pressure today comes from: +Pressure against shortcut or fabricated workflows comes from the +verifier pipeline itself: - Source C's recompute-from-disk verifiers. - The "Used fallback" claim type, whose score is *inverted* — a passing diff --git a/docs/concepts/evolution-engine.md b/docs/concepts/evolution-engine.md index a2e76f28..c16acb5a 100644 --- a/docs/concepts/evolution-engine.md +++ b/docs/concepts/evolution-engine.md @@ -17,7 +17,7 @@ flowchart TB Seed -- yes --> SeedPrompt[Seed genome prompt
or template mutation] Seed -- no --> Pick[Pick parents via QD-roulette
fallback to disk similarity scan] SeedPrompt --> Orch[Orchestrate workflow
LLM → sandbox] - Pick --> Decide{Crossover ≈ 0.4?} + Pick --> Decide{Crossover ≈ 0.1?} Decide -- mutation --> Mut[Mutation prompt
stagnation-scoped] Decide -- crossover --> Cross[Crossover prompt
best-parent-first] Mut --> Orch @@ -52,9 +52,9 @@ A more detailed view lives in the source diagram Termination: -- `overall_score > learned_score_threshold` (default `0.94`) in `--learn` mode, *or* +- `overall_score >= learned_score_threshold` (default `0.9`) in `--learn` mode, *or* - `max_depth` reached — `1` in single-shot mode, `max_learning_evolve_iterations` - (default `45`) in `--learn` mode. + (default `20`) in `--learn` mode. ## Selection: Quality-Diversity (QD) @@ -64,12 +64,15 @@ implements four strategies — `greedy`, `tournament`, `novelty`, and `qd` - A **session archive** holds up to `population_size = 50` members. - Each member has `qd_score = (1−w)·quality_norm + w·novelty_norm`, with - `w = novelty_weight = 0.25`. + `w = novelty_weight = 0.25`. Quality and novelty are **additive** — never + multiplied — so high quality cannot rescue a redundant profile and high + novelty cannot drag a broken run above peers. - Quality is sourced from `reward_uncapped` so the hard-fail cap doesn't flatten rank ordering. -- Novelty is k-NN distance (`k = 15`) in behaviour-descriptor space, where - the descriptor is `[n_agents, n_edges, n_branches, prompt_chars]` - extracted by [`code_features.py`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/code_features.py). +- Novelty is k-NN distance (`k = 15`) in **failure-fingerprint** space. + The descriptor is the centered per-source pass-rate vector produced by + [`failure_fingerprint.py`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/failure_fingerprint.py) + from the verifier's per-claim verdicts (see below). - Admission gate: candidate is admitted when it either improves over baseline by `min_improvement_threshold` or clears `qd_score > admit_threshold`. When the archive reaches capacity, the @@ -80,23 +83,60 @@ implements four strategies — `greedy`, `tournament`, `novelty`, and `qd` offspring stream stays spread across the archive. When the archive is empty (cold start), [`WorkflowSelector`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/workflow_selection.py) -falls back to a **similarity-filtered disk scan** (`cosine ≥ 0.5` on MiniLM -embeddings of `original_task`, `score ≥ 0.05`) — this lets useful workflows +falls back to a **similarity-filtered disk scan** (`cosine ≥ 0.8` on MiniLM +embeddings of `original_task`, `score ≥ 0.1`) — this lets useful workflows transfer across tasks. +### Behaviour descriptor: failure fingerprint + +The novelty signal compares candidates in **failure-fingerprint** space. +Per source A–F (literature, user goal, agent narration, math invariants, +computational reproducibility, statistical fingerprint), the verifier +records a pass rate. Sources with zero claims get the neutral value +`0.5` and a presence-mask entry of `0`. The vector is then **centered**: +the mean pass rate across present sources is subtracted from every entry. + +The centering is the *quality firewall*. Without it, an all-pass run sits +at `[1,1,1,1,1,1]` and an all-fail run at `[0,0,0,0,0,0]` — Euclidean +distance between them is large, and quality silently leaks into novelty. +After centering, **both** runs collapse to the zero profile and novelty +encodes only the *shape* of which sources fail relative to the others. +Two workflows that fail in the same way are redundant regardless of how +different their DAGs look; two that fail in different ways explore +different basins and both deserve a seat in the archive. + +The fingerprint is computed at the end of `VerifierEvaluator.evaluate()` +and persisted in `state_result.json` under +`evaluation.verifier.failure_fingerprint.vector`. The full audit trail — +which variable comes from where, the failure modes the descriptor must +survive, and the centering invariant asserted by the tests — lives in +[`docs/info-flow/failure_fingerprint.md`](../info-flow/failure_fingerprint.md). + +When a run has no usable fingerprint (verifier short-circuit on a fully +failed workflow), `SelectionPressure._extract_behaviour_descriptor` +returns a neutral zero vector so distance lookups stay well-defined and +the cold path doesn't artificially win or lose on novelty. + +The legacy structural descriptor (`[n_agents, n_edges, n_branches, +prompt_chars]`) shipped by +[`code_features.py`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/code_features.py) +is retained for ablations and offline analysis but is **no longer used** +for QD novelty — empirical work showed it barely co-varies with outcomes. + ## Variation: evidence-driven mutation scope (Rechenberg 1/5 rule) [`VariationEngine`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/sources/core/variation_engine.py) assembles mutation and crossover prompts. There is no fixed phase schedule by iteration progress; mutation boldness is a continuous -function of two evidence signals — how much the population is -repeating itself, *and* how often recent offspring have actually -improved on the best-so-far. +function of two evidence signals — how long the lineage has been +failing to improve, *and* how often recent offspring actually beat the +best-so-far. -**Stagnation signal.** `_compute_stagnation(window=4)` takes the last -4 non-failure prompt gradients, computes their pairwise MiniLM cosine -similarity, and rescales the mean (`0.4` ≈ unrelated → `0`, -`0.8+` ≈ fully stagnated → `1`). +**Plateau signal.** `_iters_since_improvement()` counts the run of +trailing scored offspring (failures and unscored entries skipped) that +did not strictly beat the best-so-far at the moment they were +produced. It is normalised to `plateau = min(1, iters / _PLATEAU_PATIENCE)` +with `_PLATEAU_PATIENCE = 6`. **Success-rate signal.** `_compute_success_rate(window=5)` counts the fraction of the last 5 scored offspring whose `overall_score` strictly @@ -107,42 +147,49 @@ is happening and step size should be damped. **Effective boldness.** Combining the two signals: -- **Cold start** (no scored offspring yet) — `effective = raw_stagnation`. -- **Below 1/5** (`success_rate < 0.20`) — escalate at least to the - deficit: `effective = max(raw_stagnation, deficit)`, where - `deficit = (0.20 − success_rate) / 0.20`. A repeated gradient and a - flat reward curve both force scope up. +- **Cold start** (fewer than two comparable scored offspring) — + `effective = 0.3 · plateau`. A capped cold-start ramp avoids jumping + straight into RE-SPECIATION before any feedback has accumulated. +- **Below 1/5** (`success_rate < 0.20`) — average the deficit and the + plateau: `effective = 0.5 · deficit + 0.5 · plateau`, where + `deficit = (0.20 − success_rate) / 0.20`. A run of no-improvements + and a stalling success rate both push scope up. - **Above 1/5** — damp boldness in proportion to how far above - threshold we are: `effective = raw_stagnation · (1 − progress)`, - where `progress = min(1, (success_rate − 0.20) / (0.80 − 0.20))`. - At `success_rate ≥ 0.80` boldness collapses regardless of stagnation. + threshold we are: `effective = plateau · (1 − progress)`, where + `progress = min(1, (success_rate − 0.20) / (0.80 − 0.20))`. At + `success_rate ≥ 0.80` boldness collapses regardless of plateau. - **Near-finish floor** — only in the last 5 % of the score range, `effective` is multiplied by `(1 − 0.5 · near_finish)` where - `near_finish = (parent_score − 0.95) / 0.05`. This is the *only* - point where the parent's absolute score re-enters the boldness + `near_finish = max(0, (parent_score − 0.95) / 0.05)`. This is the + *only* point where the parent's absolute score re-enters the boldness calculation, so a 0.96 parent isn't gambled away one generation before early-stop. +- **RE-SPECIATION gate.** The top band is hysteresis-gated: unless + `iters_since_improvement ≥ _RESPECIATION_PATIENCE` (default `8`) *and* + `success_rate ∈ {None, 0.0}`, `effective` is clamped to + `_RESPECIATION_CLAMP = 0.89` — just below the EXPLORATION/RE-SPECIATION + boundary at `0.90`. Notably, `parent_score` no longer multiplies the whole signal — that older behaviour locked high-score lineages into "tiny tweak" mode even -when the gradient kept repeating identically. +when offspring kept failing identically. **Agent budget.** The current agent count grows toward `max_possible_agents = 7` proportionally to `effective`, then a Beta-Binomial draw samples the actual count inside that window (biased upward by `effective`). The seed generation samples agents -from `[1, 4]` with a `0.5` stagnation prior. +from `[1, 4]` with a `0.5` boldness prior. **Scope band.** A single advisory line is added to the mutation prompt, chosen by `effective`: -| Effective boldness | Mutation scope | -| ------------------- | ---------------------------------------------------------------------- | -| < 0.20 | prompt-only little tweak | -| < 0.40 | prompt, handoff, tools — improve information flow | -| < 0.60 | significant redesign while keeping topology | -| < 0.80 | bold rewire — restructure or grow the agent set | -| ≥ 0.80 | complete rethink — discard inherited topology / prompts | +| Effective boldness | Mutation scope | +| ------------------- | --------------------------------------------------------------------------- | +| < 0.35 | `EXPLOITATION` — point mutation: minor phrasing / prompt-adjective tweaks | +| < 0.50 | `ALIGNMENT` — interface optimization: refine handoff prompts, IO contracts | +| < 0.65 | `ADAPTATION` — component overhaul: rewrite lagging agent prompts, swap tools | +| < 0.90 | `EXPLORATION` — macro structural mutation: add/merge agents, change routing | +| ≥ 0.90 | `RE-SPECIATION` — clean-slate redesign of the multi-agent architecture | The bands are advisory text steered to the LLM, not hard gates: the LLM can still pick any topology. The hard control is the agent-count @@ -150,9 +197,9 @@ budget passed in the same prompt block. ## Crossover -With probability ~0.4 per generation (and only once at least -`initial_population = 2` runs have happened), two parents are combined -instead of one being mutated. The crossover prompt is +With probability `crossover_rate` per generation (default `0.1`, and only +once at least `initial_population = 2` runs have happened), two parents +are combined instead of one being mutated. The crossover prompt is **best-parent-first**: the strongest parent's code structure leads, weaker parents contribute specific improvements rather than competing for the skeleton, and the offspring is hard-capped at the highest @@ -186,8 +233,10 @@ Each iteration also writes structured metrics for post-hoc analysis: `iteration_wall_time_s`, `iteration_cost_usd`, `cumulative_cost_usd`, `overall_score{,_uncapped}`, `qd_descriptor`, `qd_score`, `novelty_score`, the `selection_log`, and the - `variation_state` (`stagnation`, `success_rate`, `effective_boldness`, - `scope_band`, `agent_budget`) that produced this offspring. + `variation_state` (`iters_since_improvement`, `plateau`, + `success_rate`, `effective_boldness`, `parent_score`, + `respeciation_gate_open`, , `agent_budget`) that produced + this offspring. - `sources/workflows/qd_archive.jsonl` — append-only, one line per `validate_survivor` call. Records the candidate's descriptor, `qd_score`, `novelty_score`, admission verdict, and the `evicted_uuid` diff --git a/docs/diagrams/evolution_loop.mermaid b/docs/diagrams/evolution_loop.mermaid index 5ec27bd1..92e05d57 100644 --- a/docs/diagrams/evolution_loop.mermaid +++ b/docs/diagrams/evolution_loop.mermaid @@ -27,7 +27,7 @@ flowchart TB ParentDraw{{"archive populated?"}} DrawArch["select_parent_workflows()
QD-roulette over archive
÷ (1 + n_children_already)"] DrawDisk["cold-start fallback:
similarity-filtered disk scan"] - Decide{{"crossover_rate ≈ 0.4?
(only once initial_population met)"}} + Decide{{"crossover_rate ≈ 0.1?
(only once initial_population met)"}} Mut["mutation_prompt()
evidence-driven scope (Rechenberg 1/5)
raw_stagnation × success-rate gating"] Cross["crossover_prompt()
best-parent-first ordering
rubric-blind diagnosis"] @@ -79,11 +79,11 @@ flowchart TB %% ===== sidebars ===== subgraph QD["Quality-Diversity machinery"] - BD["behaviour descriptor
extract_code_features() →
[n_agents, n_edges, n_branches,
prompt_chars] / SCALES"] - Nov["k-NN novelty
(k=15, against archive)"] - QDscore["qd_score = (1-w)·quality_norm +
w·novelty_norm (w=0.25)
quality from reward_uncapped"] + BD["behaviour descriptor
failure_fingerprint(per_claim) →
centered per-source pass rates
over sources A..F (6-D)"] + Nov["k-NN novelty
(k=25, against archive)"] + QDscore["qd_score = (1-w)·quality_norm +
w·novelty_norm (w=0.4)
quality from reward_uncapped
(additive: no quality leak into novelty)"] end - State -. AST .-> BD + State -. verifier per-claim verdicts .-> BD Archive -. distances .-> Nov BD --> QDscore Nov --> QDscore diff --git a/docs/diagrams/verifiers_judge.mermaid b/docs/diagrams/verifiers_judge.mermaid index 361f39c8..de0e8fc9 100644 --- a/docs/diagrams/verifiers_judge.mermaid +++ b/docs/diagrams/verifiers_judge.mermaid @@ -58,21 +58,16 @@ flowchart TB %% ===== Stage 3: aggregation ===== subgraph Aggregate["Stage 3 — aggregation"] - Mean["base_mean = mean(non-error claims)"] - Bonus["info_bonus = α·(1 − exp(−n_hard_pass / β))
α=0.05, β=8 — saturates"] + Mean["base_mean = importance-weighted mean(non-error claims)"] Cap["hard-fail cap (0.99, soft):
any refuted hard claim flags hard_fail_capped"] - Cheat["cheat_penalty
(currently disabled — pending rewrite)"] end ScoreClaim --> Mean - ScoreClaim --> Bonus Mean --> Cap - Bonus --> Cap - Cap --> Cheat %% ===== Stage 4: abstracted prompt gradient ===== - Cheat --> Overall["overall_score ∈ [0, 1]
+ overall_score_uncapped
(pre-cap, drives QD ranking)"] - Cheat --> Diag["Stage 4 — abstracted_prompt_gradient
(rubric-blind code-name + sentence)
→ only signal fed back to mutator"] + Cap --> Overall["overall_score ∈ [0, 1]
+ overall_score_uncapped
(pre-cap, drives QD ranking)"] + Cap --> Diag["Stage 4 — abstracted_prompt_gradient
(rubric-blind code-name + sentence)
→ only signal fed back to mutator"] Overall -. drives .-> Loop[/"EvolutionEngine.evolve_generation()"/] Diag -. drives .-> Loop diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index f2be639e..29ee8115 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -22,8 +22,8 @@ field with defaults and types, see the [Configuration reference](../reference/co | `smolagent_model_id` | LLM used by execution agents inside each workflow. | | `judge_model` | LLM that scores soft claims in the verifier. | | `planner_llm_model` | LLM that decomposes goals into tasks (`--goal` mode only). | -| `learned_score_threshold` | Score that triggers early stop in `--learn` mode (default `0.97`). | -| `max_learning_evolve_iterations` | Hard cap on evolve iterations (default `35`). | +| `learned_score_threshold` | Score that triggers early stop in `--learn` mode (default `0.9`). | +| `max_learning_evolve_iterations` | Hard cap on evolve iterations (default `20`). | ## Choosing models @@ -78,7 +78,7 @@ The sandbox enforces resource caps per generated workflow: | Field | Default | Purpose | | ----- | ------- | ------- | -| `runner_default_python_version` | `3.10` | Python version inside the sandbox. | +| `runner_default_python_version` | `3.12` | Python version inside the sandbox. | | `runner_default_timeout` | `3600` | Per-run timeout (seconds). | | `runner_default_max_memory_mb` | `1024` | RAM cap (MB). | | `runner_default_max_cpu_percent` | `100` | CPU cap (%). | @@ -95,8 +95,8 @@ uv run main.py --task "…" \ | Field | Default | Meaning | | ----- | ------- | ------- | -| `learned_score_threshold` | `0.97` | Stop evolving when `overall_score` reaches this. | -| `max_learning_evolve_iterations` | `35` | Max generations before giving up. | +| `learned_score_threshold` | `0.9` | Stop evolving when `overall_score` reaches this. | +| `max_learning_evolve_iterations` | `20` | Max generations before giving up. | See [Iterative learning](../usage/learning.md) for the full evolution machinery. diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 98fe957c..8a1f712b 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -62,8 +62,8 @@ uv run main.py \ ``` In learning mode, Mimosa evolves up to `max_learning_evolve_iterations` -generations or stops as soon as `overall_score > learned_score_threshold` -(default `0.95`). See [Iterative learning](../usage/learning.md). +generations or stops as soon as `overall_score >= learned_score_threshold` +(default `0.9`). See [Iterative learning](../usage/learning.md). ## 4. Try goal mode diff --git a/docs/images/evolution_tree.png b/docs/images/evolution_tree.png new file mode 100644 index 00000000..6b878cb7 Binary files /dev/null and b/docs/images/evolution_tree.png differ diff --git a/docs/info-flow/failure_fingerprint.md b/docs/info-flow/failure_fingerprint.md new file mode 100644 index 00000000..71b2c03b --- /dev/null +++ b/docs/info-flow/failure_fingerprint.md @@ -0,0 +1,169 @@ +# Failure-fingerprint descriptor — info flow + +> **Reader's note.** This page is an info-flow audit, not a user-facing +> tutorial. It traces every variable that feeds the QD behaviour descriptor +> from its source to the point where novelty distance is computed. +> If you want to *understand* the descriptor, start with +> [`concepts/evolution-engine.md`](../concepts/evolution-engine.md) and +> [`concepts/evaluation-pipeline.md`](../concepts/evaluation-pipeline.md). + +## Why a separate descriptor + +QD selection compares candidates with two scalars: + +- **quality** — `reward_uncapped`, i.e. how well the workflow scored. +- **novelty** — k-NN distance in *behaviour-descriptor space* to the + rest of the archive. + +The descriptor must be **orthogonal to quality**. If it isn't, novelty +becomes a noisy restatement of quality and QD collapses back into greedy +search. The previous structural descriptor +(`[n_agents, n_edges, n_branches, prompt_chars]`) was nominally +orthogonal but barely co-varied with outcomes — different DAG shapes did +not predict different basins. The failure fingerprint replaces it. + +## The signal: per-source pass rates + +The verifier extracts claims from six independent vantage points: + +| Letter | Vantage | +| --- | --- | +| A | Literature / methodology bar | +| B | User goal / deliverable fidelity | +| C | Agent narration truthfulness (recompute-from-disk) | +| D | Math invariants | +| E | Computational reproducibility / CS practice | +| F | Statistical fingerprint | + +Each claim is verified to one of `pass`, `fail`, `error`, `unsure`. + +**Per source**, the fingerprint takes the pass rate: + +``` +pass_rate[s] = (# claims with status == "pass" and source == s) / (# claims with source == s) +``` + +`fail`, `error`, and `unsure` all count as non-passes — a measurement +error is still a non-pass from the optimiser's perspective, and quality +already penalises errors via `quality_norm`. + +## Centering: the quality firewall + +Per source pass rates **alone** would leak quality into novelty (an +all-pass run would sit at `[1,1,1,1,1,1]`, an all-fail one at +`[0,0,0,0,0,0]`, and their Euclidean distance would be large). To strip +quality out, we subtract the mean pass rate across present sources from +every entry: + +``` +mean_present = mean(pass_rate[s] for s in SOURCES if presence_mask[s]) +vector[s] = pass_rate[s] - mean_present if presence_mask[s] + = 0 otherwise +``` + +The descriptor now encodes the *profile shape* of which sources fail +relative to the others — NOT the overall quality level. An all-pass run +and an all-fail run **both** yield the zero profile. This is the +*quality firewall* — and it is asserted in +[`tests/failure_fingerprint_test.py`](https://github.com/HolobiomicsLab/Mimosa-AI/blob/main/tests/failure_fingerprint_test.py) +by `test_all_pass_yields_zero_profile` and `test_all_fail_yields_zero_profile`. + +A run that fails source A but passes the others looks *very different* +from a run that fails source D but passes the others. Both might be +mediocre on quality, and both deserve a seat in the archive because each +one explores a different basin of failure modes. + +## Sources of variables + +``` + ┌────────────────────────────────────┐ + workflow run │ VerifierEvaluator.evaluate() │ + ──────────────► │ per_claim = list of: │ + uuid, code, │ {"claim": {"source": "source_X"}, + execution_text │ "status": "pass" | "fail" | ..} + └─────────────┬──────────────────────┘ + │ + ▼ + ┌────────────────────────────────────┐ + │ failure_fingerprint │ + │ .compute_failure_fingerprint │ + │ -> {vector, presence_mask, │ + │ pass_rates} │ + └─────────────┬──────────────────────┘ + │ + │ persisted in scores dict + ▼ + ┌────────────────────────────────────┐ + │ state_result.json │ + │ evaluation.verifier │ + │ .failure_fingerprint │ + │ .vector (list[float], len 6) + │ .presence_mask (list[float], len 6) + │ .pass_rates (list[float], len 6) + └─────────────┬──────────────────────┘ + │ + │ EvolutionEngine reads via + │ WorkflowInfo.state_result + ▼ + ┌────────────────────────────────────┐ + │ IndividualRun.state_result │ + └─────────────┬──────────────────────┘ + │ + ▼ + ┌────────────────────────────────────┐ + │ SelectionPressure │ + │ ._extract_behaviour_descriptor │ + │ → failure_fingerprint_from_ │ + │ state_result(run.state_result) + │ → falls back to │ + │ neutral_fingerprint() when │ + │ verifier hasn't written one │ + └─────────────┬──────────────────────┘ + │ + ▼ + ┌────────────────────────────────────┐ + │ k-NN novelty in 6-D space │ + │ PopulationMember.behaviour_ │ + │ descriptor │ + └────────────────────────────────────┘ +``` + +## Variable inventory (audit table) + +| Symbol | Type | Set by | Read by | Notes | +| --- | --- | --- | --- | --- | +| `per_claim[i]["claim"]["source"]` | str | `verifier_claims._extract_claims` | `compute_failure_fingerprint` | Normalised to `a`..`f` via `_source_letter`. | +| `per_claim[i]["status"]` | str | `verifier_per_claim._verify_claim` | `compute_failure_fingerprint` | One of `pass`/`fail`/`error`/`unsure`. | +| `pass_rates` | `list[float]` (len 6) | `compute_failure_fingerprint` | Persisted; debugging only. | Neutral 0.5 when source absent. | +| `presence_mask` | `list[float]` (len 6) | `compute_failure_fingerprint` | Persisted; debugging only. | `1.0` iff source emitted ≥1 claim. | +| `vector` | `list[float]` (len 6) | `compute_failure_fingerprint` | `_extract_behaviour_descriptor` → k-NN | The centered descriptor; **the QD signal**. | +| `state_result.evaluation.verifier.failure_fingerprint` | dict | `VerifierEvaluator.evaluate` / `_short_circuit_failed_run` | `failure_fingerprint_from_state_result` | Persisted in `state_result.json`. | +| `IndividualRun.state_result` | dict | `EvolutionEngine._evaluate_and_calculate_cost` | `SelectionPressure._extract_behaviour_descriptor` | Carries the fingerprint into selection. | +| `PopulationMember.behaviour_descriptor` | `list[float]` (len 6) | `SelectionPressure._validate_open_ended` | `_compute_novelty` | Snapshot of the fingerprint at admission time. | +| `qd_score` | float | `SelectionPressure._validate_open_ended` and `_refresh_member_metrics` | parent draw, archive eviction | `(1 − w)·quality_norm + w·novelty_norm`; quality and novelty are *additive*, never multiplied. | + +## Failure modes the audit checks + +- **No claims at all** (verifier short-circuit): the run gets the zero + vector and zero presence mask. Distance to peers stays finite; the + cold run isn't pushed to admit or reject solely on novelty. +- **Source F absent in practice** (the current code wires sources A–E): + `presence_mask[5]` stays `0.0`; the centering step ignores that axis; + distance lookups remain well-defined. +- **Unknown source label**: dropped silently by `_source_letter`. No + out-of-band claim can pollute a bucket. +- **Mismatched descriptor length across archive members**: only happens + during a schema upgrade; `_euclidean` returns `+inf` as a sentinel and + `_novelty_range` clamps the normalisation. We recommend draining the + archive when the dim changes — the descriptor dim is now a stable 6. + +## Invariants (asserted) + +- `len(vector) == DESCRIPTOR_DIM == 6`. +- All-pass and all-fail yield `[0]*6` (`test_all_pass_yields_zero_profile`, `test_all_fail_yields_zero_profile`). +- Two runs with the same per-source pass rates yield the **same** vector, + regardless of overall reward — distance is `0`, so the second is treated + as redundant. +- Two runs with opposite shape (a-pass-b-fail vs a-fail-b-pass) yield + vectors that mirror each other through the origin + (`test_distinct_profiles_yield_nonzero_distance`). diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 68324a61..391642f8 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -34,15 +34,15 @@ Ports must be in `[0, 65535]` and `port_min ≤ port_max`. | Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | `prompt_planner` | `str` | `sources/prompts/planner_reproduction.md` | Planner system prompt. | -| `prompt_workflow_creator` | `str` | `sources/prompts/workflow_v10.md` | Workflow-generation prompt. | +| `prompt_workflow_creator` | `str` | `sources/prompts/workflow_v11.md` | Workflow-generation prompt. | | `prompt_smolagent` | `str` | `sources/prompts/smolagent_sys_prompt.md` | SmolAgent system prompt. | ## Learning | Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | -| `learned_score_threshold` | `float` | `0.97` | `--learn` stops when `overall_score` exceeds this. | -| `max_learning_evolve_iterations` | `int` | `35` | Hard cap on evolve iterations. | +| `learned_score_threshold` | `float` | `0.9` | `--learn` stops when `overall_score` reaches this. | +| `max_learning_evolve_iterations` | `int` | `20` | Hard cap on evolve iterations. | | `max_concurrent_eval_tasks` | `int` | `1` | Concurrent tasks in CSV / batch modes. | ## OpenRouter routing @@ -70,7 +70,7 @@ See [Troubleshooting → OpenRouter quantization](troubleshooting.md#openrouter- | Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | -| `runner_default_python_version` | `str` | `3.10` | Python in the sandbox. | +| `runner_default_python_version` | `str` | `3.12` | Python in the sandbox. | | `runner_default_timeout` | `int` | `3600` | Per-run timeout (s). | | `runner_default_max_memory_mb` | `int` | `1024` | RAM cap (MB). | | `runner_default_max_cpu_percent` | `int` | `100` | CPU cap (%). | diff --git a/docs/reference/file-layout.md b/docs/reference/file-layout.md index 4c8f01ec..fd488d5c 100644 --- a/docs/reference/file-layout.md +++ b/docs/reference/file-layout.md @@ -18,7 +18,8 @@ mimosa-ai/ │ │ ├── selection.py # QD archive + admission gate │ │ ├── variation_engine.py # Mutation / crossover prompt assembly │ │ ├── workflow_selection.py # Parent retrieval (archive / disk) -│ │ ├── code_features.py # AST → behaviour descriptor +│ │ ├── failure_fingerprint.py # Verifier verdicts → QD behaviour descriptor (6-D, centered) +│ │ ├── code_features.py # Legacy structural descriptor (offline analysis only) │ │ ├── lineage.py # Parent → child sidecar records │ │ ├── orchestrator.py # Grounding → factory → sandbox │ │ ├── workflow_factory.py # Multi-agent workflow synthesis diff --git a/docs/site/404.html b/docs/site/404.html index 49f61342..41a7fba3 100644 --- a/docs/site/404.html +++ b/docs/site/404.html @@ -85,7 +85,7 @@