build(agent): melter#14fd4b iteration
This commit is contained in:
parent
0c31b95761
commit
65fe7ea6de
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
from .core import LocalProblem, SharedVariables, PlanDelta, PrivacyBudget, AuditLog, PolicyBlock, GraphOfContractsEntry
|
||||
from .solver import SolverConfig, compile_to_solver, simulate_solver_step
|
||||
from .adapters import RoverAdapter, HabitatAdapter
|
||||
from .registry import default_registry, InMemoryRegistry
|
||||
|
||||
__all__ = [
|
||||
"LocalProblem",
|
||||
|
|
@ -14,4 +16,8 @@ __all__ = [
|
|||
"SolverConfig",
|
||||
"compile_to_solver",
|
||||
"simulate_solver_step",
|
||||
"RoverAdapter",
|
||||
"HabitatAdapter",
|
||||
"default_registry",
|
||||
"InMemoryRegistry",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
"""Adapters package for CatOpt Studio MVP.
|
||||
|
||||
This package contains toy adapter implementations that translate device-
|
||||
specific LocalProblem instances into a small canonical mapping and emit
|
||||
PlanDelta objects. These are intentionally small, deterministic, and
|
||||
well-tested to serve as examples for adapter development.
|
||||
"""
|
||||
|
||||
from .rover_planner import RoverAdapter
|
||||
from .habitat_module import HabitatAdapter
|
||||
|
||||
__all__ = ["RoverAdapter", "HabitatAdapter"]
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
"""Toy habitat module adapter.
|
||||
|
||||
The HabitatAdapter provides a simple mapping for habitat-style LocalProblem
|
||||
instances and produces small PlanDelta objects. It serves as a complementary
|
||||
example adapter to the rover planner.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Any
|
||||
|
||||
from ..core import LocalProblem, PlanDelta
|
||||
|
||||
|
||||
@dataclass
|
||||
class HabitatAdapter:
|
||||
name: str = "habitat_module"
|
||||
|
||||
def to_canonical(self, lp: LocalProblem) -> Dict[str, Any]:
|
||||
return {
|
||||
"adapter": self.name,
|
||||
"id": lp.id,
|
||||
"domain": lp.domain,
|
||||
"assets_count": len(lp.assets or {}),
|
||||
"constraints": lp.constraints,
|
||||
}
|
||||
|
||||
def propose_delta(self, lp: LocalProblem, author: str = "habitat") -> PlanDelta:
|
||||
# Simple policy: if any safety constraint mentions 'no_move', emit
|
||||
# a delta that pauses movement; otherwise, propose a nominal plan.
|
||||
delta = {}
|
||||
for c in lp.constraints:
|
||||
if "no_move" in c:
|
||||
delta["action"] = "pause"
|
||||
delta["reason"] = "safety_constraint"
|
||||
break
|
||||
else:
|
||||
delta["action"] = "nominal"
|
||||
|
||||
return PlanDelta(delta=delta, author=author, contract_id=lp.id)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
"""Toy rover planner adapter.
|
||||
|
||||
The RoverAdapter demonstrates a minimal adapter that maps a LocalProblem
|
||||
from a rover domain into a canonical representation and can propose a
|
||||
PlanDelta. It purposely avoids heavy dependencies and is deterministic
|
||||
for testability.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Any
|
||||
|
||||
from ..core import LocalProblem, PlanDelta
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoverAdapter:
|
||||
name: str = "rover_planner"
|
||||
|
||||
def to_canonical(self, lp: LocalProblem) -> Dict[str, Any]:
|
||||
"""Map a rover LocalProblem to a tiny canonical schema.
|
||||
|
||||
The canonical mapping includes an adapter tag, core identifiers, and
|
||||
a simple summary of assets and objective to be used by downstream
|
||||
components.
|
||||
"""
|
||||
return {
|
||||
"adapter": self.name,
|
||||
"id": lp.id,
|
||||
"domain": lp.domain,
|
||||
"assets_summary": list(lp.assets.keys()),
|
||||
"objective": lp.objective,
|
||||
}
|
||||
|
||||
def propose_delta(self, lp: LocalProblem, author: str = "rover") -> PlanDelta:
|
||||
"""Produce a deterministic PlanDelta for tests and demos.
|
||||
|
||||
This toy implementation produces a delta that instructs a rover to
|
||||
hold position if a "battery" asset exists and its value is low.
|
||||
"""
|
||||
delta = {}
|
||||
battery = lp.assets.get("battery")
|
||||
if isinstance(battery, (int, float)) and battery < 20:
|
||||
delta["action"] = "hold_position"
|
||||
delta["reason"] = "low_battery"
|
||||
else:
|
||||
delta["action"] = "proceed"
|
||||
return PlanDelta(delta=delta, author=author, contract_id=lp.id)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
"""In-memory registry and minimal API for CatOpt Studio MVP.
|
||||
|
||||
This module provides a tiny, process-local registry suitable for tests and a
|
||||
conformance harness. It exposes the minimal operations requested in the idea
|
||||
description: register_contract, push_signal, propose_delta, get_provenance.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class RegistryEntry:
|
||||
contract_id: str
|
||||
adapter: str
|
||||
schema: Dict[str, Any]
|
||||
created: datetime = field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
class InMemoryRegistry:
|
||||
def __init__(self):
|
||||
self._contracts: Dict[str, RegistryEntry] = {}
|
||||
self._signals: List[Dict[str, Any]] = []
|
||||
self._deltas: List[Dict[str, Any]] = []
|
||||
|
||||
def register_contract(self, contract_id: str, adapter: str, schema: Dict[str, Any]) -> RegistryEntry:
|
||||
entry = RegistryEntry(contract_id=contract_id, adapter=adapter, schema=schema)
|
||||
self._contracts[contract_id] = entry
|
||||
return entry
|
||||
|
||||
def push_signal(self, contract_id: str, signal: Dict[str, Any]) -> None:
|
||||
record = {"contract_id": contract_id, "signal": signal, "timestamp": datetime.utcnow()}
|
||||
self._signals.append(record)
|
||||
|
||||
def propose_delta(self, contract_id: str, delta: Dict[str, Any], author: Optional[str] = None) -> None:
|
||||
record = {"contract_id": contract_id, "delta": delta, "author": author or "anonymous", "timestamp": datetime.utcnow()}
|
||||
self._deltas.append(record)
|
||||
|
||||
def get_provenance(self, contract_id: str) -> Dict[str, Any]:
|
||||
# Return a tiny provenance summary for the given contract
|
||||
signals = [s for s in self._signals if s["contract_id"] == contract_id]
|
||||
deltas = [d for d in self._deltas if d["contract_id"] == contract_id]
|
||||
entry = self._contracts.get(contract_id)
|
||||
return {
|
||||
"contract_id": contract_id,
|
||||
"registered": entry.created if entry else None,
|
||||
"signals_count": len(signals),
|
||||
"deltas_count": len(deltas),
|
||||
}
|
||||
|
||||
|
||||
# Module-level default registry for convenience in tests and examples
|
||||
default_registry = InMemoryRegistry()
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
from catopt_studio import LocalProblem
|
||||
from catopt_studio.adapters import RoverAdapter, HabitatAdapter
|
||||
|
||||
|
||||
def test_rover_adapter_propose_delta_low_battery():
|
||||
lp = LocalProblem(id="r1", domain="rover", assets={"battery": 10}, objective="navigate")
|
||||
ra = RoverAdapter()
|
||||
delta = ra.propose_delta(lp, author="tester")
|
||||
assert delta.author == "tester"
|
||||
assert delta.contract_id == "r1"
|
||||
assert delta.delta.get("action") == "hold_position"
|
||||
|
||||
|
||||
def test_rover_to_canonical_contains_expected_keys():
|
||||
lp = LocalProblem(id="r2", domain="rover", assets={"wheel": 2}, objective="survey")
|
||||
ra = RoverAdapter()
|
||||
canon = ra.to_canonical(lp)
|
||||
assert canon["adapter"] == "rover_planner"
|
||||
assert canon["id"] == "r2"
|
||||
|
||||
|
||||
def test_habitat_adapter_respects_no_move_constraint():
|
||||
lp = LocalProblem(id="h1", domain="habitat", assets={}, constraints=["no_move_zone"], objective="manage")
|
||||
ha = HabitatAdapter()
|
||||
delta = ha.propose_delta(lp, author="hub")
|
||||
assert delta.delta.get("action") == "pause"
|
||||
assert delta.author == "hub"
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
from catopt_studio.registry import InMemoryRegistry
|
||||
|
||||
|
||||
def test_register_and_provenance():
|
||||
reg = InMemoryRegistry()
|
||||
reg.register_contract("c1", "rover_planner", {"schema": "v1"})
|
||||
reg.push_signal("c1", {"s": 1})
|
||||
reg.propose_delta("c1", {"d": 2}, author="alice")
|
||||
prov = reg.get_provenance("c1")
|
||||
assert prov["contract_id"] == "c1"
|
||||
assert prov["signals_count"] == 1
|
||||
assert prov["deltas_count"] == 1
|
||||
Loading…
Reference in New Issue