From 42a95a52b512fcc4fde3b0bfbc347330726f4d66 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Sat, 9 May 2026 19:45:07 +0200 Subject: [PATCH] feat(genome): HypothesisAgentGenome with deterministic id and serde Dataclass per genoma agente ipotesi con campi prompt/feature/temperature/ top_p/model_tier/lookback/style + parent_ids/generation. Id sha1[:16] deterministico su contenuto canonico (feature_access ordinate, float arrotondati). to_dict/from_dict per persistenza. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/multi_swarm/genome/__init__.py | 0 src/multi_swarm/genome/hypothesis.py | 72 ++++++++++++++++++++++++++++ tests/unit/test_genome_hypothesis.py | 50 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/multi_swarm/genome/__init__.py create mode 100644 src/multi_swarm/genome/hypothesis.py create mode 100644 tests/unit/test_genome_hypothesis.py diff --git a/src/multi_swarm/genome/__init__.py b/src/multi_swarm/genome/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/multi_swarm/genome/hypothesis.py b/src/multi_swarm/genome/hypothesis.py new file mode 100644 index 0000000..8b1a4e5 --- /dev/null +++ b/src/multi_swarm/genome/hypothesis.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import hashlib +import json +from dataclasses import dataclass, field +from enum import StrEnum +from typing import Any + + +class ModelTier(StrEnum): + B = "B" # Sonnet 4.6 via Anthropic + C = "C" # Qwen 2.5 72B via OpenRouter + + +@dataclass +class HypothesisAgentGenome: + system_prompt: str + feature_access: list[str] + temperature: float + top_p: float + model_tier: ModelTier + lookback_window: int + cognitive_style: str + parent_ids: list[str] = field(default_factory=list) + generation: int = 0 + id: str = "" + + def __post_init__(self) -> None: + if not self.id: + self.id = self._compute_id() + + def _compute_id(self) -> str: + payload = { + "system_prompt": self.system_prompt, + "feature_access": sorted(self.feature_access), + "temperature": round(self.temperature, 4), + "top_p": round(self.top_p, 4), + "model_tier": self.model_tier.value, + "lookback_window": self.lookback_window, + "cognitive_style": self.cognitive_style, + } + s = json.dumps(payload, sort_keys=True) + return hashlib.sha1(s.encode()).hexdigest()[:16] + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "system_prompt": self.system_prompt, + "feature_access": self.feature_access, + "temperature": self.temperature, + "top_p": self.top_p, + "model_tier": self.model_tier.value, + "lookback_window": self.lookback_window, + "cognitive_style": self.cognitive_style, + "parent_ids": self.parent_ids, + "generation": self.generation, + } + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> HypothesisAgentGenome: + return cls( + system_prompt=data["system_prompt"], + feature_access=list(data["feature_access"]), + temperature=float(data["temperature"]), + top_p=float(data["top_p"]), + model_tier=ModelTier(data["model_tier"]), + lookback_window=int(data["lookback_window"]), + cognitive_style=data["cognitive_style"], + parent_ids=list(data.get("parent_ids", [])), + generation=int(data.get("generation", 0)), + id=data.get("id", ""), + ) diff --git a/tests/unit/test_genome_hypothesis.py b/tests/unit/test_genome_hypothesis.py new file mode 100644 index 0000000..7cbe8b0 --- /dev/null +++ b/tests/unit/test_genome_hypothesis.py @@ -0,0 +1,50 @@ +from multi_swarm.genome.hypothesis import HypothesisAgentGenome, ModelTier + + +def test_genome_creation_defaults(): + g = HypothesisAgentGenome( + system_prompt="Pensa come un fisico.", + feature_access=["close", "volume"], + temperature=0.9, + top_p=0.95, + model_tier=ModelTier.C, + lookback_window=200, + cognitive_style="physicist", + ) + assert g.id is not None + assert g.parent_ids == [] + assert g.generation == 0 + + +def test_genome_serialization_roundtrip(): + g = HypothesisAgentGenome( + system_prompt="Pensa come un biologo.", + feature_access=["close", "high", "low"], + temperature=1.1, + top_p=0.9, + model_tier=ModelTier.C, + lookback_window=300, + cognitive_style="biologist", + parent_ids=["abc"], + generation=5, + ) + payload = g.to_dict() + g2 = HypothesisAgentGenome.from_dict(payload) + assert g2.system_prompt == g.system_prompt + assert g2.feature_access == g.feature_access + assert g2.temperature == g.temperature + assert g2.parent_ids == g.parent_ids + assert g2.generation == g.generation + assert g2.id == g.id + + +def test_genome_id_is_deterministic_on_content(): + g1 = HypothesisAgentGenome( + system_prompt="X", feature_access=["close"], temperature=0.5, + top_p=0.9, model_tier=ModelTier.C, lookback_window=100, cognitive_style="x", + ) + g2 = HypothesisAgentGenome( + system_prompt="X", feature_access=["close"], temperature=0.5, + top_p=0.9, model_tier=ModelTier.C, lookback_window=100, cognitive_style="x", + ) + assert g1.id == g2.id