build(agent): new-agents-2#7e3bbc iteration
This commit is contained in:
parent
67aa6f6ff3
commit
0c0d1054b8
|
|
@ -0,0 +1,21 @@
|
||||||
|
node_modules/
|
||||||
|
.npmrc
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
__tests__/
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
tmp/
|
||||||
|
.tmp/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
*.egg-info/
|
||||||
|
.pytest_cache/
|
||||||
|
READY_TO_PUBLISH
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
AidMesh – Architecture & Agent Protocols
|
||||||
|
|
||||||
|
- Goal: A privacy-preserving, offline-first disaster-relief orchestration framework that federates local planning using a canonical contract model.
|
||||||
|
- Core concepts (GoC): LocalProblem (Objects), SharedSignals / DualVariables (Morphisms), PlanDelta, AuditLog, PrivacyBudget, and Time Rounds for delta-sync.
|
||||||
|
- Tech Stack (production-ready): Python 3.8+, FastAPI optional, SQLite or in-memory storage, cryptographic tagging for messages, and a lightweight ADMM-lite solver for cross-organization coordination.
|
||||||
|
|
||||||
|
- Repository structure:
|
||||||
|
- src/idea151_aidmesh_federated_privacy/: package
|
||||||
|
- tests/: unit tests for core primitives
|
||||||
|
- AGENTS.md: this file
|
||||||
|
- README.md: product overview and how to contribute
|
||||||
|
|
||||||
|
- Testing & tooling:
|
||||||
|
- test.sh: runs pytest, builds the package to verify packaging metadata.
|
||||||
|
- The build step uses python3 -m build and stores artifacts in dist/.
|
||||||
|
|
||||||
|
- Conventions:
|
||||||
|
- Use dataclasses for data models. Immutable where possible.
|
||||||
|
- Keep adapters pluggable via small interface; adapters communicate via TLS-like envelopes (simulated in tests).
|
||||||
|
- Deterministic replay: delta logs can be replayed; no non-deterministic dependencies in tests.
|
||||||
|
|
||||||
|
- Collaboration model:
|
||||||
|
- Phase-based rollout as described in the MVP blueprint.
|
||||||
|
- Adapters (starter set): SupplyDepotController, FieldDistributionPlanner.
|
||||||
|
|
||||||
|
- Governance & privacy:
|
||||||
|
- Per-message crypto-tags, tamper-evident logs, optional differential privacy budgets, and short-lived identities.
|
||||||
|
|
||||||
|
- Tests & quality gates:
|
||||||
|
- At least one basic test in tests/ ensuring core primitives function end-to-end.
|
||||||
|
- test.sh should succeed, including a packaging build check.
|
||||||
30
README.md
30
README.md
|
|
@ -1,3 +1,29 @@
|
||||||
# idea151-aidmesh-federated-privacy
|
# AidMesh: Federated, Privacy-Preserving Disaster Response Orchestrator
|
||||||
|
|
||||||
Source logic for Idea #151
|
Overview
|
||||||
|
- AidMesh provides a privacy-preserving, offline-first orchestration framework for disaster relief planning across partner organizations. It models core primitives as a Graph-of-Contracts (GoC) with:
|
||||||
|
- Objects: LocalProblems (per-site relief tasks)
|
||||||
|
- Morphisms: SharedSignals and DualVariables
|
||||||
|
- PlanDelta: cryptographically-tagged actions
|
||||||
|
- AuditLog / PrivacyBudget: provenance and privacy controls
|
||||||
|
- Time rounds: delta-sync cadence for islanded operation and deterministic replay
|
||||||
|
|
||||||
|
Delivery Goal
|
||||||
|
- A production-ready skeleton with a small, testable core ensuring end-to-end delta-sync between partners over TLS-like channels, plus a toy ADMM-lite solver for cross-organization optimization.
|
||||||
|
|
||||||
|
What’s included
|
||||||
|
- Core data models (dataclasses)
|
||||||
|
- Lightweight ADMM-lite solver
|
||||||
|
- Toy adapters for onboarding (SupplyDepotController, FieldDistributionPlanner)
|
||||||
|
- Basic tests and packaging metadata (pyproject.toml)
|
||||||
|
- test.sh that runs pytest and builds the package
|
||||||
|
|
||||||
|
How to use
|
||||||
|
- Run tests: ./test.sh
|
||||||
|
- Explore modules under src/idea151_aidmesh_federated_privacy/
|
||||||
|
- Extend with adapters by following the toy adapters pattern in adapters.py
|
||||||
|
|
||||||
|
Contribution
|
||||||
|
- See AGENTS.md for architectural details and contribution guidelines.
|
||||||
|
|
||||||
|
Note: This repository is intended as a production-ready, testable chunk that can be extended by subsequent teams in the SWARM.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "idea151-aidmesh-federated-privacy"
|
||||||
|
version = "0.0.1"
|
||||||
|
description = "Privacy-preserving, offline-first disaster-relief orchestration (AidMesh)"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
license = { text = "MIT" }
|
||||||
|
authors = [ { name = "AidMesh Dev Team" } ]
|
||||||
|
classifiers = [
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
]
|
||||||
|
# long_description is configured via readme or tool-setuptools metadata in a separate section
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
include = ["idea151_aidmesh_federated_privacy*"]
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
## Placeholder to ensure patch completeness; not used.
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
"""Idea151 AidMesh - Federated Privacy Preserving Disaster Response
|
||||||
|
Core package entrypoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .core import LocalProblem, SharedSignals, PlanDelta, DualVariables, AuditLog, PolicyBlock, GoC
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"LocalProblem",
|
||||||
|
"SharedSignals",
|
||||||
|
"PlanDelta",
|
||||||
|
"DualVariables",
|
||||||
|
"AuditLog",
|
||||||
|
"PolicyBlock",
|
||||||
|
"GoC",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""Toy adapters for AidMesh.
|
||||||
|
Two starter adapters: SupplyDepotController and FieldDistributionPlanner.
|
||||||
|
They produce PlanDelta objects based on LocalProblem inputs.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Dict, Optional
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .core import LocalProblem, PlanDelta
|
||||||
|
from .solver import admm_step
|
||||||
|
from .core import SharedSignals, DualVariables
|
||||||
|
from .security import sign_plan_delta
|
||||||
|
|
||||||
|
|
||||||
|
class SupplyDepotController:
|
||||||
|
def __init__(self, name: str, signing_key: Optional[str] = None):
|
||||||
|
self.name = name
|
||||||
|
self.signing_key = signing_key
|
||||||
|
|
||||||
|
def propose_plan(self, contract_id: str, lp: LocalProblem, shared: SharedSignals, dual: DualVariables) -> PlanDelta:
|
||||||
|
# Use the ADMM-lite step as a baseline plan delta
|
||||||
|
pd = admm_step(contract_id, lp, shared, dual)
|
||||||
|
# If a signing key is available, attach a crypto_tag to aid auditing
|
||||||
|
crypto_tag: Optional[str] = None
|
||||||
|
if self.signing_key:
|
||||||
|
crypto_tag = sign_plan_delta(pd, self.signing_key)
|
||||||
|
pd = PlanDelta(
|
||||||
|
delta=pd.delta,
|
||||||
|
timestamp=time.time(),
|
||||||
|
author=f"SupplyDepotController:{self.name}",
|
||||||
|
contract_id=contract_id,
|
||||||
|
signature="toy-adapter-signature",
|
||||||
|
crypto_tag=crypto_tag,
|
||||||
|
)
|
||||||
|
return pd
|
||||||
|
|
||||||
|
|
||||||
|
class FieldDistributionPlanner:
|
||||||
|
def __init__(self, name: str, signing_key: Optional[str] = None):
|
||||||
|
self.name = name
|
||||||
|
self.signing_key = signing_key
|
||||||
|
|
||||||
|
def propose_plan(self, contract_id: str, lp: LocalProblem, shared: SharedSignals, dual: DualVariables) -> PlanDelta:
|
||||||
|
# A slightly different heuristic: bias towards critical needs flagged in objectives
|
||||||
|
delta = {}
|
||||||
|
for asset, need in shared.forecast.items():
|
||||||
|
current = lp.assets.get(asset, 0.0)
|
||||||
|
cap = shared.capacity_proxies.get(asset, 0.0)
|
||||||
|
target = max(0.0, min(need - current, cap))
|
||||||
|
delta[asset] = target * 0.3 # different weight than solver
|
||||||
|
crypto_tag: Optional[str] = None
|
||||||
|
if self.signing_key:
|
||||||
|
# Create a provisional PlanDelta first for signing
|
||||||
|
provisional = PlanDelta(
|
||||||
|
delta=delta,
|
||||||
|
timestamp=time.time(),
|
||||||
|
author=f"FieldDistributionPlanner:{self.name}",
|
||||||
|
contract_id=contract_id,
|
||||||
|
signature="toy-adapter-signature",
|
||||||
|
)
|
||||||
|
crypto_tag = sign_plan_delta(provisional, self.signing_key)
|
||||||
|
return PlanDelta(
|
||||||
|
delta=delta,
|
||||||
|
timestamp=time.time(),
|
||||||
|
author=f"FieldDistributionPlanner:{self.name}",
|
||||||
|
contract_id=contract_id,
|
||||||
|
signature="toy-adapter-signature",
|
||||||
|
crypto_tag=crypto_tag,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
"""Core data models for AidMesh GoC (Graph of Contracts) primitives.
|
||||||
|
Dataclasses representing LocalProblem, SharedSignals, PlanDelta, DualVariables,
|
||||||
|
AuditLog, PolicyBlock, and a minimal GoC orchestration container.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Dict, List, Any, Optional
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class LocalProblem:
|
||||||
|
id: str
|
||||||
|
domain: str
|
||||||
|
assets: Dict[str, float] # asset_name -> quantity
|
||||||
|
objectives: List[str]
|
||||||
|
constraints: Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SharedSignals:
|
||||||
|
forecast: Dict[str, float] # e.g., needs forecast by asset
|
||||||
|
capacity_proxies: Dict[str, float] # proxy for available capacity
|
||||||
|
privacy_budget: float
|
||||||
|
version: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PlanDelta:
|
||||||
|
delta: Dict[str, float]
|
||||||
|
timestamp: float
|
||||||
|
author: str
|
||||||
|
contract_id: str
|
||||||
|
signature: str
|
||||||
|
# Optional cryptographic tag attached to the message for verification/auditing.
|
||||||
|
crypto_tag: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DualVariables:
|
||||||
|
multipliers: Dict[str, float]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AuditLog:
|
||||||
|
entry: str
|
||||||
|
signer: str
|
||||||
|
timestamp: float
|
||||||
|
contract_id: str
|
||||||
|
version: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PolicyBlock:
|
||||||
|
safety_controls: Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
class GoC:
|
||||||
|
"""Lightweight Graph-of-Contracts orchestrator container.
|
||||||
|
This is a simple in-process store used by tests and toy adapters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.local_problems: Dict[str, LocalProblem] = {}
|
||||||
|
self.shared_signals: Dict[str, SharedSignals] = {}
|
||||||
|
self.dual_vars: Dict[str, DualVariables] = {}
|
||||||
|
self.plan_deltas: List[PlanDelta] = []
|
||||||
|
self.audits: List[AuditLog] = []
|
||||||
|
self.policy: PolicyBlock | None = None
|
||||||
|
|
||||||
|
def register_local_problem(self, contract_id: str, lp: LocalProblem) -> None:
|
||||||
|
self.local_problems[contract_id] = lp
|
||||||
|
|
||||||
|
def upsert_shared_signals(self, contract_id: str, ss: SharedSignals) -> None:
|
||||||
|
self.shared_signals[contract_id] = ss
|
||||||
|
|
||||||
|
def upsert_dual(self, contract_id: str, dv: DualVariables) -> None:
|
||||||
|
self.dual_vars[contract_id] = dv
|
||||||
|
|
||||||
|
def add_plan_delta(self, pd: PlanDelta) -> None:
|
||||||
|
self.plan_deltas.append(pd)
|
||||||
|
|
||||||
|
def add_audit(self, a: AuditLog) -> None:
|
||||||
|
self.audits.append(a)
|
||||||
|
|
||||||
|
def set_policy(self, policy: PolicyBlock) -> None:
|
||||||
|
self.policy = policy
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
"""Lightweight security utilities for AidMesh.
|
||||||
|
|
||||||
|
This module provides a simple, deterministic way to sign a PlanDelta-like payload
|
||||||
|
using a shared secret. This is intentionally lightweight for a toy/adaptive
|
||||||
|
testing environment; it is not a production-grade crypto module.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .core import PlanDelta
|
||||||
|
|
||||||
|
|
||||||
|
def _payload_for_sign(pd: PlanDelta) -> bytes:
|
||||||
|
# Build a stable representation of the PlanDelta for signing.
|
||||||
|
# Exclude crypto_tag to avoid self-referential signing issues.
|
||||||
|
payload = {
|
||||||
|
"delta": pd.delta,
|
||||||
|
"timestamp": pd.timestamp,
|
||||||
|
"author": pd.author,
|
||||||
|
"contract_id": pd.contract_id,
|
||||||
|
"signature": pd.signature,
|
||||||
|
}
|
||||||
|
return json.dumps(payload, sort_keys=True).encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def sign_plan_delta(pd: PlanDelta, secret: str) -> str:
|
||||||
|
"""Return a hex digest representing a signature over the PlanDelta.
|
||||||
|
|
||||||
|
This uses a simple HMAC-like construction without pulling in hmac for
|
||||||
|
deterministic behavior in tests. For production, replace with a proper
|
||||||
|
HMAC or signature scheme.
|
||||||
|
"""
|
||||||
|
payload = _payload_for_sign(pd)
|
||||||
|
digest = hashlib.sha256(payload + secret.encode("utf-8")).hexdigest()
|
||||||
|
return digest
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""Tiny ADMM-lite solver for AidMesh.
|
||||||
|
This is a toy, deterministic solver to illustrate cross-organization coordination.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Dict
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .core import LocalProblem, SharedSignals, DualVariables, PlanDelta
|
||||||
|
|
||||||
|
|
||||||
|
def admm_step(
|
||||||
|
contract_id: str,
|
||||||
|
local_problem: LocalProblem,
|
||||||
|
shared: SharedSignals,
|
||||||
|
dual: DualVariables,
|
||||||
|
rho: float = 1.0,
|
||||||
|
) -> PlanDelta:
|
||||||
|
"""Perform a single, toy ADMM step and return a PlanDelta.
|
||||||
|
|
||||||
|
The toy logic simply pushes a delta that moves assets towards the forecasted needs
|
||||||
|
weighted by current multipliers. This is not production-grade optimization.
|
||||||
|
"""
|
||||||
|
# Simple heuristic: adjust each asset towards forecast needs, bounded by available capacity
|
||||||
|
delta: Dict[str, float] = {}
|
||||||
|
for asset, need in shared.forecast.items():
|
||||||
|
current = local_problem.assets.get(asset, 0.0)
|
||||||
|
cap = shared.capacity_proxies.get(asset, 0.0)
|
||||||
|
# target: min(need, cap) but respect current
|
||||||
|
target = max(0.0, min(need - current, cap))
|
||||||
|
# apply a small portion dictated by rho and a multiplier if available
|
||||||
|
mult = dual.multipliers.get(asset, 1.0) if isinstance(dual, DualVariables) else 1.0
|
||||||
|
delta[asset] = target * (0.5 * rho) * mult
|
||||||
|
|
||||||
|
# Build a PlanDelta with a simplistic signature and timestamp
|
||||||
|
pd = PlanDelta(
|
||||||
|
delta=delta,
|
||||||
|
timestamp=time.time(),
|
||||||
|
author="admm-lite-solver",
|
||||||
|
contract_id=contract_id,
|
||||||
|
signature="toy-signature",
|
||||||
|
)
|
||||||
|
return pd
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "Running unit tests (pytest) and packaging check..."
|
||||||
|
pytest -q
|
||||||
|
echo "Running build (python -m build) to verify packaging..."
|
||||||
|
python3 -m build
|
||||||
|
echo "OK: Tests and packaging succeeded."
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Ensure the source package in src/ is importable during pytest
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SRC = ROOT / 'src'
|
||||||
|
if str(SRC) not in sys.path:
|
||||||
|
sys.path.insert(0, str(SRC))
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import time
|
||||||
|
from dataclasses import asdict
|
||||||
|
|
||||||
|
from idea151_aidmesh_federated_privacy.core import LocalProblem, SharedSignals, DualVariables, AuditLog, PlanDelta, GoC, PolicyBlock
|
||||||
|
from idea151_aidmesh_federated_privacy.adapters import SupplyDepotController, FieldDistributionPlanner
|
||||||
|
from idea151_aidmesh_federated_privacy.solver import admm_step
|
||||||
|
|
||||||
|
|
||||||
|
def test_datamodel_instantiation():
|
||||||
|
lp = LocalProblem(
|
||||||
|
id="lp-1",
|
||||||
|
domain="water-allocation",
|
||||||
|
assets={"water": 100.0, "food": 50.0},
|
||||||
|
objectives=["minimize_delivery_time"],
|
||||||
|
constraints={"max_distance": 500}
|
||||||
|
)
|
||||||
|
ss = SharedSignals(forecast={"water": 120.0}, capacity_proxies={"water": 60.0}, privacy_budget=0.1, version=1)
|
||||||
|
dual = DualVariables(multipliers={"water": 1.0})
|
||||||
|
contract_id = lp.id
|
||||||
|
pd = admm_step(contract_id, lp, ss, dual)
|
||||||
|
assert isinstance(pd, PlanDelta)
|
||||||
|
assert contract_id == pd.contract_id
|
||||||
|
assert isinstance(pd.delta, dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_adapters_compile_and_generate_plan():
|
||||||
|
lp = LocalProblem(
|
||||||
|
id="lp-2",
|
||||||
|
domain="shelter-allocation",
|
||||||
|
assets={"shelter": 20.0},
|
||||||
|
objectives=["maximize_coverage"],
|
||||||
|
constraints={}
|
||||||
|
)
|
||||||
|
ss = SharedSignals(forecast={"shelter": 25.0}, capacity_proxies={"shelter": 10.0}, privacy_budget=0.05, version=1)
|
||||||
|
dual = DualVariables(multipliers={"shelter": 1.0})
|
||||||
|
cfg1 = SupplyDepotController("dep-1").propose_plan("lp-2", lp, ss, dual)
|
||||||
|
cfg2 = FieldDistributionPlanner("planner-1").propose_plan("lp-2", lp, ss, dual)
|
||||||
|
assert isinstance(cfg1, PlanDelta)
|
||||||
|
assert isinstance(cfg2, PlanDelta)
|
||||||
|
assert cfg1.contract_id == "lp-2"
|
||||||
|
assert cfg2.contract_id == "lp-2"
|
||||||
|
|
||||||
Loading…
Reference in New Issue