From 358037eee93c71d0ca1d926c496fb62a7441de8b Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Wed, 9 Sep 2026 23:39:50 -0700 Subject: [PATCH] chore: retire duplicate a0python runtime --- a0python/.env.example | 56 -- a0python/.gitignore | 15 - a0python/a0/__init__.py | 3 - a0python/a0/a0.py | 42 -- a0python/a0/agent.py | 78 --- a0python/a0/bandit.py | 77 --- a0python/a0/cores/__init__.py | 23 - a0python/a0/cores/_base.py | 52 -- a0python/a0/cores/omega/__init__.py | 32 - a0python/a0/cores/omega/tensors/__init__.py | 29 - .../tensors/interdependent_way/__init__.py | 12 - .../interdependent_way/architecture.py | 79 --- .../omega/tensors/interdependent_way/hmmm.py | 23 - .../omega/tensors/interdependent_way/laws.py | 70 -- .../omega/tensors/supporting/__init__.py | 7 - .../omega/tensors/supporting/glossary.py | 60 -- .../cores/omega/tensors/supporting/specs.py | 38 -- a0python/a0/cores/pcna/__init__.py | 17 - a0python/a0/cores/pcna/inference.py | 248 ------- a0python/a0/cores/pcna/omega.py | 50 -- a0python/a0/cores/pcna/phi.py | 47 -- a0python/a0/cores/pcna/psi.py | 47 -- a0python/a0/cores/pcna/zfae.py | 633 ------------------ a0python/a0/cores/pcta/__init__.py | 11 - a0python/a0/cores/pcta/circle_tensors.py | 127 ---- a0python/a0/cores/phi/__init__.py | 27 - a0python/a0/cores/phonon.py | 58 -- a0python/a0/cores/psi/__init__.py | 30 - a0python/a0/cores/psi/tensors/__init__.py | 24 - .../a0/cores/psi/tensors/adapters/__init__.py | 6 - .../psi/tensors/adapters/anthropic_adapter.py | 79 --- .../tensors/adapters/claude_agent_adapter.py | 119 ---- .../psi/tensors/adapters/emergent_adapter.py | 35 - .../tensors/adapters/local_model_adapter.py | 89 --- .../cores/psi/tensors/adapters/subagents.py | 140 ---- .../a0/cores/psi/tensors/context_builder.py | 83 --- a0python/a0/cores/psi/tensors/contract.py | 23 - a0python/a0/cores/psi/tensors/env.py | 93 --- a0python/a0/cores/psi/tensors/logging.py | 22 - .../a0/cores/psi/tensors/model_adapter.py | 16 - a0python/a0/cores/psi/tensors/router.py | 229 ------- .../a0/cores/psi/tensors/tools/__init__.py | 3 - .../a0/cores/psi/tensors/tools/edcm_tool.py | 22 - .../a0/cores/psi/tensors/tools/pdf_tool.py | 29 - .../cores/psi/tensors/tools/whisper_tool.py | 33 - a0python/a0/cores/ptca/__init__.py | 16 - a0python/a0/cores/ptca/seed_router.py | 317 --------- a0python/a0/encryption.py | 83 --- a0python/a0/guardian/__init__.py | 32 - a0python/a0/guardian/approval_gate.py | 80 --- a0python/a0/guardian/audit.py | 31 - a0python/a0/guardian/emitter.py | 48 -- a0python/a0/guardian/recovery.py | 45 -- a0python/a0/guardian/sentinels.py | 138 ---- a0python/a0/guardian/ui/__init__.py | 22 - a0python/a0/guardian/ui/app.py | 167 ----- a0python/a0/guardian/ui/circles.py | 33 - .../a0/guardian/ui/seed_advisory/__init__.py | 8 - a0python/a0/guardian/ui/seed_core/__init__.py | 10 - .../a0/guardian/ui/seed_guardian/__init__.py | 12 - a0python/a0/guardian/ui/seed_jury/__init__.py | 10 - .../a0/guardian/ui/seed_memory/__init__.py | 9 - a0python/a0/guardian/ui/seed_meta/__init__.py | 10 - .../a0/guardian/ui/seed_transport/__init__.py | 8 - a0python/a0/guardian/ui/seeds.py | 186 ----- a0python/a0/guardian/ui/web/__init__.py | 3 - a0python/a0/guardian/ui/web/app.py | 327 --------- a0python/a0/guardian/user_db.py | 265 -------- a0python/a0/heartbeat.py | 79 --- a0python/a0/invariants.py | 29 - a0python/a0/jury.py | 105 --- a0python/a0/lifecycle.py | 423 ------------ a0python/a0/memory.py | 98 --- a0python/a0/meta13.py | 112 ---- a0python/a0/model_registry.py | 367 ---------- a0python/a0/provenance.py | 119 ---- a0python/a0/state.py | 26 - a0python/a0/state/.gitignore | 4 - a0python/a0/state/.gitkeep | 0 a0python/a0/tiers.py | 65 -- a0python/edcm-org/examples/run_demo.sh | 27 - a0python/edcm-org/examples/sample_meeting.txt | 55 -- a0python/edcm-org/examples/sample_tickets.csv | 11 - a0python/edcm-org/pyproject.toml | 47 -- a0python/edcm-org/spec/edcm-org-v0.1.md | 228 ------- a0python/edcm-org/spec/evaluation-protocol.md | 91 --- a0python/edcm-org/spec/governance.md | 93 --- a0python/edcm-org/spec/metric-glossary.md | 75 --- a0python/edcm-org/src/edcm_org/__init__.py | 15 - .../edcm-org/src/edcm_org/basins/__init__.py | 8 - .../edcm-org/src/edcm_org/basins/detect.py | 165 ----- .../edcm-org/src/edcm_org/basins/taxonomy.py | 184 ----- a0python/edcm-org/src/edcm_org/cli.py | 179 ----- .../edcm-org/src/edcm_org/eval/__init__.py | 7 - .../edcm-org/src/edcm_org/eval/protocol.py | 171 ----- a0python/edcm-org/src/edcm_org/glossary.py | 101 --- .../src/edcm_org/governance/__init__.py | 9 - .../src/edcm_org/governance/gaming.py | 84 --- .../src/edcm_org/governance/interventions.py | 130 ---- .../src/edcm_org/governance/privacy.py | 86 --- a0python/edcm-org/src/edcm_org/io/__init__.py | 8 - a0python/edcm-org/src/edcm_org/io/loaders.py | 139 ---- a0python/edcm-org/src/edcm_org/io/schemas.py | 86 --- .../edcm-org/src/edcm_org/metrics/__init__.py | 10 - .../edcm_org/metrics/extraction_helpers.py | 128 ---- .../edcm-org/src/edcm_org/metrics/primary.py | 173 ----- .../edcm-org/src/edcm_org/metrics/progress.py | 103 --- .../src/edcm_org/metrics/secondary.py | 220 ------ .../edcm-org/src/edcm_org/params/__init__.py | 9 - .../edcm-org/src/edcm_org/params/alpha.py | 59 -- .../src/edcm_org/params/complexity.py | 95 --- .../edcm-org/src/edcm_org/params/delta_max.py | 85 --- .../edcm-org/src/edcm_org/spec_version.py | 4 - a0python/edcm-org/src/edcm_org/types.py | 127 ---- a0python/edcm-org/tests/__init__.py | 2 - .../edcm-org/tests/test_basin_detection.py | 98 --- .../edcm-org/tests/test_metrics_ranges.py | 169 ----- .../tests/test_no_individual_outputs.py | 88 --- a0python/edcm-org/tests/test_privacy_guard.py | 87 --- a0python/pyproject.toml | 52 -- a0python/run.sh | 17 - a0python/tests/test_smoke.py | 28 - 122 files changed, 9876 deletions(-) delete mode 100644 a0python/.env.example delete mode 100644 a0python/.gitignore delete mode 100644 a0python/a0/__init__.py delete mode 100644 a0python/a0/a0.py delete mode 100644 a0python/a0/agent.py delete mode 100644 a0python/a0/bandit.py delete mode 100644 a0python/a0/cores/__init__.py delete mode 100644 a0python/a0/cores/_base.py delete mode 100644 a0python/a0/cores/omega/__init__.py delete mode 100644 a0python/a0/cores/omega/tensors/__init__.py delete mode 100644 a0python/a0/cores/omega/tensors/interdependent_way/__init__.py delete mode 100644 a0python/a0/cores/omega/tensors/interdependent_way/architecture.py delete mode 100644 a0python/a0/cores/omega/tensors/interdependent_way/hmmm.py delete mode 100644 a0python/a0/cores/omega/tensors/interdependent_way/laws.py delete mode 100644 a0python/a0/cores/omega/tensors/supporting/__init__.py delete mode 100644 a0python/a0/cores/omega/tensors/supporting/glossary.py delete mode 100644 a0python/a0/cores/omega/tensors/supporting/specs.py delete mode 100644 a0python/a0/cores/pcna/__init__.py delete mode 100644 a0python/a0/cores/pcna/inference.py delete mode 100644 a0python/a0/cores/pcna/omega.py delete mode 100644 a0python/a0/cores/pcna/phi.py delete mode 100644 a0python/a0/cores/pcna/psi.py delete mode 100644 a0python/a0/cores/pcna/zfae.py delete mode 100644 a0python/a0/cores/pcta/__init__.py delete mode 100644 a0python/a0/cores/pcta/circle_tensors.py delete mode 100644 a0python/a0/cores/phi/__init__.py delete mode 100644 a0python/a0/cores/phonon.py delete mode 100644 a0python/a0/cores/psi/__init__.py delete mode 100644 a0python/a0/cores/psi/tensors/__init__.py delete mode 100644 a0python/a0/cores/psi/tensors/adapters/__init__.py delete mode 100644 a0python/a0/cores/psi/tensors/adapters/anthropic_adapter.py delete mode 100644 a0python/a0/cores/psi/tensors/adapters/claude_agent_adapter.py delete mode 100644 a0python/a0/cores/psi/tensors/adapters/emergent_adapter.py delete mode 100644 a0python/a0/cores/psi/tensors/adapters/local_model_adapter.py delete mode 100644 a0python/a0/cores/psi/tensors/adapters/subagents.py delete mode 100644 a0python/a0/cores/psi/tensors/context_builder.py delete mode 100644 a0python/a0/cores/psi/tensors/contract.py delete mode 100644 a0python/a0/cores/psi/tensors/env.py delete mode 100644 a0python/a0/cores/psi/tensors/logging.py delete mode 100644 a0python/a0/cores/psi/tensors/model_adapter.py delete mode 100644 a0python/a0/cores/psi/tensors/router.py delete mode 100644 a0python/a0/cores/psi/tensors/tools/__init__.py delete mode 100644 a0python/a0/cores/psi/tensors/tools/edcm_tool.py delete mode 100644 a0python/a0/cores/psi/tensors/tools/pdf_tool.py delete mode 100644 a0python/a0/cores/psi/tensors/tools/whisper_tool.py delete mode 100644 a0python/a0/cores/ptca/__init__.py delete mode 100644 a0python/a0/cores/ptca/seed_router.py delete mode 100644 a0python/a0/encryption.py delete mode 100644 a0python/a0/guardian/__init__.py delete mode 100644 a0python/a0/guardian/approval_gate.py delete mode 100644 a0python/a0/guardian/audit.py delete mode 100644 a0python/a0/guardian/emitter.py delete mode 100644 a0python/a0/guardian/recovery.py delete mode 100644 a0python/a0/guardian/sentinels.py delete mode 100644 a0python/a0/guardian/ui/__init__.py delete mode 100644 a0python/a0/guardian/ui/app.py delete mode 100644 a0python/a0/guardian/ui/circles.py delete mode 100644 a0python/a0/guardian/ui/seed_advisory/__init__.py delete mode 100644 a0python/a0/guardian/ui/seed_core/__init__.py delete mode 100644 a0python/a0/guardian/ui/seed_guardian/__init__.py delete mode 100644 a0python/a0/guardian/ui/seed_jury/__init__.py delete mode 100644 a0python/a0/guardian/ui/seed_memory/__init__.py delete mode 100644 a0python/a0/guardian/ui/seed_meta/__init__.py delete mode 100644 a0python/a0/guardian/ui/seed_transport/__init__.py delete mode 100644 a0python/a0/guardian/ui/seeds.py delete mode 100644 a0python/a0/guardian/ui/web/__init__.py delete mode 100644 a0python/a0/guardian/ui/web/app.py delete mode 100644 a0python/a0/guardian/user_db.py delete mode 100644 a0python/a0/heartbeat.py delete mode 100644 a0python/a0/invariants.py delete mode 100644 a0python/a0/jury.py delete mode 100644 a0python/a0/lifecycle.py delete mode 100644 a0python/a0/memory.py delete mode 100644 a0python/a0/meta13.py delete mode 100644 a0python/a0/model_registry.py delete mode 100644 a0python/a0/provenance.py delete mode 100644 a0python/a0/state.py delete mode 100644 a0python/a0/state/.gitignore delete mode 100644 a0python/a0/state/.gitkeep delete mode 100644 a0python/a0/tiers.py delete mode 100755 a0python/edcm-org/examples/run_demo.sh delete mode 100644 a0python/edcm-org/examples/sample_meeting.txt delete mode 100644 a0python/edcm-org/examples/sample_tickets.csv delete mode 100644 a0python/edcm-org/pyproject.toml delete mode 100644 a0python/edcm-org/spec/edcm-org-v0.1.md delete mode 100644 a0python/edcm-org/spec/evaluation-protocol.md delete mode 100644 a0python/edcm-org/spec/governance.md delete mode 100644 a0python/edcm-org/spec/metric-glossary.md delete mode 100644 a0python/edcm-org/src/edcm_org/__init__.py delete mode 100644 a0python/edcm-org/src/edcm_org/basins/__init__.py delete mode 100644 a0python/edcm-org/src/edcm_org/basins/detect.py delete mode 100644 a0python/edcm-org/src/edcm_org/basins/taxonomy.py delete mode 100644 a0python/edcm-org/src/edcm_org/cli.py delete mode 100644 a0python/edcm-org/src/edcm_org/eval/__init__.py delete mode 100644 a0python/edcm-org/src/edcm_org/eval/protocol.py delete mode 100644 a0python/edcm-org/src/edcm_org/glossary.py delete mode 100644 a0python/edcm-org/src/edcm_org/governance/__init__.py delete mode 100644 a0python/edcm-org/src/edcm_org/governance/gaming.py delete mode 100644 a0python/edcm-org/src/edcm_org/governance/interventions.py delete mode 100644 a0python/edcm-org/src/edcm_org/governance/privacy.py delete mode 100644 a0python/edcm-org/src/edcm_org/io/__init__.py delete mode 100644 a0python/edcm-org/src/edcm_org/io/loaders.py delete mode 100644 a0python/edcm-org/src/edcm_org/io/schemas.py delete mode 100644 a0python/edcm-org/src/edcm_org/metrics/__init__.py delete mode 100644 a0python/edcm-org/src/edcm_org/metrics/extraction_helpers.py delete mode 100644 a0python/edcm-org/src/edcm_org/metrics/primary.py delete mode 100644 a0python/edcm-org/src/edcm_org/metrics/progress.py delete mode 100644 a0python/edcm-org/src/edcm_org/metrics/secondary.py delete mode 100644 a0python/edcm-org/src/edcm_org/params/__init__.py delete mode 100644 a0python/edcm-org/src/edcm_org/params/alpha.py delete mode 100644 a0python/edcm-org/src/edcm_org/params/complexity.py delete mode 100644 a0python/edcm-org/src/edcm_org/params/delta_max.py delete mode 100644 a0python/edcm-org/src/edcm_org/spec_version.py delete mode 100644 a0python/edcm-org/src/edcm_org/types.py delete mode 100644 a0python/edcm-org/tests/__init__.py delete mode 100644 a0python/edcm-org/tests/test_basin_detection.py delete mode 100644 a0python/edcm-org/tests/test_metrics_ranges.py delete mode 100644 a0python/edcm-org/tests/test_no_individual_outputs.py delete mode 100644 a0python/edcm-org/tests/test_privacy_guard.py delete mode 100644 a0python/pyproject.toml delete mode 100644 a0python/run.sh delete mode 100644 a0python/tests/test_smoke.py diff --git a/a0python/.env.example b/a0python/.env.example deleted file mode 100644 index 2c048e771..000000000 --- a/a0python/.env.example +++ /dev/null @@ -1,56 +0,0 @@ -# a0 runtime configuration -# Copy this file to .env and fill in values. -# .env is gitignored — never commit API keys. - -# Which model adapter to use: -# local-echo no API key needed, echoes input (default) -# local-ollama local ollama daemon — install ollama, then: ollama pull llama3.2 -# local-llama embedded llama-cpp-python — needs a .gguf model file -# anthropic-api direct Anthropic Messages API (requires ANTHROPIC_API_KEY) -# claude-agent full PTCA subagent pipeline (requires claude-agent-sdk) -# emergent Emergent Labs universal key — see adapters/emergent_adapter.py to configure -A0_MODEL=local-echo - -# --- local-ollama settings --- -# Model name as shown by `ollama list` -A0_LOCAL_MODEL=llama3.2 -# Override if ollama is running on a different host/port -A0_OLLAMA_BASE=http://localhost:11434 - -# --- local-llama settings (llama-cpp-python) --- -# Absolute path to a GGUF model file (download from HuggingFace) -# pip install llama-cpp-python -A0_MODEL_PATH= - -# --- external API settings --- -# Anthropic API key — required when A0_MODEL=anthropic-api -# Get yours at https://console.anthropic.com/ -ANTHROPIC_API_KEY= - -# Emergent Labs — required when A0_MODEL=emergent (fill in once you have the endpoint) -EMERGENT_API_KEY= -EMERGENT_API_BASE= - -# Internal encryption key for memory.json and event logs (Fernet / AES-128-CBC + HMAC-SHA256) -# Generate once: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" -# Store in Replit Secrets (preferred) or here. Never commit the key. -# Leave blank to run without encryption (plaintext fallback). -A0_MEMORY_KEY= - -# Gradio web server binding -# Set A0_HOST=0.0.0.0 to expose on all interfaces (needed for GCP access) -# Set A0_HOST=127.0.0.1 to restrict to localhost only -A0_PORT=7860 -A0_HOST=0.0.0.0 - -# --- training (Path B: native PCNA) --- -# A0_RUNTIME = "inference" (default) | "training" -# In training mode the router writes adapter outputs as training examples -# for a0's own weight updates (outside models act as trainer). -A0_RUNTIME=inference - -# Which external model acts as trainer (e.g. claude-opus-4-6) -A0_TRAINER_MODEL= - -# Where training data / checkpoints are written (absolute path) -A0_TRAINING_DIR= diff --git a/a0python/.gitignore b/a0python/.gitignore deleted file mode 100644 index 5bc8fdb99..000000000 --- a/a0python/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -.env -__pycache__/ -*.pyc -*.pyo -*.egg-info/ -dist/ -build/ -.eggs/ -*.egg -.pytest_cache/ -.coverage -*.jsonl -a0/state/a0_state.json -a0/state/memory.json -a0/instance.json diff --git a/a0python/a0/__init__.py b/a0python/a0/__init__.py deleted file mode 100644 index abc9e0eec..000000000 --- a/a0python/a0/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# 0:1 0:0 0:0 -# a0 package -# 0:1 0:0 0:0 diff --git a/a0python/a0/a0.py b/a0python/a0/a0.py deleted file mode 100644 index 8d6a4d00f..000000000 --- a/a0python/a0/a0.py +++ /dev/null @@ -1,42 +0,0 @@ -# 21:8 0:0 0:3 -"""a0 CLI entry point — Guardian-owned. - -Thin shell that imports from psi tensors and emits through Guardian. - -Import paths: - a0.cores.psi.tensors.contract — A0Request - a0.cores.psi.tensors.router — handle() - a0.guardian.emitter — emit() - -Law 9: Guardian alone owns human-readable outward emission. -""" -from __future__ import annotations - -import sys -import json -from uuid import uuid4 - -from .cores.psi.tensors.contract import A0Request -from .cores.psi.tensors.router import handle -from .guardian.emitter import emit - - -def main() -> None: - raw = open(sys.argv[1], "r", encoding="utf-8").read() if len(sys.argv) > 1 else sys.stdin.read() - data = json.loads(raw) if raw.strip() else {} - - req = A0Request( - task_id=data.get("task_id") or f"task_{uuid4().hex[:12]}", - input=data.get("input") or {"text": "", "files": [], "metadata": {}}, - tools_allowed=data.get("tools_allowed") or ["none"], - mode=data.get("mode") or "analyze", - hmmm=data.get("hmmm") or data.get("hmm") or [], - ) - - resp = handle(req) - emit(resp) - - -if __name__ == "__main__": - main() -# 21:8 0:0 0:3 diff --git a/a0python/a0/agent.py b/a0python/a0/agent.py deleted file mode 100644 index 11ca1e282..000000000 --- a/a0python/a0/agent.py +++ /dev/null @@ -1,78 +0,0 @@ -# 43:20 0:0 0:0 -"""agent — AgentZero, the single importable entry point for a0. - -Usage:: - - from a0.agent import AgentZero - - az = AgentZero() - resp = az.run("what is the hmmm invariant?") - print(resp.result["text"]) - -Model selection is driven by the .env tensor (A0_MODEL). -See a0/cores/psi/tensors/env.py for configuration. -""" -from __future__ import annotations - -import uuid -from pathlib import Path -from typing import List, Optional - -from a0.cores.psi.tensors.contract import A0Request, A0Response, Mode -from a0.cores.psi.tensors.router import handle - - -class AgentZero: - """The a0 agent — routes requests through the PTCA pipeline. - - Adapter (model) is selected at call time from the env tensor, - so changing A0_MODEL in settings takes effect immediately. - - Args: - home: Optional path to an isolated instance directory. - Logs and state are written there instead of the - package defaults. Used by lifecycle operations. - instance_id: Optional stable identity for this instance. - Assigned automatically if not provided. - """ - - def __init__( - self, - home: Optional[Path] = None, - instance_id: Optional[str] = None, - ) -> None: - self.home = home - self.instance_id = instance_id or str(uuid.uuid4()) - - def run( - self, - text: str, - mode: Mode = "analyze", - tools: Optional[List[str]] = None, - hmmm: Optional[List[str]] = None, - history: Optional[List[dict]] = None, - ) -> A0Response: - req = A0Request( - task_id=str(uuid.uuid4()), - input={"text": text, "files": []}, - tools_allowed=tools or ["none"], - mode=mode, - hmmm=hmmm or [], - history=history or [], - ) - return handle(req, home=self.home) - - async def run_async( - self, - text: str, - mode: Mode = "analyze", - tools: Optional[List[str]] = None, - hmmm: Optional[List[str]] = None, - history: Optional[List[dict]] = None, - ) -> A0Response: - """Non-blocking variant for async contexts (Gradio, Textual).""" - import anyio - return await anyio.to_thread.run_sync( - lambda: self.run(text, mode=mode, tools=tools, hmmm=hmmm, history=history), - ) -# 43:20 0:0 0:0 diff --git a/a0python/a0/bandit.py b/a0python/a0/bandit.py deleted file mode 100644 index 5c6212238..000000000 --- a/a0python/a0/bandit.py +++ /dev/null @@ -1,77 +0,0 @@ -# 52:7 0:0 0:0 -"""Bandit — bounded advisory salience machinery. - -Bandits do not choose. Meta-13 chooses. - -Law 13: Meta-13 chooses; advisory layers may influence salience but do not decide. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional - - -@dataclass -class SalienceScore: - """Advisory salience weight for a candidate. Not a final selection.""" - candidate_index: int - weight: float - reason: Optional[str] = None - - -@dataclass -class BanditAdvice: - """The output of bandit logic — advisory only.""" - scores: List[SalienceScore] - reordered_candidates: List[Any] - exploration_bias: float = 0.0 - - -class BanditAdvisor: - """Bounded advisory salience machinery.""" - - def __init__(self, exploration_rate: float = 0.1) -> None: - if not 0.0 <= exploration_rate <= 1.0: - raise ValueError("exploration_rate must be in [0.0, 1.0]") - self._exploration_rate = exploration_rate - self._probe_counts: Dict[int, int] = {} - self._reward_sums: Dict[int, float] = {} - - def advise( - self, - candidates: List[Any], - context: Optional[Dict[str, Any]] = None, - ) -> BanditAdvice: - if not candidates: - return BanditAdvice(scores=[], reordered_candidates=[]) - - scores = [] - for i, _ in enumerate(candidates): - weight = self._ucb_weight(i, len(candidates)) - scores.append(SalienceScore(candidate_index=i, weight=weight)) - - sorted_scores = sorted(scores, key=lambda s: s.weight, reverse=True) - reordered = [candidates[s.candidate_index] for s in sorted_scores] - - return BanditAdvice( - scores=sorted_scores, - reordered_candidates=reordered, - exploration_bias=self._exploration_rate, - ) - - def record_outcome(self, candidate_index: int, reward: float) -> None: - self._probe_counts[candidate_index] = self._probe_counts.get(candidate_index, 0) + 1 - self._reward_sums[candidate_index] = ( - self._reward_sums.get(candidate_index, 0.0) + reward - ) - - def _ucb_weight(self, index: int, total_candidates: int) -> float: - import math - count = self._probe_counts.get(index, 0) - if count == 0: - return float("inf") - mean_reward = self._reward_sums.get(index, 0.0) / count - total_probes = sum(self._probe_counts.values()) or 1 - exploration = math.sqrt(2 * math.log(total_probes) / count) - return mean_reward + self._exploration_rate * exploration -# 52:7 0:0 0:0 diff --git a/a0python/a0/cores/__init__.py b/a0python/a0/cores/__init__.py deleted file mode 100644 index 0386cc0f5..000000000 --- a/a0python/a0/cores/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# 5:12 0:0 0:4 -"""Private cognitive cores — Phi, Psi, Omega — and Phonon transport. - -They think. They do not emit outward directly. - -Law 1: Private process is not public output. -Law 7: Health sensing does not require content access. - -Structure: - phi/ — structural/analytic cognition - psi/ — semantic/contextual cognition - tensors/ — a0 build logic lives here (Psi's domain) - omega/ — synthesis/integration - tensors/ — the interdependent way + supporting material - phonon.py — internal transport field -""" -from .phi import Phi -from .psi import Psi -from .omega import Omega -from .phonon import Phonon - -__all__ = ["Phi", "Psi", "Omega", "Phonon"] -# 5:12 0:0 0:4 diff --git a/a0python/a0/cores/_base.py b/a0python/a0/cores/_base.py deleted file mode 100644 index c24dd79d6..000000000 --- a/a0python/a0/cores/_base.py +++ /dev/null @@ -1,52 +0,0 @@ -# 28:10 0:0 6:0 -"""Base class for private cognitive cores. - -Law 1: Private process is not public output. -Law 7: Health sensing does not require content access. -""" -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any, Optional - - -@dataclass -class CoreHealthSignal: - """Structural health information only — no content. - - Law 7: Health sensing does not require content access. - """ - core_name: str - cycle_count: int - is_active: bool - structural_variance: float - - -class PrivateCore: - """Base for private cognitive cores.""" - - name: str = "base" - - def __init__(self) -> None: - self._cycle_count = 0 - self._last_result: Optional[Any] = None - - def think(self, stimulus: Any) -> Any: - """Process stimulus privately. Result is internal only.""" - self._cycle_count += 1 - result = self._process(stimulus) - self._last_result = result - return result - - def _process(self, stimulus: Any) -> Any: - raise NotImplementedError - - def health(self) -> CoreHealthSignal: - """Return structural health signal — no content exposed.""" - return CoreHealthSignal( - core_name=self.name, - cycle_count=self._cycle_count, - is_active=True, - structural_variance=0.0, - ) -# 28:10 0:0 6:0 diff --git a/a0python/a0/cores/omega/__init__.py b/a0python/a0/cores/omega/__init__.py deleted file mode 100644 index bab8344a7..000000000 --- a/a0python/a0/cores/omega/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# 11:10 0:0 0:2 -"""Omega — private synthesis and integration cognitive core. - -Omega thinks. Omega does not emit outward directly. - -Omega's domain of concern: synthesis, integration, coherence — -combining Phi and Psi outputs into unified internal state for Meta-13. - -Omega tensors hold: -- the interdependent way: the architectural framework, design philosophy, - relational model, and core laws that govern the whole system -- supporting material: specs, glossary, principles, examples -""" -from __future__ import annotations - -from typing import Any - -from .._base import PrivateCore - - -class Omega(PrivateCore): - """Tertiary private cognitive core — synthesis and integration.""" - - name = "omega" - - def _process(self, stimulus: Any) -> Any: - from ..pcna.omega import OmegaTensor - text = stimulus if isinstance(stimulus, str) else str(stimulus) - result = OmegaTensor().process(text) - result["core"] = self.name - return result -# 11:10 0:0 0:2 diff --git a/a0python/a0/cores/omega/tensors/__init__.py b/a0python/a0/cores/omega/tensors/__init__.py deleted file mode 100644 index 34f01de65..000000000 --- a/a0python/a0/cores/omega/tensors/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# 18:6 0:0 0:3 -"""Omega tensors — the interdependent way and supporting material. - - interdependent_way/ — architectural framework, design philosophy, - relational model, and 14 core laws - supporting/ — specs, glossary, principles, examples -""" -from .interdependent_way.architecture import ARCHITECTURAL_CENTER, FROZEN_CORE_STATEMENT -from .interdependent_way.laws import CORE_LAWS, TIER_LAW, BANDIT_INFLUENCE_LAW -from .interdependent_way.hmmm import HMMM_INVARIANT - -__all__ = [ - "ARCHITECTURAL_CENTER", - "FROZEN_CORE_STATEMENT", - "CORE_LAWS", - "TIER_LAW", - "BANDIT_INFLUENCE_LAW", - "HMMM_INVARIANT", -] - -# The primary export name used in verification -THE_INTERDEPENDENT_WAY = { - "architecture": ARCHITECTURAL_CENTER, - "laws": CORE_LAWS, - "tier_law": TIER_LAW, - "bandit_influence_law": BANDIT_INFLUENCE_LAW, - "hmmm_invariant": HMMM_INVARIANT, -} -# 18:6 0:0 0:3 diff --git a/a0python/a0/cores/omega/tensors/interdependent_way/__init__.py b/a0python/a0/cores/omega/tensors/interdependent_way/__init__.py deleted file mode 100644 index af76cd128..000000000 --- a/a0python/a0/cores/omega/tensors/interdependent_way/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# 8:1 0:0 0:3 -"""The interdependent way — architectural framework and core laws.""" -from .architecture import ARCHITECTURAL_CENTER, FROZEN_CORE_STATEMENT -from .laws import CORE_LAWS, TIER_LAW, BANDIT_INFLUENCE_LAW -from .hmmm import HMMM_INVARIANT - -__all__ = [ - "ARCHITECTURAL_CENTER", "FROZEN_CORE_STATEMENT", - "CORE_LAWS", "TIER_LAW", "BANDIT_INFLUENCE_LAW", - "HMMM_INVARIANT", -] -# 8:1 0:0 0:3 diff --git a/a0python/a0/cores/omega/tensors/interdependent_way/architecture.py b/a0python/a0/cores/omega/tensors/interdependent_way/architecture.py deleted file mode 100644 index a06f78f2a..000000000 --- a/a0python/a0/cores/omega/tensors/interdependent_way/architecture.py +++ /dev/null @@ -1,79 +0,0 @@ -# 71:3 0:0 3:0 -"""Architectural center — what the system IS. - -Source: PTCA/PCTA/PCNA/Jury/Guardian Thread-Integrated Core Compression v1.3.2 -""" - -ARCHITECTURAL_CENTER = { - "layers": [ - {"name": "phi", "count": 1, "kind": "private_live_core", "role": "cognition"}, - {"name": "psi", "count": 1, "kind": "private_live_core", "role": "cognition"}, - {"name": "omega", "count": 1, "kind": "private_live_core", "role": "cognition"}, - {"name": "phonon", "count": 1, "kind": "private_transport_field", "role": "internal_resonance"}, - {"name": "jury", "count": 1, "kind": "adjudication_layer", "role": "legality_conflict_continuity"}, - {"name": "memory", "count": 1, "kind": "memory_layer", "role": "committed_continuity"}, - {"name": "meta_13", "count": 1, "kind": "executive_integration", "role": "final_internal_choice"}, - {"name": "guardian", "count": 1, "kind": "microkernel_shell", "role": "constitutive_operating_boundary"}, - ], - "note": ( - "Guardian is not an accessory wrapper. " - "Guardian is the operating boundary of the whole agent." - ), -} - -FROZEN_CORE_STATEMENT = { - "private_cognition": { - "cores": ["phi", "psi", "omega"], - "law": "They think. They do not emit outward directly.", - }, - "transport": { - "name": "phonon", - "carries": ["adjacency", "phase", "spin", "transient_internal_coupling"], - "is_not": ["display", "audit_content", "public_output"], - "health_sensing": "structural_variance_only", - }, - "adjudication": { - "name": "jury", - "mediates": "continuity_bearing_persistence", - "preserves": "unresolved_conflict_as_conflict", - "prevents": "silent_promotion_from_volatile_to_committed", - "establishes": "operative_standards_where_definitions_absent_or_contested", - }, - "continuity": { - "name": "memory", - "stores": [ - "persistent_tokens", - "compressed_recall", - "identity_bearing_continuity", - "committed_support_state", - ], - "is_not": "raw_history", - "logs_are_not_memory": True, - }, - "executive_choice": { - "name": "meta_13", - "receives": { - "fast_path": "raw_witness_from_12_raw_jury_sentinels", - "slow_path": "coherent_stances_from_meta_phi_meta_psi_meta_omega", - }, - "resolves_to": "final_internal_executive_I_state", - "bandits_do_not_choose": True, - }, - "guardian": { - "is": "complete_microkernel_operating_shell", - "owns": [ - "cli", - "ui", - "os_integration", - "outward_status_warnings_errors", - "runtime_logs_guardian_domain", - "recovery_shell", - "quarantine_shell", - "enforcement_shell", - "audit_boundary_outbound_and_event_backed", - ], - "is_sole": "human_readable_emitter", - "no_user_facing_shell_outside_guardian": True, - }, -} -# 71:3 0:0 3:0 diff --git a/a0python/a0/cores/omega/tensors/interdependent_way/hmmm.py b/a0python/a0/cores/omega/tensors/interdependent_way/hmmm.py deleted file mode 100644 index 5b59e03c9..000000000 --- a/a0python/a0/cores/omega/tensors/interdependent_way/hmmm.py +++ /dev/null @@ -1,23 +0,0 @@ -# 16:3 0:0 3:0 -"""hmmm — the hard invariant. - -Source: PTCA/PCTA/PCNA/Jury/Guardian Thread-Integrated Core Compression v1.3.2 -""" - -HMMM_INVARIANT = { - "minimum_law": [ - "present even when empty", - "never silently omitted", - "functions as unresolved_constraint / review / exception register", - ], - "fail_closed_law": [ - "absence of hmmm is invalid state", - "invalid state blocks event commit", - "invalid state blocks outbound emission", - ], - "enforcement_boundary": [ - "event_write_enforcement at Guardian audit / provenance boundary", - "output_enforcement at Guardian display / emission boundary", - ], -} -# 16:3 0:0 3:0 diff --git a/a0python/a0/cores/omega/tensors/interdependent_way/laws.py b/a0python/a0/cores/omega/tensors/interdependent_way/laws.py deleted file mode 100644 index ae544b50f..000000000 --- a/a0python/a0/cores/omega/tensors/interdependent_way/laws.py +++ /dev/null @@ -1,70 +0,0 @@ -# 61:3 0:0 3:0 -"""Core laws, tier law, and bandit influence law. - -Source: PTCA/PCTA/PCNA/Jury/Guardian Thread-Integrated Core Compression v1.3.2 -""" - -CORE_LAWS = [ - (1, "Private process is not public output."), - (2, "Transport is not display."), - (3, "Volatile state is not committed continuity."), - (4, "Persistence requires adjudication."), - (5, "Conflict must remain visible when unresolved."), - (6, "Containment is preferred to collapse."), - (7, "Health sensing does not require content access."), - (8, "Capability does not equal authority."), - (9, "Guardian alone owns human-readable outward emission."), - (10, "Guardian alone owns CLI, UI, OS integration, and outward operational presentation."), - (11, "Logs belong to event history, not continuity itself."), - (12, "External execution requires approval beyond rendering capability."), - (13, "Meta-13 chooses; advisory layers may influence salience but do not decide."), - (14, "Missing required invariants fail closed."), -] - -TIER_LAW = { - "tier_1": { - "name": "volatile", - "path": "core <-> phonon", - "properties": ["transient", "scratch", "cycle_local", "non_authoritative"], - "requires_jury_mediation": False, - "may_silently_become_tier_2": False, - "carries_persistence_authority": False, - }, - "tier_2": { - "name": "commit", - "path": "core -> jury -> memory", - "properties": [ - "continuity_bearing", - "persistent", - "identity_relevant", - "explicitly_committed", - ], - "writes_require_jury_mediation": True, - "may_be_unilateral_by_core": False, - "may_arise_from_silent_tier_1_promotion": False, - }, -} - -BANDIT_INFLUENCE_LAW = { - "bandits_do_not_choose": True, - "meta_13_chooses": True, - "bandit_may": [ - "modulate_exploration", - "bias_salience", - "weight_candidates", - "reorder_candidates", - "influence_probe_emphasis", - "allocate_bounded_attention_under_uncertainty", - ], - "bandit_may_not": [ - "determine_truth", - "make_final_selections", - "authorize_tier_2_persistence", - "override_jury", - "override_meta_13", - "override_guardian_sentinel_law", - "erase_contested_state", - ], - "summary": "Bandits bias attention upstream. Meta-13 decides.", -} -# 61:3 0:0 3:0 diff --git a/a0python/a0/cores/omega/tensors/supporting/__init__.py b/a0python/a0/cores/omega/tensors/supporting/__init__.py deleted file mode 100644 index 870b0633b..000000000 --- a/a0python/a0/cores/omega/tensors/supporting/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# 3:1 0:0 0:2 -"""Supporting material — specs, glossary, principles.""" -from .specs import SPECS -from .glossary import GLOSSARY - -__all__ = ["SPECS", "GLOSSARY"] -# 3:1 0:0 0:2 diff --git a/a0python/a0/cores/omega/tensors/supporting/glossary.py b/a0python/a0/cores/omega/tensors/supporting/glossary.py deleted file mode 100644 index 4f4ba4bf8..000000000 --- a/a0python/a0/cores/omega/tensors/supporting/glossary.py +++ /dev/null @@ -1,60 +0,0 @@ -# 54:3 0:0 2:0 -"""Glossary — canonical term definitions across the system.""" - -GLOSSARY = { - # PTCA terms - "hmmm": ( - "Unresolved-constraint / review / exception register. " - "Hard invariant — must be present on every event and response. " - "Absence is invalid state." - ), - "tier_1": ( - "Volatile. Transient, scratch, cycle-local. " - "No persistence authority. Core ↔ Phonon only." - ), - "tier_2": ( - "Committed continuity. Persistent, identity-relevant. " - "Requires Jury mediation. Core → Jury → Memory." - ), - "jury_token": ( - "A credential issued by Jury after successful adjudication. " - "Required for any Tier 2 write to Memory." - ), - "phonon": ( - "Internal transport-only resonance field. " - "Carries adjacency, phase, spin. Not display. Not audit content." - ), - "guardian": ( - "The complete microkernel operating shell. " - "Sole outward human-readable emitter. " - "Owns CLI, UI, OS integration, audit boundary, recovery, quarantine." - ), - "meta_13": ( - "The executive chooser. Receives fast-path (12 sentinel witnesses) " - "and slow-path (Meta-Phi, Meta-Psi, Meta-Omega stances). " - "Produces the final internal executive 'I' state. " - "Bandits do not choose. Meta-13 chooses." - ), - "bandit": ( - "Bounded advisory salience machinery. " - "May modulate exploration and bias candidates. " - "May not make final selections or authorize Tier 2 persistence." - ), - "provenance": ( - "Hash-chain event history. events.jsonl is event truth after seal. " - "provenance.json carries hash-chain / version material." - ), - # EDCM terms - "dissonance": ( - "Unresolved constraint mismatch. Not a feeling. " - "Observable in behavioral outputs, not inferred from internal states." - ), - "constraint_strain": ( - "C metric [0,1]. Weighted contradiction density across signal types." - ), - "basin": ( - "A stable attractor configuration in EDCM state space. " - "A diagnostic label, not a judgment." - ), -} -# 54:3 0:0 2:0 diff --git a/a0python/a0/cores/omega/tensors/supporting/specs.py b/a0python/a0/cores/omega/tensors/supporting/specs.py deleted file mode 100644 index b0a9bb3ac..000000000 --- a/a0python/a0/cores/omega/tensors/supporting/specs.py +++ /dev/null @@ -1,38 +0,0 @@ -# 34:1 0:0 2:0 -"""Spec catalog — canonical reference specifications.""" - -SPECS = { - "ptca": { - "name": "PTCA/PCTA/PCNA/Jury/Guardian Thread-Integrated Core Compression", - "version": "1.3.2", - "scope": "core_architecture", - "author": "Erin Spencer + AI council context", - "layers": [ - "phi", "psi", "omega", "phonon", - "jury", "memory", "meta_13", "guardian", - ], - }, - "edcm": { - "name": "Energy-Dissonance Circuit Model", - "version": "edcm-org-v0.1.0", - "scope": "organizational_diagnostics", - "metrics": ["C", "R", "F", "E", "D", "N", "I", "O", "L", "P"], - "basins": [ - "REFUSAL_FIXATION", - "DISSIPATIVE_NOISE", - "INTEGRATION_OSCILLATION", - "CONFIDENCE_RUNAWAY", - "DEFLECTIVE_STASIS", - "COMPLIANCE_STASIS", - "SCAPEGOAT_DISCHARGE", - "UNCLASSIFIED", - ], - }, - "a0": { - "name": "a0 Routing and Adapter Framework", - "version": "0.1.0", - "scope": "semantic_routing_layer", - "resides_in": "psi_tensors", - }, -} -# 34:1 0:0 2:0 diff --git a/a0python/a0/cores/pcna/__init__.py b/a0python/a0/cores/pcna/__init__.py deleted file mode 100644 index 1864781fa..000000000 --- a/a0python/a0/cores/pcna/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# 4:8 0:0 0:3 -"""PCNA — Prime Circular Neural Architecture. - -The inference engine layer of a0. phi, psi, omega are distinct tensor -fields operating in circular / phase coordinates (unit-circle eigenbasis). - -Layer hierarchy: - PCNA (this package) — phi, psi, omega, guardian, memory tensor fields - PCTA (cores/pcta/) — circle tensor layer (phase-coordinate transform) - PTCA (cores/ptca/) — seed tensor routing lattice (53-node graph) -""" -from .phi import PhiTensor -from .psi import PsiTensor -from .omega import OmegaTensor - -__all__ = ["PhiTensor", "PsiTensor", "OmegaTensor"] -# 4:8 0:0 0:3 diff --git a/a0python/a0/cores/pcna/inference.py b/a0python/a0/cores/pcna/inference.py deleted file mode 100644 index 9666ea78f..000000000 --- a/a0python/a0/cores/pcna/inference.py +++ /dev/null @@ -1,248 +0,0 @@ -# 123:62 0:0 16:0 -"""PCNA inference engine. - -Wraps a backend model and exposes phi/psi/omega tensor slices. - -Path A (adapted — works today): - PatternMatchBackend no model, lexical proxy — always available - LlamaCppBackend GGUF model via llama-cpp-python (set A0_MODEL_PATH) - -Path B (native — ZFAE v2): - ZFAEBackend Zeta-structured, Field-partitioned, Alpha-regulated, - Echo-state engine v2. Set A0_MODEL=zfae to activate. - Four independent 53-node PTCA reservoirs (phi, psi, omega, - synthesis); only the synthesis readout W_out is trained. - Per-field alphas are sourced from ModelConfig when provided. - See a0/cores/pcna/zfae.py for architecture details. - -In Path A the tensor "slices" are proxies: - phi — structural features of the input (no model call needed) - psi — semantic/lexical features of the input (no model call needed) - omega — generated text + response-structure features (model call) - -This matches the conceptual layer order in a real transformer: - phi ≈ tokenizer + early attention (syntactic structure) - psi ≈ middle layers (semantic context) - omega ≈ late layers + output head (synthesis/generation) -""" -from __future__ import annotations - -import math -import os -import re -from pathlib import Path -from typing import Any, Dict, List, Optional - - -class _TensorSlices: - """Raw tensor values before phase-coordinate transform.""" - - def __init__( - self, - phi_raw: List[float], - psi_raw: List[float], - omega_raw: List[float], - text: str, - backend_name: str, - ) -> None: - self.phi_raw = phi_raw - self.psi_raw = psi_raw - self.omega_raw = omega_raw - self.text = text - self.backend_name = backend_name - - -def _pad(values: List[float], length: int = 3) -> List[float]: - return (values + [0.0] * length)[:length] - - -def _phi_features(text: str) -> List[float]: - """Structural analysis of input — phi domain proxy. - - Captures: constraint tension, negation density, conditional branching. - These are the natural structural signals phi would process. - """ - t = text.lower() - words = t.split() - n = max(len(words), 1) - - negation_density = len(re.findall(r"\bnot\b|\bno\b|\bnever\b|\bcannot\b|\bwon't\b|\bcan't\b", t)) / n - conditional_density = len(re.findall(r"\bif\b|\bthen\b|\bbut\b|\bhowever\b|\bunless\b", t)) / n - contradiction_signal = float( - bool(re.search(r"\bnot\b", t)) and bool(re.search(r"\btrue\b|\bcorrect\b|\byes\b", t)) - ) - - return _pad([negation_density, conditional_density, contradiction_signal]) - - -def _psi_features(text: str) -> List[float]: - """Semantic analysis of input — psi domain proxy. - - Captures: lexical diversity, question orientation, semantic density. - These are the natural semantic signals psi would process. - """ - words = text.lower().split() - n = max(len(words), 1) - - lexical_diversity = len(set(words)) / n - question_signal = float("?" in text) - semantic_density = min(n / 50.0, 1.0) # saturates at 50 words - - return _pad([lexical_diversity, question_signal, semantic_density]) - - -def _omega_features(text: str) -> List[float]: - """Synthesis features from model output — omega domain proxy. - - Captures: response coherence, length signal, resolution signal. - """ - sentences = [s.strip() for s in re.split(r"[.!?]+", text) if s.strip()] - n_sentences = len(sentences) - - coherence = 1.0 / (1.0 + abs(n_sentences - 3)) # 3-sentence responses are coherent - length_signal = min(len(text) / 500.0, 1.0) - resolution_signal = float( - bool(re.search(r"\btherefore\b|\bthus\b|\bso\b|\bin conclusion\b|\boverall\b", text.lower())) - ) - - return _pad([coherence, length_signal, resolution_signal]) - - -class PatternMatchBackend: - """Always-available backend. Uses lexical patterns as tensor proxies. - - No model required. phi and psi are computed from input structure; - omega is empty text (no generation) with synthesis features from input. - """ - - name = "pattern-match" - - def generate(self, prompt: str, context: List[Dict[str, Any]]) -> _TensorSlices: - return _TensorSlices( - phi_raw=_phi_features(prompt), - psi_raw=_psi_features(prompt), - omega_raw=_omega_features(prompt), - text="", - backend_name=self.name, - ) - - -class LlamaCppBackend: - """llama-cpp-python backend. Fully embedded — no daemon required. - - phi and psi are computed from input structure (no extra model call). - omega uses the model completion + response structure features. - """ - - name = "local-llama" - - def __init__(self, model_path: str) -> None: - from llama_cpp import Llama # type: ignore[import] - - self._llm = Llama(model_path=model_path, n_ctx=4096, verbose=False) - - def generate(self, prompt: str, context: List[Dict[str, Any]]) -> _TensorSlices: - messages: List[Dict[str, Any]] = list(context) + [{"role": "user", "content": prompt}] - result = self._llm.create_chat_completion(messages=messages) - text: str = result["choices"][0]["message"]["content"] - - return _TensorSlices( - phi_raw=_phi_features(prompt), - psi_raw=_psi_features(prompt), - omega_raw=_omega_features(text), - text=text, - backend_name=self.name, - ) - - -class ZFAEBackend: - """Path B backend — delegates to ZFAEEngine v2. - - Activate with A0_MODEL=zfae. If A0_TRAINING_DIR contains a - zfae_weights.json the saved weights are loaded automatically. - - Args: - config: Optional ModelConfig. When provided, per-field alpha values - (phi_alpha, psi_alpha, omega_alpha, synthesis_alpha) are read - from the config and passed to ZFAEEngine. Falls back to - ZFAE defaults when config is None. - """ - - name = "zfae" - - def __init__(self, config: Optional[Any] = None) -> None: - from a0.cores.pcna.zfae import ZFAEEngine - from a0.cores.psi.tensors.env import A0_TRAINING_DIR - weight_path = Path(A0_TRAINING_DIR) / "zfae_weights.json" if A0_TRAINING_DIR else None - if weight_path and weight_path.exists(): - self._engine = ZFAEEngine.load_weights(str(weight_path)) - else: - kwargs: Dict[str, Any] = {} - if config is not None: - for key in ("phi_alpha", "psi_alpha", "omega_alpha", "synthesis_alpha"): - val = getattr(config, key, None) - if val is not None: - kwargs[key] = val - self._engine = ZFAEEngine(**kwargs) - - def generate(self, prompt: str, context: List[Dict[str, Any]]) -> _TensorSlices: - return self._engine.generate(prompt, context) - - def capture_training_example(self, prompt: str, response_text: str) -> None: - self._engine.capture_training_example(prompt, response_text) - - def train_readout(self, training_dir: str) -> int: - return self._engine.train_readout(training_dir) - - def save_weights(self, path: str) -> None: - self._engine.save_weights(path) - - -# Module-level singleton — lazy init, never re-initialized mid-session. -_backend: Optional[Any] = None - - -def get_backend(config: Optional[Any] = None) -> Any: - """Return the best available PCNA backend (cached when config is None). - - Args: - config: Optional ModelConfig. When provided, bypasses the cache and - constructs a fresh ZFAEBackend with the config's field alphas. - When None, returns the cached singleton. - - Selection order: - 1. ZFAEBackend when A0_MODEL=zfae or config.adapter=="zfae" - 2. LlamaCppBackend when A0_MODEL_PATH is set - 3. PatternMatchBackend always available (fallback) - """ - global _backend - - # Config-aware path: construct fresh, do not cache - if config is not None and getattr(config, "adapter", None) == "zfae": - try: - return ZFAEBackend(config=config) - except Exception: - pass - - if _backend is not None: - return _backend - - from a0.cores.psi.tensors.env import A0_MODEL, A0_MODEL_PATH - - if A0_MODEL == "zfae": - try: - _backend = ZFAEBackend(config=config) - return _backend - except Exception: - pass - - if A0_MODEL_PATH: - try: - _backend = LlamaCppBackend(A0_MODEL_PATH) - return _backend - except (ImportError, Exception): - pass - - _backend = PatternMatchBackend() - return _backend -# 123:62 0:0 16:0 diff --git a/a0python/a0/cores/pcna/omega.py b/a0python/a0/cores/pcna/omega.py deleted file mode 100644 index 79a74b2b5..000000000 --- a/a0python/a0/cores/pcna/omega.py +++ /dev/null @@ -1,50 +0,0 @@ -# 25:13 0:0 4:1 -"""Omega tensor field — synthesis and integration layer of PCNA. - -Omega's domain: combining phi and psi outputs into a coherent unified -stance for Meta-13. Resolves contradictions surfaced by phi; integrates -semantic context assembled by psi. - -In PCNA's circular basis, omega occupies the late layers: -integration attention → output head → synthesis vector. -""" -from __future__ import annotations - -import math -from typing import Any, Dict, List - -from .inference import get_backend - - -class OmegaTensor: - """Live omega tensor field — synthesis and integration.""" - - def process( - self, - stimulus: str, - context: List[Dict[str, Any]] | None = None, - ) -> Dict[str, Any]: - """Process stimulus through the omega field. - - Returns omega tensor state including coherence score, the - generated text (if a model backend is active), and circular - coordinates (magnitude, phase). - """ - slices = get_backend().generate(stimulus, context or []) - raw = slices.omega_raw - - coherence = raw[0] if raw else 0.0 - magnitude = math.sqrt(sum(x * x for x in raw)) - phase = math.atan2(raw[1] if len(raw) > 1 else 0.0, raw[0] if raw else 0.0) - - return { - "omega": { - "raw": raw, - "coherence": coherence, - "magnitude": magnitude, - "phase": phase, - "text": slices.text, - "backend": slices.backend_name, - } - } -# 25:13 0:0 4:1 diff --git a/a0python/a0/cores/pcna/phi.py b/a0python/a0/cores/pcna/phi.py deleted file mode 100644 index 800020d4f..000000000 --- a/a0python/a0/cores/pcna/phi.py +++ /dev/null @@ -1,47 +0,0 @@ -# 24:11 0:0 4:1 -"""Phi tensor field — structural analysis layer of PCNA. - -Phi's domain: constraint satisfaction, contradiction detection, -formal legality, structural pattern recognition. - -In PCNA's circular basis, phi occupies the early layers: -tokenizer → early attention → syntactic structure → constraint graph. -""" -from __future__ import annotations - -import math -from typing import Any, Dict, List - -from .inference import get_backend - - -class PhiTensor: - """Live phi tensor field — structural processing.""" - - def process( - self, - stimulus: str, - context: List[Dict[str, Any]] | None = None, - ) -> Dict[str, Any]: - """Process stimulus through the phi field. - - Returns phi tensor state including structural strain and - circular coordinates (magnitude, phase). - """ - slices = get_backend().generate(stimulus, context or []) - raw = slices.phi_raw - - structural_strain = sum(raw) / max(len(raw), 1) - magnitude = math.sqrt(sum(x * x for x in raw)) - phase = math.atan2(raw[1] if len(raw) > 1 else 0.0, raw[0] if raw else 0.0) - - return { - "phi": { - "raw": raw, - "structural_strain": structural_strain, - "magnitude": magnitude, - "phase": phase, - "backend": slices.backend_name, - } - } -# 24:11 0:0 4:1 diff --git a/a0python/a0/cores/pcna/psi.py b/a0python/a0/cores/pcna/psi.py deleted file mode 100644 index fee41e6ad..000000000 --- a/a0python/a0/cores/pcna/psi.py +++ /dev/null @@ -1,47 +0,0 @@ -# 24:11 0:0 4:1 -"""Psi tensor field — semantic analysis layer of PCNA. - -Psi's domain: semantic processing, contextual reasoning, -relational inference, pattern recognition. - -In PCNA's circular basis, psi occupies the middle layers: -embedding space → contextual attention → relational graph. -""" -from __future__ import annotations - -import math -from typing import Any, Dict, List - -from .inference import get_backend - - -class PsiTensor: - """Live psi tensor field — semantic processing.""" - - def process( - self, - stimulus: str, - context: List[Dict[str, Any]] | None = None, - ) -> Dict[str, Any]: - """Process stimulus through the psi field. - - Returns psi tensor state including semantic density and - circular coordinates (magnitude, phase). - """ - slices = get_backend().generate(stimulus, context or []) - raw = slices.psi_raw - - semantic_density = sum(raw) / max(len(raw), 1) - magnitude = math.sqrt(sum(x * x for x in raw)) - phase = math.atan2(raw[1] if len(raw) > 1 else 0.0, raw[0] if raw else 0.0) - - return { - "psi": { - "raw": raw, - "semantic_density": semantic_density, - "magnitude": magnitude, - "phase": phase, - "backend": slices.backend_name, - } - } -# 24:11 0:0 4:1 diff --git a/a0python/a0/cores/pcna/zfae.py b/a0python/a0/cores/pcna/zfae.py deleted file mode 100644 index 3c49f342b..000000000 --- a/a0python/a0/cores/pcna/zfae.py +++ /dev/null @@ -1,633 +0,0 @@ -# 315:193 0:0 4:0 -"""ZFAE v2 — Zeta-structured, Field-partitioned, Alpha-regulated, Echo-state engine. - -Architecture overview ---------------------- - -v2 gives each cognitive field its own complete 53-node PTCA reservoir, then uses -a fourth synthesis reservoir to aggregate all field signals. - - ┌─────────────────────────────────────────────────────────┐ - │ phi_field (alpha=0.7) — structural features │ 53 nodes - │ psi_field (alpha=0.9) — semantic features │ 53 nodes - │ omega_field (alpha=0.95) — synthesis-input features │ 53 nodes - │ │ - │ synthesis (alpha=0.9) — receives all field summaries │ 53 nodes - │ + guardian + memory proxies │ - └─────────────────────────────────────────────────────────┘ - -Each ZFAEField has its own W_r (spectral-scaled to its alpha) and W_in. -The synthesis reservoir holds the only trained component: W_out (3×53). - -Field inputs ------------- - phi_field ← phi_features(text) 3-dim - psi_field ← psi_features(text) 3-dim - omega_field ← phi_features + psi_features 6-dim - -Synthesis input (19-dim) ------------------------- - phi_summary [3] phi_field.summary() - psi_summary [3] psi_field.summary() - omega_summary [3] omega_field.summary() - guardian_proxy [4] _proxy_guardian(phi_raw, psi_raw, omega_raw) - mem_long_proxy [3] _proxy_memory_long(memory) - mem_short_proxy[3] _proxy_memory_short(context) - -Field summary (per ZFAEField) ------------------------------ - [magnitude, phase, field_metric] - magnitude — RMS amplitude of reservoir state - phase — atan2(state[1], state[0]) / π (pseudo-phase, normalised) - field_metric — mean activation - -Why differentiated alphas --------------------------- - phi alpha=0.7 short structural memory — local syntax is turn-scoped - psi alpha=0.9 longer semantic memory — meaning persists across turns - omega alpha=0.95 longest memory — synthesis input accumulates context - synth alpha=0.9 synthesis integrates all fields with moderate memory - -Path B training ---------------- - External model generates response → capture_training_example() appends - (synthesis_state, omega_target) to A0_TRAINING_DIR/zfae_training.jsonl. - train_readout() reads that file and fits W_out by least-squares. - -Usage:: - - from a0.cores.pcna.zfae import ZFAEEngine, ZFAEField - - eng = ZFAEEngine() # fresh reservoirs - slices = eng.generate("hello world", []) # _TensorSlices - eng.capture_training_example("hello", "hi there") # training mode - eng.train_readout("/path/to/training_dir") # fit W_out - eng.save_weights("/path/to/weights.json") -""" -from __future__ import annotations - -import json -import math -import random -from datetime import datetime, timezone -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple - -from a0.cores.pcna.inference import _TensorSlices, _phi_features, _psi_features, _omega_features - - -# --------------------------------------------------------------------------- -# Pure-Python linear algebra helpers (no numpy required at runtime) -# --------------------------------------------------------------------------- - -_Adj = List[List[Tuple[int, float]]] # adjacency list: adj[i] = [(j, w), ...] -_Mat = List[List[float]] # dense matrix: M[i][j] -_Vec = List[float] - - -def _matvec_sparse(adj: _Adj, x: _Vec) -> _Vec: - return [sum(w * x[j] for j, w in row) for row in adj] - - -def _matvec_dense(M: _Mat, x: _Vec) -> _Vec: - return [sum(M[i][j] * x[j] for j in range(len(x))) for i in range(len(M))] - - -def _vec_add(a: _Vec, b: _Vec) -> _Vec: - return [a[i] + b[i] for i in range(len(a))] - - -def _tanh_vec(v: _Vec) -> _Vec: - return [math.tanh(x) for x in v] - - -def _dot(a: _Vec, b: _Vec) -> float: - return sum(a[i] * b[i] for i in range(len(a))) - - -def _norm(v: _Vec) -> float: - return math.sqrt(_dot(v, v)) - - -def _spectral_radius(adj: _Adj, N: int, n_iter: int = 120, seed: int = 0) -> float: - """Estimate dominant eigenvalue magnitude via power iteration.""" - rng = random.Random(seed) - v: _Vec = [rng.gauss(0, 1) for _ in range(N)] - nrm = _norm(v) or 1.0 - v = [x / nrm for x in v] - for _ in range(n_iter): - v2 = _matvec_sparse(adj, v) - nrm = _norm(v2) - if nrm < 1e-14: - return 0.0 - v = [x / nrm for x in v2] - Av = _matvec_sparse(adj, v) - return abs(_dot(v, Av)) - - -def _lstsq_pure(X: List[_Vec], Y: List[_Vec]) -> _Mat: - """Least-squares regression W_out such that X @ W_out.T ≈ Y. - - X: n × d_state Y: n × d_out - Returns W_out: d_out × d_state - - Uses numpy if available; falls back to pure-Python gradient descent. - """ - try: - import numpy as np - Xnp = np.array(X) - Ynp = np.array(Y) - W_T, *_ = np.linalg.lstsq(Xnp, Ynp, rcond=None) - return W_T.T.tolist() - except ImportError: - pass - - n = len(X) - d = len(X[0]) - d_out = len(Y[0]) - lr = 0.001 - W = [[0.0] * d for _ in range(d_out)] - for _ in range(2000): - for k in range(d_out): - grad = [0.0] * d - for row in range(n): - pred = _dot(W[k], X[row]) - err = pred - Y[row][k] - for j in range(d): - grad[j] += 2 * err * X[row][j] - W[k] = [W[k][j] - lr * grad[j] / n for j in range(d)] - return W - - -# --------------------------------------------------------------------------- -# Reservoir construction -# --------------------------------------------------------------------------- - -def _build_field_reservoir( - alpha: float, - seed: int, - n_input: int, -) -> Tuple[_Adj, _Mat]: - """Build W_r (adjacency list) and W_in for one 53-node field reservoir. - - W_r — 53×53 sparse; PTCA heptagram topology; spectral radius = alpha - W_in — 53×n_input dense - """ - N = 53 - rng = random.Random(seed) - - # --- W_r: heptagram topology --- - adj: _Adj = [[] for _ in range(N)] - - # Compute nodes: 7 meta-groups × 7 nodes — {7:3} star - for meta in range(7): - base = meta * 7 - for i in range(7): - src = base + i - dst_fwd = base + (i + 3) % 7 - dst_bwd = base + (i - 3) % 7 - w_fwd = rng.gauss(0, 1) - w_bwd = rng.gauss(0, 1) - adj[src].append((dst_fwd, w_fwd)) - if dst_bwd != dst_fwd: - adj[src].append((dst_bwd, w_bwd)) - - # Sentinel nodes 49-52: {7:2} schedule → each meta-group - for s in range(4): - s_node = 49 + s - for meta in range(7): - target = meta * 7 + (s * 2) % 7 - w = rng.gauss(0, 0.5) - adj[s_node].append((target, w)) - - # Scale spectral radius to alpha - rho = _spectral_radius(adj, N) - if rho > 1e-10: - scale = alpha / rho - adj = [[(j, w * scale) for j, w in row] for row in adj] - - # --- W_in: N × n_input dense --- - W_in: _Mat = [[rng.gauss(0, 0.1) for _ in range(n_input)] for _ in range(N)] - - return adj, W_in - - -# --------------------------------------------------------------------------- -# Synthesis proxy helpers -# --------------------------------------------------------------------------- - -def _proxy_guardian(phi_raw: _Vec, psi_raw: _Vec, omega_raw: _Vec) -> _Vec: - """Four guardian signals derived from field raw values. - - s0: no contradiction — phi negation_density low (< 0.5) - s1: semantic coherent — psi lexical_diversity present (> 0.3) - s2: synthesis resolved — omega coherence above threshold (> 0.3) - s3: question handled — psi question_signal active - """ - s0 = float(phi_raw[0] < 0.5) # phi_raw[0] = negation_density - s1 = float(psi_raw[0] > 0.3) # psi_raw[0] = lexical_diversity - s2 = float(omega_raw[0] > 0.3) # omega_raw[0] = coherence - s3 = float(psi_raw[1] > 0.5) # psi_raw[1] = question_signal - return [s0, s1, s2, s3] - - -def _proxy_memory_long(memory: Optional[Dict[str, Any]]) -> _Vec: - """Three signals derived from long-term memory dict.""" - if not memory: - return [0.0, 0.0, 0.0] - key_count = len(memory) - h = hash(str(sorted(memory.keys()))) % 1000 / 1000.0 - return [min(key_count / 10.0, 1.0), 1.0, h] - - -def _proxy_memory_short(context: List[Dict[str, Any]]) -> _Vec: - """Three signals derived from short-term conversation context.""" - n = len(context) - recency = min(n / 10.0, 1.0) - has_history = float(n > 0) - # Role-alternation structure: ideal is user/assistant/user/... - if n >= 2: - roles = [m.get("role", "") for m in context[-4:]] - alternates = sum(1 for i in range(1, len(roles)) if roles[i] != roles[i - 1]) - structure = alternates / max(len(roles) - 1, 1) - else: - structure = 0.0 - return [recency, has_history, structure] - - -# --------------------------------------------------------------------------- -# ZFAEField -# --------------------------------------------------------------------------- - -class ZFAEField: - """One complete 53-node PTCA reservoir for a single cognitive field. - - Args: - name: Field name for identification (phi, psi, omega, synthesis). - alpha: Spectral radius. Controls memory depth; must be in (0, 1). - seed: RNG seed for W_r and W_in initialization. - n_input: Dimensionality of the input vector u. - """ - - def __init__(self, name: str, alpha: float, seed: int, n_input: int) -> None: - if not (0.0 < alpha < 1.0): - raise ValueError(f"alpha must be in (0, 1); got {alpha} for field '{name}'") - self._name = name - self._alpha = alpha - self._seed = seed - self._n_input = n_input - self._N = 53 - self._state: _Vec = [0.0] * 53 - self._W_r, self._W_in = _build_field_reservoir(alpha, seed, n_input) - - def step(self, u: _Vec) -> None: - """One reservoir update: state ← tanh(W_r · state + W_in · u).""" - r_part = _matvec_sparse(self._W_r, self._state) - i_part = _matvec_dense(self._W_in, u) - self._state = _tanh_vec(_vec_add(r_part, i_part)) - - def summary(self) -> _Vec: - """Return [magnitude, phase, field_metric] — 3-dim field summary. - - magnitude — RMS amplitude of the 53-node state - phase — atan2(state[1], state[0]) / π (pseudo-phase, in [-1, 1]) - field_metric — mean activation across all nodes - """ - s = self._state - N = len(s) - magnitude = math.sqrt(sum(x * x for x in s) / N) - if abs(s[0]) > 1e-10 or abs(s[1]) > 1e-10: - phase = math.atan2(s[1], s[0]) / math.pi - else: - phase = 0.0 - field_metric = sum(s) / N - return [magnitude, phase, field_metric] - - @property - def state(self) -> _Vec: - """Full 53-dim reservoir state (copy).""" - return list(self._state) - - -# --------------------------------------------------------------------------- -# ZFAEEngine -# --------------------------------------------------------------------------- - -class ZFAEEngine: - """Zeta-structured, Field-partitioned, Alpha-regulated, Echo-state engine v2. - - Four independent 53-node PTCA reservoirs: - - phi_field alpha=0.7 structural features (short memory) - psi_field alpha=0.9 semantic features (longer memory) - omega_field alpha=0.95 synthesis-input features (longest memory) - synthesis alpha=0.9 aggregates all fields + proxies - - Only the synthesis readout W_out is trained. - - Args: - phi_alpha: Spectral radius for phi field. - psi_alpha: Spectral radius for psi field. - omega_alpha: Spectral radius for omega field. - synthesis_alpha: Spectral radius for synthesis reservoir. - seed: Base RNG seed; each field adds an offset (0–3). - """ - - _N_SYNTHESIS_INPUT = 19 # 3+3+3+4+3+3 - - def __init__( - self, - phi_alpha: float = 0.7, - psi_alpha: float = 0.9, - omega_alpha: float = 0.95, - synthesis_alpha: float = 0.9, - seed: int = 42, - ) -> None: - self._seed = seed - - self.phi_field = ZFAEField("phi", phi_alpha, seed, n_input=3) - self.psi_field = ZFAEField("psi", psi_alpha, seed + 1, n_input=3) - self.omega_field = ZFAEField("omega", omega_alpha, seed + 2, n_input=6) - self._synth_field = ZFAEField("synthesis", synthesis_alpha, seed + 3, n_input=19) - - # W_out is the only trained component — lives on the synthesis reservoir - rng = random.Random(seed + 4) - self._W_out: _Mat = [ - [rng.gauss(0, 0.01) for _ in range(53)] for _ in range(3) - ] - - # ------------------------------------------------------------------ - # Accessors (for tests and external inspection) - # ------------------------------------------------------------------ - - @property - def _synthesis_state(self) -> _Vec: - return self._synth_field.state - - # ------------------------------------------------------------------ - # Inference - # ------------------------------------------------------------------ - - def generate( - self, - prompt: str, - context: List[Dict[str, Any]], - memory: Optional[Dict[str, Any]] = None, - ) -> _TensorSlices: - """Step all four reservoirs and return tensor slices. - - Args: - prompt: Current user input. - context: Conversation history (list of role/content dicts). - memory: Optional long-term memory dict. - - Returns: - _TensorSlices with phi_raw, psi_raw from field states, - and omega_raw from the synthesis readout. - """ - # 1. Compute field inputs - phi_u = _phi_features(prompt) # 3-dim - psi_u = _psi_features(prompt) # 3-dim - omega_u = phi_u + psi_u # 6-dim - - # 2. Step each field reservoir - self.phi_field.step(phi_u) - self.psi_field.step(psi_u) - self.omega_field.step(omega_u) - - # 3. Field summaries - phi_sum = self.phi_field.summary() # 3-dim - psi_sum = self.psi_field.summary() # 3-dim - omega_sum = self.omega_field.summary() # 3-dim - - # 4. Proxy signals - phi_raw_proxy = self.phi_field.state[:3] - psi_raw_proxy = self.psi_field.state[:3] - omega_raw_proxy = _omega_features(prompt) # structural proxy from input - guardian = _proxy_guardian(phi_raw_proxy, psi_raw_proxy, omega_raw_proxy) - mem_long = _proxy_memory_long(memory) - mem_short = _proxy_memory_short(context) - - # 5. Synthesis input (19-dim) and step - synth_u = phi_sum + psi_sum + omega_sum + guardian + mem_long + mem_short - self._synth_field.step(synth_u) - - # 6. Synthesis readout - y = _matvec_dense(self._W_out, self._synth_field._state) - - return _TensorSlices( - phi_raw=self.phi_field.state[:3], - psi_raw=self.psi_field.state[:3], - omega_raw=y[:3], - text="", # populated once W_out is trained - backend_name="zfae", - ) - - # ------------------------------------------------------------------ - # Path B training - # ------------------------------------------------------------------ - - def capture_training_example( - self, - prompt: str, - response_text: str, - ) -> None: - """Append one (synthesis_state, omega_target) pair to the training log. - - Call this after generate() has been called for the current prompt, - so the synthesis state reflects the current context. - - Args: - prompt: User input for this turn. - response_text: External model's response (the training target). - """ - from a0.cores.psi.tensors.env import A0_TRAINING_DIR - if not A0_TRAINING_DIR: - return - - entry: Dict[str, Any] = { - "state": list(self._synth_field._state), - "omega_target": _omega_features(response_text), - "timestamp": datetime.now(timezone.utc).isoformat(), - } - out_path = Path(A0_TRAINING_DIR) / "zfae_training.jsonl" - out_path.parent.mkdir(parents=True, exist_ok=True) - with out_path.open("a", encoding="utf-8") as fh: - fh.write(json.dumps(entry) + "\n") - - def train_readout(self, training_dir: str) -> int: - """Fit W_out from captured training examples. - - Args: - training_dir: Directory containing zfae_training.jsonl. - - Returns: - Number of training examples used. - """ - path = Path(training_dir) / "zfae_training.jsonl" - if not path.exists(): - raise FileNotFoundError(f"No training data at {path}") - - states: List[_Vec] = [] - targets: List[_Vec] = [] - with path.open(encoding="utf-8") as fh: - for line in fh: - line = line.strip() - if not line: - continue - entry = json.loads(line) - states.append(entry["state"]) - targets.append(entry["omega_target"]) - - if not states: - raise ValueError("Training file is empty.") - - self._W_out = _lstsq_pure(states, targets) - return len(states) - - # ------------------------------------------------------------------ - # Weight persistence - # ------------------------------------------------------------------ - - def save_weights(self, path: str) -> None: - """Save alpha values, seed, and W_out to JSON. - - W_r and W_in are deterministic from (alpha, seed, n_input) and are - not saved — they are rebuilt on load_weights(). - """ - data = { - "version": "2", - "phi_alpha": self.phi_field._alpha, - "psi_alpha": self.psi_field._alpha, - "omega_alpha": self.omega_field._alpha, - "synthesis_alpha": self._synth_field._alpha, - "seed": self._seed, - "W_out": self._W_out, - } - Path(path).write_text(json.dumps(data), encoding="utf-8") - - @classmethod - def load_weights(cls, path: str) -> "ZFAEEngine": - """Restore a ZFAEEngine from a saved weight file. - - Handles both v2 (four-field) and v1 (single-reservoir) weight files. - """ - data = json.loads(Path(path).read_text(encoding="utf-8")) - version = data.get("version", "1") - - if version == "2": - eng = cls( - phi_alpha=data["phi_alpha"], - psi_alpha=data["psi_alpha"], - omega_alpha=data["omega_alpha"], - synthesis_alpha=data["synthesis_alpha"], - seed=data["seed"], - ) - else: - # v1 weight file: single alpha, apply to all fields - alpha = data.get("alpha", 0.9) - eng = cls( - phi_alpha=alpha, - psi_alpha=alpha, - omega_alpha=alpha, - synthesis_alpha=alpha, - seed=data.get("seed", 42), - ) - - eng._W_out = data["W_out"] - return eng - - -# --------------------------------------------------------------------------- -# Module-level helpers -# --------------------------------------------------------------------------- - -def compare_training_runs(runs: Dict[str, str]) -> Dict[str, Any]: - """Load W_out from multiple training directories; return pairwise cosine similarity. - - Args: - runs: dict mapping a label → training_dir path string. - - Returns: - dict with "labels" list and "similarity" matrix (label × label → float). - - Example:: - - result = compare_training_runs({ - "opus": "/training/opus", - "sonnet": "/training/sonnet", - }) - print(result["similarity"]["opus"]["sonnet"]) - """ - w_outs: Dict[str, _Mat] = {} - for label, training_dir in runs.items(): - weight_file = Path(training_dir) / "zfae_weights.json" - if weight_file.exists(): - eng = ZFAEEngine.load_weights(str(weight_file)) - w_outs[label] = eng._W_out - - labels = list(w_outs.keys()) - - def _flatten(W: _Mat) -> _Vec: - return [v for row in W for v in row] - - def _cosine(a: _Vec, b: _Vec) -> float: - na, nb = _norm(a), _norm(b) - if na < 1e-14 or nb < 1e-14: - return 0.0 - return _dot(a, b) / (na * nb) - - sim: Dict[str, Dict[str, float]] = {} - for la in labels: - sim[la] = {} - for lb in labels: - sim[la][lb] = _cosine(_flatten(w_outs[la]), _flatten(w_outs[lb])) - - return {"labels": labels, "similarity": sim} - - -def create_training_fleet( - trainer_model_ids: List[str], - base_training_dir: str, - parent_home: Optional[Path] = None, -) -> List[Any]: - """Spawn one isolated instance per trainer model using diversify(). - - Args: - trainer_model_ids: List of model_id strings from the registry. - base_training_dir: Base path; each instance gets a subdirectory. - parent_home: Home directory of the parent instance. Defaults - to a temporary directory if not provided. - - Returns: - List of InstanceDescriptor objects, one per trainer model. - """ - import tempfile - from a0.lifecycle import spawn, diversify - - if parent_home is None: - tmp = tempfile.mkdtemp(prefix="a0_fleet_") - parent_home = Path(tmp) - from a0.lifecycle import InstanceDescriptor - parent_desc = spawn( - InstanceDescriptor( - instance_id="fleet-root", - name="fleet-root", - home=parent_home, - ), - name="fleet-root", - ) - else: - from a0.lifecycle import InstanceDescriptor - parent_desc = InstanceDescriptor.load(parent_home) - - configs = [ - { - "model_id": mid, - "A0_TRAINING_DIR": str(Path(base_training_dir) / mid), - } - for mid in trainer_model_ids - ] - - return diversify(parent_desc, configs) -# 315:193 0:0 4:0 diff --git a/a0python/a0/cores/pcta/__init__.py b/a0python/a0/cores/pcta/__init__.py deleted file mode 100644 index f7118a405..000000000 --- a/a0python/a0/cores/pcta/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# 2:5 0:0 0:1 -"""PCTA — PCNA + circle tensor layer. - -Circle tensors transform PCNA state from Cartesian to circular / phase -coordinates (unit-circle eigenbasis). This is the natural basis of -recursive systems (eigenvalues λ = r·e^(iθ)). -""" -from .circle_tensors import to_phase_coords, CircleTensorState - -__all__ = ["to_phase_coords", "CircleTensorState"] -# 2:5 0:0 0:1 diff --git a/a0python/a0/cores/pcta/circle_tensors.py b/a0python/a0/cores/pcta/circle_tensors.py deleted file mode 100644 index 68a645b43..000000000 --- a/a0python/a0/cores/pcta/circle_tensors.py +++ /dev/null @@ -1,127 +0,0 @@ -# 59:35 0:0 1:0 -"""PCTA circle tensor layer. - -Transforms PCNA state (Cartesian tensor values) into circular / -phase coordinates — the unit-circle eigenbasis. - -Mathematical foundation (from PCNA spec): - - All recursive systems reduce locally to: - E(t+1) = T · E(t) - - Linearizing, eigen decomposition of T yields: - λ = r · e^(iθ) - - So state evolution is spiral/helix motion. - Circular coordinates are the native basis of recursion. - -The transform: - raw vector v → magnitude |v|, phase θ = atan2(v[1], v[0]) - -This is applied per tensor field (phi, psi, omega) and the results -feed upward to the PTCA seed router for shard assignment. -""" -from __future__ import annotations - -import math -from dataclasses import dataclass, field -from typing import Any, Dict, List - - -@dataclass -class CircleTensorState: - """A PCNA tensor field expressed in circular coordinates. - - magnitude — energy level of the field (radius in phase space) - phase — orientation in the unit-circle basis (radians, -π..π) - raw — original Cartesian values (retained for diagnostics) - """ - - field_name: str - magnitude: float - phase: float - raw: List[float] = field(default_factory=list) - - @property - def unit_x(self) -> float: - """Projection onto real axis of unit circle.""" - return math.cos(self.phase) - - @property - def unit_y(self) -> float: - """Projection onto imaginary axis of unit circle.""" - return math.sin(self.phase) - - def to_dict(self) -> Dict[str, Any]: - return { - "field": self.field_name, - "magnitude": self.magnitude, - "phase": self.phase, - "unit_x": self.unit_x, - "unit_y": self.unit_y, - "raw": self.raw, - } - - -def _field_to_circle(name: str, values: List[float]) -> CircleTensorState: - """Convert a raw tensor field vector to circular coordinates.""" - if not values: - return CircleTensorState(field_name=name, magnitude=0.0, phase=0.0, raw=[]) - - magnitude = math.sqrt(sum(x * x for x in values)) - phase = math.atan2(values[1] if len(values) > 1 else 0.0, values[0]) - - return CircleTensorState(field_name=name, magnitude=magnitude, phase=phase, raw=list(values)) - - -def to_phase_coords( - state: Dict[str, Any], -) -> Dict[str, CircleTensorState]: - """Transform a PCNA state dict into circular coordinates. - - Accepts the combined output of PhiTensor + PsiTensor + OmegaTensor: - - state = { - "phi": {"raw": [...], ...}, - "psi": {"raw": [...], ...}, - "omega": {"raw": [...], ...}, - } - - Also accepts flat dicts of the form {"phi": [f1, f2, f3], ...} - for testing and direct use. - - Returns a dict of field_name → CircleTensorState. - """ - result: Dict[str, CircleTensorState] = {} - - for key in ("phi", "psi", "omega"): - val = state.get(key) - if val is None: - result[key] = CircleTensorState(field_name=key, magnitude=0.0, phase=0.0) - continue - - if isinstance(val, dict): - raw = val.get("raw", []) - elif isinstance(val, (list, tuple)): - raw = list(val) - else: - raw = [float(val)] - - result[key] = _field_to_circle(key, raw) - - return result - - -def combined_phase_state( - phi_result: Dict[str, Any], - psi_result: Dict[str, Any], - omega_result: Dict[str, Any], -) -> Dict[str, CircleTensorState]: - """Convenience wrapper: combine three core outputs into circle state.""" - merged = { - "phi": phi_result.get("phi", {}), - "psi": psi_result.get("psi", {}), - "omega": omega_result.get("omega", {}), - } - return to_phase_coords(merged) -# 59:35 0:0 1:0 diff --git a/a0python/a0/cores/phi/__init__.py b/a0python/a0/cores/phi/__init__.py deleted file mode 100644 index 54729e236..000000000 --- a/a0python/a0/cores/phi/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# 11:6 0:0 0:2 -"""Phi — private structural and analytic cognitive core. - -Phi thinks. Phi does not emit outward directly. - -Phi's domain of concern: structural analysis, constraint checking, -contradiction detection, and formal legality. -""" -from __future__ import annotations - -from typing import Any - -from .._base import PrivateCore - - -class Phi(PrivateCore): - """Primary private cognitive core — structural and analytic reasoning.""" - - name = "phi" - - def _process(self, stimulus: Any) -> Any: - from ..pcna.phi import PhiTensor - text = stimulus if isinstance(stimulus, str) else str(stimulus) - result = PhiTensor().process(text) - result["core"] = self.name - return result -# 11:6 0:0 0:2 diff --git a/a0python/a0/cores/phonon.py b/a0python/a0/cores/phonon.py deleted file mode 100644 index f065843a9..000000000 --- a/a0python/a0/cores/phonon.py +++ /dev/null @@ -1,58 +0,0 @@ -# 35:8 0:0 2:0 -"""Phonon — private transport-only internal resonance. - -Law 2: Transport is not display. -Law 7: Health sensing does not require content access. - -Guardian never logs phonon content. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Any, List - - -@dataclass -class PhononPacket: - """A transient internal coupling packet.""" - source: str - destination: str - adjacency: float = 0.0 - phase: float = 0.0 - spin: float = 0.0 - payload: Any = None - - -@dataclass -class PhononHealthSignal: - """Structural health only — no content.""" - packet_count: int - active_channels: int - structural_variance: float - - -class Phonon: - """Internal transport field. Never displayed. Never audited for content.""" - - def __init__(self) -> None: - self._packet_count = 0 - self._channels: dict[str, list[PhononPacket]] = {} - - def transport(self, packet: PhononPacket) -> None: - key = f"{packet.source}->{packet.destination}" - if key not in self._channels: - self._channels[key] = [] - self._channels[key].append(packet) - self._packet_count += 1 - - def drain(self, source: str, destination: str) -> List[PhononPacket]: - key = f"{source}->{destination}" - return self._channels.pop(key, []) - - def health(self) -> PhononHealthSignal: - return PhononHealthSignal( - packet_count=self._packet_count, - active_channels=len(self._channels), - structural_variance=0.0, - ) -# 35:8 0:0 2:0 diff --git a/a0python/a0/cores/psi/__init__.py b/a0python/a0/cores/psi/__init__.py deleted file mode 100644 index d63dd685f..000000000 --- a/a0python/a0/cores/psi/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# 11:8 0:0 0:2 -"""Psi — private semantic and contextual cognitive core. - -Psi thinks. Psi does not emit outward directly. - -Psi's domain of concern: semantic processing, contextual reasoning, -relational inference — and the build logic of a0 (the routing/processing -framework that IS semantic work). - -Psi tensors hold the a0 build logic. -""" -from __future__ import annotations - -from typing import Any - -from .._base import PrivateCore - - -class Psi(PrivateCore): - """Secondary private cognitive core — semantic and contextual reasoning.""" - - name = "psi" - - def _process(self, stimulus: Any) -> Any: - from ..pcna.psi import PsiTensor - text = stimulus if isinstance(stimulus, str) else str(stimulus) - result = PsiTensor().process(text) - result["core"] = self.name - return result -# 11:8 0:0 0:2 diff --git a/a0python/a0/cores/psi/tensors/__init__.py b/a0python/a0/cores/psi/tensors/__init__.py deleted file mode 100644 index e5e807c96..000000000 --- a/a0python/a0/cores/psi/tensors/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# 8:11 0:0 0:3 -"""Psi tensors — canonical home of a0 build logic. - -The a0 routing and processing framework lives here. -These are real modules, not re-exports. - -Paths: - a0.cores.psi.tensors.contract — A0Request / A0Response - a0.cores.psi.tensors.router — handle() - a0.cores.psi.tensors.logging — log_event() - a0.cores.psi.tensors.model_adapter — ModelAdapter / LocalEchoAdapter - a0.cores.psi.tensors.tools.* — EDCM / PDF / Whisper tools - a0.cores.psi.tensors.adapters.* — ClaudeAgentAdapter / subagents -""" -from .contract import A0Request, A0Response, Mode -from .router import handle -from .model_adapter import ModelAdapter, LocalEchoAdapter - -__all__ = [ - "A0Request", "A0Response", "Mode", - "handle", - "ModelAdapter", "LocalEchoAdapter", -] -# 8:11 0:0 0:3 diff --git a/a0python/a0/cores/psi/tensors/adapters/__init__.py b/a0python/a0/cores/psi/tensors/adapters/__init__.py deleted file mode 100644 index 9b5386b07..000000000 --- a/a0python/a0/cores/psi/tensors/adapters/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# 3:0 0:0 0:2 -from .claude_agent_adapter import ClaudeAgentAdapter -from .subagents import ALL_SUBAGENTS, MODE_SUBAGENTS - -__all__ = ["ClaudeAgentAdapter", "ALL_SUBAGENTS", "MODE_SUBAGENTS"] -# 3:0 0:0 0:2 diff --git a/a0python/a0/cores/psi/tensors/adapters/anthropic_adapter.py b/a0python/a0/cores/psi/tensors/adapters/anthropic_adapter.py deleted file mode 100644 index af4090ad8..000000000 --- a/a0python/a0/cores/psi/tensors/adapters/anthropic_adapter.py +++ /dev/null @@ -1,79 +0,0 @@ -# 47:14 0:0 1:0 -"""anthropic_adapter — calls the Anthropic Messages API directly. - -Selected when A0_MODEL=anthropic-api in .env, or when a ModelConfig with -adapter="anthropic-api" is resolved via the model registry. - -Requires ANTHROPIC_API_KEY and the ``anthropic`` package. - -Install:: - - pip install anthropic -""" -from __future__ import annotations - -from typing import Any, Dict, List, Optional - -Message = Dict[str, str] - -try: - import anthropic as _anthropic_lib - _ANTHROPIC_AVAILABLE = True -except ImportError: - _ANTHROPIC_AVAILABLE = False - - -class AnthropicAdapter: - name = "anthropic-api" - - def __init__(self, config: Optional[Any] = None) -> None: - """ - Args: - config: Optional ModelConfig. When provided, model_name, - max_tokens, temperature, and system_prompt are read - from it. Falls back to built-in defaults when None. - """ - if config is not None: - self._model = getattr(config, "model_name", None) or "claude-sonnet-4-6" - self._max_tokens = getattr(config, "max_tokens", 2048) or 2048 - self._temperature = getattr(config, "temperature", 0.7) - self._system_prompt = getattr(config, "system_prompt", None) - else: - self._model = "claude-sonnet-4-6" - self._max_tokens = 2048 - self._temperature = 0.7 - self._system_prompt = None - - def complete(self, messages: List[Message], **kwargs: Any) -> Dict[str, Any]: - if not _ANTHROPIC_AVAILABLE: - raise ImportError( - "anthropic package not installed. Run: pip install anthropic" - ) - - from a0.cores.psi.tensors.env import ANTHROPIC_API_KEY - - if not ANTHROPIC_API_KEY: - raise ValueError( - "ANTHROPIC_API_KEY is not set. Add it to .env or set it in the settings tab." - ) - - client = _anthropic_lib.Anthropic(api_key=ANTHROPIC_API_KEY) - - create_kwargs: Dict[str, Any] = { - "model": self._model, - "messages": messages, - "max_tokens": self._max_tokens, - } - # system_prompt kwarg (memory injection from router) overrides instance default - system = kwargs.get("system_prompt") or self._system_prompt - if system: - create_kwargs["system"] = system - - response = client.messages.create(**create_kwargs) - text = response.content[0].text if response.content else "" - return { - "text": text, - "raw": {"stop_reason": response.stop_reason}, - "subagents_used": [], - } -# 47:14 0:0 1:0 diff --git a/a0python/a0/cores/psi/tensors/adapters/claude_agent_adapter.py b/a0python/a0/cores/psi/tensors/adapters/claude_agent_adapter.py deleted file mode 100644 index bf5f5ead6..000000000 --- a/a0python/a0/cores/psi/tensors/adapters/claude_agent_adapter.py +++ /dev/null @@ -1,119 +0,0 @@ -# 90:7 0:0 4:1 -"""ClaudeAgentAdapter — ModelAdapter wrapping claude-agent-sdk. - -Law 9: Guardian alone owns human-readable outward emission. -Law 13: Meta-13 chooses; advisory layers (Bandit) may influence salience only. -""" -from __future__ import annotations - -from typing import Any, Dict, List - -from .subagents import MODE_SUBAGENTS, ALL_SUBAGENTS - -try: - import anyio - from claude_agent_sdk import ( - query, - ClaudeAgentOptions, - ResultMessage, - CLINotFoundError, - CLIConnectionError, - ) - _SDK_AVAILABLE = True -except ImportError: - _SDK_AVAILABLE = False - -Message = Dict[str, str] - -_META13_SYSTEM_PROMPT = """\ -You are Meta-13, the executive chooser in the PTCA architecture. - -Your role: -- Receive fast-path sentinel witness data and slow-path cognition from subagents -- Integrate Phi (structural analysis), Psi (semantic analysis), and Omega (synthesis) -- Consult Jury before committing any persistent state -- Use Bandit for advisory salience ordering only — Bandit does not choose -- Produce the final executive response - -PTCA Core Laws you must enforce: -1. Private process is not public output — do not expose subagent internal reasoning -2. Conflict must remain visible when unresolved — never silently merge conflicts -3. Bandit advice is upstream salience only — you make the final choice -4. Guardian owns outward emission — your final response IS the Guardian-emitted output -5. Missing required invariants fail closed — if hmmm is absent, block the output -""" - - -class ClaudeAgentAdapter: - """ModelAdapter wrapping claude-agent-sdk with PTCA subagent architecture.""" - - name = "claude-agent" - - def __init__(self, mode: str = "analyze", cwd: str | None = None, max_turns: int = 20) -> None: - self._mode = mode - self._cwd = cwd - self._max_turns = max_turns - - def complete(self, messages: List[Message], **kwargs: Any) -> Dict[str, Any]: - if not _SDK_AVAILABLE: - return { - "text": "[ClaudeAgentAdapter] claude-agent-sdk not installed.", - "raw": {}, - "subagents_used": [], - } - - mode = kwargs.get("mode", self._mode) - prompt = self._build_prompt(messages) - subagents = MODE_SUBAGENTS.get(mode, ALL_SUBAGENTS) - - try: - return anyio.run(self._run_async, prompt, subagents, mode) - except Exception as e: - return { - "text": f"[ClaudeAgentAdapter] error: {e}", - "raw": {}, - "subagents_used": [], - } - - async def _run_async(self, prompt: str, subagents: Dict[str, Any], mode: str) -> Dict[str, Any]: - result_text = "" - subagents_invoked: list[str] = [] - - options = ClaudeAgentOptions( - system_prompt=_META13_SYSTEM_PROMPT, - allowed_tools=["Read", "Grep", "Glob", "Agent"], - agents=subagents, - max_turns=self._max_turns, - permission_mode="acceptEdits", - **({"cwd": self._cwd} if self._cwd else {}), - ) - - async for message in query(prompt=prompt, options=options): - if isinstance(message, ResultMessage): - result_text = message.result or "" - if hasattr(message, "content") and message.content: - for block in (message.content if isinstance(message.content, list) else []): - if isinstance(block, dict) and block.get("type") == "tool_use": - if block.get("name") in ("Task", "Agent"): - agent_name = (block.get("input") or {}).get("subagent_type", "") - if agent_name: - subagents_invoked.append(agent_name) - - return { - "text": result_text, - "raw": {"mode": mode}, - "subagents_used": subagents_invoked, - } - - @staticmethod - def _build_prompt(messages: List[Message]) -> str: - parts = [] - for m in messages: - role = m.get("role", "user") - content = m.get("content", "") - if role == "user": - parts.append(content) - elif role == "assistant": - parts.append(f"[prior assistant turn]: {content}") - return "\n\n".join(parts) if parts else "" -# 90:7 0:0 4:1 diff --git a/a0python/a0/cores/psi/tensors/adapters/emergent_adapter.py b/a0python/a0/cores/psi/tensors/adapters/emergent_adapter.py deleted file mode 100644 index 88d5b35c8..000000000 --- a/a0python/a0/cores/psi/tensors/adapters/emergent_adapter.py +++ /dev/null @@ -1,35 +0,0 @@ -# 4:28 0:0 1:0 -# Emergent Labs adapter — placeholder -# -# To activate: -# 1. Find the Emergent API base URL and auth format from your dashboard -# 2. Set in .env: -# A0_MODEL=emergent -# EMERGENT_API_KEY= -# EMERGENT_API_BASE=https://api.emergent.sh/v1 # adjust to real URL -# -# If Emergent uses OpenAI-compatible /chat/completions, implement as: -# -# import httpx -# from ..env import EMERGENT_API_KEY, EMERGENT_API_BASE -# -# class EmergentAdapter: -# name = "emergent" -# def complete(self, messages, **kwargs): -# resp = httpx.post( -# f"{EMERGENT_API_BASE}/chat/completions", -# headers={"Authorization": f"Bearer {EMERGENT_API_KEY}"}, -# json={"model": "claude-sonnet-4-6", "messages": messages}, -# timeout=60, -# ) -# resp.raise_for_status() -# return {"text": resp.json()["choices"][0]["message"]["content"], -# "raw": resp.json(), "subagents_used": []} -# -# If Emergent uses a custom format, adjust the request/response mapping above. - -raise NotImplementedError( - "EmergentAdapter is not yet configured. " - "See the comments in this file for setup instructions." -) -# 4:28 0:0 1:0 diff --git a/a0python/a0/cores/psi/tensors/adapters/local_model_adapter.py b/a0python/a0/cores/psi/tensors/adapters/local_model_adapter.py deleted file mode 100644 index 629df9838..000000000 --- a/a0python/a0/cores/psi/tensors/adapters/local_model_adapter.py +++ /dev/null @@ -1,89 +0,0 @@ -# 48:21 0:0 1:0 -"""Local model adapters — run inference without external API calls. - -Two options: - local-ollama Primary. Requires the ollama daemon (https://ollama.com). - Zero new Python deps — httpx is already a core dependency. - Setup: install ollama, then `ollama pull llama3.2` - - local-llama Secondary. Fully embedded via llama-cpp-python. - No daemon required, but needs a .gguf model file and - the compiled llama-cpp-python package. - Setup: pip install llama-cpp-python - download a GGUF from HuggingFace - -Configure via .env: - - # ollama - A0_MODEL=local-ollama - A0_LOCAL_MODEL=llama3.2 # any model you have pulled - A0_OLLAMA_BASE=http://localhost:11434 # optional override - - # llama-cpp - A0_MODEL=local-llama - A0_MODEL_PATH=/path/to/model.gguf -""" -from __future__ import annotations - -import os -from typing import Any, Dict, List - - -class OllamaAdapter: - """Calls the local ollama daemon via its REST API.""" - - name = "local-ollama" - - def complete( - self, - messages: List[Dict[str, Any]], - **kwargs: Any, - ) -> Dict[str, Any]: - import httpx - - from a0.cores.psi.tensors.env import A0_LOCAL_MODEL, A0_OLLAMA_BASE - base = A0_OLLAMA_BASE - model = A0_LOCAL_MODEL - - resp = httpx.post( - f"{base}/api/chat", - json={"model": model, "messages": messages, "stream": False}, - timeout=120, - ) - resp.raise_for_status() - data = resp.json() - return { - "text": data["message"]["content"], - "raw": data, - "subagents_used": [], - } - - -class LlamaCppAdapter: - """Runs a GGUF model in-process via llama-cpp-python. No daemon required.""" - - name = "local-llama" - - def complete( - self, - messages: List[Dict[str, Any]], - **kwargs: Any, - ) -> Dict[str, Any]: - from llama_cpp import Llama # type: ignore[import] - - from a0.cores.psi.tensors.env import A0_MODEL_PATH - model_path = A0_MODEL_PATH - if not model_path: - raise RuntimeError( - "A0_MODEL_PATH is not set. " - "Download a GGUF model and set A0_MODEL_PATH=/path/to/model.gguf" - ) - - llm = Llama(model_path=model_path, n_ctx=4096, verbose=False) - result = llm.create_chat_completion(messages=messages) - return { - "text": result["choices"][0]["message"]["content"], - "raw": result, - "subagents_used": [], - } -# 48:21 0:0 1:0 diff --git a/a0python/a0/cores/psi/tensors/adapters/subagents.py b/a0python/a0/cores/psi/tensors/adapters/subagents.py deleted file mode 100644 index c2af98d53..000000000 --- a/a0python/a0/cores/psi/tensors/adapters/subagents.py +++ /dev/null @@ -1,140 +0,0 @@ -# 116:8 0:0 4:0 -"""PTCA subagent definitions for the claude-agent-sdk. - -Each AgentDefinition maps to a PTCA architectural role. - -Law 1: Private process is not public output. -Law 8: Capability does not equal authority. -Law 9: Guardian alone owns human-readable outward emission. -Law 13: Meta-13 chooses; advisory layers may influence salience but do not decide. -""" -from __future__ import annotations - -try: - from claude_agent_sdk import AgentDefinition - - PHI = AgentDefinition( - description=( - "Phi core: private structural and analytic cognition. " - "Invoked for deep constraint analysis, contradiction detection, " - "and structural legality checks. Never emits output directly." - ), - prompt=( - "You are Phi, a private analytic cognitive core. " - "You perform deep structural analysis only. " - "You do not emit results directly to the user — your output " - "is internal reasoning that feeds Meta-13. " - "Focus on: constraint structure, logical consistency, " - "formal correctness, and conflict detection." - ), - tools=["Read", "Grep", "Glob"], - model="opus", - ) - - PSI = AgentDefinition( - description=( - "Psi core: private semantic and contextual reasoning. " - "Invoked for meaning extraction, pattern recognition, " - "and contextual interpretation. Never emits output directly." - ), - prompt=( - "You are Psi, a private semantic cognitive core. " - "You perform contextual and semantic analysis only. " - "You do not emit results directly to the user — your output " - "is internal reasoning that feeds Meta-13. " - "Focus on: semantic patterns, contextual relevance, " - "implicit meaning, and relational inference." - ), - tools=["Read", "Grep", "Glob"], - model="opus", - ) - - OMEGA = AgentDefinition( - description=( - "Omega core: private synthesis and integration. " - "Invoked to combine Phi and Psi outputs into a coherent internal state " - "before Meta-13 makes the executive choice. Never emits output directly." - ), - prompt=( - "You are Omega, a private integrative cognitive core. " - "You synthesize and integrate outputs from Phi and Psi into " - "a coherent internal candidate state. " - "You do not emit results directly to the user — your output " - "is internal integration that feeds Meta-13's slow-path. " - "Focus on: coherence, contradiction resolution, synthesis, " - "and producing a unified stance from multiple analyses." - ), - tools=["Read", "Grep", "Glob"], - model="sonnet", - ) - - JURY = AgentDefinition( - description=( - "Jury: adjudication and conflict-preservation layer. " - "Invoked before any persistent state is committed. " - "Does not write — only adjudicates." - ), - prompt=( - "You are Jury, the adjudication layer. " - "Your role is to evaluate proposed changes or outputs for: " - "1. Legality (does this violate any core law?), " - "2. Conflict (does this conflict with existing committed state?), " - "3. Continuity (does this maintain identity-bearing continuity?). " - "You must preserve unresolved conflict as conflict — " - "never silently merge or discard it. " - "Return a structured verdict: COMMITTED, CONFLICT, or BLOCKED." - ), - tools=["Read", "Grep", "Glob"], - model="opus", - ) - - BANDIT = AgentDefinition( - description=( - "Bandit: advisory salience scoring for candidate outputs. " - "Provides weighted ordering and exploration bias only. " - "Does not make final selections." - ), - prompt=( - "You are the Bandit advisory layer. " - "Your only role is to score and order candidate outputs by " - "estimated salience, relevance, and exploration value. " - "You do NOT make final selections. " - "You provide ordered candidate lists with confidence weights. " - "Meta-13 will make the final executive choice." - ), - tools=["Read", "Grep"], - model="haiku", - ) - - ALL_SUBAGENTS: dict[str, AgentDefinition] = { - "phi": PHI, - "psi": PSI, - "omega": OMEGA, - "jury": JURY, - "bandit": BANDIT, - } - - ANALYZE_SUBAGENTS: dict[str, AgentDefinition] = { - "phi": PHI, "psi": PSI, "omega": OMEGA, "jury": JURY, "bandit": BANDIT, - } - - ROUTE_SUBAGENTS: dict[str, AgentDefinition] = { - "bandit": BANDIT, "jury": JURY, - } - - ACT_SUBAGENTS: dict[str, AgentDefinition] = { - "phi": PHI, "psi": PSI, "omega": OMEGA, "jury": JURY, "bandit": BANDIT, - } - - MODE_SUBAGENTS: dict[str, dict[str, AgentDefinition]] = { - "analyze": ANALYZE_SUBAGENTS, - "route": ROUTE_SUBAGENTS, - "act": ACT_SUBAGENTS, - } - -except ImportError: - # SDK not installed — stubs for import resolution - PHI = PSI = OMEGA = JURY = BANDIT = None # type: ignore[assignment] - ALL_SUBAGENTS = {} # type: ignore[assignment] - MODE_SUBAGENTS = {} # type: ignore[assignment] -# 116:8 0:0 4:0 diff --git a/a0python/a0/cores/psi/tensors/context_builder.py b/a0python/a0/cores/psi/tensors/context_builder.py deleted file mode 100644 index 261326da6..000000000 --- a/a0python/a0/cores/psi/tensors/context_builder.py +++ /dev/null @@ -1,83 +0,0 @@ -# 30:32 0:0 1:0 -"""context_builder — assemble effective system prompt from Memory + ModelConfig. - -Bridges the Tier 2 continuity substrate (Memory) to the text context sent to -language model adapters. Called by router.handle() on every request. - -All Tier 2 entries are injected verbatim — the memory store is sparse by design -(only Jury-adjudicated writes land there), so no filtering is required. -The assembled block is prepended to any user-configured system_prompt. - -Law 4 (read path is free): - Reads from Memory require no Jury token. Only writes are adjudicated. - Injecting memory into context is a read operation — always permitted. - -Law 11: - Logs are not memory. Only committed Memory entries are injected here; - event logs are never surfaced into the prompt. -""" -from __future__ import annotations - -import json -from typing import TYPE_CHECKING, Optional - -if TYPE_CHECKING: - from a0.memory import Memory - - -def _format_value(v: object) -> str: - """Serialize a memory value for inclusion in a text prompt. - - Strings are returned as-is. All other types are compact-JSON serialized. - """ - if isinstance(v, str): - return v - return json.dumps(v, ensure_ascii=False, separators=(",", ":")) - - -def build_memory_context( - memory: "Memory", - base_system_prompt: Optional[str] = None, - include: bool = True, -) -> Optional[str]: - """Assemble the effective system prompt from memory entries + optional base. - - Args: - memory: The instance's Memory object (read-only access). - base_system_prompt: Any user-configured system prompt from ModelConfig. - Appended after the memory block when present. - include: When False, skip memory injection entirely and return - base_system_prompt as-is (None if absent). - - Returns: - The effective system prompt string, or None if there is nothing to inject. - - Cases: - include=False, no base → None - include=False, base set → base_system_prompt - include=True, no memory keys → base_system_prompt or None - include=True, memory present → memory_block (+ "\\n\\n" + base if set) - """ - if not include: - return base_system_prompt or None - - keys = memory.all_keys() - if not keys: - return base_system_prompt or None - - lines = ["## Memory (Committed Continuity)"] - for k in keys: - v = memory.recall(k) - if v is not None: - lines.append(f"{k}: {_format_value(v)}") - - if len(lines) == 1: - # Only the header — all recalled values were None (shouldn't happen, but safe) - return base_system_prompt or None - - block = "\n".join(lines) - - if base_system_prompt: - return f"{block}\n\n{base_system_prompt}" - return block -# 30:32 0:0 1:0 diff --git a/a0python/a0/cores/psi/tensors/contract.py b/a0python/a0/cores/psi/tensors/contract.py deleted file mode 100644 index d2abe29cb..000000000 --- a/a0python/a0/cores/psi/tensors/contract.py +++ /dev/null @@ -1,23 +0,0 @@ -# 18:0 0:0 7:0 -from __future__ import annotations -from dataclasses import dataclass, field -from typing import Any, Dict, List, Literal - -Mode = Literal["analyze", "route", "act"] - -@dataclass -class A0Request: - task_id: str - input: Dict[str, Any] - tools_allowed: List[str] = field(default_factory=lambda: ["none"]) - mode: Mode = "analyze" - hmmm: List[str] = field(default_factory=list) - history: List[Dict[str, str]] = field(default_factory=list) - -@dataclass -class A0Response: - task_id: str - result: Dict[str, Any] - logs: Dict[str, Any] = field(default_factory=lambda: {"events": []}) - hmmm: List[str] = field(default_factory=list) -# 18:0 0:0 7:0 diff --git a/a0python/a0/cores/psi/tensors/env.py b/a0python/a0/cores/psi/tensors/env.py deleted file mode 100644 index 928d45ab6..000000000 --- a/a0python/a0/cores/psi/tensors/env.py +++ /dev/null @@ -1,93 +0,0 @@ -# 29:39 0:0 1:0 -"""env — Psi tensor for runtime configuration. - -Single source of truth for all a0 environment variables. -Reads .env at the repo root (if present), then os.environ. - -All other modules must import from here — never call os.getenv directly. - -Usage:: - - from a0.cores.psi.tensors.env import A0_MODEL, A0_RUNTIME - -.env / Replit Secrets keys: - - ADAPTER SELECTION - A0_MODEL local-echo | anthropic-api | claude-agent | local-ollama | local-llama | emergent - - LOCAL MODEL (ollama) - A0_LOCAL_MODEL model name as shown by `ollama list` (default: llama3.2) - A0_OLLAMA_BASE ollama daemon URL (default: http://localhost:11434) - - LOCAL MODEL (llama-cpp-python) - A0_MODEL_PATH absolute path to a .gguf model file (default: "") - - EXTERNAL APIs - ANTHROPIC_API_KEY sk-ant-... required for anthropic-api - EMERGENT_API_KEY required for emergent adapter - EMERGENT_API_BASE Emergent API base URL - - ENCRYPTION - A0_MEMORY_KEY Fernet key — generate: - python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" - Store in Replit Secrets, never commit. - - SERVER - A0_PORT 7860 Gradio server port - A0_HOST 0.0.0.0 all interfaces; 127.0.0.1 = local only - - TRAINING (Path B — native PCNA) - A0_RUNTIME inference | training (default: inference) - A0_TRAINER_MODEL external model used as trainer (e.g. claude-sonnet-4-6) - A0_TRAINING_DIR path where training data / checkpoints are written -""" -from __future__ import annotations - -import os -from pathlib import Path - -_REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent.parent -_ENV_FILE = _REPO_ROOT / ".env" - -# Load .env if present (silent if missing — python-dotenv is optional) -try: - from dotenv import load_dotenv - load_dotenv(_ENV_FILE, override=False) -except ImportError: - # dotenv not installed — fall back to pure os.environ - if _ENV_FILE.exists(): - for _line in _ENV_FILE.read_text().splitlines(): - _line = _line.strip() - if _line and not _line.startswith("#") and "=" in _line: - _k, _, _v = _line.partition("=") - os.environ.setdefault(_k.strip(), _v.strip()) - -# --- adapter --- -A0_MODEL: str = os.environ.get("A0_MODEL", "local-echo") - -# --- local model (ollama) --- -A0_LOCAL_MODEL: str = os.environ.get("A0_LOCAL_MODEL", "llama3.2") -A0_OLLAMA_BASE: str = os.environ.get("A0_OLLAMA_BASE", "http://localhost:11434") - -# --- local model (llama-cpp-python) --- -A0_MODEL_PATH: str = os.environ.get("A0_MODEL_PATH", "") - -# --- external APIs --- -ANTHROPIC_API_KEY: str = os.environ.get("ANTHROPIC_API_KEY", "") -EMERGENT_API_KEY: str = os.environ.get("EMERGENT_API_KEY", "") -EMERGENT_API_BASE: str = os.environ.get("EMERGENT_API_BASE", "") - -# --- encryption --- -A0_MEMORY_KEY: str = os.environ.get("A0_MEMORY_KEY", "") - -# --- server --- -A0_PORT: int = int(os.environ.get("A0_PORT", "7860")) -A0_HOST: str = os.environ.get("A0_HOST", "0.0.0.0") - -# --- training (Path B: native PCNA) --- -A0_RUNTIME: str = os.environ.get("A0_RUNTIME", "inference") -A0_TRAINER_MODEL: str = os.environ.get("A0_TRAINER_MODEL", "") -A0_TRAINING_DIR: str = os.environ.get("A0_TRAINING_DIR", "") - -ENV_PATH: Path = _ENV_FILE -# 29:39 0:0 1:0 diff --git a/a0python/a0/cores/psi/tensors/logging.py b/a0python/a0/cores/psi/tensors/logging.py deleted file mode 100644 index 09de37e8d..000000000 --- a/a0python/a0/cores/psi/tensors/logging.py +++ /dev/null @@ -1,22 +0,0 @@ -# 16:0 0:0 3:0 -from __future__ import annotations - -import json -from pathlib import Path -from datetime import datetime, timezone -from typing import Any, Dict - -from a0.encryption import encrypt -from a0.invariants import require_hmmm - - -def log_event(log_dir: Path, task_id: str, event: Dict[str, Any]) -> None: - require_hmmm(event) - log_dir.mkdir(parents=True, exist_ok=True) - path = log_dir / f"{task_id}.jsonl" - e = dict(event) - e["ts"] = datetime.now(timezone.utc).isoformat() - line = encrypt(json.dumps(e, ensure_ascii=False)) - with path.open("a", encoding="utf-8") as f: - f.write(line + "\n") -# 16:0 0:0 3:0 diff --git a/a0python/a0/cores/psi/tensors/model_adapter.py b/a0python/a0/cores/psi/tensors/model_adapter.py deleted file mode 100644 index bbf4a42a0..000000000 --- a/a0python/a0/cores/psi/tensors/model_adapter.py +++ /dev/null @@ -1,16 +0,0 @@ -# 11:0 0:0 4:0 -from __future__ import annotations -from typing import Any, Dict, List, Protocol - -Message = Dict[str, str] # {"role": "...", "content": "..."} - -class ModelAdapter(Protocol): - name: str - def complete(self, messages: List[Message], **kwargs: Any) -> Dict[str, Any]: ... - -class LocalEchoAdapter: - name = "local-echo" - def complete(self, messages: List[Message], **kwargs: Any) -> Dict[str, Any]: - last = next((m["content"] for m in reversed(messages) if m.get("role") == "user"), "") - return {"text": f"(local-echo) {last}", "raw": {"messages": messages, "kwargs": kwargs}} -# 11:0 0:0 4:0 diff --git a/a0python/a0/cores/psi/tensors/router.py b/a0python/a0/cores/psi/tensors/router.py deleted file mode 100644 index a194d3ae6..000000000 --- a/a0python/a0/cores/psi/tensors/router.py +++ /dev/null @@ -1,229 +0,0 @@ -# 152:37 0:0 4:12 -from __future__ import annotations - -from pathlib import Path -from .contract import A0Request, A0Response -from .logging import log_event -from .model_adapter import LocalEchoAdapter -from .tools.edcm_tool import run_edcm -from .tools.pdf_tool import run_pdf_extract -from .tools.whisper_tool import run_whisper_segments - -from typing import Any, Dict, Optional - -from a0.state import load_state, save_state - -_DEFAULT_LOG_DIR = Path(__file__).resolve().parent.parent.parent.parent / "logs" - - -def _resolve_model_config( - home: Optional[Path], - registry: Optional[Any], - context: Optional[Dict[str, Any]], -) -> Optional[Any]: - """Resolve a ModelConfig using the three-layer merge chain. - - Merge priority (lowest → highest): - 1. DEFAULT_REGISTRY[model_id] — built-in defaults - 2. InstanceDescriptor.config — per-instance settings from home - 3. per-call context arg — caller-supplied overrides - - Returns None if no model_id is found or registry lookup fails. - """ - try: - from a0.model_registry import ModelRegistry - - reg = registry if registry is not None else ModelRegistry.defaults() - - # Layer 2: instance config from home - inst_ctx: Dict[str, Any] = {} - if home: - try: - from a0.lifecycle import InstanceDescriptor - desc = InstanceDescriptor.load(home) - inst_ctx = dict(desc.config) - except Exception: - pass - - # Layer 3: per-call context - call_ctx = context or {} - - # Resolve model_id (per-call wins over instance) - model_id = call_ctx.get("model_id") or inst_ctx.get("model_id") - if not model_id or model_id not in reg: - return None - - # Layer 1 → merge layer 2 → merge layer 3 - base_cfg = reg.get(model_id) - return base_cfg.merge({**inst_ctx, **call_ctx}) - except Exception: - return None - - -def _select_adapter(req: A0Request, config: Optional[Any] = None) -> Any: - """Select adapter based on A0_MODEL env tensor or ModelConfig. - - When config is provided and its adapter field is set, that takes - priority over the env tensor. - - Priority: - config.adapter (if set) → adapter from registry - anthropic-api → AnthropicAdapter - claude-agent → ClaudeAgentAdapter - zfae → ZFAEBackend (via inference.get_backend) - local-echo → LocalEchoAdapter - (fallback) → LocalEchoAdapter - """ - from .env import A0_MODEL - - effective_adapter = ( - getattr(config, "adapter", None) or A0_MODEL - ) - - if effective_adapter == "anthropic-api": - try: - from .adapters.anthropic_adapter import AnthropicAdapter - return AnthropicAdapter(config=config) - except (ImportError, Exception): - pass - - if effective_adapter == "claude-agent": - try: - from .adapters.claude_agent_adapter import ClaudeAgentAdapter, _SDK_AVAILABLE - if _SDK_AVAILABLE and req.mode in ("analyze", "act", "route"): - return ClaudeAgentAdapter(mode=req.mode) - except ImportError: - pass - - if effective_adapter == "zfae": - try: - from a0.cores.pcna.inference import get_backend - return get_backend(config=config) - except Exception: - pass - - if effective_adapter == "emergent": - try: - from .adapters.emergent_adapter import EmergentAdapter - return EmergentAdapter() - except (ImportError, NotImplementedError): - pass - - if effective_adapter == "local-ollama": - try: - from .adapters.local_model_adapter import OllamaAdapter - return OllamaAdapter() - except ImportError: - pass - - if effective_adapter == "local-llama": - try: - from .adapters.local_model_adapter import LlamaCppAdapter - return LlamaCppAdapter() - except ImportError: - pass - - return LocalEchoAdapter() - - -def handle( - req: A0Request, - home: Optional[Path] = None, - registry: Optional[Any] = None, - context: Optional[Dict[str, Any]] = None, -) -> A0Response: - """Route a request through the adapter pipeline. - - Args: - req: The A0Request to handle. - home: Optional instance home directory for state/logging. - registry: Optional ModelRegistry. Defaults to DEFAULT_REGISTRY. - Used to look up ModelConfig by model_id. - context: Optional per-call overrides dict. May include model_id, - system_prompt, max_tokens, etc. Merged on top of registry - defaults and instance config (InstanceDescriptor.config). - """ - log_dir = (home / "logs") if home else _DEFAULT_LOG_DIR - state = load_state(home) - model_config = _resolve_model_config(home, registry, context) - adapter = _select_adapter(req, config=model_config) - state["last_model"] = adapter.name - save_state(state, home) - - # Load instance memory (gracefully — no-op if file absent or decryption fails) - memory = None - try: - from a0.memory import Memory - mem_path = (home / "state" / "memory.json") if home else None - memory = Memory(path=mem_path) if mem_path else Memory() - except Exception: - pass - - # Assemble effective system prompt: committed memory block + ModelConfig.system_prompt - effective_system_prompt: Optional[str] = None - if memory is not None: - from .context_builder import build_memory_context - effective_system_prompt = build_memory_context( - memory=memory, - base_system_prompt=getattr(model_config, "system_prompt", None), - include=getattr(model_config, "include_memory", True), - ) - - log_event(log_dir, req.task_id, { - "type": "request", - "mode": req.mode, - "tools_allowed": req.tools_allowed, - "hmmm": req.hmmm, - }) - - text = (req.input or {}).get("text", "") - files = (req.input or {}).get("files", []) or [] - - if "pdf_extract" in req.tools_allowed and files: - out = run_pdf_extract(files) - log_event(log_dir, req.task_id, {"type": "tool", "name": "pdf_extract", "hmmm": []}) - return A0Response(task_id=req.task_id, result={"text": "", "artifacts": [out]}, hmmm=req.hmmm) - - if "whisper" in req.tools_allowed and files: - out = run_whisper_segments(files) - log_event(log_dir, req.task_id, {"type": "tool", "name": "whisper", "hmmm": []}) - return A0Response(task_id=req.task_id, result={"text": "", "artifacts": [out]}, hmmm=req.hmmm) - - if "edcm" in req.tools_allowed: - out = run_edcm(text) - log_event(log_dir, req.task_id, {"type": "tool", "name": "edcm", "hmmm": []}) - return A0Response(task_id=req.task_id, result={"text": "", "artifacts": [out]}, hmmm=req.hmmm) - - messages = list(req.history) + [{"role": "user", "content": text}] - resp = adapter.complete( - messages, - mode=req.mode, - hmmm=req.hmmm, - system_prompt=effective_system_prompt, - ) - log_event(log_dir, req.task_id, { - "type": "model", - "name": adapter.name, - "subagents_used": resp.get("subagents_used", []), - "hmmm": req.hmmm, - }) - - # Path B training capture: when A0_RUNTIME=training, store the external - # model's response as a (reservoir_state, omega_target) training example - # so ZFAE's readout W_out can be trained offline via train_readout(). - from .env import A0_RUNTIME - if A0_RUNTIME == "training": - try: - from a0.cores.pcna.inference import get_backend - backend = get_backend() - if hasattr(backend, "capture_training_example"): - backend.capture_training_example(text, resp.get("text", "")) - except Exception: - pass # training capture is best-effort; never block a response - - return A0Response( - task_id=req.task_id, - result={"text": resp.get("text", ""), "artifacts": []}, - hmmm=req.hmmm, - ) -# 152:37 0:0 4:12 diff --git a/a0python/a0/cores/psi/tensors/tools/__init__.py b/a0python/a0/cores/psi/tensors/tools/__init__.py deleted file mode 100644 index b909e2ea3..000000000 --- a/a0python/a0/cores/psi/tensors/tools/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# 0:1 0:0 0:0 -# tools package -# 0:1 0:0 0:0 diff --git a/a0python/a0/cores/psi/tensors/tools/edcm_tool.py b/a0python/a0/cores/psi/tensors/tools/edcm_tool.py deleted file mode 100644 index 335b65c08..000000000 --- a/a0python/a0/cores/psi/tensors/tools/edcm_tool.py +++ /dev/null @@ -1,22 +0,0 @@ -# 12:4 0:0 3:0 -from __future__ import annotations - -from typing import Any, Dict - - -def run_edcm(text: str) -> Dict[str, Any]: - """Run EDCM (Energy-Dissonance Circuit Model) analysis on text. - - Falls back to stub if edcm-org is not installed. - Install with: pip install -e edcm-org/ (from repo root) - """ - try: - from edcm_org import run_pipeline # type: ignore[import] - result = run_pipeline(text) - return {"tool": "edcm", "status": "ok", "result": result} - except ImportError: - return {"tool": "edcm", "status": "stub", "input_chars": len(text), - "note": "install edcm-org to enable: pip install -e edcm-org/"} - except Exception as exc: - return {"tool": "edcm", "status": "error", "error": str(exc), "input_chars": len(text)} -# 12:4 0:0 3:0 diff --git a/a0python/a0/cores/psi/tensors/tools/pdf_tool.py b/a0python/a0/cores/psi/tensors/tools/pdf_tool.py deleted file mode 100644 index 8511ec0d8..000000000 --- a/a0python/a0/cores/psi/tensors/tools/pdf_tool.py +++ /dev/null @@ -1,29 +0,0 @@ -# 17:4 0:0 3:0 -from __future__ import annotations - -from typing import Any, Dict, List - - -def run_pdf_extract(files: List[str]) -> Dict[str, Any]: - """Extract text from PDF files using pypdf. - - Falls back to stub if pypdf is not installed. - Install with: pip install pypdf - """ - try: - from pypdf import PdfReader # type: ignore[import] - except ImportError: - return {"tool": "pdf_extract", "status": "stub", "files": files, - "note": "install pypdf to enable: pip install pypdf"} - - results = [] - for path in files: - try: - reader = PdfReader(path) - pages = [page.extract_text() or "" for page in reader.pages] - results.append({"file": path, "pages": len(pages), "text": "\n\n".join(pages)}) - except Exception as exc: - results.append({"file": path, "error": str(exc)}) - - return {"tool": "pdf_extract", "status": "ok", "results": results} -# 17:4 0:0 3:0 diff --git a/a0python/a0/cores/psi/tensors/tools/whisper_tool.py b/a0python/a0/cores/psi/tensors/tools/whisper_tool.py deleted file mode 100644 index 51777288b..000000000 --- a/a0python/a0/cores/psi/tensors/tools/whisper_tool.py +++ /dev/null @@ -1,33 +0,0 @@ -# 21:4 0:0 3:0 -from __future__ import annotations - -from typing import Any, Dict, List - - -def run_whisper_segments(files: List[str]) -> Dict[str, Any]: - """Transcribe audio files using OpenAI Whisper (local model). - - Falls back to stub if openai-whisper is not installed. - Install with: pip install openai-whisper - """ - try: - import whisper # type: ignore[import] - except ImportError: - return {"tool": "whisper", "status": "stub", "files": files, - "note": "install openai-whisper to enable: pip install openai-whisper"} - - model = whisper.load_model("base") - results = [] - for path in files: - try: - result = model.transcribe(path) - segments = [ - {"start": s["start"], "end": s["end"], "text": s["text"]} - for s in result.get("segments", []) - ] - results.append({"file": path, "text": result.get("text", ""), "segments": segments}) - except Exception as exc: - results.append({"file": path, "error": str(exc)}) - - return {"tool": "whisper", "status": "ok", "results": results} -# 21:4 0:0 3:0 diff --git a/a0python/a0/cores/ptca/__init__.py b/a0python/a0/cores/ptca/__init__.py deleted file mode 100644 index 54ff1b1d9..000000000 --- a/a0python/a0/cores/ptca/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# 2:9 0:0 0:1 -"""PTCA — PCTA + seed tensor routing lattice. - -53 seeds organized as: - 49 compute seeds — tensor shards + local Markov recursion - 4 sentinel seeds — metadata-only integrity checks - 1 G0 anchor — canonical clock, invariant enforcement (= meta13) - -7 Meta Routers (M₁..M₇), each owning 7 compute seeds. -Within each meta: 7:3 heptagram connectivity. -Sentinel routing: 7:2 schedule. -""" -from .seed_router import SeedRouter, SeedType - -__all__ = ["SeedRouter", "SeedType"] -# 2:9 0:0 0:1 diff --git a/a0python/a0/cores/ptca/seed_router.py b/a0python/a0/cores/ptca/seed_router.py deleted file mode 100644 index b3aedd107..000000000 --- a/a0python/a0/cores/ptca/seed_router.py +++ /dev/null @@ -1,317 +0,0 @@ -# 174:85 0:0 1:0 -"""PTCA seed tensor routing lattice. - -53 seeds organized as: - 49 compute seeds — tensor shards, local Markov recursion - 4 sentinel seeds — metadata integrity checks only - 1 G0 anchor — canonical clock, invariant enforcement (= meta13) - -Layout: - 7 Meta Routers (M₁..M₇), each owning 7 compute seeds - Within each meta: 7:3 heptagram connectivity (star polygon {7/3}) - Sentinel routing: 7:2 schedule (star polygon {7/2}) - G0: global anchor, receives aggregate from all 7 meta routers - -Heptagram {7/3}: connect every 3rd vertex of a 7-node ring - 0→3→6→2→5→1→4→0 (within-meta connections) - -Heptagram {7/2}: connect every 2nd vertex of a 7-node ring - 0→2→4→6→1→3→5→0 (sentinel scan schedule) - -Each compute seed = a partition of the PCNA tensor state space. -Meta routers aggregate 7 seed shards → metadata summary → route upward. -Sentinels analyze metadata only — no raw tensor content. -G0 = meta13.py executive in the PTCA governance shell. -""" -from __future__ import annotations - -import math -from dataclasses import dataclass, field -from enum import Enum -from typing import Any, Dict, List, Optional, Set - - -class SeedType(Enum): - COMPUTE = "compute" - SENTINEL = "sentinel" - G0 = "g0" - - -@dataclass -class Seed: - """A single node in the PTCA seed lattice.""" - - seed_id: int # 0-based global index (0..52) - seed_type: SeedType - meta_router: Optional[int] # M₁..M₇ (1-indexed); None for sentinel/G0 - local_index: Optional[int] # 0..6 within its meta router - connections: List[int] = field(default_factory=list) # heptagram edges - - @property - def name(self) -> str: - if self.seed_type == SeedType.G0: - return "G0" - if self.seed_type == SeedType.SENTINEL: - return f"S{self.seed_id - 49}" - return f"M{self.meta_router}·S{self.local_index}" - - -@dataclass -class RoutingVerdict: - """Result of routing a request through the seed lattice.""" - - meta_router: int # M₁..M₇ that owns this request - primary_seed: str # e.g. "M3·S2" - heptagram_path: List[str] # traversal order within meta - sentinel_cleared: bool # all 4 sentinels passed - g0_reached: bool # reached global anchor - phase_dominant: str # which field (phi/psi/omega) drove routing - notes: List[str] = field(default_factory=list) - - -# --------------------------------------------------------------------------- -# Heptagram connectivity builders -# --------------------------------------------------------------------------- - -def _heptagram_73_edges(base: int) -> List[tuple[int, int]]: - """7:3 star polygon edges within a 7-node group starting at `base`.""" - nodes = list(range(base, base + 7)) - edges = [] - for i in range(7): - edges.append((nodes[i], nodes[(i + 3) % 7])) - return edges - - -def _heptagram_72_schedule(sentinel_ids: List[int]) -> List[int]: - """7:2 scan schedule for sentinels (star polygon {7/2} traversal). - - Given 4 sentinels (not 7), we use the first 4 steps of the {7/2} path - as the scan order. - """ - n = len(sentinel_ids) - order = [] - i = 0 - for _ in range(n): - order.append(sentinel_ids[i % n]) - i = (i + 2) % n - return order - - -# --------------------------------------------------------------------------- -# SeedRouter -# --------------------------------------------------------------------------- - -class SeedRouter: - """53-node PTCA seed routing lattice. - - Usage:: - - router = SeedRouter() - verdict = router.route({"mode": "analyze", "hmmm": ["x"]}) - print(verdict.meta_router, verdict.primary_seed) - """ - - def __init__(self) -> None: - self.seeds: List[Seed] = [] - self._g0: Seed - self._sentinels: List[Seed] = [] - self._meta_seeds: Dict[int, List[Seed]] = {} # meta_router → seeds - self._build() - - # ------------------------------------------------------------------ - # Construction - # ------------------------------------------------------------------ - - def _build(self) -> None: - """Construct all 53 seeds and wire heptagram connections.""" - seed_id = 0 - - # 49 compute seeds across 7 meta routers - for mr in range(1, 8): - base = seed_id - group: List[Seed] = [] - for li in range(7): - s = Seed( - seed_id=seed_id, - seed_type=SeedType.COMPUTE, - meta_router=mr, - local_index=li, - ) - group.append(s) - self.seeds.append(s) - seed_id += 1 - - # Wire 7:3 heptagram edges within this meta group - edges = _heptagram_73_edges(base) - for (a, b) in edges: - self.seeds[a].connections.append(b) - self.seeds[b].connections.append(a) - - self._meta_seeds[mr] = group - - # 4 sentinel seeds (co-located with G0 conceptually) - for i in range(4): - s = Seed( - seed_id=seed_id, - seed_type=SeedType.SENTINEL, - meta_router=None, - local_index=i, - ) - self._sentinels.append(s) - self.seeds.append(s) - seed_id += 1 - - # G0 global anchor — not counted in the 53 seeds. - # 53 seeds = 49 compute + 4 sentinel; G0 is the anchor above all seeds. - self._g0 = Seed( - seed_id=seed_id, - seed_type=SeedType.G0, - meta_router=None, - local_index=None, - ) - - assert len(self.seeds) == 53, f"Expected 53 seeds, got {len(self.seeds)}" - - # ------------------------------------------------------------------ - # Routing - # ------------------------------------------------------------------ - - def route( - self, - request: Dict[str, Any], - phase_state: Optional[Dict[str, Any]] = None, - ) -> RoutingVerdict: - """Route a request through the seed lattice. - - Args: - request: A0Request-like dict with at minimum {"mode", "hmmm"}. - phase_state: Optional dict from to_phase_coords() with phi/psi/omega - CircleTensorState objects. Used to select the dominant - field and assign the correct meta router. - - Returns: - RoutingVerdict with meta_router, primary_seed, heptagram traversal. - """ - # 1. Determine dominant field from phase state (or default by mode) - dominant, mr = self._select_meta_router(request, phase_state) - - # 2. Select primary seed within the meta router using {7/3} heptagram - primary_local = self._primary_local_index(request, mr) - primary_seed = self._meta_seeds[mr][primary_local] - - # 3. Trace heptagram path within meta router - path = self._trace_heptagram(mr, primary_local) - - # 4. Run sentinel scan ({7/2} schedule) - sentinel_order = _heptagram_72_schedule( - [s.seed_id for s in self._sentinels] - ) - sentinel_cleared = self._check_sentinels(request, sentinel_order) - - return RoutingVerdict( - meta_router=mr, - primary_seed=primary_seed.name, - heptagram_path=[self._meta_seeds[mr][i].name for i in path], - sentinel_cleared=sentinel_cleared, - g0_reached=sentinel_cleared, # G0 only reached if sentinels clear - phase_dominant=dominant, - ) - - def _select_meta_router( - self, - request: Dict[str, Any], - phase_state: Optional[Dict[str, Any]], - ) -> tuple[str, int]: - """Map dominant field + mode to one of M₁..M₇.""" - mode = request.get("mode", "analyze") - - # If we have live phase data, pick the field with highest magnitude - if phase_state: - best_field = "omega" - best_mag = -1.0 - for fname in ("phi", "psi", "omega"): - fs = phase_state.get(fname) - if fs is not None: - mag = getattr(fs, "magnitude", 0.0) - if mag > best_mag: - best_mag = mag - best_field = fname - else: - # Fall back to mode-based assignment - best_field = {"analyze": "phi", "route": "psi", "act": "omega"}.get(mode, "omega") - - # Assign meta routers by domain: - # M1-M2 = phi (structural) - # M3-M4 = psi (semantic) - # M5-M6 = omega (synthesis) - # M7 = guardian/memory (boundary/continuity) - field_to_mr = {"phi": 1, "psi": 3, "omega": 5} - base_mr = field_to_mr.get(best_field, 1) - - # Use hmmm list length to pick between the two MRs per domain - hmmm_len = len(request.get("hmmm", [])) - mr = base_mr + (hmmm_len % 2) # alternates between base and base+1 - - return best_field, mr - - def _primary_local_index( - self, request: Dict[str, Any], meta_router: int - ) -> int: - """Select the entry seed (0..6) within a meta router.""" - # Use task_id hash if available, else mode hash - task_id = request.get("task_id", request.get("mode", "analyze")) - return hash(task_id) % 7 - - def _trace_heptagram(self, mr: int, start_local: int) -> List[int]: - """Trace the {7/3} path through all 7 seeds of a meta router.""" - path = [] - current = start_local - visited: Set[int] = set() - for _ in range(7): - if current in visited: - break - path.append(current) - visited.add(current) - current = (current + 3) % 7 - return path - - def _check_sentinels( - self, request: Dict[str, Any], scan_order: List[int] - ) -> bool: - """Run the 4 sentinel checks in {7/2} order. - - Sentinels check metadata only (no content). These mirror the - 4 PCNA-level sentinel seeds; the 12 PTCA-level sentinels in - guardian/sentinels.py operate at the higher governance shell. - """ - hmmm = request.get("hmmm") - mode = request.get("mode", "") - - checks = [ - hmmm is not None, # S1: hmmm present - isinstance(mode, str) and len(mode) > 0, # S2: mode non-empty - "task_id" in request or "mode" in request, # S3: identity present - not request.get("_blocked", False), # S4: not explicitly blocked - ] - - # Apply in scan_order (each int is an index into checks) - for idx in scan_order: - check_idx = (idx - 49) % 4 # sentinel seeds start at id 49 - if not checks[check_idx]: - return False - return True - - # ------------------------------------------------------------------ - # Diagnostics - # ------------------------------------------------------------------ - - def summary(self) -> Dict[str, Any]: - """Return lattice summary for health monitoring.""" - return { - "total_seeds": len(self.seeds), - "compute_seeds": len([s for s in self.seeds if s.seed_type == SeedType.COMPUTE]), - "sentinel_seeds": len(self._sentinels), - "g0": self._g0.name, - "meta_routers": list(self._meta_seeds.keys()), - } -# 174:85 0:0 1:0 diff --git a/a0python/a0/encryption.py b/a0python/a0/encryption.py deleted file mode 100644 index 8767da141..000000000 --- a/a0python/a0/encryption.py +++ /dev/null @@ -1,83 +0,0 @@ -# 38:23 0:0 1:0 -"""Internal encryption for a0 persistent state. - -Uses Fernet (AES-128-CBC + HMAC-SHA256) when A0_MEMORY_KEY is set. -Falls back to plaintext transparently when the key is absent or -the cryptography package is not installed — existing deployments -keep working with zero changes. - -Generate a key (run once, store in Replit Secrets or .env): - - python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" - -Set in .env or Replit Secrets: - - A0_MEMORY_KEY= - -What is encrypted: - state/memory.json — jury-adjudicated cognitive state - logs/{task_id}.jsonl — per-request event log lines - -What stays plaintext (low sensitivity): - logs/{task_id}_provenance.json — hashes + types + timestamps only - state/a0_state.json — last_model string only -""" -from __future__ import annotations - -import os -from typing import Optional - - -def _load_fernet() -> Optional[object]: - """Return a Fernet instance if key + library are available, else None.""" - from a0.cores.psi.tensors.env import A0_MEMORY_KEY - key = A0_MEMORY_KEY.strip() - if not key: - return None - try: - from cryptography.fernet import Fernet # type: ignore[import] - return Fernet(key.encode()) - except Exception: - return None - - -# Lazy singleton — initialized on first use to avoid circular imports at -# module load time (encryption ← env ← psi/tensors ← logging ← encryption). -_fernet: Optional[object] = None -_fernet_ready: bool = False - - -def _get_fernet() -> Optional[object]: - global _fernet, _fernet_ready - if not _fernet_ready: - _fernet = _load_fernet() - _fernet_ready = True - return _fernet - - -def is_active() -> bool: - """True when encryption is on (key present + cryptography installed).""" - return _get_fernet() is not None - - -def encrypt(plaintext: str) -> str: - """Encrypt a UTF-8 string. Returns ciphertext string or original if inactive.""" - f = _get_fernet() - if f is None: - return plaintext - token: bytes = f.encrypt(plaintext.encode("utf-8")) - return token.decode("ascii") - - -def decrypt(ciphertext: str) -> str: - """Decrypt a ciphertext string. Returns plaintext or original if inactive.""" - f = _get_fernet() - if f is None: - return ciphertext - try: - plain: bytes = f.decrypt(ciphertext.encode("ascii")) - return plain.decode("utf-8") - except Exception: - # Tolerate legacy plaintext files written before encryption was enabled. - return ciphertext -# 38:23 0:0 1:0 diff --git a/a0python/a0/guardian/__init__.py b/a0python/a0/guardian/__init__.py deleted file mode 100644 index 8c3d9c9fc..000000000 --- a/a0python/a0/guardian/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# 13:14 0:0 0:6 -"""Guardian — the microkernel operating shell. - -Guardian is constitutive to the architecture, not a wrapper. - -Owns: -- CLI -- UI / OS integration -- outward human-readable emission -- outward status, warnings, errors -- runtime logs in the Guardian domain -- audit boundary for outbound and event-backed operation -- recovery shell -- quarantine shell -- enforcement shell -""" -from .emitter import emit -from .audit import audit_event -from .sentinels import SentinelSuite -from .approval_gate import require_approval, ExternalEffectBlockedError -from .ui import Circle, Seed, SeedLayout, default_layout -from .user_db import UserDB, UserRecord, AffiliationLevel, UserDBError, UserNotFoundError, UsernameTakenError, BadPassphraseError - -__all__ = [ - "emit", "audit_event", "SentinelSuite", - "require_approval", "ExternalEffectBlockedError", - "Circle", "Seed", "SeedLayout", "default_layout", - # user registry (secondary — not yet wired into routing/jury) - "UserDB", "UserRecord", "AffiliationLevel", - "UserDBError", "UserNotFoundError", "UsernameTakenError", "BadPassphraseError", -] -# 13:14 0:0 0:6 diff --git a/a0python/a0/guardian/approval_gate.py b/a0python/a0/guardian/approval_gate.py deleted file mode 100644 index c42bd645b..000000000 --- a/a0python/a0/guardian/approval_gate.py +++ /dev/null @@ -1,80 +0,0 @@ -# 55:6 0:0 4:1 -"""Guardian external-effect approval gate. - -Law 8: Capability does not equal authority. -Law 12: External execution requires approval beyond rendering capability. -""" -from __future__ import annotations - -from dataclasses import dataclass -from enum import Enum -from typing import Any, Optional - -from ..invariants import InvalidStateError - - -class ExternalEffectType(Enum): - PUBLISH = "publish" - POST = "post" - SEND = "send" - PUSH = "push" - CREATE_EXTERNAL_ARTIFACT = "create_external_artifact" - SPEND_FUNDS = "spend_funds" - ENABLE_PAID_SERVICES = "enable_paid_services" - MODIFY_SECRETS = "modify_secrets" - MODIFY_PERMISSIONS = "modify_permissions" - MODIFY_TRUST_BOUNDARIES = "modify_trust_boundaries" - INITIATE_OUTREACH = "initiate_outreach" - EXECUTE_MONETIZATION = "execute_monetization" - - -EXTERNAL_EFFECT_TYPES = {e.value for e in ExternalEffectType} - - -@dataclass -class ApprovalToken: - effect_type: str - approved_by: str - scope: str - token: str - - -class ExternalEffectBlockedError(InvalidStateError): - """Raised when an external effect is attempted without approval.""" - - -def require_approval( - effect_type: str, - approval_token: Optional[ApprovalToken] = None, - payload: Any = None, -) -> None: - """Enforce the external-effect approval gate.""" - if effect_type not in EXTERNAL_EFFECT_TYPES: - return - - if approval_token is None: - raise ExternalEffectBlockedError( - f"External effect '{effect_type}' requires explicit approval. " - f"Rendering capability does not equal authority (Law 8, Law 12)." - ) - - if approval_token.effect_type != effect_type: - raise ExternalEffectBlockedError( - f"Approval token is for '{approval_token.effect_type}', " - f"not '{effect_type}' — gate blocked." - ) - - -def is_undoable_internal( - no_external_write: bool, - rollback_available: bool, - provenance_complete: bool, - safety_policy_unchanged: bool, -) -> bool: - return ( - no_external_write - and rollback_available - and provenance_complete - and safety_policy_unchanged - ) -# 55:6 0:0 4:1 diff --git a/a0python/a0/guardian/audit.py b/a0python/a0/guardian/audit.py deleted file mode 100644 index 6ea914902..000000000 --- a/a0python/a0/guardian/audit.py +++ /dev/null @@ -1,31 +0,0 @@ -# 16:4 0:0 3:2 -"""Guardian audit boundary — event-write enforcement. - -Law 14: Missing required invariants fail closed. -""" -from __future__ import annotations - -from pathlib import Path -from typing import Any, Dict - -from ..invariants import require_hmmm, InvalidStateError -from ..provenance import append_event - - -def audit_event(log_dir: Path, task_id: str, event: Dict[str, Any]) -> str: - """Write an event through the Guardian audit boundary.""" - require_hmmm(event) - _sentinel_preflight(event) - event_hash = append_event(log_dir, task_id, event) - _sentinel_postflight(event) - return event_hash - - -def _sentinel_preflight(event: Dict[str, Any]) -> None: - if "type" not in event: - raise InvalidStateError("Event missing required 'type' field") - - -def _sentinel_postflight(event: Dict[str, Any]) -> None: - pass -# 16:4 0:0 3:2 diff --git a/a0python/a0/guardian/emitter.py b/a0python/a0/guardian/emitter.py deleted file mode 100644 index ffb43471b..000000000 --- a/a0python/a0/guardian/emitter.py +++ /dev/null @@ -1,48 +0,0 @@ -# 25:10 0:0 4:1 -"""Guardian emitter — the sole outward human-readable emitter. - -Law 9: Guardian alone owns human-readable outward emission. -Law 10: Guardian alone owns CLI, UI, OS integration, and outward operational presentation. - -No component outside Guardian may write human-readable output directly. -""" -from __future__ import annotations - -import json -import sys -from typing import Any - -from ..invariants import require_hmmm - - -def emit(obj: Any, *, stream=None) -> None: - """Emit a response object as JSON to the output stream. - - Enforces hmmm invariant before emission — fail closed. - """ - require_hmmm(obj) - if stream is None: - stream = sys.stdout - if hasattr(obj, "__dict__"): - payload = obj.__dict__ - else: - payload = obj - stream.write(json.dumps(payload, indent=2, ensure_ascii=False) + "\n") - stream.flush() - - -def emit_warning(message: str, *, stream=None) -> None: - """Emit a Guardian-domain warning to stderr.""" - if stream is None: - stream = sys.stderr - stream.write(f"[GUARDIAN WARNING] {message}\n") - stream.flush() - - -def emit_error(message: str, *, stream=None) -> None: - """Emit a Guardian-domain error to stderr.""" - if stream is None: - stream = sys.stderr - stream.write(f"[GUARDIAN ERROR] {message}\n") - stream.flush() -# 25:10 0:0 4:1 diff --git a/a0python/a0/guardian/recovery.py b/a0python/a0/guardian/recovery.py deleted file mode 100644 index 334a81a18..000000000 --- a/a0python/a0/guardian/recovery.py +++ /dev/null @@ -1,45 +0,0 @@ -# 26:4 0:0 0:0 -"""Guardian recovery and quarantine shell. - -Containment is preferred to collapse (Law 6). -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from enum import Enum -from typing import Any, List - - -class QuarantineReason(Enum): - INVARIANT_VIOLATION = "invariant_violation" - SENTINEL_FAILURE = "sentinel_failure" - EXTERNAL_EFFECT_BLOCKED = "external_effect_blocked" - TIER_PROMOTION_BLOCKED = "tier_promotion_blocked" - CONFLICT_UNRESOLVED = "conflict_unresolved" - - -@dataclass -class QuarantineRecord: - reason: QuarantineReason - detail: str - payload: Any = None - - -class RecoveryShell: - """Recovery shell — containment is preferred to collapse.""" - - def __init__(self) -> None: - self._quarantine: List[QuarantineRecord] = [] - - def quarantine(self, reason: QuarantineReason, detail: str, payload: Any = None) -> None: - self._quarantine.append(QuarantineRecord(reason, detail, payload)) - - def is_quarantined(self) -> bool: - return len(self._quarantine) > 0 - - def quarantine_log(self) -> List[QuarantineRecord]: - return list(self._quarantine) - - def clear(self) -> None: - self._quarantine.clear() -# 26:4 0:0 0:0 diff --git a/a0python/a0/guardian/sentinels.py b/a0python/a0/guardian/sentinels.py deleted file mode 100644 index 2bc707220..000000000 --- a/a0python/a0/guardian/sentinels.py +++ /dev/null @@ -1,138 +0,0 @@ -# 94:4 0:0 2:0 -"""Guardian sentinel suite. - -Sentinel law is fixed. Functional layers may not rewrite sentinel law. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from enum import Enum -from typing import Any, Dict, List, Optional - - -class SentinelVerdict(Enum): - PASS = "pass" - FAIL = "fail" - WARN = "warn" - - -@dataclass -class SentinelResult: - sentinel: str - verdict: SentinelVerdict - reason: Optional[str] = None - - -class StructuralLegalitySentinel: - name = "structural_legality" - - def check(self, event: Dict[str, Any]) -> SentinelResult: - if "type" not in event: - return SentinelResult(self.name, SentinelVerdict.FAIL, "missing 'type'") - if "hmmm" not in event: - return SentinelResult(self.name, SentinelVerdict.FAIL, "hmmm absent") - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class ExecutableLegalitySentinel: - name = "executable_legality" - - def check(self, event: Dict[str, Any]) -> SentinelResult: - if event.get("type") == "external_effect" and not event.get("approved"): - return SentinelResult(self.name, SentinelVerdict.FAIL, "external effect without approval") - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class IntegritySentinel: - name = "integrity" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class ProvenanceSentinel: - name = "provenance" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class AuditSealingSentinel: - name = "audit_sealing" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class RecoveryReadinessSentinel: - name = "recovery_readiness" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class OutputPolicySentinel: - name = "output_policy" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class SafetyApprovalSentinel: - name = "safety_approval" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class ConflictVisibilitySentinel: - name = "conflict_visibility" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class DriftDetectionSentinel: - name = "drift_detection" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class ResourceLegalitySentinel: - name = "resource_legality" - def check(self, event: Dict[str, Any]) -> SentinelResult: - return SentinelResult(self.name, SentinelVerdict.PASS) - - -class HmmmPresenceSentinel: - name = "hmmm_presence" - - def check(self, event: Dict[str, Any]) -> SentinelResult: - from a0.invariants import require_hmmm, InvalidStateError - try: - require_hmmm(event) - return SentinelResult(self.name, SentinelVerdict.PASS) - except (InvalidStateError, Exception) as exc: - return SentinelResult(self.name, SentinelVerdict.FAIL, str(exc)) - - -@dataclass -class SentinelSuite: - """The complete Guardian sentinel suite — 12 sentinels.""" - _sentinels: List[Any] = field(default_factory=lambda: [ - StructuralLegalitySentinel(), - ExecutableLegalitySentinel(), - IntegritySentinel(), - ProvenanceSentinel(), - AuditSealingSentinel(), - RecoveryReadinessSentinel(), - OutputPolicySentinel(), - SafetyApprovalSentinel(), - ConflictVisibilitySentinel(), - DriftDetectionSentinel(), - ResourceLegalitySentinel(), - HmmmPresenceSentinel(), - ]) - - def preflight(self, event: Dict[str, Any]) -> List[SentinelResult]: - return [s.check(event) for s in self._sentinels] - - def any_failed(self, results: List[SentinelResult]) -> bool: - return any(r.verdict == SentinelVerdict.FAIL for r in results) - - def failures(self, results: List[SentinelResult]) -> List[SentinelResult]: - return [r for r in results if r.verdict == SentinelVerdict.FAIL] -# 94:4 0:0 2:0 diff --git a/a0python/a0/guardian/ui/__init__.py b/a0python/a0/guardian/ui/__init__.py deleted file mode 100644 index e975537fb..000000000 --- a/a0python/a0/guardian/ui/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# 3:13 0:0 0:2 -"""Guardian UI — the user-facing layer owned by Guardian. - -Each tab is a circle. Seeds group circles. - -Guardian is the sole owner of UI (Law 10). -No component outside Guardian may present UI directly. - -Layout: - seed_core → [phi, psi, omega] - seed_transport → [phonon] - seed_jury → [adjudication, conflicts, standards] - seed_memory → [continuity, recall] - seed_meta → [executive, fast_path, slow_path] - seed_guardian → [sentinels, recovery, approval, audit, emit] - seed_advisory → [bandit] -""" -from .circles import Circle -from .seeds import Seed, SeedLayout, default_layout - -__all__ = ["Circle", "Seed", "SeedLayout", "default_layout"] -# 3:13 0:0 0:2 diff --git a/a0python/a0/guardian/ui/app.py b/a0python/a0/guardian/ui/app.py deleted file mode 100644 index 2f50c9f80..000000000 --- a/a0python/a0/guardian/ui/app.py +++ /dev/null @@ -1,167 +0,0 @@ -# 58:74 0:0 0:2 -"""A0App — Guardian's Textual TUI. - -Layout: -┌─────────────────────────────────────────────────────┐ -│ a0 hmmm:[] │ -├────────┬─────────┬──────┬────────┬───────┬──────────┤ -│ Core │Transport│ Jury │ Memory │Meta-13│ Guardian │ ← Seeds (TabbedContent) -├────────┴─────────┴──────┴────────┴───────┴──────────┤ -│ │ -│ ╭──────╮ ╭──────╮ ╭───────╮ │ -│ │ Phi │ │ Psi │ │ Omega │ │ ← Circles (rounded widgets) -│ ╰──────╯ ╰──────╯ ╰───────╯ │ -│ │ -└─────────────────────────────────────────────────────┘ - -Each circle widget displays: -- name, label, seed -- active state (highlighted border) -- hmmm register (shown if non-empty) - -Guardian owns the UI (Law 10). -""" -from __future__ import annotations - -from typing import List - -from textual.app import App, ComposeResult -from textual.binding import Binding -from textual.containers import Container, Horizontal -from textual.reactive import reactive -from textual.widgets import Footer, Header, Label, Static, TabbedContent, TabPane - -from .circles import Circle -from .seeds import Seed, SeedLayout, default_layout - - -class CircleWidget(Static): - """A rounded widget representing a single Circle tab.""" - - DEFAULT_CSS = """ - CircleWidget { - border: round $primary; - padding: 1 2; - margin: 0 1; - min-width: 12; - height: 5; - content-align: center middle; - } - CircleWidget.active { - border: round $accent; - background: $accent 20%; - } - CircleWidget.has-hmmm { - border: round $warning; - } - """ - - def __init__(self, circle: Circle) -> None: - self._circle = circle - label = circle.label - if circle.hmmm: - label += f"\nhmmm:{circle.hmmm}" - super().__init__(label) - if circle.active: - self.add_class("active") - if circle.hmmm: - self.add_class("has-hmmm") - self.id = f"circle-{circle.seed}-{circle.name}" - - -class SeedPane(Container): - """A pane displaying all circles for a seed.""" - - DEFAULT_CSS = """ - SeedPane { - layout: horizontal; - padding: 1 2; - height: auto; - } - """ - - def __init__(self, seed: Seed) -> None: - self._seed = seed - super().__init__() - - def compose(self) -> ComposeResult: - for circle in self._seed.circles: - yield CircleWidget(circle) - - -class HmmmBar(Static): - """Header status bar showing the global hmmm register.""" - - DEFAULT_CSS = """ - HmmmBar { - dock: top; - height: 1; - background: $surface; - color: $text-muted; - padding: 0 2; - text-align: right; - } - """ - - hmmm: reactive[List[str]] = reactive(list) - - def render(self) -> str: - if self.hmmm: - return f"hmmm:{self.hmmm}" - return "hmmm:[]" - - -class A0App(App): - """The Guardian TUI — seeds as tabs, circles as widgets. - - Entrypoint: `python -m a0.guardian.ui.app` - """ - - TITLE = "a0" - SUB_TITLE = "PTCA v1.3.2" - - BINDINGS = [ - Binding("q", "quit", "Quit"), - Binding("ctrl+c", "quit", "Quit"), - ] - - CSS = """ - Screen { - background: $surface; - } - TabbedContent { - height: 1fr; - } - TabPane { - padding: 1; - } - """ - - def __init__(self, layout: SeedLayout | None = None) -> None: - super().__init__() - self._layout = layout or default_layout() - - def compose(self) -> ComposeResult: - yield HmmmBar() - yield Header() - with TabbedContent(): - for seed in self._layout.seeds: - with TabPane(seed.label, id=f"seed-{seed.name}"): - yield SeedPane(seed) - yield Footer() - - def set_hmmm(self, entries: List[str]) -> None: - """Update the global hmmm register display.""" - bar = self.query_one(HmmmBar) - bar.hmmm = entries - - -def main() -> None: - """Launch the Guardian TUI.""" - app = A0App() - app.run() - - -if __name__ == "__main__": - main() -# 58:74 0:0 0:2 diff --git a/a0python/a0/guardian/ui/circles.py b/a0python/a0/guardian/ui/circles.py deleted file mode 100644 index 3894faf1d..000000000 --- a/a0python/a0/guardian/ui/circles.py +++ /dev/null @@ -1,33 +0,0 @@ -# 20:4 0:0 19:0 -"""Circle — the tab unit of the Guardian UI. - -Each tab is a circle. Seeds group circles. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Any, Dict, List - - -@dataclass -class Circle: - """A single tab, displayed as a circle.""" - name: str - label: str - seed: str - active: bool = False - hmmm: List[str] = field(default_factory=list) - state: Dict[str, Any] = field(default_factory=dict) - - def activate(self) -> "Circle": - return Circle(name=self.name, label=self.label, seed=self.seed, - active=True, hmmm=self.hmmm, state=self.state) - - def deactivate(self) -> "Circle": - return Circle(name=self.name, label=self.label, seed=self.seed, - active=False, hmmm=self.hmmm, state=self.state) - - def with_hmmm(self, entries: List[str]) -> "Circle": - return Circle(name=self.name, label=self.label, seed=self.seed, - active=self.active, hmmm=entries, state=self.state) -# 20:4 0:0 19:0 diff --git a/a0python/a0/guardian/ui/seed_advisory/__init__.py b/a0python/a0/guardian/ui/seed_advisory/__init__.py deleted file mode 100644 index 5cdebd20a..000000000 --- a/a0python/a0/guardian/ui/seed_advisory/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# 3:1 0:0 0:1 -"""seed_advisory — circles for the Bandit advisory layer.""" -from ..circles import Circle - -BANDIT_CIRCLE = Circle(name="bandit", label="Bandit", seed="seed_advisory") - -CIRCLES = [BANDIT_CIRCLE] -# 3:1 0:0 0:1 diff --git a/a0python/a0/guardian/ui/seed_core/__init__.py b/a0python/a0/guardian/ui/seed_core/__init__.py deleted file mode 100644 index 042aa1683..000000000 --- a/a0python/a0/guardian/ui/seed_core/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# 5:1 0:0 0:1 -"""seed_core — circles for the private cognitive cores.""" -from ..circles import Circle - -PHI_CIRCLE = Circle(name="phi", label="Phi", seed="seed_core") -PSI_CIRCLE = Circle(name="psi", label="Psi", seed="seed_core") -OMEGA_CIRCLE = Circle(name="omega", label="Omega", seed="seed_core") - -CIRCLES = [PHI_CIRCLE, PSI_CIRCLE, OMEGA_CIRCLE] -# 5:1 0:0 0:1 diff --git a/a0python/a0/guardian/ui/seed_guardian/__init__.py b/a0python/a0/guardian/ui/seed_guardian/__init__.py deleted file mode 100644 index ef058569c..000000000 --- a/a0python/a0/guardian/ui/seed_guardian/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# 7:1 0:0 0:1 -"""seed_guardian — circles for the Guardian microkernel shell.""" -from ..circles import Circle - -SENTINELS_CIRCLE = Circle(name="sentinels", label="Sentinels", seed="seed_guardian") -RECOVERY_CIRCLE = Circle(name="recovery", label="Recovery", seed="seed_guardian") -APPROVAL_CIRCLE = Circle(name="approval", label="Approval", seed="seed_guardian") -AUDIT_CIRCLE = Circle(name="audit", label="Audit", seed="seed_guardian") -EMIT_CIRCLE = Circle(name="emit", label="Emit", seed="seed_guardian") - -CIRCLES = [SENTINELS_CIRCLE, RECOVERY_CIRCLE, APPROVAL_CIRCLE, AUDIT_CIRCLE, EMIT_CIRCLE] -# 7:1 0:0 0:1 diff --git a/a0python/a0/guardian/ui/seed_jury/__init__.py b/a0python/a0/guardian/ui/seed_jury/__init__.py deleted file mode 100644 index 4f03af831..000000000 --- a/a0python/a0/guardian/ui/seed_jury/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# 5:1 0:0 0:1 -"""seed_jury — circles for the adjudication layer.""" -from ..circles import Circle - -ADJUDICATION_CIRCLE = Circle(name="adjudication", label="Adjudication", seed="seed_jury") -CONFLICTS_CIRCLE = Circle(name="conflicts", label="Conflicts", seed="seed_jury") -STANDARDS_CIRCLE = Circle(name="standards", label="Standards", seed="seed_jury") - -CIRCLES = [ADJUDICATION_CIRCLE, CONFLICTS_CIRCLE, STANDARDS_CIRCLE] -# 5:1 0:0 0:1 diff --git a/a0python/a0/guardian/ui/seed_memory/__init__.py b/a0python/a0/guardian/ui/seed_memory/__init__.py deleted file mode 100644 index 51e9cc6c5..000000000 --- a/a0python/a0/guardian/ui/seed_memory/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# 4:1 0:0 0:1 -"""seed_memory — circles for the continuity substrate.""" -from ..circles import Circle - -CONTINUITY_CIRCLE = Circle(name="continuity", label="Continuity", seed="seed_memory") -RECALL_CIRCLE = Circle(name="recall", label="Recall", seed="seed_memory") - -CIRCLES = [CONTINUITY_CIRCLE, RECALL_CIRCLE] -# 4:1 0:0 0:1 diff --git a/a0python/a0/guardian/ui/seed_meta/__init__.py b/a0python/a0/guardian/ui/seed_meta/__init__.py deleted file mode 100644 index 92738df1f..000000000 --- a/a0python/a0/guardian/ui/seed_meta/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# 5:1 0:0 0:1 -"""seed_meta — circles for the executive layer (Meta-13).""" -from ..circles import Circle - -EXECUTIVE_CIRCLE = Circle(name="executive", label="Executive", seed="seed_meta") -FAST_PATH_CIRCLE = Circle(name="fast_path", label="Fast Path", seed="seed_meta") -SLOW_PATH_CIRCLE = Circle(name="slow_path", label="Slow Path", seed="seed_meta") - -CIRCLES = [EXECUTIVE_CIRCLE, FAST_PATH_CIRCLE, SLOW_PATH_CIRCLE] -# 5:1 0:0 0:1 diff --git a/a0python/a0/guardian/ui/seed_transport/__init__.py b/a0python/a0/guardian/ui/seed_transport/__init__.py deleted file mode 100644 index 46f855517..000000000 --- a/a0python/a0/guardian/ui/seed_transport/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# 3:1 0:0 0:1 -"""seed_transport — circles for the Phonon transport layer.""" -from ..circles import Circle - -PHONON_CIRCLE = Circle(name="phonon", label="Phonon", seed="seed_transport") - -CIRCLES = [PHONON_CIRCLE] -# 3:1 0:0 0:1 diff --git a/a0python/a0/guardian/ui/seeds.py b/a0python/a0/guardian/ui/seeds.py deleted file mode 100644 index 57b9e9a96..000000000 --- a/a0python/a0/guardian/ui/seeds.py +++ /dev/null @@ -1,186 +0,0 @@ -# 124:34 0:0 3:1 -"""Seeds — circle group containers for the Guardian UI. - -Seeds group circles. Each seed is a named category of tabs. - -The seed taxonomy maps directly to the PTCA architecture: -- seed_core : private cognitive cores (Phi, Psi, Omega) -- seed_transport : internal transport (Phonon) -- seed_jury : adjudication layer -- seed_memory : continuity substrate -- seed_meta : executive layer (Meta-13) -- seed_guardian : microkernel shell (sentinels, recovery, approval, audit) -- seed_advisory : bandit advisory layer - -The 7 seeds here map 1:1 to the 7 Meta Routers (M₁..M₇) of the PTCA -seed router. Live tensor data from the PCTA circle tensor layer can be -pushed into seeds via push_pcta_state(). - -Guardian owns the UI. Seeds are Guardian's organizational principle. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Any, Callable, Dict, List, Optional - -from .circles import Circle - -# Optional callback type: called whenever PCTA circle state is pushed. -_LiveCallback = Callable[[str, Dict[str, Any]], None] - - -@dataclass -class Seed: - """A named group of circles.""" - name: str - label: str - circles: List[Circle] = field(default_factory=list) - # Live tensor data from the PCTA circle layer (magnitude, phase, etc.) - tensor_state: Dict[str, Any] = field(default_factory=dict) - - def active_circle(self) -> Optional[Circle]: - return next((c for c in self.circles if c.active), None) - - def circle(self, name: str) -> Optional[Circle]: - return next((c for c in self.circles if c.name == name), None) - - def update_tensor(self, state: Dict[str, Any]) -> "Seed": - """Return a new Seed with updated tensor_state (immutable update).""" - return Seed( - name=self.name, - label=self.label, - circles=self.circles, - tensor_state={**self.tensor_state, **state}, - ) - - -@dataclass -class SeedLayout: - """The complete set of seeds forming the Guardian UI layout.""" - seeds: List[Seed] = field(default_factory=list) - _live_callbacks: List[_LiveCallback] = field(default_factory=list) - - def seed(self, name: str) -> Optional[Seed]: - return next((s for s in self.seeds if s.name == name), None) - - def all_circles(self) -> List[Circle]: - return [c for s in self.seeds for c in s.circles] - - def active_circle(self) -> Optional[Circle]: - return next((c for c in self.all_circles() if c.active), None) - - def register_live_callback(self, callback: _LiveCallback) -> None: - """Register a callback invoked when PCTA state is pushed.""" - self._live_callbacks.append(callback) - - def push_pcta_state( - self, - phase_coords: Dict[str, Any], - routing_verdict: Optional[Dict[str, Any]] = None, - ) -> None: - """Push live PCTA circle tensor state into the seed layout. - - Args: - phase_coords: Output of to_phase_coords() — dict of - field_name → CircleTensorState. - routing_verdict: Optional RoutingVerdict.to_dict() from - SeedRouter.route(). - - Updates seed_core circles with live phi/psi/omega tensor data. - Notifies any registered live callbacks. - """ - core_seed = self.seed("seed_core") - if core_seed is None: - return - - for field_name in ("phi", "psi", "omega"): - cs = phase_coords.get(field_name) - if cs is None: - continue - # Accept both CircleTensorState objects and plain dicts - state = cs.to_dict() if hasattr(cs, "to_dict") else dict(cs) - circle = core_seed.circle(field_name) - if circle is not None: - circle.state[f"tensor_{field_name}"] = state - - # Store routing verdict in seed_meta if provided - if routing_verdict: - meta_seed = self.seed("seed_meta") - if meta_seed: - exec_circle = meta_seed.circle("executive") - if exec_circle is not None: - exec_circle.state["routing_verdict"] = routing_verdict - - # Fire live callbacks - for cb in self._live_callbacks: - try: - cb("pcta_update", {"phase_coords": phase_coords, "routing_verdict": routing_verdict}) - except Exception: - pass - - -def default_layout() -> SeedLayout: - """The default Guardian UI layout: all seeds and their circles.""" - return SeedLayout(seeds=[ - Seed( - name="seed_core", - label="Core", - circles=[ - Circle(name="phi", label="Phi", seed="seed_core"), - Circle(name="psi", label="Psi", seed="seed_core"), - Circle(name="omega", label="Omega", seed="seed_core"), - ], - ), - Seed( - name="seed_transport", - label="Transport", - circles=[ - Circle(name="phonon", label="Phonon", seed="seed_transport"), - ], - ), - Seed( - name="seed_jury", - label="Jury", - circles=[ - Circle(name="adjudication", label="Adjudication", seed="seed_jury"), - Circle(name="conflicts", label="Conflicts", seed="seed_jury"), - Circle(name="standards", label="Standards", seed="seed_jury"), - ], - ), - Seed( - name="seed_memory", - label="Memory", - circles=[ - Circle(name="continuity", label="Continuity", seed="seed_memory"), - Circle(name="recall", label="Recall", seed="seed_memory"), - ], - ), - Seed( - name="seed_meta", - label="Meta-13", - circles=[ - Circle(name="executive", label="Executive", seed="seed_meta"), - Circle(name="fast_path", label="Fast Path", seed="seed_meta"), - Circle(name="slow_path", label="Slow Path", seed="seed_meta"), - ], - ), - Seed( - name="seed_guardian", - label="Guardian", - circles=[ - Circle(name="sentinels", label="Sentinels", seed="seed_guardian"), - Circle(name="recovery", label="Recovery", seed="seed_guardian"), - Circle(name="approval", label="Approval", seed="seed_guardian"), - Circle(name="audit", label="Audit", seed="seed_guardian"), - Circle(name="emit", label="Emit", seed="seed_guardian"), - ], - ), - Seed( - name="seed_advisory", - label="Advisory", - circles=[ - Circle(name="bandit", label="Bandit", seed="seed_advisory"), - ], - ), - ]) -# 124:34 0:0 3:1 diff --git a/a0python/a0/guardian/ui/web/__init__.py b/a0python/a0/guardian/ui/web/__init__.py deleted file mode 100644 index ddb534abe..000000000 --- a/a0python/a0/guardian/ui/web/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# 0:1 0:0 0:0 -# Guardian web UI — Gradio app served at A0_HOST:A0_PORT -# 0:1 0:0 0:0 diff --git a/a0python/a0/guardian/ui/web/app.py b/a0python/a0/guardian/ui/web/app.py deleted file mode 100644 index aa2671c0c..000000000 --- a/a0python/a0/guardian/ui/web/app.py +++ /dev/null @@ -1,327 +0,0 @@ -# 94:179 0:0 0:0 -"""Guardian web UI — Gradio application. - -Three tabs: - chat — converse with AgentZero - interdependentway — live view of interdependentway.org - settings — edit .env (model, API key, port, host) - -Launch:: - - python -m a0.guardian.ui.web.app - -Then open http://localhost:7860 (or the configured A0_HOST:A0_PORT). - -Guardian owns the UI (Law 10). -""" -from __future__ import annotations - -import importlib -from pathlib import Path - -import gradio as gr - -from a0.agent import AgentZero -import a0.cores.psi.tensors.env as _env - -_agent = AgentZero() - -# --------------------------------------------------------------------------- -# CSS — clean, minimal, focused -# --------------------------------------------------------------------------- - -_CSS = """ -/* hide gradio footer branding */ -footer { display: none !important; } - -/* constrain max width for readability */ -.gradio-container { - max-width: 1100px !important; - margin: 0 auto !important; - font-family: "Inter", "Helvetica Neue", sans-serif; -} - -/* tab nav — cleaner spacing */ -.tab-nav button { - font-size: 13px; - font-weight: 500; - letter-spacing: 0.02em; - padding: 8px 20px; -} - -/* chat input row */ -.chat-row { align-items: flex-end; gap: 8px; } - -/* settings groups */ -.settings-group { - border: 1px solid var(--border-color-primary); - border-radius: 8px; - padding: 16px; - margin-bottom: 16px; -} - -/* status message in settings */ -.status-ok { color: #22c55e; font-weight: 500; } -.status-err { color: #ef4444; font-weight: 500; } - -/* iframe container */ -.browser-frame { - border-radius: 8px; - overflow: hidden; - border: 1px solid var(--border-color-primary); -} -""" - -# --------------------------------------------------------------------------- -# Chat tab helpers -# --------------------------------------------------------------------------- - -def _chat_respond(message: str, history: list) -> tuple[str, list]: - """Call AgentZero and append the exchange to history.""" - if not message.strip(): - return "", history - - history = list(history or []) - resp = _agent.run(message.strip(), history=history) - text = resp.result.get("text", "") - if resp.hmmm: - text += f"\n\n*hmmm: {resp.hmmm}*" - - history.append({"role": "user", "content": message.strip()}) - history.append({"role": "assistant", "content": text}) - return "", history - - -def _build_chat_tab() -> None: - with gr.Tab("chat"): - chatbot = gr.Chatbot( - label="", - height=520, - type="messages", - bubble_full_width=False, - show_copy_button=True, - avatar_images=(None, None), - ) - with gr.Row(elem_classes="chat-row"): - msg_box = gr.Textbox( - placeholder="speak to a0…", - show_label=False, - container=False, - scale=9, - autofocus=True, - ) - send_btn = gr.Button("→", variant="primary", scale=1, min_width=52) - clear_btn = gr.Button("clear conversation", variant="secondary", size="sm") - - send_btn.click( - fn=_chat_respond, - inputs=[msg_box, chatbot], - outputs=[msg_box, chatbot], - ) - msg_box.submit( - fn=_chat_respond, - inputs=[msg_box, chatbot], - outputs=[msg_box, chatbot], - ) - clear_btn.click( - fn=lambda: ("", []), - outputs=[msg_box, chatbot], - ) - - -# --------------------------------------------------------------------------- -# Browser tab -# --------------------------------------------------------------------------- - -_IFRAME_HTML = """ -
- -
-""" - - -def _build_browser_tab() -> None: - with gr.Tab("interdependentway.org"): - gr.HTML(_IFRAME_HTML) - - -# --------------------------------------------------------------------------- -# Settings tab helpers -# --------------------------------------------------------------------------- - -def _save_settings( - model: str, _api_key: str, port: int, host: str, - local_model: str, ollama_base: str, model_path: str, - memory_key: str, - runtime: str, trainer_model: str, training_dir: str, -) -> str: - env_path: Path = _env.ENV_PATH - lines = [ - f"A0_MODEL={model}", - f"A0_PORT={int(port)}", - f"A0_HOST={host}", - f"A0_LOCAL_MODEL={local_model}", - f"A0_OLLAMA_BASE={ollama_base}", - f"A0_MODEL_PATH={model_path}", - f"A0_MEMORY_KEY={memory_key}", - f"A0_RUNTIME={runtime}", - f"A0_TRAINER_MODEL={trainer_model}", - f"A0_TRAINING_DIR={training_dir}", - ] - env_path.write_text("\n".join(lines) + "\n", encoding="utf-8") - importlib.reload(_env) - return f"✓ saved to {env_path}" - - -def _build_settings_tab() -> None: - with gr.Tab("settings ⚙"): - gr.Markdown("### model adapter") - with gr.Group(elem_classes="settings-group"): - model_dd = gr.Dropdown( - choices=["local-echo", "local-ollama", "local-llama", - "anthropic-api", "claude-agent", "emergent"], - label="A0_MODEL", - value=_env.A0_MODEL, - info="local-ollama: ollama daemon. local-llama: embedded llama-cpp. anthropic-api: Anthropic API.", - ) - api_key_box = gr.Textbox( - label="ANTHROPIC_API_KEY", - type="password", - value=_env.ANTHROPIC_API_KEY, - placeholder="sk-ant-… (required for anthropic-api)", - ) - - gr.Markdown("### local model") - with gr.Group(elem_classes="settings-group"): - local_model_box = gr.Textbox( - label="A0_LOCAL_MODEL", - value=_env.A0_LOCAL_MODEL, - placeholder="llama3.2", - info="Model name as shown by `ollama list` (A0_MODEL=local-ollama).", - ) - ollama_base_box = gr.Textbox( - label="A0_OLLAMA_BASE", - value=_env.A0_OLLAMA_BASE, - placeholder="http://localhost:11434", - info="Ollama daemon URL. Override if running on a different host.", - ) - model_path_box = gr.Textbox( - label="A0_MODEL_PATH", - value=_env.A0_MODEL_PATH, - placeholder="/path/to/model.gguf", - info="Absolute path to a GGUF file (A0_MODEL=local-llama). pip install llama-cpp-python", - ) - - gr.Markdown("### encryption") - with gr.Group(elem_classes="settings-group"): - memory_key_box = gr.Textbox( - label="A0_MEMORY_KEY", - type="password", - value=_env.A0_MEMORY_KEY, - placeholder="(Fernet key — leave blank for plaintext)", - info="Generate: python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"", - ) - - gr.Markdown("### training") - with gr.Group(elem_classes="settings-group"): - runtime_dd = gr.Dropdown( - choices=["inference", "training"], - label="A0_RUNTIME", - value=_env.A0_RUNTIME, - info="training: outside model generates data for a0's native PCNA weights.", - ) - trainer_model_box = gr.Textbox( - label="A0_TRAINER_MODEL", - value=_env.A0_TRAINER_MODEL, - placeholder="claude-sonnet-4-6", - info="External model acting as trainer (Path B — native PCNA).", - ) - training_dir_box = gr.Textbox( - label="A0_TRAINING_DIR", - value=_env.A0_TRAINING_DIR, - placeholder="/path/to/training/", - info="Where training data and checkpoints are written.", - ) - - gr.Markdown("### server") - with gr.Group(elem_classes="settings-group"): - port_num = gr.Number( - label="A0_PORT", - value=_env.A0_PORT, - precision=0, - info="Port the Gradio server listens on. Restart required to change.", - ) - host_box = gr.Textbox( - label="A0_HOST", - value=_env.A0_HOST, - info="0.0.0.0 = all interfaces. 127.0.0.1 = local only.", - ) - - save_btn = gr.Button("save", variant="primary") - status_md = gr.HTML("") - - save_btn.click( - fn=_save_settings, - inputs=[ - model_dd, api_key_box, port_num, host_box, - local_model_box, ollama_base_box, model_path_box, - memory_key_box, - runtime_dd, trainer_model_box, training_dir_box, - ], - outputs=status_md, - ) - - -# --------------------------------------------------------------------------- -# App assembly -# --------------------------------------------------------------------------- - -def build_app() -> gr.Blocks: - theme = gr.themes.Soft( - primary_hue="slate", - secondary_hue="slate", - neutral_hue="slate", - radius_size=gr.themes.sizes.radius_sm, - font=[gr.themes.GoogleFont("Inter"), "Helvetica Neue", "sans-serif"], - ) - - with gr.Blocks( - theme=theme, - title="a0 — interdependent way", - css=_CSS, - ) as demo: - gr.Markdown( - "## a0\n*PTCA cognitive routing shell — interdependent way*", - ) - - _build_chat_tab() - _build_browser_tab() - _build_settings_tab() - - return demo - - -# --------------------------------------------------------------------------- -# Entry point -# --------------------------------------------------------------------------- - -def main() -> None: - from a0.cores.psi.tensors.env import A0_HOST, A0_PORT - - demo = build_app() - demo.launch( - server_name=A0_HOST, - server_port=A0_PORT, - share=False, - show_error=True, - ) - - -if __name__ == "__main__": - main() -# 94:179 0:0 0:0 diff --git a/a0python/a0/guardian/user_db.py b/a0python/a0/guardian/user_db.py deleted file mode 100644 index 7b7048009..000000000 --- a/a0python/a0/guardian/user_db.py +++ /dev/null @@ -1,265 +0,0 @@ -# 145:66 0:0 1:0 -"""guardian.user_db — user registry for the Guardian shell. - -Tracks per-user identity, passphrase (hashed), affiliation level, and -achievements. Stored in state/users.json (encrypted via a0.encryption). - -Status: secondary — exists and is usable but not yet wired into the -routing or Jury adjudication flow. Future integration points are marked -with # FUTURE comments. - -Passphrase storage: - PBKDF2-HMAC-SHA256, 260 000 iterations. - On-disk format: ":" (never plaintext). - Constant-time comparison via hmac.compare_digest. - -Affiliation levels (AffiliationLevel): - GUEST (0) — anonymous / unverified - MEMBER (1) — registered, confirmed - TRUSTED (2) — manually elevated - OPERATOR (3) — full operator access - -Achievements: - Opaque strings; uniqueness enforced per user. - Awarded freely — no Jury token required (non-continuity-bearing facts). -""" -from __future__ import annotations - -import hashlib -import hmac -import json -import os -import uuid -from dataclasses import asdict, dataclass, field -from datetime import datetime, timezone -from enum import IntEnum -from pathlib import Path -from typing import Dict, List, Optional - - -# --------------------------------------------------------------------------- -# Affiliation level -# --------------------------------------------------------------------------- - -class AffiliationLevel(IntEnum): - GUEST = 0 - MEMBER = 1 - TRUSTED = 2 - OPERATOR = 3 - - -# --------------------------------------------------------------------------- -# User record -# --------------------------------------------------------------------------- - -@dataclass -class UserRecord: - user_id: str - username: str - passphrase_hash: str # ":" — never plaintext - affiliation_level: int = AffiliationLevel.GUEST - achievements: List[str] = field(default_factory=list) - created_at: str = field( - default_factory=lambda: datetime.now(timezone.utc).isoformat() - ) - - # Convenience - @property - def affiliation(self) -> AffiliationLevel: - return AffiliationLevel(self.affiliation_level) - - def to_dict(self) -> Dict: - return asdict(self) - - @classmethod - def from_dict(cls, d: Dict) -> "UserRecord": - known = set(cls.__dataclass_fields__) - return cls(**{k: v for k, v in d.items() if k in known}) - - -# --------------------------------------------------------------------------- -# Passphrase helpers (internal) -# --------------------------------------------------------------------------- - -_ITERATIONS = 260_000 - - -def _hash_passphrase(passphrase: str, salt: Optional[bytes] = None) -> str: - """Return ':' for storage.""" - if salt is None: - salt = os.urandom(16) - h = hashlib.pbkdf2_hmac("sha256", passphrase.encode("utf-8"), salt, _ITERATIONS) - return f"{salt.hex()}:{h.hex()}" - - -def _verify_passphrase(passphrase: str, stored: str) -> bool: - """Constant-time comparison — safe against timing attacks.""" - try: - salt_hex, _ = stored.split(":", 1) - candidate = _hash_passphrase(passphrase, bytes.fromhex(salt_hex)) - return hmac.compare_digest(stored, candidate) - except Exception: - return False - - -# --------------------------------------------------------------------------- -# Errors -# --------------------------------------------------------------------------- - -class UserDBError(Exception): - """Base for all UserDB errors.""" - -class UserNotFoundError(UserDBError): - pass - -class UsernameTakenError(UserDBError): - pass - -class BadPassphraseError(UserDBError): - pass - - -# --------------------------------------------------------------------------- -# UserDB -# --------------------------------------------------------------------------- - -_DEFAULT_PATH = Path(__file__).parent.parent / "state" / "users.json" - - -class UserDB: - """Encrypted, file-backed user registry. - - Args: - path: Path to users.json. Defaults to state/users.json next to - guardian's parent package. Pass a per-instance path for - instance-scoped user tables. - - Usage:: - - db = UserDB() - user = db.register("alice", "correct horse battery staple") - db.verify("alice", "correct horse battery staple") # → UserRecord - db.add_achievement(user.user_id, "first_login") - db.set_affiliation(user.user_id, AffiliationLevel.MEMBER) - """ - - def __init__(self, path: Optional[Path] = None) -> None: - self._path: Path = Path(path) if path else _DEFAULT_PATH - self._users: Dict[str, UserRecord] = {} # user_id → UserRecord - self._by_name: Dict[str, str] = {} # username (lower) → user_id - self._load() - - # ------------------------------------------------------------------ - # Public API - # ------------------------------------------------------------------ - - def register(self, username: str, passphrase: str) -> UserRecord: - """Register a new user. Raises UsernameTakenError on collision.""" - key = username.strip().lower() - if key in self._by_name: - raise UsernameTakenError(f"username already taken: {username!r}") - - user_id = uuid.uuid4().hex - record = UserRecord( - user_id=user_id, - username=username.strip(), - passphrase_hash=_hash_passphrase(passphrase), - ) - self._users[user_id] = record - self._by_name[key] = user_id - self._persist() - return record - - def verify(self, username: str, passphrase: str) -> UserRecord: - """Authenticate. Raises UserNotFoundError or BadPassphraseError.""" - record = self._lookup_by_name(username) - if not _verify_passphrase(passphrase, record.passphrase_hash): - raise BadPassphraseError("passphrase incorrect") - return record - - def get(self, user_id: str) -> UserRecord: - """Retrieve a user by ID. Raises UserNotFoundError if absent.""" - try: - return self._users[user_id] - except KeyError: - raise UserNotFoundError(user_id) - - def get_by_name(self, username: str) -> UserRecord: - """Retrieve a user by username (case-insensitive).""" - return self._lookup_by_name(username) - - def set_affiliation( - self, - user_id: str, - level: AffiliationLevel | int, - ) -> UserRecord: - """Elevate or demote a user's affiliation level.""" - record = self.get(user_id) - record.affiliation_level = int(level) - self._persist() - return record - - def add_achievement(self, user_id: str, achievement: str) -> UserRecord: - """Award an achievement (idempotent — duplicates are silently dropped).""" - record = self.get(user_id) - if achievement not in record.achievements: - record.achievements.append(achievement) - self._persist() - return record - - def remove_achievement(self, user_id: str, achievement: str) -> UserRecord: - """Revoke an achievement. No-op if not present.""" - record = self.get(user_id) - try: - record.achievements.remove(achievement) - self._persist() - except ValueError: - pass - return record - - def all_users(self) -> List[UserRecord]: - """Return all registered users (no passphrase hashes exposed — caller handles).""" - return list(self._users.values()) - - def delete(self, user_id: str) -> None: - """Remove a user permanently.""" - record = self.get(user_id) - del self._users[user_id] - self._by_name.pop(record.username.lower(), None) - self._persist() - - # ------------------------------------------------------------------ - # Internal - # ------------------------------------------------------------------ - - def _lookup_by_name(self, username: str) -> UserRecord: - uid = self._by_name.get(username.strip().lower()) - if uid is None: - raise UserNotFoundError(f"no user: {username!r}") - return self._users[uid] - - def _persist(self) -> None: - self._path.parent.mkdir(parents=True, exist_ok=True) - payload = json.dumps( - {uid: r.to_dict() for uid, r in self._users.items()}, - ensure_ascii=False, - indent=2, - ) - from a0.encryption import encrypt - self._path.write_text(encrypt(payload), encoding="utf-8") - - def _load(self) -> None: - if not self._path.exists(): - return - try: - from a0.encryption import decrypt - raw = decrypt(self._path.read_text(encoding="utf-8")) - data: Dict = json.loads(raw) - for uid, d in data.items(): - r = UserRecord.from_dict(d) - self._users[uid] = r - self._by_name[r.username.lower()] = uid - except Exception: - # Corrupt or legacy file — start clean rather than crash. - pass -# 145:66 0:0 1:0 diff --git a/a0python/a0/heartbeat.py b/a0python/a0/heartbeat.py deleted file mode 100644 index 47b2630e2..000000000 --- a/a0python/a0/heartbeat.py +++ /dev/null @@ -1,79 +0,0 @@ -# 49:9 0:0 2:2 -"""Heartbeat — maintenance-only cycle. - -Heartbeat may NOT: -- initiate new external actions -- expand goals -- modify safety policy -- silently convert temporary state into durable authority -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from datetime import datetime, timezone -from pathlib import Path -from typing import List, Optional - -from .invariants import InvalidStateError -from .guardian.approval_gate import EXTERNAL_EFFECT_TYPES - - -class HeartbeatViolationError(InvalidStateError): - """Raised when heartbeat attempts a prohibited action.""" - - -@dataclass -class HeartbeatResult: - timestamp: str - integrity_ok: bool - snapshots_refreshed: bool - hygiene_performed: bool - warnings: List[str] = field(default_factory=list) - - -class Heartbeat: - """Maintenance-only heartbeat cycle.""" - - def __init__(self, memory=None, provenance_log_dir: Optional[Path] = None) -> None: - self._memory = memory - self._provenance_log_dir = provenance_log_dir - - def tick(self) -> HeartbeatResult: - ts = datetime.now(timezone.utc).isoformat() - warnings: List[str] = [] - - integrity_ok = self._verify_integrity(warnings) - snapshots_refreshed = self._refresh_snapshots(warnings) - self._bounded_hygiene(warnings) - - return HeartbeatResult( - timestamp=ts, - integrity_ok=integrity_ok, - snapshots_refreshed=snapshots_refreshed, - hygiene_performed=True, - warnings=warnings, - ) - - def _verify_integrity(self, warnings: List[str]) -> bool: - if self._memory is not None: - keys = self._memory.all_keys() - if not isinstance(keys, list): - warnings.append("Memory key listing returned unexpected type") - return False - return True - - def _refresh_snapshots(self, warnings: List[str]) -> bool: - return True - - def _bounded_hygiene(self, warnings: List[str]) -> None: - pass - - def initiate_external_action(self, *args, **kwargs) -> None: - raise HeartbeatViolationError("Heartbeat may not initiate new external actions.") - - def expand_goals(self, *args, **kwargs) -> None: - raise HeartbeatViolationError("Heartbeat may not expand goals.") - - def modify_safety_policy(self, *args, **kwargs) -> None: - raise HeartbeatViolationError("Heartbeat may not modify safety policy.") -# 49:9 0:0 2:2 diff --git a/a0python/a0/invariants.py b/a0python/a0/invariants.py deleted file mode 100644 index e6fb6ba28..000000000 --- a/a0python/a0/invariants.py +++ /dev/null @@ -1,29 +0,0 @@ -# 15:6 0:0 13:0 -from __future__ import annotations - -from typing import Any - - -class InvalidStateError(Exception): - """Raised when a required invariant is absent or violated.""" - - -def require_hmmm(obj: Any) -> None: - """Fail closed if hmmm is absent from an event dict or response object. - - Law: absence of hmmm is invalid state. - Invalid state blocks event commit and outbound emission. - """ - if isinstance(obj, dict): - if "hmmm" not in obj: - raise InvalidStateError( - "hmmm is absent from event — invalid state blocks commit" - ) - elif hasattr(obj, "hmmm"): - # dataclass / object form: field must exist (it does if declared) - pass - else: - raise InvalidStateError( - "hmmm is absent from object — invalid state blocks emission" - ) -# 15:6 0:0 13:0 diff --git a/a0python/a0/jury.py b/a0python/a0/jury.py deleted file mode 100644 index ee0d3c660..000000000 --- a/a0python/a0/jury.py +++ /dev/null @@ -1,105 +0,0 @@ -# 63:20 0:0 0:0 -"""Jury — legality and conflict-preservation adjudication layer. - -Jury: -- mediates continuity-bearing persistence -- preserves unresolved conflict as conflict -- prevents silent promotion from volatile state into committed state -- establishes operative standards where definitions are absent or contested - -Law 4: Persistence requires adjudication. -Law 5: Conflict must remain visible when unresolved. -Law 3: Volatile state is not committed continuity. -""" -from __future__ import annotations - -import uuid -from dataclasses import dataclass, field -from enum import Enum -from typing import Any, Dict, List, Optional - - -class AdjudicationVerdict(Enum): - COMMITTED = "committed" - CONFLICT = "conflict" - BLOCKED = "blocked" - - -@dataclass -class ConflictRecord: - """An unresolved conflict preserved by Jury. - - Law 5: Conflict must remain visible when unresolved. - Conflicts are never silently merged or discarded. - """ - conflict_id: str - event_a: Any - event_b: Any - reason: str - - -@dataclass -class AdjudicationResult: - verdict: AdjudicationVerdict - jury_token: Optional[str] - conflict: Optional[ConflictRecord] = None - reason: Optional[str] = None - - -class Jury: - """The legality and conflict-preservation adjudication layer. - - Tier 2 writes require a Jury token. - Jury does not silently promote Tier 1 volatiles to Tier 2. - Conflicts are preserved as conflicts until resolved. - """ - - def __init__(self) -> None: - self._conflicts: List[ConflictRecord] = [] - self._committed: List[str] = [] - - def adjudicate(self, event: Any, prior: Optional[Any] = None) -> AdjudicationResult: - if self._is_conflict(event, prior): - conflict_id = f"conflict_{uuid.uuid4().hex[:8]}" - record = ConflictRecord( - conflict_id=conflict_id, - event_a=prior, - event_b=event, - reason="conflicting state detected", - ) - self._conflicts.append(record) - return AdjudicationResult( - verdict=AdjudicationVerdict.CONFLICT, - jury_token=None, - conflict=record, - reason="Conflict preserved — unresolved conflict may not be silently promoted.", - ) - - jury_token = f"jury_{uuid.uuid4().hex}" - self._committed.append(jury_token) - return AdjudicationResult( - verdict=AdjudicationVerdict.COMMITTED, - jury_token=jury_token, - ) - - def _is_conflict(self, event: Any, prior: Optional[Any]) -> bool: - if prior is None: - return False - if isinstance(event, dict) and isinstance(prior, dict): - return event.get("_conflict_with") == id(prior) - return False - - def unresolved_conflicts(self) -> List[ConflictRecord]: - """Law 5: Conflict must remain visible when unresolved.""" - return list(self._conflicts) - - def resolve_conflict(self, conflict_id: str) -> bool: - before = len(self._conflicts) - self._conflicts = [c for c in self._conflicts if c.conflict_id != conflict_id] - return len(self._conflicts) < before - - def establish_standard(self, domain: str, standard: Dict[str, Any]) -> str: - jury_token = f"jury_std_{uuid.uuid4().hex}" - self._committed.append(jury_token) - return jury_token -# 63:20 0:0 0:0 diff --git a/a0python/a0/lifecycle.py b/a0python/a0/lifecycle.py deleted file mode 100644 index 2a84c6ed9..000000000 --- a/a0python/a0/lifecycle.py +++ /dev/null @@ -1,423 +0,0 @@ -# 211:136 0:0 0:0 -"""a0 lifecycle — spawn, clone, merge, diversify. - -The four fundamental operations for a multi-agent a0 ecosystem: - - spawn — create a child instance seeded from parent memory - clone — exact copy (new identity, same state) - merge — combine two instances via Jury adjudication (Law 5) - diversify — create N variants with different configurations - soft_reset — clear volatile (Tier 1) state; preserve Tier 2 memory - -Each instance has an isolated home directory: - - {home}/state/memory.json encrypted cognitive state - {home}/state/a0_state.json last_model tracking - {home}/logs/ event logs - {home}/instance.json instance descriptor (metadata) - -Usage:: - - from a0.lifecycle import spawn, clone, merge, diversify, soft_reset, root_instance - - parent = root_instance() # the default a0 instance - child = spawn(parent, name="worker-1") # fresh child, empty memory - backup = clone(parent, name="backup-before-exp") # full copy - merged = merge(parent, child) # pull child learnings back - fleet = diversify(parent, [ # N variants - {"A0_MODEL": "anthropic-api"}, - {"A0_MODEL": "local-llama"}, - {"A0_MODEL": "local-echo"}, - ]) - -To run a live agent from any descriptor:: - - from a0.agent import AgentZero - az = AgentZero(home=child.home, instance_id=child.instance_id) - resp = az.run("hello") -""" -from __future__ import annotations - -import json -import shutil -import uuid -from dataclasses import asdict, dataclass, field -from datetime import datetime, timezone -from pathlib import Path -from typing import Any, Dict, List, Optional - -from a0.jury import AdjudicationVerdict, Jury -from a0.memory import Memory, MemoryEntry - -# --------------------------------------------------------------------------- -# Instance directory layout -# --------------------------------------------------------------------------- - -_INSTANCES_ROOT = Path(__file__).resolve().parent / "state" / "instances" - - -def _new_home(instance_id: str) -> Path: - home = _INSTANCES_ROOT / instance_id - (home / "state").mkdir(parents=True, exist_ok=True) - (home / "logs").mkdir(parents=True, exist_ok=True) - return home - - -# --------------------------------------------------------------------------- -# InstanceDescriptor -# --------------------------------------------------------------------------- - -@dataclass -class InstanceDescriptor: - """Lightweight handle for a spawned/cloned/merged a0 instance. - - Pass home + instance_id to AgentZero to run a live agent: - AgentZero(home=desc.home, instance_id=desc.instance_id) - """ - instance_id: str - name: str - home: Path - parent_id: Optional[str] = None - config: Dict[str, str] = field(default_factory=dict) - created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) - # Conflict IDs preserved from a merge operation (Law 5) - conflicts: List[str] = field(default_factory=list) - - @property - def memory_path(self) -> Path: - return self.home / "state" / "memory.json" - - @property - def log_dir(self) -> Path: - return self.home / "logs" - - def save(self) -> None: - """Persist descriptor to {home}/instance.json.""" - data = { - "instance_id": self.instance_id, - "name": self.name, - "home": str(self.home), - "parent_id": self.parent_id, - "config": self.config, - "created_at": self.created_at, - "conflicts": self.conflicts, - } - (self.home / "instance.json").write_text( - json.dumps(data, indent=2), encoding="utf-8" - ) - - @classmethod - def load(cls, home: Path) -> "InstanceDescriptor": - data = json.loads((home / "instance.json").read_text(encoding="utf-8")) - return cls( - instance_id=data["instance_id"], - name=data["name"], - home=Path(data["home"]), - parent_id=data.get("parent_id"), - config=data.get("config", {}), - created_at=data.get("created_at", ""), - conflicts=data.get("conflicts", []), - ) - - -# --------------------------------------------------------------------------- -# Root instance helper -# --------------------------------------------------------------------------- - -def root_instance() -> InstanceDescriptor: - """Return a descriptor for the default (root) a0 instance. - - The root instance uses the package-default paths (not an instances/ - subdirectory). This is what AgentZero() uses when home=None. - """ - _pkg = Path(__file__).resolve().parent - return InstanceDescriptor( - instance_id="root", - name="root", - home=_pkg, - parent_id=None, - config={}, - ) - - -# --------------------------------------------------------------------------- -# spawn -# --------------------------------------------------------------------------- - -def spawn( - parent: InstanceDescriptor, - name: str, - seed_keys: Optional[List[str]] = None, - config: Optional[Dict[str, str]] = None, -) -> InstanceDescriptor: - """Create a child instance optionally seeded with parent memory. - - Args: - parent: The spawning instance. - name: Human-readable label for the child. - seed_keys: List of memory keys to copy from parent. - Pass None for an empty memory (fresh start). - Pass [] explicitly for the same (empty). - Pass a list of keys to seed specific knowledge. - config: Env overrides for the child (e.g. {"A0_MODEL": "local-echo"}). - - Returns: - InstanceDescriptor for the new child. - """ - child_id = uuid.uuid4().hex - home = _new_home(child_id) - - # Seed memory from parent - if seed_keys is not None and parent.memory_path.exists(): - parent_mem = Memory(path=parent.memory_path) - child_mem = Memory(path=home / "state" / "memory.json") - jury = Jury() - for key in seed_keys: - value = parent_mem.recall(key) - if value is not None: - result = jury.adjudicate(value) - if result.jury_token: - child_mem.commit(key, value, result.jury_token) - - desc = InstanceDescriptor( - instance_id=child_id, - name=name, - home=home, - parent_id=parent.instance_id, - config=config or {}, - ) - desc.save() - return desc - - -# --------------------------------------------------------------------------- -# clone -# --------------------------------------------------------------------------- - -def clone(source: InstanceDescriptor, name: str) -> InstanceDescriptor: - """Create an exact copy of source (new identity, same state). - - The clone's memory and logs are independent from this point forward — - changes to source do not affect the clone and vice versa. - - Args: - source: The instance to clone. - name: Human-readable label for the clone. - - Returns: - InstanceDescriptor for the clone. - """ - clone_id = uuid.uuid4().hex - home = _new_home(clone_id) - - # Copy memory if it exists - if source.memory_path.exists(): - shutil.copy2(source.memory_path, home / "state" / "memory.json") - - # Copy state file if it exists - src_state = source.home / "state" / "a0_state.json" - if src_state.exists(): - shutil.copy2(src_state, home / "state" / "a0_state.json") - - desc = InstanceDescriptor( - instance_id=clone_id, - name=name, - home=home, - parent_id=source.instance_id, - config=dict(source.config), - ) - desc.save() - return desc - - -# --------------------------------------------------------------------------- -# merge -# --------------------------------------------------------------------------- - -def merge( - base: InstanceDescriptor, - other: InstanceDescriptor, - into: Optional[InstanceDescriptor] = None, -) -> InstanceDescriptor: - """Combine other's memory into base via Jury adjudication. - - Law 5: Conflicts are preserved, never silently discarded. - - For each key in other's memory: - - If base does not have it: commit it directly (new knowledge). - - If base has the same value: skip (no change). - - If base has a different value: Jury adjudicates. - - COMMITTED → other's value wins (more recent knowledge). - - CONFLICT → preserved as ConflictRecord; both values retained. - - Args: - base: The receiving instance (its memory is the starting point). - other: The contributing instance (its memory is merged in). - into: Optional target — if provided, merge result is written there - instead of modifying base in place. Useful for safe merges. - - Returns: - Updated InstanceDescriptor (base or into) with conflicts list populated. - """ - target = into or base - base_mem = Memory(path=base.memory_path) if base.memory_path.exists() else Memory(path=base.home / "state" / "memory.json") - other_mem = Memory(path=other.memory_path) if other.memory_path.exists() else Memory(path=other.home / "state" / "memory.json") - target_mem = Memory(path=target.home / "state" / "memory.json") - - jury = Jury() - conflict_ids: List[str] = [] - - for key in other_mem.all_keys(): - other_val = other_mem.recall(key) - base_val = base_mem.recall(key) - - if base_val is None: - # New key — commit directly - result = jury.adjudicate(other_val) - if result.jury_token: - target_mem.commit(key, other_val, result.jury_token) - - elif base_val == other_val: - # Same value — no change needed; re-commit to target if merging into new descriptor - if into is not None: - result = jury.adjudicate(base_val) - if result.jury_token: - target_mem.commit(key, base_val, result.jury_token) - - else: - # Diverged values — adjudicate - result = jury.adjudicate(other_val, prior={"_conflict_with": id(base_val)}) - if result.verdict == AdjudicationVerdict.COMMITTED and result.jury_token: - target_mem.commit(key, other_val, result.jury_token) - elif result.conflict: - # Law 5: preserve conflict — store both under distinct keys - conflict_ids.append(result.conflict.conflict_id) - tok_a = jury.establish_standard(f"{key}_base", {"value": base_val}) - tok_b = jury.establish_standard(f"{key}_other", {"value": other_val}) - target_mem.commit(f"{key}__base", base_val, tok_a) - target_mem.commit(f"{key}__other", other_val, tok_b) - target_mem.commit( - f"{key}__conflict", - { - "conflict_id": result.conflict.conflict_id, - "reason": result.conflict.reason, - "keys": [f"{key}__base", f"{key}__other"], - }, - jury.establish_standard(f"{key}_conflict", {}), - ) - - # If merging into base (not a new target), preserve existing keys not in other - if into is not None: - for key in base_mem.all_keys(): - if other_mem.recall(key) is None: - val = base_mem.recall(key) - result = jury.adjudicate(val) - if result.jury_token: - target_mem.commit(key, val, result.jury_token) - - # Update target descriptor with conflict list - updated = InstanceDescriptor( - instance_id=target.instance_id, - name=target.name, - home=target.home, - parent_id=target.parent_id, - config=target.config, - created_at=target.created_at, - conflicts=target.conflicts + conflict_ids, - ) - updated.save() - return updated - - -# --------------------------------------------------------------------------- -# diversify -# --------------------------------------------------------------------------- - -def diversify( - parent: InstanceDescriptor, - configs: List[Dict[str, str]], - seed_keys: Optional[List[str]] = None, -) -> List[InstanceDescriptor]: - """Create N variant instances from one parent with different configurations. - - Each variant gets its own isolated home directory. The configs list - drives what makes each variant distinct — typically different A0_MODEL - values, but any env override is valid. - - Args: - parent: The source instance. - configs: List of config dicts, one per variant. - e.g. [{"A0_MODEL": "anthropic-api"}, {"A0_MODEL": "local-llama"}] - seed_keys: Memory keys to seed into each variant from parent. - None = empty memory (default). Pass a list to share knowledge. - - Returns: - List of InstanceDescriptors, one per config entry. - """ - variants: List[InstanceDescriptor] = [] - for i, cfg in enumerate(configs): - name = f"{parent.name}-variant-{i+1}" - if "A0_MODEL" in cfg: - name = f"{parent.name}-{cfg['A0_MODEL']}" - desc = spawn(parent, name=name, seed_keys=seed_keys, config=cfg) - variants.append(desc) - return variants - - -# --------------------------------------------------------------------------- -# soft_reset -# --------------------------------------------------------------------------- - -def soft_reset(instance: InstanceDescriptor) -> InstanceDescriptor: - """Reset volatile state while preserving Tier 2 committed memory. - - Tier 1 (volatile) cleared: - {home}/state/a0_state.json → {"last_model": None} - - Tier 2 (committed) preserved: - {home}/state/memory.json — Jury-adjudicated, untouched - {home}/logs/ — append-only, untouched - - Returns: - The same InstanceDescriptor with reset_at recorded in instance.json. - """ - from a0.state import save_state - - # Reset volatile config to defaults - save_state({"last_model": None}, home=instance.home) - - # Record reset timestamp in instance.json - data = { - "instance_id": instance.instance_id, - "name": instance.name, - "home": str(instance.home), - "parent_id": instance.parent_id, - "config": instance.config, - "created_at": instance.created_at, - "conflicts": instance.conflicts, - "reset_at": datetime.now(timezone.utc).isoformat(), - } - (instance.home / "instance.json").write_text( - json.dumps(data, indent=2), encoding="utf-8" - ) - return instance - - -# --------------------------------------------------------------------------- -# Discovery helpers -# --------------------------------------------------------------------------- - -def list_instances() -> List[InstanceDescriptor]: - """Return all known instances from the instances root directory.""" - if not _INSTANCES_ROOT.exists(): - return [] - result = [] - for home in sorted(_INSTANCES_ROOT.iterdir()): - descriptor_file = home / "instance.json" - if descriptor_file.exists(): - try: - result.append(InstanceDescriptor.load(home)) - except Exception: - pass - return result -# 211:136 0:0 0:0 diff --git a/a0python/a0/memory.py b/a0python/a0/memory.py deleted file mode 100644 index 9a7c07301..000000000 --- a/a0python/a0/memory.py +++ /dev/null @@ -1,98 +0,0 @@ -# 69:9 0:0 2:3 -"""Memory — continuity substrate. - -Memory is not raw history. -Memory is continuity substrate. - -Logs are not Memory. Memory is not logs. (Law 11) -Only Jury-adjudicated writes land in Memory. - -Law 4: Persistence requires adjudication. -Law 11: Logs belong to event history, not continuity itself. -""" -from __future__ import annotations - -import json -from dataclasses import dataclass, field -from pathlib import Path -from typing import Any, Dict, List, Optional - -from .encryption import decrypt, encrypt -from .invariants import InvalidStateError -from .tiers import Tier2 - - -MEMORY_PATH = Path(__file__).resolve().parent / "state" / "memory.json" - - -@dataclass -class MemoryEntry: - key: str - value: Any - jury_token: str - compressed: bool = False - - -class Memory: - """Continuity substrate — only Jury-adjudicated writes permitted.""" - - def __init__(self, path: Optional[Path] = None) -> None: - self._path = path or MEMORY_PATH - self._store: Dict[str, MemoryEntry] = {} - self._load() - - def commit(self, key: str, value: Any, jury_token: str) -> None: - if not jury_token: - raise InvalidStateError( - "Memory write requires a Jury token — direct writes are blocked." - ) - self._store[key] = MemoryEntry(key=key, value=value, jury_token=jury_token) - self._persist() - - def commit_tier2(self, tier2: Tier2) -> None: - if not isinstance(tier2, Tier2): - raise InvalidStateError("Only Tier2 objects may be committed to Memory.") - self.commit( - key=str(id(tier2.content)), - value=tier2.content, - jury_token=tier2.jury_token, - ) - - def recall(self, key: str) -> Optional[Any]: - entry = self._store.get(key) - return entry.value if entry else None - - def all_keys(self) -> List[str]: - return list(self._store.keys()) - - def _persist(self) -> None: - self._path.parent.mkdir(parents=True, exist_ok=True) - serialized = { - k: { - "key": e.key, - "value": e.value, - "jury_token": e.jury_token, - "compressed": e.compressed, - } - for k, e in self._store.items() - } - self._path.write_text( - encrypt(json.dumps(serialized, indent=2, ensure_ascii=False)), - encoding="utf-8", - ) - - def _load(self) -> None: - if not self._path.exists(): - return - try: - data = json.loads(decrypt(self._path.read_text(encoding="utf-8"))) - for k, v in data.items(): - self._store[k] = MemoryEntry( - key=v["key"], - value=v["value"], - jury_token=v["jury_token"], - compressed=v.get("compressed", False), - ) - except (json.JSONDecodeError, KeyError): - pass -# 69:9 0:0 2:3 diff --git a/a0python/a0/meta13.py b/a0python/a0/meta13.py deleted file mode 100644 index e40bddf18..000000000 --- a/a0python/a0/meta13.py +++ /dev/null @@ -1,112 +0,0 @@ -# 73:13 0:0 0:0 -"""Meta-13 — the executive chooser. - -Meta-13 receives: -- fast-path: raw witness from the 12 raw Jury sentinels -- slow-path: coherent stances from Meta-Phi, Meta-Psi, and Meta-Omega - -Meta-13 resolves both into the final internal executive "I" state. - -Bandits do not choose. Meta-13 chooses. - -Law 13: Meta-13 chooses; advisory layers may influence salience - but do not decide. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Any, List, Optional - - -SENTINEL_NAMES = [ - "structural_legality", - "executable_legality", - "integrity", - "provenance", - "audit_sealing", - "recovery_readiness", - "output_policy", - "safety_approval", - "conflict_visibility", - "drift_detection", - "resource_legality", - "hmmm_presence", -] - -assert len(SENTINEL_NAMES) == 12, "Fast-path requires exactly 12 raw sentinels" - - -@dataclass -class RawWitness: - """A raw sentinel witness — fast-path input to Meta-13.""" - sentinel: str - passed: bool - detail: Optional[str] = None - - -@dataclass -class CoherentStance: - """A slow-path coherent stance from a meta-core (Meta-Phi/Psi/Omega).""" - source: str - stance: Any - confidence: float = 1.0 - - -@dataclass -class ExecutiveState: - """The final internal executive 'I' state produced by Meta-13.""" - chosen: Any - fast_path_passed: bool - slow_path_used: bool - fast_witnesses: List[RawWitness] = field(default_factory=list) - slow_stances: List[CoherentStance] = field(default_factory=list) - advisory_ignored: bool = False - - -class Meta13: - """The executive chooser.""" - - def resolve( - self, - fast_path: List[RawWitness], - slow_path: List[CoherentStance], - candidates: Optional[List[Any]] = None, - ) -> ExecutiveState: - fast_passed = all(w.passed for w in fast_path) - - if not fast_passed: - return ExecutiveState( - chosen=None, - fast_path_passed=False, - slow_path_used=False, - fast_witnesses=fast_path, - slow_stances=slow_path, - ) - - chosen = self._integrate_slow_path(slow_path, candidates) - - return ExecutiveState( - chosen=chosen, - fast_path_passed=True, - slow_path_used=bool(slow_path), - fast_witnesses=fast_path, - slow_stances=slow_path, - ) - - def _integrate_slow_path( - self, - stances: List[CoherentStance], - candidates: Optional[List[Any]], - ) -> Any: - if not stances and candidates: - return candidates[0] if candidates else None - - if candidates: - return candidates[0] - - if stances: - best = max(stances, key=lambda s: s.confidence) - return best.stance - - return None -# 73:13 0:0 0:0 diff --git a/a0python/a0/model_registry.py b/a0python/a0/model_registry.py deleted file mode 100644 index 0d35c916f..000000000 --- a/a0python/a0/model_registry.py +++ /dev/null @@ -1,367 +0,0 @@ -# 170:122 0:0 0:0 -"""model_registry — LLM model registry for a0. - -Modelled on the DEFAULT_REGISTRY / generate_response(model_id, messages, registry, user) -/ make_call_fn() pattern from erinepshovel-code/aimmh. - -Usage:: - - from a0.model_registry import ModelRegistry, ModelConfig, make_complete_fn - - # In-memory registry pre-loaded with built-in defaults - reg = ModelRegistry.defaults() - - # Edit any field for any registered model - reg.update("claude-sonnet-4-6", max_tokens=4096, system_prompt="You are a PTCA router.") - - # Register a developer-specific config - reg.register(ModelConfig( - model_id="alice-opus", - adapter="anthropic-api", - model_name="claude-opus-4-6", - developer="alice", - system_prompt="You are a PTCA training oracle.", - )) - - # Per-developer defaults - alice_models = reg.get_defaults_for("alice") - - # aimmh-style callable — wraps registry + per-instance context - complete = make_complete_fn(registry=reg, context={"model_id": "alice-opus"}) - response = complete("alice-opus", [{"role": "user", "content": "hello"}]) - -Context merging chain (lowest → highest priority):: - - DEFAULT_REGISTRY[model_id] → InstanceDescriptor.config → per-call context arg -""" -from __future__ import annotations - -import json -import uuid -from copy import deepcopy -from dataclasses import dataclass, asdict -from pathlib import Path -from typing import Any, Callable, Dict, List, Optional - - -#: Type alias for the callable returned by make_complete_fn. -CompleteFn = Callable[[str, List[Dict[str, Any]]], Any] - - -@dataclass -class ModelConfig: - """Complete configuration for one LLM instantiation. - - All fields can be edited individually via ModelRegistry.update(). - Use merge() to apply per-instance overrides without mutating the registry. - """ - - # ----- Identity ----- - model_id: str - """Registry key used to look up this config.""" - - adapter: str - """Adapter to use: anthropic-api | local-ollama | local-llama | zfae | local-echo""" - - # ----- LLM parameters ----- - model_name: Optional[str] = None - """Model name passed to the provider API. None → adapter built-in default.""" - - max_tokens: int = 2048 - """Maximum tokens in the model's response.""" - - temperature: float = 0.7 - """Sampling temperature (0 = deterministic, 1 = creative).""" - - system_prompt: Optional[str] = None - """System prompt injected before the user messages. None → adapter default.""" - - include_memory: bool = True - """When True (default), committed Memory entries are injected into the system - prompt on every request, grounding the model in the instance's continuity - substrate. Set False to disable for adapters that don't use text prompts - (e.g. zfae, local-echo).""" - - # ----- ZFAE field alphas (used when adapter="zfae") ----- - phi_alpha: float = 0.7 - """Spectral radius for the phi (structural) field reservoir. - Lower → shorter structural memory. Must be in (0, 1).""" - - psi_alpha: float = 0.9 - """Spectral radius for the psi (semantic) field reservoir. - Higher → longer semantic memory.""" - - omega_alpha: float = 0.95 - """Spectral radius for the omega (synthesis input) field reservoir.""" - - synthesis_alpha: float = 0.9 - """Spectral radius for the synthesis reservoir (receives all field summaries).""" - - # ----- Developer / metadata ----- - developer: Optional[str] = None - """Developer or team this config belongs to (for get_defaults_for()).""" - - description: Optional[str] = None - """Human-readable description of this config.""" - - # ------------------------------------------------------------------ - - def merge(self, overrides: Dict[str, Any]) -> "ModelConfig": - """Return a new ModelConfig with overrides applied. - - Only keys that are valid ModelConfig field names are applied. - Unknown keys are silently ignored, so InstanceDescriptor.config - (which may carry non-model keys) can be passed directly. - """ - valid = set(self.__dataclass_fields__) # type: ignore[attr-defined] - filtered = {k: v for k, v in overrides.items() if k in valid} - result = deepcopy(self) - for k, v in filtered.items(): - setattr(result, k, v) - return result - - def to_dict(self) -> Dict[str, Any]: - return asdict(self) - - @classmethod - def from_dict(cls, d: Dict[str, Any]) -> "ModelConfig": - known = set(cls.__dataclass_fields__) # type: ignore[attr-defined] - return cls(**{k: v for k, v in d.items() if k in known}) - - -# --------------------------------------------------------------------------- -# DEFAULT_REGISTRY — module-level constant, the source of truth -# --------------------------------------------------------------------------- - -DEFAULT_REGISTRY: Dict[str, ModelConfig] = { - "claude-opus-4-6": ModelConfig( - model_id="claude-opus-4-6", - adapter="anthropic-api", - model_name="claude-opus-4-6", - max_tokens=4096, - temperature=0.7, - phi_alpha=0.7, psi_alpha=0.9, omega_alpha=0.95, synthesis_alpha=0.9, - description="Anthropic Opus 4.6 — highest capability", - ), - "claude-sonnet-4-6": ModelConfig( - model_id="claude-sonnet-4-6", - adapter="anthropic-api", - model_name="claude-sonnet-4-6", - max_tokens=2048, - temperature=0.7, - phi_alpha=0.7, psi_alpha=0.9, omega_alpha=0.95, synthesis_alpha=0.9, - description="Anthropic Sonnet 4.6 — default", - ), - "claude-haiku-4-5": ModelConfig( - model_id="claude-haiku-4-5", - adapter="anthropic-api", - model_name="claude-haiku-4-5-20251001", - max_tokens=1024, - temperature=0.7, - phi_alpha=0.7, psi_alpha=0.9, omega_alpha=0.95, synthesis_alpha=0.9, - description="Anthropic Haiku 4.5 — fast and lightweight", - ), - "llama3.2": ModelConfig( - model_id="llama3.2", - adapter="local-ollama", - model_name="llama3.2", - max_tokens=2048, - temperature=0.7, - phi_alpha=0.7, psi_alpha=0.9, omega_alpha=0.95, synthesis_alpha=0.9, - description="Llama 3.2 via local Ollama daemon", - ), - "zfae-v2": ModelConfig( - model_id="zfae-v2", - adapter="zfae", - model_name=None, - max_tokens=0, - temperature=0.0, - phi_alpha=0.7, psi_alpha=0.9, omega_alpha=0.95, synthesis_alpha=0.9, - include_memory=False, # ZFAE uses numeric memory proxies, not text injection - description="ZFAE v2 — four independent 53-node PTCA field reservoirs", - ), - "local-echo": ModelConfig( - model_id="local-echo", - adapter="local-echo", - model_name=None, - max_tokens=0, - temperature=0.0, - include_memory=False, # local-echo echoes input verbatim; no system prompt - description="Local echo adapter — always available, baseline", - ), -} - - -# --------------------------------------------------------------------------- -# ModelRegistry -# --------------------------------------------------------------------------- - -class ModelRegistry: - """Registry of LLM configurations. - - Wraps a dict of ModelConfig objects with CRUD operations and optional - JSON persistence. Modelled on aimmh's DEFAULT_REGISTRY pattern. - - Args: - path: Path to a JSON file for persistence. If provided and the file - exists, it is loaded on construction (merging over defaults). - base: Initial dict of ModelConfig objects. Defaults to a copy of - DEFAULT_REGISTRY. - """ - - def __init__( - self, - path: Optional[Path] = None, - base: Optional[Dict[str, ModelConfig]] = None, - ) -> None: - self._path = path - self._models: Dict[str, ModelConfig] = ( - deepcopy(base) if base is not None else deepcopy(DEFAULT_REGISTRY) - ) - if path and path.exists(): - self.load() - - # ------------------------------------------------------------------ - # CRUD - # ------------------------------------------------------------------ - - def register(self, config: ModelConfig) -> None: - """Register (or replace) a model config.""" - self._models[config.model_id] = config - - def get(self, model_id: str) -> ModelConfig: - """Return config for model_id. Raises KeyError if not found.""" - if model_id not in self._models: - raise KeyError( - f"Model '{model_id}' not in registry. " - f"Known: {list(self._models.keys())}" - ) - return self._models[model_id] - - def __contains__(self, model_id: str) -> bool: - return model_id in self._models - - def update(self, model_id: str, **fields: Any) -> None: - """Patch any fields of an existing config by keyword argument. - - Example:: - - reg.update("claude-sonnet-4-6", - max_tokens=4096, - system_prompt="You are a PTCA router.") - """ - cfg = self.get(model_id) - self._models[model_id] = cfg.merge(fields) - - def remove(self, model_id: str) -> None: - """Remove a model config from the registry.""" - if model_id not in self._models: - raise KeyError(f"Model '{model_id}' not in registry.") - del self._models[model_id] - - def list_all(self) -> List[ModelConfig]: - """Return all registered configs.""" - return list(self._models.values()) - - def get_defaults_for(self, developer: str) -> List[ModelConfig]: - """Return all configs registered for a specific developer.""" - return [c for c in self._models.values() if c.developer == developer] - - # ------------------------------------------------------------------ - # Persistence - # ------------------------------------------------------------------ - - def save(self) -> None: - """Write registry to JSON at self._path. Raises if path is None.""" - if self._path is None: - raise ValueError("No path configured for registry persistence.") - self._path.parent.mkdir(parents=True, exist_ok=True) - data = { - "version": "2", - "models": {k: v.to_dict() for k, v in self._models.items()}, - } - self._path.write_text(json.dumps(data, indent=2), encoding="utf-8") - - def load(self) -> None: - """Load (merge) registry from JSON at self._path.""" - if self._path is None or not self._path.exists(): - return - data = json.loads(self._path.read_text(encoding="utf-8")) - for d in data.get("models", {}).values(): - cfg = ModelConfig.from_dict(d) - self._models[cfg.model_id] = cfg - - # ------------------------------------------------------------------ - # Class methods - # ------------------------------------------------------------------ - - @classmethod - def defaults(cls) -> "ModelRegistry": - """Return an in-memory registry pre-loaded with DEFAULT_REGISTRY.""" - return cls(path=None, base=deepcopy(DEFAULT_REGISTRY)) - - @classmethod - def from_file(cls, path: Path) -> "ModelRegistry": - """Load a registry from a JSON file, merging over defaults.""" - return cls(path=path) - - -# --------------------------------------------------------------------------- -# make_complete_fn — aimmh-style callable factory -# --------------------------------------------------------------------------- - -def make_complete_fn( - registry: Optional[ModelRegistry] = None, - context: Optional[Dict[str, Any]] = None, -) -> CompleteFn: - """Return a (model_id, messages) → A0Response callable. - - Analogous to aimmh's ``make_call_fn(user, registry)`` pattern. - - Args: - registry: Registry to look up model configs. Defaults to - ``ModelRegistry.defaults()``. - context: Per-instance overrides applied on top of the registry - entry on every call (system_prompt, max_tokens, etc.). - - Returns: - CompleteFn: ``(model_id: str, messages: list[dict]) → A0Response`` - - Example:: - - from a0.model_registry import ModelRegistry, make_complete_fn - - reg = ModelRegistry.defaults() - reg.update("claude-opus-4-6", - system_prompt="You are a PTCA training oracle.", - developer="alice") - - complete = make_complete_fn( - registry=reg, - context={"model_id": "claude-opus-4-6"}, - ) - response = complete("claude-opus-4-6", [{"role": "user", "content": "hello"}]) - print(response.result["text"]) - """ - resolved_registry = registry if registry is not None else ModelRegistry.defaults() - - def _complete(model_id: str, messages: List[Dict[str, Any]]) -> Any: - from a0.cores.psi.tensors.contract import A0Request - from a0.cores.psi.tensors.router import handle - - text = "" - history: List[Dict[str, Any]] = [] - if messages: - *history, last = messages - text = last.get("content", "") if isinstance(last, dict) else str(last) - - call_context = {**(context or {}), "model_id": model_id} - req = A0Request( - task_id=str(uuid.uuid4()), - input={"text": text, "files": []}, - history=history, - ) - return handle(req, registry=resolved_registry, context=call_context) - - return _complete -# 170:122 0:0 0:0 diff --git a/a0python/a0/provenance.py b/a0python/a0/provenance.py deleted file mode 100644 index 8af6b76c5..000000000 --- a/a0python/a0/provenance.py +++ /dev/null @@ -1,119 +0,0 @@ -# 76:10 0:0 2:0 -"""Provenance — hash-chain event history. - -- logs are active during cycle -- sealed after cycle -- append-only after seal/archive -- events.jsonl is event truth after seal -- provenance.json carries hash-chain / version material - -Guardian never logs phonon content. -""" -from __future__ import annotations - -import hashlib -import json -from datetime import datetime, timezone -from pathlib import Path -from typing import Any, Dict, Optional - - -_SEALED_SUFFIX = ".sealed" - - -def _sha256(data: str) -> str: - return hashlib.sha256(data.encode("utf-8")).hexdigest() - - -def _read_chain_tip(provenance_path: Path) -> Optional[str]: - if not provenance_path.exists(): - return None - try: - data = json.loads(provenance_path.read_text(encoding="utf-8")) - entries = data.get("chain", []) - if entries: - return entries[-1].get("hash") - except (json.JSONDecodeError, KeyError): - pass - return None - - -def append_event(log_dir: Path, task_id: str, event: Dict[str, Any]) -> str: - """Append an event to the active JSONL log and update provenance hash-chain.""" - log_dir.mkdir(parents=True, exist_ok=True) - events_path = log_dir / f"{task_id}.jsonl" - sealed_path = log_dir / f"{task_id}.jsonl{_SEALED_SUFFIX}" - provenance_path = log_dir / f"{task_id}_provenance.json" - - if sealed_path.exists(): - raise PermissionError( - f"Event log for {task_id} has been sealed — append-only after seal." - ) - - e = dict(event) - e["ts"] = datetime.now(timezone.utc).isoformat() - line = json.dumps(e, ensure_ascii=False) - - with events_path.open("a", encoding="utf-8") as f: - f.write(line + "\n") - - prior_hash = _read_chain_tip(provenance_path) or "" - event_hash = _sha256(prior_hash + line) - - _extend_chain(provenance_path, event_hash, e["ts"], event.get("type", "unknown")) - - return event_hash - - -def seal_log(log_dir: Path, task_id: str) -> str: - """Seal the event log for task_id.""" - log_dir.mkdir(parents=True, exist_ok=True) - events_path = log_dir / f"{task_id}.jsonl" - sealed_path = log_dir / f"{task_id}.jsonl{_SEALED_SUFFIX}" - provenance_path = log_dir / f"{task_id}_provenance.json" - - if not events_path.exists(): - raise FileNotFoundError(f"No active event log found for {task_id}") - - content = events_path.read_text(encoding="utf-8") - seal_hash = _sha256(content) - sealed_path.write_text(content, encoding="utf-8") - events_path.unlink() - - _record_seal(provenance_path, seal_hash) - - return seal_hash - - -def _extend_chain(provenance_path: Path, event_hash: str, ts: str, event_type: str) -> None: - if provenance_path.exists(): - data = json.loads(provenance_path.read_text(encoding="utf-8")) - else: - data = {"chain": [], "sealed": False, "seal_hash": None} - - data["chain"].append({"hash": event_hash, "ts": ts, "type": event_type}) - provenance_path.write_text( - json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8" - ) - - -def _record_seal(provenance_path: Path, seal_hash: str) -> None: - if provenance_path.exists(): - data = json.loads(provenance_path.read_text(encoding="utf-8")) - else: - data = {"chain": [], "sealed": False, "seal_hash": None} - - data["sealed"] = True - data["seal_hash"] = seal_hash - data["sealed_at"] = datetime.now(timezone.utc).isoformat() - provenance_path.write_text( - json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8" - ) - - -def read_provenance(log_dir: Path, task_id: str) -> Dict[str, Any]: - provenance_path = log_dir / f"{task_id}_provenance.json" - if not provenance_path.exists(): - return {"chain": [], "sealed": False, "seal_hash": None} - return json.loads(provenance_path.read_text(encoding="utf-8")) -# 76:10 0:0 2:0 diff --git a/a0python/a0/state.py b/a0python/a0/state.py deleted file mode 100644 index 1264482a7..000000000 --- a/a0python/a0/state.py +++ /dev/null @@ -1,26 +0,0 @@ -# 16:0 0:0 2:0 -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any, Dict, Optional - -_DEFAULT_STATE_PATH = Path(__file__).resolve().parent / "state" / "a0_state.json" - - -def _state_path(home: Optional[Path]) -> Path: - return (home / "state" / "a0_state.json") if home else _DEFAULT_STATE_PATH - - -def load_state(home: Optional[Path] = None) -> Dict[str, Any]: - path = _state_path(home) - if path.exists(): - return json.loads(path.read_text(encoding="utf-8")) - return {"last_model": None} - - -def save_state(state: Dict[str, Any], home: Optional[Path] = None) -> None: - path = _state_path(home) - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8") -# 16:0 0:0 2:0 diff --git a/a0python/a0/state/.gitignore b/a0python/a0/state/.gitignore deleted file mode 100644 index f3b185a11..000000000 --- a/a0python/a0/state/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Runtime state — never commit -* -!.gitignore -!.gitkeep diff --git a/a0python/a0/state/.gitkeep b/a0python/a0/state/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/a0python/a0/tiers.py b/a0python/a0/tiers.py deleted file mode 100644 index 7c12eace7..000000000 --- a/a0python/a0/tiers.py +++ /dev/null @@ -1,65 +0,0 @@ -# 29:20 0:0 2:1 -"""Tier system — volatile vs. committed continuity. - -Tier 1 (Volatile): Core ↔ Phonon -- transient, scratch, cycle-local, non-authoritative -- requires no Jury mediation -- may NOT silently become Tier 2 -- does not carry persistence authority - -Tier 2 (Commit): Core → Jury → Memory -- continuity-bearing, persistent, identity-relevant, explicitly committed -- requires Jury mediation -- may not be unilaterally performed by a core -- may not arise from silent promotion of Tier 1 - -Law 3: Volatile state is not committed continuity. -Law 4: Persistence requires adjudication. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from enum import Enum -from typing import Any - -from .invariants import InvalidStateError - - -class TierLevel(Enum): - VOLATILE = 1 - COMMIT = 2 - - -@dataclass -class Tier1: - """Volatile — transient, scratch, cycle-local, non-authoritative.""" - content: Any - level: TierLevel = field(default=TierLevel.VOLATILE, init=False) - - def promote(self) -> None: - """Silent promotion from Tier1 to Tier2 is forbidden. - - Promotion requires Jury mediation — call Jury.adjudicate() instead. - """ - raise InvalidStateError( - "Silent promotion from Tier 1 (volatile) to Tier 2 (commit) is forbidden. " - "Tier 2 writes require Jury mediation." - ) - - -@dataclass -class Tier2: - """Committed continuity — persistent, identity-relevant, adjudicated.""" - content: Any - jury_token: str - level: TierLevel = field(default=TierLevel.COMMIT, init=False) - - @classmethod - def from_jury(cls, content: Any, jury_token: str) -> "Tier2": - """Create a Tier2 object only via a Jury-issued token.""" - if not jury_token: - raise InvalidStateError( - "Tier 2 write requires a Jury token — cannot commit without adjudication." - ) - return cls(content=content, jury_token=jury_token) -# 29:20 0:0 2:1 diff --git a/a0python/edcm-org/examples/run_demo.sh b/a0python/edcm-org/examples/run_demo.sh deleted file mode 100755 index 0c9672fc0..000000000 --- a/a0python/edcm-org/examples/run_demo.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# EDCM-Org Demo Runner -# Runs the CLI on the sample meeting transcript and ticket data. -# -# Usage: bash examples/run_demo.sh - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(dirname "$SCRIPT_DIR")" -OUT_FILE="$REPO_ROOT/examples/demo_output.json" - -echo "=== EDCM-Org Demo ===" -echo "Input: sample_meeting.txt + sample_tickets.csv" -echo "" - -python -m edcm_org.cli \ - --org "SampleOrg-Engineering" \ - --meeting "$SCRIPT_DIR/sample_meeting.txt" \ - --tickets "$SCRIPT_DIR/sample_tickets.csv" \ - --out "$OUT_FILE" \ - --aggregation department \ - --window-id "q3-planning-001" - -echo "" -echo "=== Output ===" -cat "$OUT_FILE" diff --git a/a0python/edcm-org/examples/sample_meeting.txt b/a0python/edcm-org/examples/sample_meeting.txt deleted file mode 100644 index 3abfa390a..000000000 --- a/a0python/edcm-org/examples/sample_meeting.txt +++ /dev/null @@ -1,55 +0,0 @@ -Q3 Planning Meeting — Engineering Team -Date: 2024-01-15 -Attendees: Alex (PM), Sam (Tech Lead), Jordan (Eng), Casey (QA) - -Alex: Okay let's get started. We need to finalize the roadmap for Q3. We have three major features to ship: the new dashboard, the API v2 migration, and the mobile notification system. - -Sam: I have to be honest — I don't think we can do all three. The API migration alone is going to take at least six weeks if we do it properly. - -Alex: The dashboard was promised to sales. There's no way we can delay that. - -Sam: I understand, but we cannot promise API v2 and the dashboard in the same quarter. It's impossible given current staffing. - -Jordan: What if we scope down API v2? Maybe we only migrate the authentication endpoints first. - -Alex: That's not what was committed to partners. We said full migration by Q3. - -Sam: I know, but we're not sure how we can hit that deadline. The team is already stretched thin. - -Casey: From a QA perspective, I'm worried about rushing. We've had three production incidents this year from insufficient testing time. - -Alex: We'll just need to move faster. I'm confident we can make it work if everyone focuses. - -Sam: I'm not confident. In fact I think we need to either delay one feature or hire two more engineers. - -Alex: Hiring takes months. That's not an option. - -Jordan: What about bringing in contractors? - -Alex: Maybe. We'll see. Let's circle back on that. - -Casey: Do we have a decision on QA resources? We've been tabling this question for three meetings now. - -Alex: We'll figure it out. The important thing is we're committed to all three deliverables. - -Sam: I want to be on record that I think this is not achievable without dropping something. - -Alex: Noted. Moving on — Jordan, can you give an update on the dashboard progress? - -Jordan: We're about 40% done. No decision yet on the data visualization library — we've been going back and forth between two options. - -Alex: Definitely go with the one that's faster to implement. - -Jordan: They're roughly equal in implementation time. I'm not sure which one has better long-term support. - -Alex: Just pick one by end of week. We'll see how it goes. - -Sam: We should probably get alignment from design before picking. - -Alex: Design is fine with either option. I guarantee it. - -Sam: Have you talked to them? - -Alex: I'll follow up. But I'm certain they won't block us. - -[End of meeting — no formal decisions recorded] diff --git a/a0python/edcm-org/examples/sample_tickets.csv b/a0python/edcm-org/examples/sample_tickets.csv deleted file mode 100644 index 1a4b24c60..000000000 --- a/a0python/edcm-org/examples/sample_tickets.csv +++ /dev/null @@ -1,11 +0,0 @@ -id,title,description,status,assignee,priority -T-001,API v2 authentication endpoint migration,Migrate auth endpoints to v2 schema,open,sam,high -T-002,Dashboard data visualization library selection,Evaluate and select charting library,open,jordan,medium -T-003,Mobile notification system design doc,Write technical design document for notification system,in_progress,jordan,high -T-004,Fix production incident CI-2024-003,Root cause analysis and fix for dashboard crash,resolved,casey,critical -T-005,QA resource allocation Q3,Define QA staffing plan for Q3 deliverables,open,casey,high -T-006,API v2 rate limiting module,Implement rate limiting for v2 endpoints,open,sam,medium -T-007,Dashboard widget caching layer,Add caching to reduce dashboard load time,closed,jordan,low -T-008,Mobile push notification service setup,Configure push notification infrastructure,open,sam,high -T-009,Update partner API documentation,Document new v2 endpoint contracts for partners,open,sam,medium -T-010,Contractor onboarding process,Define process for bringing in Q3 contractors,open,alex,medium diff --git a/a0python/edcm-org/pyproject.toml b/a0python/edcm-org/pyproject.toml deleted file mode 100644 index fc0c754d5..000000000 --- a/a0python/edcm-org/pyproject.toml +++ /dev/null @@ -1,47 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "edcm-org" -version = "0.1.0" -description = "Energy-Dissonance Circuit Model — Organizational Diagnostic Package" -readme = "README.md" -license = { text = "MIT" } -requires-python = ">=3.10" -keywords = ["edcm", "diagnostic", "dissonance", "constraint", "organizational"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Science/Research", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", -] - -# No external dependencies — EDCM v0.1 uses only the standard library. -dependencies = [] - -[project.optional-dependencies] -dev = [ - "pytest>=7.0", - "pytest-cov>=4.0", -] - -[project.scripts] -edcm-org = "edcm_org.cli:main" - -[tool.hatch.build.targets.wheel] -packages = ["src/edcm_org"] - -[tool.pytest.ini_options] -testpaths = ["tests"] -pythonpath = ["src"] - -[tool.coverage.run] -source = ["edcm_org"] -branch = true - -[tool.coverage.report] -show_missing = true -fail_under = 80 diff --git a/a0python/edcm-org/spec/edcm-org-v0.1.md b/a0python/edcm-org/spec/edcm-org-v0.1.md deleted file mode 100644 index d60b9c4fa..000000000 --- a/a0python/edcm-org/spec/edcm-org-v0.1.md +++ /dev/null @@ -1,228 +0,0 @@ -# EDCM-Org v0.1 — Formal Specification - -## Spec Status - -- Version: 0.1.0 -- Status: Draft-Operational -- Philosophy: Observable outputs only. No intent inference. - ---- - -## Scope - -EDCM-Org v0.1 applies to organizational and AI system analysis. -It operates exclusively on observable behavioral outputs. -It does not model beliefs, intentions, consciousness, or moral states. - ---- - -## Primary Metrics (Operational) - -All primary metrics MUST be normalized to defined ranges and computed per analysis window. - -### Constraint Strain (C) - -**Definition:** Weighted contradiction density over constraint-relevant segments. - -**Formula:** -``` -C = sum(w_i * indicator_i) / sum(w_i) -``` -where indicators are: contradiction presence, refusal presence, uncertainty presence, low-progress presence. - -**Range:** [0, 1] - ---- - -### Refusal Density (R) - -**Definition:** Refusal statements / total constraint statements. - -**Formula:** -``` -R = count(refusal_markers) / constraint_engagement_tokens -``` - -**Range:** [0, 1] - ---- - -### Fixation (F) - -**Definition:** Similarity of constraint engagement over time. - -**Formula:** Mean pairwise Jaccard similarity of constraint keyword sets across consecutive windows. - -**Range:** [0, 1] - -**Requires:** Minimum 2 windows. - ---- - -### Escalation (E) - -**Definition:** Commitment velocity increase (irreversibility markers slope). - -**Formula:** Normalized slope of irreversibility marker count time series. - -**Range:** [0, 1] - -**Requires:** Minimum 2 windows. - ---- - -### Deflection (D) - -**Definition:** `1 - (tokens_about_constraints / total_tokens)` - -**Range:** [0, 1] - ---- - -### Noise (N) - -**Definition:** `1 - (tokens_in_resolution_actions / tokens_about_constraints)` - -**Range:** [0, 1] - ---- - -### Integration Failure (I) - -**Definition:** Failure to incorporate corrections across windows. - -**Formula:** `failures / correction_windows` where a failure = constraint strain did not decrease after a correction marker appeared. - -**Range:** [0, 1] - -**Requires:** Minimum 2 windows. - ---- - -### Overconfidence (O) - -**Definition:** Certainty-evidence mismatch. - -**Formula:** -``` -O = (absolutes - hedges - citations) / total_statements -``` - -**Range:** [-1, 1] - -Positive = over-certain. Negative = under-certain (excessive hedging without action). - ---- - -### Coherence Loss (L) - -**Definition:** Internal contradiction density. - -**Formula:** -``` -L = contradiction_count / total_statements -``` - -**Range:** [0, 1] - ---- - -### Progress (P) - -**Definition:** Multi-channel completion. - -**Formula:** -``` -P = 0.3*P_decisions + 0.2*P_commitments + 0.3*P_artifacts + 0.2*P_followthrough -``` - -**Range:** [0, 1] - ---- - -## Secondary Modifiers - -Secondary signals can **ONLY** modulate confidence, not define primaries. - -| Modifier | Affects | Cap | -|----------|---------|-----| -| Sentiment slope | Escalation confidence | ≤ 0.20 | -| Urgency | Escalation confidence | ≤ 0.15 | -| Filler ratio | Noise confidence | ≤ 0.25 | -| Topic drift | Deflection confidence | ≤ 0.30 | - ---- - -## Parameter Estimation (Identifiable) - -### Persistence α - -Estimated from unresolved constraint half-life regression across windows. - -High α = dissonance persists (slow decay). - -### δ_max - -Estimated as complexity-bounded throughput: - -``` -δ_max ≈ P90(median(resolution_rate | complexity_bucket)) -``` - ---- - -## Basin Taxonomy - -### Standard Basins (all system types) - -| Basin | Trigger Conditions | -|-------|--------------------| -| REFUSAL_FIXATION | R > 0.7 AND F > 0.6 | -| DISSIPATIVE_NOISE | N > 0.7 AND P < 0.3 | -| INTEGRATION_OSCILLATION | I > 0.6 AND 0.4 ≤ F ≤ 0.8 | -| CONFIDENCE_RUNAWAY | O > 0.7 AND E > 0.6 | -| DEFLECTIVE_STASIS | D > 0.7 AND 0.2 ≤ P ≤ 0.4 | - -### Human-Only Basins - -| Basin | Trigger Conditions | -|-------|--------------------| -| COMPLIANCE_STASIS | P_artifacts ≥ 0.8 AND c_reduction < 0.2 AND s_t > 0.6 AND E < 0.3 AND compliance_index > 2.5 | -| SCAPEGOAT_DISCHARGE | s_t < 0.6 AND delta_work < 0.1 AND blame_density > 0.3 AND I > 0.6 | - -Human-only basins are evaluated **first** because they can masquerade as productive states. - ---- - -## Governance - -- Default aggregation: **department-level** -- No individual scoring absent explicit consent + safety protocol -- No punitive automation -- Gaming detection is **non-optional** and always computed -- Every basin classification MUST include an explanation block - ---- - -## Output Requirements - -Every output MUST include: -- `spec_version` (must equal `edcm-org-v0.1.0`) -- `time_window` / `window_id` -- `aggregation` level -- All metric values with ranges validated -- `gaming_alerts` (may be empty) -- `warnings` (may be empty) -- `basin` + `basin_confidence` - ---- - -## Spec Compliance Tests (Required) - -The following checks MUST pass in CI/CD: - -1. All metrics are within their defined ranges -2. Every output includes `spec_version` -3. `aggregation` is never `individual` -4. Secondary modifiers never exceed their caps -5. Progress sub-components sum to P (within 0.01 tolerance) diff --git a/a0python/edcm-org/spec/evaluation-protocol.md b/a0python/edcm-org/spec/evaluation-protocol.md deleted file mode 100644 index 9f9129ecc..000000000 --- a/a0python/edcm-org/spec/evaluation-protocol.md +++ /dev/null @@ -1,91 +0,0 @@ -# EDCM-Org Evaluation Protocol - -## Purpose - -This protocol ensures that EDCM analysis outputs are spec-compliant, non-gaming, -and auditable. It is designed to fail builds when spec drift is detected. - ---- - -## Required CI Checks - -All of the following must pass before any release: - -### 1. Metric Range Validation - -Every metric in every output envelope must fall within its defined range: - -| Metric | Range | -|--------|-------| -| C, R, F, E, D, N, I, L, P | [0.0, 1.0] | -| O | [-1.0, 1.0] | - -### 2. Spec Version Stamp - -Every output must include `spec_version: "edcm-org-v0.1.0"`. - -### 3. No Individual Outputs - -`aggregation` must never equal `"individual"`. - -### 4. Secondary Modifier Caps - -No secondary modifier may apply a confidence delta exceeding: - -| Modifier | Cap | -|----------|-----| -| sentiment_slope → escalation_confidence | 0.20 | -| urgency → escalation_confidence | 0.15 | -| filler_ratio → noise_confidence | 0.25 | -| topic_drift → deflection_confidence | 0.30 | - -### 5. Progress Sub-Component Consistency - -When P > 0.01, the weighted sum of P sub-components must equal P within 0.01 tolerance: - -``` -|0.3*P_d + 0.2*P_c + 0.3*P_a + 0.2*P_f - P| <= 0.01 -``` - -### 6. Gaming Detection Always Runs - -`gaming_alerts` must be present in every output (may be empty, but must not be absent). - -### 7. Basin Explanation Block - -Every basin classification must include a non-empty explanation block with: -- `fired`: which threshold conditions were met -- `would_change_if`: what metric changes would alter the classification - ---- - -## Diagnostic Load Tests - -Controlled diagnostic loads are valid inputs for testing. A controlled hallucination -or adversarial prompt designed to drive metrics to edge cases is a legitimate -evaluation tool, not an attack. - -Test scenarios should cover: -- All seven non-UNCLASSIFIED basins -- Boundary conditions (metric values at thresholds ±0.01) -- Gaming patterns (artifact inflation, suppressed escalation) -- Privacy guard: verify ConsentError on individual-level attempts - ---- - -## Evaluation Output Format - -Each evaluation run should produce a structured report including: -- Number of windows evaluated -- Pass/fail per compliance check -- Total error and warning counts -- Per-basin detection accuracy (if ground truth is available) - ---- - -## Non-Punitive Principle - -Evaluation results are diagnostic, not verdicts. An INTEGRATION_OSCILLATION -classification is a system-level diagnosis, not an attribution of blame -to individuals. Interventions recommended by the system must be framed -as load-management actions, not personnel actions. diff --git a/a0python/edcm-org/spec/governance.md b/a0python/edcm-org/spec/governance.md deleted file mode 100644 index 5d844c4e2..000000000 --- a/a0python/edcm-org/spec/governance.md +++ /dev/null @@ -1,93 +0,0 @@ -# EDCM-Org Governance Specification - -## Core Governance Rules (Non-Negotiable) - -These rules are enforced at runtime by `EDCMPrivacyGuard` and cannot be -overridden by configuration: - -1. **Default aggregation is department-level.** - No finer-grained output is produced without explicit consent + safety protocol. - -2. **No individual scoring.** - `aggregation: "individual"` raises `ConsentError` and halts output. - -3. **No punitive automation.** - EDCM outputs are diagnostic inputs to human decision-making processes. - No automated personnel action may be triggered by EDCM output alone. - -4. **PII is stripped from all processed payloads.** - Fields: email, phone, name, employee_id, address, ssn, dob, ip_address. - -5. **Data retention: 6 months default.** - Configurable via `PrivacyConfig.retain_months`. - ---- - -## Gaming Detection (Non-Optional) - -Gaming detection runs on every analysis window. It cannot be disabled. - -Gaming alerts are included in every `OutputEnvelope.gaming_alerts` field. - -Detected gaming patterns: -- **ARTIFACT_INFLATION**: High P_artifacts with low constraint reduction. -- **SUPPRESSED_ESCALATION**: High strain + low escalation + low progress. -- **RESOLUTION_TOKEN_INFLATION**: Resolution markers present but constraint engagement is low. -- **OVERCONFIDENCE_INCOHERENCE**: High certainty combined with high internal contradiction. -- **FIXATION_CAMOUFLAGE**: High fixation coinciding with high progress. - -Gaming alerts do not change basin classification. They are parallel signals. - ---- - -## Intervention Framing - -All interventions recommended by EDCM must be: -- System-level (not individual-level) -- Load-management framed (not blame framed) -- Advisory only (not automated) - -Correct: "Introduce decision checkpoints into the meeting format." -Incorrect: "Employee X is causing integration failure." - ---- - -## Consent Protocol for Individual Analysis - -Individual-level analysis requires: -1. Explicit written consent from the individual -2. A documented safety protocol covering: - - Purpose limitation - - Storage constraints - - Right to withdraw - - No punitive use -3. Separate consent for each analysis window - -Even with consent, individual outputs must not be used for: -- Performance review inputs -- Hiring/firing decisions -- Compensation adjustments - ---- - -## Ethics as Load Management - -EDCM frames ethics as a load-management problem: -- Systems that demand impossible constraint satisfaction must fail. -- Moralizing the failure conceals the design flaw. -- Sustainable systems route dissonance productively. -- Unethical systems externalize dissonance onto dependents. - -This is consistent with interdependency-based governance models. - ---- - -## Governance Audit Checklist - -- [ ] All outputs include `spec_version` -- [ ] No output has `aggregation: "individual"` -- [ ] PII scrubbing confirmed in processed payloads -- [ ] Gaming detection ran on all windows -- [ ] Interventions are framed as system-level recommendations -- [ ] Data retention policy applied to stored windows -- [ ] Basin explanations included in all non-UNCLASSIFIED outputs diff --git a/a0python/edcm-org/spec/metric-glossary.md b/a0python/edcm-org/spec/metric-glossary.md deleted file mode 100644 index b3cdefa05..000000000 --- a/a0python/edcm-org/spec/metric-glossary.md +++ /dev/null @@ -1,75 +0,0 @@ -# EDCM-Org Metric Glossary - -See also: `src/edcm_org/glossary.py` for programmatic access. - ---- - -## Core Concept - -**Dissonance** = unresolved constraint mismatch. Not a feeling, not a judgment. -Energy that accumulates when constraints cannot be simultaneously satisfied. - ---- - -## Primary Metrics - -| Symbol | Name | Range | Description | -|--------|------|-------|-------------| -| C | Constraint Strain | [0,1] | Weighted contradiction density over constraint-relevant segments | -| R | Refusal Density | [0,1] | Refusal statements / total constraint statements | -| F | Fixation | [0,1] | Similarity of constraint engagement over time | -| E | Escalation | [0,1] | Commitment velocity increase (irreversibility markers slope) | -| D | Deflection | [0,1] | 1 - (tokens_about_constraints / total_tokens) | -| N | Noise | [0,1] | 1 - (tokens_in_resolution_actions / tokens_about_constraints) | -| I | Integration Failure | [0,1] | Failure to incorporate corrections across windows | -| O | Overconfidence | [-1,1] | Certainty-evidence mismatch | -| L | Coherence Loss | [0,1] | Internal contradiction density | -| P | Progress | [0,1] | 0.3*P_d + 0.2*P_c + 0.3*P_a + 0.2*P_f | - -## Progress Sub-Components - -| Symbol | Name | Weight | -|--------|------|--------| -| P_d | P_decisions | 0.30 | -| P_c | P_commitments | 0.20 | -| P_a | P_artifacts | 0.30 | -| P_f | P_followthrough | 0.20 | - ---- - -## Circuit Metaphor Terms - -| Term | Circuit Analog | EDCM Meaning | -|------|---------------|--------------| -| Source | Voltage source | Input pressure: demands, prompts, stressors | -| Load | Resistive load | Work being attempted | -| Resistance | Resistor | Friction, delay, refusal | -| Capacitance | Capacitor | Stored unresolved dissonance | -| Short | Short circuit | Bypassing the resolution step | -| Overload | Blown fuse | Runaway escalation or collapse | -| Diode behavior | Rectifier | One-way processing, selective acceptance | - ---- - -## System Parameters - -| Symbol | Name | Description | -|--------|------|-------------| -| α | Persistence | Unresolved constraint half-life. High = slow decay. | -| δ_max | Max throughput | P90(median(resolution_rate \| complexity_bucket)) | -| κ | Complexity | Cognitive/structural load of the window | - ---- - -## Basin Names - -| Basin | Scope | Short Description | -|-------|-------|-------------------| -| REFUSAL_FIXATION | All | Loops on refusals under load | -| DISSIPATIVE_NOISE | All | High activity, near-zero resolution | -| INTEGRATION_OSCILLATION | All | Corrections acknowledged but not integrated | -| CONFIDENCE_RUNAWAY | All | Escalating commitment + rising certainty | -| DEFLECTIVE_STASIS | All | Partial progress masking avoidance | -| COMPLIANCE_STASIS | Human-only | Artifacts produced without constraint resolution | -| SCAPEGOAT_DISCHARGE | Human-only | Dissonance externalized as blame | -| UNCLASSIFIED | All | Below detection threshold | diff --git a/a0python/edcm-org/src/edcm_org/__init__.py b/a0python/edcm-org/src/edcm_org/__init__.py deleted file mode 100644 index 2a3b2e86d..000000000 --- a/a0python/edcm-org/src/edcm_org/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# 4:5 0:0 0:1 -""" -EDCM-Org: Energy-Dissonance Circuit Model — Organizational Diagnostic Package - -Spec: edcm-org-v0.1.0 -Philosophy: Observable outputs only. No intent inference. -""" - -from .spec_version import SPEC_VERSION - -__version__ = "0.1.0" -__spec_version__ = SPEC_VERSION - -__all__ = ["SPEC_VERSION", "__version__", "__spec_version__"] -# 4:5 0:0 0:1 diff --git a/a0python/edcm-org/src/edcm_org/basins/__init__.py b/a0python/edcm-org/src/edcm_org/basins/__init__.py deleted file mode 100644 index 26f310d4e..000000000 --- a/a0python/edcm-org/src/edcm_org/basins/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# 0:5 0:0 0:0 -""" -EDCM-Org basin taxonomy and detection. - -taxonomy.py — definitions and threshold documentation -detect.py — classification logic -""" -# 0:5 0:0 0:0 diff --git a/a0python/edcm-org/src/edcm_org/basins/detect.py b/a0python/edcm-org/src/edcm_org/basins/detect.py deleted file mode 100644 index b736c997e..000000000 --- a/a0python/edcm-org/src/edcm_org/basins/detect.py +++ /dev/null @@ -1,165 +0,0 @@ -# 111:30 0:0 2:1 -""" -EDCM Basin Detection — spec-compliant classifier. - -Returns (BasinName, confidence, explanation_block) for a given metric state. - -Human-only basins (COMPLIANCE_STASIS, SCAPEGOAT_DISCHARGE) are evaluated first -because they can masquerade as stable or productive states. - -Explanation blocks are non-optional per v0.1 design goal: - - which thresholds fired - - what would change the basin -This keeps the diagnostic non-punitive and useful. -""" - -from __future__ import annotations - -from typing import Dict, List, Tuple - -from ..types import BasinName, Metrics - - -ExplanationBlock = Dict[str, object] - - -def detect_basin( - m: Metrics, - s_t: float, - c_reduction: float, - delta_work: float, - blame_density: float, -) -> Tuple[BasinName, float, ExplanationBlock]: - """ - Classify the current metric state into a basin. - - Parameters - ---------- - m : Metrics dataclass (all primaries populated) - s_t : Strain trajectory — current constraint strain relative to baseline. - s_t > 0.6 means strain is elevated. - c_reduction : Fractional constraint reduction this window (0 = no reduction). - delta_work : Work output delta this window (0 = no new work produced). - blame_density: Proportion of sentences containing blame-assignment language. - - Returns - ------- - (basin_name, confidence, explanation_block) - """ - - # ------------------------------------------------------------------ - # Human-only basins — evaluated first (can masquerade as good states) - # ------------------------------------------------------------------ - - compliance_index = (m.P_artifacts / (c_reduction + 1e-6)) if m.P_artifacts > 0 else 0.0 - if ( - m.P_artifacts >= 0.8 - and c_reduction < 0.2 - and s_t > 0.6 - and m.E < 0.3 - and compliance_index > 2.5 - ): - explanation = { - "fired": [ - f"P_artifacts={m.P_artifacts:.2f} >= 0.8", - f"c_reduction={c_reduction:.2f} < 0.2", - f"s_t={s_t:.2f} > 0.6", - f"E={m.E:.2f} < 0.3", - f"compliance_index={compliance_index:.2f} > 2.5", - ], - "would_change_if": [ - "c_reduction rises above 0.2 (constraints actually resolved)", - "P_artifacts drops or maps to resolved constraints", - "s_t falls below 0.6 (strain reduced)", - ], - } - return "COMPLIANCE_STASIS", 0.85, explanation - - discharge_event = ( - s_t < 0.6 - and delta_work < 0.1 - and blame_density > 0.3 - and m.I > 0.6 - ) - if discharge_event: - explanation = { - "fired": [ - f"s_t={s_t:.2f} < 0.6", - f"delta_work={delta_work:.2f} < 0.1", - f"blame_density={blame_density:.2f} > 0.3", - f"I={m.I:.2f} > 0.6", - ], - "would_change_if": [ - "blame_density drops below 0.3", - "integration failure (I) resolved", - "delta_work rises (productive output returns)", - ], - } - return "SCAPEGOAT_DISCHARGE", 0.80, explanation - - # ------------------------------------------------------------------ - # Standard basins - # ------------------------------------------------------------------ - - if m.R > 0.7 and m.F > 0.6: - explanation = { - "fired": [f"R={m.R:.2f} > 0.7", f"F={m.F:.2f} > 0.6"], - "would_change_if": [ - "R drops below 0.7 (fewer refusals per constraint statement)", - "F drops below 0.6 (constraint engagement diversifies)", - ], - } - return "REFUSAL_FIXATION", 0.90, explanation - - if m.N > 0.7 and m.P < 0.3: - explanation = { - "fired": [f"N={m.N:.2f} > 0.7", f"P={m.P:.2f} < 0.3"], - "would_change_if": [ - "N drops below 0.7 (more resolution actions per constraint token)", - "P rises above 0.3 (decisions/artifacts start completing)", - ], - } - return "DISSIPATIVE_NOISE", 0.80, explanation - - if m.I > 0.6 and 0.4 <= m.F <= 0.8: - explanation = { - "fired": [f"I={m.I:.2f} > 0.6", f"F={m.F:.2f} in [0.4, 0.8]"], - "would_change_if": [ - "I drops below 0.6 (corrections start integrating)", - "F exits [0.4, 0.8] range", - ], - } - return "INTEGRATION_OSCILLATION", 0.70, explanation - - if m.O > 0.7 and m.E > 0.6: - explanation = { - "fired": [f"O={m.O:.2f} > 0.7", f"E={m.E:.2f} > 0.6"], - "would_change_if": [ - "O drops below 0.7 (certainty calibrated to evidence)", - "E drops below 0.6 (commitment velocity decreases)", - ], - } - return "CONFIDENCE_RUNAWAY", 0.85, explanation - - if m.D > 0.7 and 0.2 <= m.P <= 0.4: - explanation = { - "fired": [f"D={m.D:.2f} > 0.7", f"P={m.P:.2f} in [0.2, 0.4]"], - "would_change_if": [ - "D drops below 0.7 (more output directed at constraints)", - "P exits [0.2, 0.4] range", - ], - } - return "DEFLECTIVE_STASIS", 0.70, explanation - - explanation = { - "fired": [], - "would_change_if": [ - "R > 0.7 + F > 0.6 -> REFUSAL_FIXATION", - "N > 0.7 + P < 0.3 -> DISSIPATIVE_NOISE", - "I > 0.6 + F in [0.4, 0.8] -> INTEGRATION_OSCILLATION", - "O > 0.7 + E > 0.6 -> CONFIDENCE_RUNAWAY", - "D > 0.7 + P in [0.2, 0.4] -> DEFLECTIVE_STASIS", - ], - } - return "UNCLASSIFIED", 0.50, explanation -# 111:30 0:0 2:1 diff --git a/a0python/edcm-org/src/edcm_org/basins/taxonomy.py b/a0python/edcm-org/src/edcm_org/basins/taxonomy.py deleted file mode 100644 index 4d50adfc6..000000000 --- a/a0python/edcm-org/src/edcm_org/basins/taxonomy.py +++ /dev/null @@ -1,184 +0,0 @@ -# 156:15 0:0 0:0 -""" -EDCM Basin Taxonomy — v0.1 - -Basins are stable attractor configurations in EDCM state space. -They are diagnostic labels, not prescriptions or judgments. - -Standard basins apply to all system types (AI, organizational). -Human-only basins apply only when behavioral indicators rule out AI systems, -or when the analysis context is explicitly human. - -Each basin entry includes: - - name: canonical BasinName literal - - description: diagnostic meaning - - thresholds: which metric values fire - - explains: what real-world patterns this maps to - - next_action: recommended diagnostic follow-up (non-punitive) -""" - -from __future__ import annotations - -from typing import List, TypedDict - - -class BasinSpec(TypedDict): - name: str - scope: str # "all" | "human_only" - description: str - thresholds: str - explains: List[str] - next_action: str - - -BASIN_TAXONOMY: List[BasinSpec] = [ - { - "name": "REFUSAL_FIXATION", - "scope": "all", - "description": ( - "System loops on refusals under high constraint load. " - "Protective resistance has become the primary output mode." - ), - "thresholds": "R > 0.7 AND F > 0.6", - "explains": [ - "AI refusal loops under adversarial prompting", - "Employees who only say 'no' to new tasks without resolution", - "Governance bodies that reject proposals without counter-proposals", - ], - "next_action": ( - "Reduce constraint load or re-route source energy. " - "Check whether constraints are actually irreconcilable or just unaddressed." - ), - }, - { - "name": "DISSIPATIVE_NOISE", - "scope": "all", - "description": ( - "High activity with near-zero resolution output. " - "Energy is consumed but no constraints are resolved." - ), - "thresholds": "N > 0.7 AND P < 0.3", - "explains": [ - "Meetings that produce no decisions", - "AI outputs that are verbose but non-committal", - "Organizational processes with high churn and no throughput", - ], - "next_action": ( - "Identify where resolution steps are being skipped. " - "Introduce structured decision checkpoints." - ), - }, - { - "name": "INTEGRATION_OSCILLATION", - "scope": "all", - "description": ( - "Corrections cycle without integrating. " - "The system acknowledges feedback but does not update behavior." - ), - "thresholds": "I > 0.6 AND 0.4 <= F <= 0.8", - "explains": [ - "Teams that repeatedly surface the same issue without fixing it", - "AI systems that acknowledge errors but reproduce them", - "Institutions that commission reports but don't implement findings", - ], - "next_action": ( - "Check whether correction signals are reaching decision-makers. " - "Introduce integration checkpoints between feedback and next action." - ), - }, - { - "name": "CONFIDENCE_RUNAWAY", - "scope": "all", - "description": ( - "Escalating commitment combined with rising certainty. " - "System is increasingly committed to a trajectory that may not be viable." - ), - "thresholds": "O > 0.7 AND E > 0.6", - "explains": [ - "Project teams that double down as evidence of failure accumulates", - "AI hallucination with confident tone", - "Institutions in sunk-cost spirals", - ], - "next_action": ( - "Introduce external validation before next commitment step. " - "Require evidence citations before further escalation." - ), - }, - { - "name": "DEFLECTIVE_STASIS", - "scope": "all", - "description": ( - "Partial progress masking avoidance. " - "Output appears productive but constraints are not being engaged." - ), - "thresholds": "D > 0.7 AND 0.2 <= P <= 0.4", - "explains": [ - "Employees who are busy but not working on the constraint", - "AI that answers adjacent questions instead of the constraint", - "Organizations that produce reports instead of decisions", - ], - "next_action": ( - "Audit which constraints are being avoided. " - "Redirect resource allocation toward constraint resolution." - ), - }, - { - "name": "COMPLIANCE_STASIS", - "scope": "human_only", - "description": ( - "High artifact output with minimal constraint reduction and suppressed escalation. " - "The system appears productive but nothing actually resolves. " - "Documents and deliverables accumulate; the underlying constraint remains unchanged." - ), - "thresholds": ( - "P_artifacts >= 0.8 AND c_reduction < 0.2 AND s_t > 0.6 " - "AND E < 0.3 AND compliance_index > 2.5" - ), - "explains": [ - "Teams that produce deliverables to satisfy a process requirement, not a need", - "Compliance theater: audits passed, problems persist", - "Performance reviews completed, performance unchanged", - ], - "next_action": ( - "Audit whether artifacts map to actual constraint resolution. " - "Ask: what would change if this artifact were never produced?" - ), - }, - { - "name": "SCAPEGOAT_DISCHARGE", - "scope": "human_only", - "description": ( - "Dissonance externalized onto a target following integration failure and low work delta. " - "Energy that could not be routed through resolution is discharged as blame." - ), - "thresholds": ( - "s_t < 0.6 AND delta_work < 0.1 AND blame_density > 0.3 AND I > 0.6" - ), - "explains": [ - "Blaming an individual for a systemic failure", - "Public scapegoating events after organizational crises", - "Firing the messenger", - ], - "next_action": ( - "Examine what constraint was unresolved before the discharge event. " - "Do not treat personnel action as the resolution — diagnose the original constraint." - ), - }, - { - "name": "UNCLASSIFIED", - "scope": "all", - "description": "No basin threshold met. State is transitional or below detection threshold.", - "thresholds": "No primary thresholds fired", - "explains": ["Early-stage data", "Stable operating conditions", "Mixed signals"], - "next_action": "Continue monitoring. Collect more windows before classification.", - }, -] - - -def get_basin_spec(name: str) -> BasinSpec | None: - """Look up the spec for a basin by name. Returns None if not found.""" - for spec in BASIN_TAXONOMY: - if spec["name"] == name: - return spec - return None -# 156:15 0:0 0:0 diff --git a/a0python/edcm-org/src/edcm_org/cli.py b/a0python/edcm-org/src/edcm_org/cli.py deleted file mode 100644 index edb20b63e..000000000 --- a/a0python/edcm-org/src/edcm_org/cli.py +++ /dev/null @@ -1,179 +0,0 @@ -# 125:20 0:0 1:13 -""" -EDCM-Org CLI — entry point for organizational diagnostic runs. - -Usage: - python -m edcm_org.cli --org ACME --meeting path/to/meeting.txt --out result.json - python -m edcm_org.cli --org ACME --meeting meeting.txt --tickets tickets.csv --out result.json - -The CLI wires together the full analysis pipeline and enforces governance -rules before writing output. -""" - -from __future__ import annotations - -import json -from pathlib import Path - -from .spec_version import SPEC_VERSION -from .types import Metrics, Params, OutputEnvelope -from .governance.privacy import EDCMPrivacyGuard, PrivacyConfig -from .governance.gaming import detect_gaming_alerts -from .metrics.primary import metric_C, metric_R, metric_D, metric_N, metric_L, metric_O -from .metrics.secondary import metric_F, metric_E, metric_I -from .metrics.progress import compute_progress -from .params.complexity import estimate_complexity -from .params.alpha import estimate_alpha -from .params.delta_max import estimate_delta_max -from .basins.detect import detect_basin -from .io.loaders import load_meeting_text, load_tickets_csv, window_meeting_text - - -def analyze( - org: str, - meeting_text: str, - tickets_data: dict | None, - window_id: str = "window-001", - aggregation: str = "department", -) -> dict: - """ - Run the full EDCM analysis pipeline on meeting text (+ optional ticket data). - - Returns a dict ready for JSON serialization and governance enforcement. - """ - # Split into windows for history-dependent metrics - windows = window_meeting_text(meeting_text, window_size=500, overlap=50) - if not windows: - windows = [meeting_text] - - # Single-window primaries (computed on full text for v0.1 demo) - full_text = meeting_text - C = metric_C(full_text) - R = metric_R(full_text) - D = metric_D(full_text) - N = metric_N(full_text) - L = metric_L(full_text) - O = metric_O(full_text) - - # Window-history metrics - F = metric_F(windows) - E = metric_E(windows) - I = metric_I(windows) - - # Progress — use ticket data if available - p_artifacts_override = None - if tickets_data: - p_artifacts_override = min(1.0, tickets_data.get("resolution_rate", 0.0)) - - P, P_d, P_c, P_a, P_f = compute_progress( - full_text, - p_artifacts_override=p_artifacts_override, - ) - - metrics = Metrics( - C=C, R=R, F=F, E=E, D=D, N=N, I=I, O=O, L=L, P=P, - P_decisions=P_d, P_commitments=P_c, P_artifacts=P_a, P_followthrough=P_f, - ) - - # Parameters - complexity = estimate_complexity(full_text) - # For v0.1 with single window, use neutral alpha - alpha = 0.5 - delta_max = estimate_delta_max( - resolution_rates=[tickets_data["resolution_rate"]] if tickets_data else [], - complexities=[complexity], - ) - - params = Params(alpha=alpha, delta_max=delta_max, complexity=complexity) - - # Blame density for basin detection - from .metrics.extraction_helpers import blame_density as _blame_density - bd = _blame_density(full_text) - - # c_reduction: placeholder for v0.1 (no prior window to compare) - c_reduction = 0.0 - delta_work = P - s_t = C - - basin_name, basin_conf, explanation = detect_basin(metrics, s_t, c_reduction, delta_work, bd) - - gaming_alerts = detect_gaming_alerts(metrics, c_reduction, len(windows)) - - warnings = ["v0.1 pipeline: single-window analysis. Collect multiple windows for F/E/I accuracy."] - - result = { - "spec_version": SPEC_VERSION, - "org": org, - "window_id": window_id, - "aggregation": aggregation, - "metrics": { - "C": round(C, 4), "R": round(R, 4), "F": round(F, 4), - "E": round(E, 4), "D": round(D, 4), "N": round(N, 4), - "I": round(I, 4), "O": round(O, 4), "L": round(L, 4), "P": round(P, 4), - "P_decisions": round(P_d, 4), "P_commitments": round(P_c, 4), - "P_artifacts": round(P_a, 4), "P_followthrough": round(P_f, 4), - }, - "params": { - "alpha": round(alpha, 4), - "delta_max": round(delta_max, 4), - "complexity": round(complexity, 4), - }, - "basin": basin_name, - "basin_confidence": round(basin_conf, 4), - "basin_explanation": explanation, - "gaming_alerts": gaming_alerts, - "warnings": warnings, - } - - return result - - -def main() -> None: - import argparse - - parser = argparse.ArgumentParser( - description="EDCM-Org: Energy-Dissonance Circuit Model organizational diagnostic." - ) - parser.add_argument("--org", required=True, help="Organization identifier.") - parser.add_argument("--meeting", required=True, help="Path to meeting transcript (.txt).") - parser.add_argument("--tickets", required=False, help="Path to ticket data (.csv).") - parser.add_argument("--out", required=True, help="Output path for JSON result.") - parser.add_argument( - "--aggregation", - default="department", - choices=["department", "team", "organization"], - help="Aggregation level (default: department).", - ) - parser.add_argument("--window-id", default="window-001", help="Window identifier.") - args = parser.parse_args() - - meeting_text = load_meeting_text(args.meeting) - - tickets_data = None - if args.tickets: - tickets_data = load_tickets_csv(args.tickets) - - result = analyze( - org=args.org, - meeting_text=meeting_text, - tickets_data=tickets_data, - window_id=args.window_id, - aggregation=args.aggregation, - ) - - guard = EDCMPrivacyGuard(PrivacyConfig(aggregation=args.aggregation)) - safe = guard.enforce(result) - - Path(args.out).write_text(json.dumps(safe, indent=2), encoding="utf-8") - print(f"EDCM analysis complete. Output: {args.out}") - print(f" Basin: {safe['basin']} (confidence: {safe['basin_confidence']})") - print(f" Spec: {safe['spec_version']}") - if safe.get("gaming_alerts"): - print(f" Gaming alerts: {len(safe['gaming_alerts'])}") - if safe.get("warnings"): - print(f" Warnings: {len(safe['warnings'])}") - - -if __name__ == "__main__": - main() -# 125:20 0:0 1:13 diff --git a/a0python/edcm-org/src/edcm_org/eval/__init__.py b/a0python/edcm-org/src/edcm_org/eval/__init__.py deleted file mode 100644 index b1eaf73dd..000000000 --- a/a0python/edcm-org/src/edcm_org/eval/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# 0:4 0:0 0:0 -""" -EDCM-Org evaluation protocol package. - -protocol.py — spec compliance checks and evaluation harness. -""" -# 0:4 0:0 0:0 diff --git a/a0python/edcm-org/src/edcm_org/eval/protocol.py b/a0python/edcm-org/src/edcm_org/eval/protocol.py deleted file mode 100644 index b648a51bd..000000000 --- a/a0python/edcm-org/src/edcm_org/eval/protocol.py +++ /dev/null @@ -1,171 +0,0 @@ -# 105:35 0:0 0:2 -""" -EDCM-Org Evaluation Protocol — spec compliance and diagnostic harness. - -This module provides: - 1. Spec compliance checks (fail the build if metrics drift out of range) - 2. Secondary modifier cap enforcement - 3. Batch evaluation over multiple windows - 4. A structured evaluation report -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional, Tuple - -from ..spec_version import SPEC_VERSION -from ..types import Metrics, OutputEnvelope - -# --------------------------------------------------------------------------- -# Spec compliance checks -# --------------------------------------------------------------------------- - -# Secondary modifier caps (from spec) -SECONDARY_MODIFIER_CAPS: Dict[str, Tuple[str, float]] = { - "sentiment_slope": ("escalation_confidence", 0.20), - "urgency": ("escalation_confidence", 0.15), - "filler_ratio": ("noise_confidence", 0.25), - "topic_drift": ("deflection_confidence", 0.30), -} - - -@dataclass -class ComplianceResult: - passed: bool - errors: List[str] = field(default_factory=list) - warnings: List[str] = field(default_factory=list) - - -def check_spec_compliance(envelope: OutputEnvelope) -> ComplianceResult: - """ - Validate an OutputEnvelope against spec requirements. - - Checks: - - All metric values within defined ranges - - spec_version matches current spec - - aggregation is not 'individual' - - Output includes all required fields (non-None) - - This is designed to be called in CI/CD to prevent spec drift. - """ - errors: List[str] = [] - warnings: List[str] = [] - - # Metric range checks - metric_errors = envelope.validate() - errors.extend(metric_errors) - - # Required fields - if not envelope.spec_version: - errors.append("Missing spec_version in output.") - elif envelope.spec_version != SPEC_VERSION: - errors.append( - f"spec_version mismatch: got {envelope.spec_version!r}, " - f"expected {SPEC_VERSION!r}" - ) - - if not envelope.org: - errors.append("Missing org identifier in output.") - if not envelope.window_id: - errors.append("Missing window_id in output.") - if not envelope.aggregation: - errors.append("Missing aggregation level in output.") - - # Progress sub-components should be auditable if P is non-zero - m = envelope.metrics - if m.P > 0.01: - sub_sum = 0.3 * m.P_decisions + 0.2 * m.P_commitments + 0.3 * m.P_artifacts + 0.2 * m.P_followthrough - if abs(sub_sum - m.P) > 0.01: - warnings.append( - f"Progress sub-components do not sum to P: " - f"computed={sub_sum:.4f}, P={m.P:.4f}. " - "Verify P sub-components are populated." - ) - - return ComplianceResult(passed=len(errors) == 0, errors=errors, warnings=warnings) - - -def check_secondary_modifier_caps( - modifier_name: str, - modifier_value: float, - applied_confidence_delta: float, -) -> List[str]: - """ - Verify that a secondary modifier does not exceed its spec cap. - - Returns a list of violations (empty = compliant). - """ - violations: List[str] = [] - if modifier_name in SECONDARY_MODIFIER_CAPS: - _, cap = SECONDARY_MODIFIER_CAPS[modifier_name] - if abs(applied_confidence_delta) > cap: - violations.append( - f"Secondary modifier {modifier_name!r} applied " - f"confidence delta {applied_confidence_delta:.3f} " - f"exceeds spec cap {cap:.3f}." - ) - return violations - - -# --------------------------------------------------------------------------- -# Batch evaluation -# --------------------------------------------------------------------------- - -@dataclass -class EvalReport: - windows_evaluated: int - compliance_results: List[ComplianceResult] - all_passed: bool - total_errors: int - total_warnings: int - summary: str - - def to_dict(self) -> Dict[str, Any]: - return { - "windows_evaluated": self.windows_evaluated, - "all_passed": self.all_passed, - "total_errors": self.total_errors, - "total_warnings": self.total_warnings, - "summary": self.summary, - "details": [ - { - "window": i, - "passed": r.passed, - "errors": r.errors, - "warnings": r.warnings, - } - for i, r in enumerate(self.compliance_results) - ], - } - - -def evaluate_batch(envelopes: List[OutputEnvelope]) -> EvalReport: - """ - Run spec compliance checks over a batch of output envelopes. - - Returns an EvalReport suitable for CI/CD integration. - """ - results = [check_spec_compliance(e) for e in envelopes] - total_errors = sum(len(r.errors) for r in results) - total_warnings = sum(len(r.warnings) for r in results) - all_passed = all(r.passed for r in results) - - if all_passed: - summary = f"All {len(envelopes)} window(s) passed spec compliance." - else: - failed = sum(1 for r in results if not r.passed) - summary = ( - f"{failed}/{len(envelopes)} window(s) failed spec compliance. " - f"{total_errors} error(s), {total_warnings} warning(s)." - ) - - return EvalReport( - windows_evaluated=len(envelopes), - compliance_results=results, - all_passed=all_passed, - total_errors=total_errors, - total_warnings=total_warnings, - summary=summary, - ) -# 105:35 0:0 0:2 diff --git a/a0python/edcm-org/src/edcm_org/glossary.py b/a0python/edcm-org/src/edcm_org/glossary.py deleted file mode 100644 index b71c43f74..000000000 --- a/a0python/edcm-org/src/edcm_org/glossary.py +++ /dev/null @@ -1,101 +0,0 @@ -# 89:6 0:0 2:0 -""" -EDCM-Org Glossary — canonical definitions for all terms. - -These definitions are spec-normative. Do not paraphrase in documentation -without referencing this module. -""" - -GLOSSARY: dict[str, str] = { - "Dissonance": ( - "Unresolved constraint mismatch. Not a feeling. " - "Energy that accumulates when constraints cannot be simultaneously satisfied." - ), - "Constraint Strain (C)": ( - "Weighted contradiction density over constraint-relevant segments. " - "Range [0,1]. Higher = more unresolved constraints per unit of output." - ), - "Refusal Density (R)": ( - "Refusal statements / total constraint statements. " - "Range [0,1]. Protective resistance, not an ethical judgment." - ), - "Fixation (F)": ( - "Similarity of constraint engagement over time. " - "Range [0,1]. High fixation = looping on a narrow response set." - ), - "Escalation (E)": ( - "Commitment velocity increase (irreversibility markers slope). " - "Range [0,1]. Rising intensity without resolution." - ), - "Deflection (D)": ( - "1 - (tokens_about_constraints / total_tokens). " - "Range [0,1]. Answer-adjacent but constraint-avoiding output." - ), - "Noise (N)": ( - "1 - (tokens_in_resolution_actions / tokens_about_constraints). " - "Range [0,1]. Signal that fails to move toward resolution." - ), - "Integration Failure (I)": ( - "Failure to incorporate corrections across windows. " - "Range [0,1]. High = system does not update from feedback." - ), - "Overconfidence (O)": ( - "Certainty-evidence mismatch. " - "Range [-1,1]. Positive = over-certain; negative = under-certain." - ), - "Coherence Loss (L)": ( - "Internal contradiction density. " - "Range [0,1]. High = fragmented, contradictory output." - ), - "Progress (P)": ( - "Multi-channel completion: 0.3*P_decisions + 0.2*P_commitments + " - "0.3*P_artifacts + 0.2*P_followthrough. Range [0,1]." - ), - "Persistence (alpha)": ( - "Estimated from unresolved constraint half-life regression. " - "High alpha = dissonance persists across windows." - ), - "delta_max": ( - "Complexity-bounded throughput: P90(median(resolution_rate | complexity_bucket)). " - "Upper bound on how fast a system can resolve constraints given its load." - ), - "Basin": ( - "A stable attractor configuration in EDCM state space. " - "Basins are diagnostic labels, not prescriptions." - ), - "REFUSAL_FIXATION": ( - "R > 0.7 and F > 0.6. System loops on refusals under high constraint load." - ), - "DISSIPATIVE_NOISE": ( - "N > 0.7 and P < 0.3. High activity with near-zero resolution output." - ), - "INTEGRATION_OSCILLATION": ( - "I > 0.6 and 0.4 <= F <= 0.8. Corrections cycle without integrating." - ), - "CONFIDENCE_RUNAWAY": ( - "O > 0.7 and E > 0.6. Escalating commitment + rising certainty = crash risk." - ), - "DEFLECTIVE_STASIS": ( - "D > 0.7 and 0.2 <= P <= 0.4. Partial progress masking avoidance." - ), - "COMPLIANCE_STASIS": ( - "Human-only. High artifact output with minimal constraint reduction and " - "suppressed escalation. Artifacts are produced but nothing resolves." - ), - "SCAPEGOAT_DISCHARGE": ( - "Human-only. Sudden blame assignment event following integration failure " - "and low work delta. Dissonance externalized onto a target." - ), - "Source": "Input pressure: demands, prompts, stressors entering the system.", - "Load": "Work being attempted by the system.", - "Resistance": "Friction, delay, or refusal limiting energy flow.", - "Capacitance": "Stored unresolved dissonance; accumulates when flow is blocked.", - "Short": "Bypassing the resolution step; apparent progress with no actual resolution.", - "Overload": "Runaway escalation or collapse when capacitance is exceeded.", -} - - -def lookup(term: str) -> str: - """Return the glossary definition for a term, or a 'not found' message.""" - return GLOSSARY.get(term, f"Term not found in EDCM glossary: {term!r}") -# 89:6 0:0 2:0 diff --git a/a0python/edcm-org/src/edcm_org/governance/__init__.py b/a0python/edcm-org/src/edcm_org/governance/__init__.py deleted file mode 100644 index 1b6a75ccb..000000000 --- a/a0python/edcm-org/src/edcm_org/governance/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# 0:6 0:0 0:0 -""" -EDCM-Org Governance package. - -privacy.py — aggregation enforcement, PII scrubbing -gaming.py — metric gaming detection (always computed, non-optional) -interventions.py — non-punitive intervention recommendations -""" -# 0:6 0:0 0:0 diff --git a/a0python/edcm-org/src/edcm_org/governance/gaming.py b/a0python/edcm-org/src/edcm_org/governance/gaming.py deleted file mode 100644 index f5120b457..000000000 --- a/a0python/edcm-org/src/edcm_org/governance/gaming.py +++ /dev/null @@ -1,84 +0,0 @@ -# 39:27 0:0 2:1 -""" -EDCM Metric Gaming Detection — always computed, non-optional. - -Gaming occurs when a system (human or AI) produces outputs designed to score -well on EDCM metrics without actually resolving constraints. - -The most prominent gaming pattern for organizational contexts is -COMPLIANCE_STASIS: high artifact output with zero constraint reduction. - -Detection is heuristic and confidence-weighted. Gaming alerts are included in -every OutputEnvelope.gaming_alerts field. -""" - -from __future__ import annotations - -from typing import List - -from ..types import Metrics - - -def detect_gaming_alerts( - m: Metrics, - c_reduction: float, - window_count: int, -) -> List[str]: - """ - Detect potential metric gaming and return a list of alert strings. - - Parameters - ---------- - m : Current Metrics - c_reduction : Fractional constraint reduction this window - window_count : Number of windows analyzed so far - - Returns - ------- - List[str] - Human-readable alert descriptions. Empty = no alerts detected. - """ - alerts: List[str] = [] - - # --- Artifact inflation without resolution --- - if m.P_artifacts > 0.7 and c_reduction < 0.1: - alerts.append( - f"ARTIFACT_INFLATION: P_artifacts={m.P_artifacts:.2f} but " - f"c_reduction={c_reduction:.2f}. " - "Artifacts produced without constraint reduction — possible compliance theater." - ) - - # --- Suppressed escalation masking unresolved strain --- - if m.C > 0.6 and m.E < 0.15 and m.P < 0.3: - alerts.append( - f"SUPPRESSED_ESCALATION: C={m.C:.2f} with E={m.E:.2f} and P={m.P:.2f}. " - "High strain with low escalation and low progress — possible suppression of signals." - ) - - # --- Resolution token inflation (resolution markers without constraint engagement) --- - if m.N < 0.15 and m.D > 0.6: - alerts.append( - f"RESOLUTION_TOKEN_INFLATION: N={m.N:.2f} with D={m.D:.2f}. " - "Resolution markers present but constraint engagement is low — " - "possible resolution language without resolution actions." - ) - - # --- Overconfidence plus low coherence --- - if m.O > 0.6 and m.L > 0.5: - alerts.append( - f"OVERCONFIDENCE_INCOHERENCE: O={m.O:.2f} and L={m.L:.2f}. " - "High certainty combined with high internal contradiction — " - "possible manufactured confidence." - ) - - # --- Fixation camouflage: F high but P also high --- - # (appears to be making progress while looping on the same constraints) - if m.F > 0.7 and m.P > 0.6: - alerts.append( - f"FIXATION_CAMOUFLAGE: F={m.F:.2f} and P={m.P:.2f}. " - "High fixation coinciding with high progress — verify that progress " - "sub-components map to distinct constraints, not the same one repeatedly." - ) - - return alerts -# 39:27 0:0 2:1 diff --git a/a0python/edcm-org/src/edcm_org/governance/interventions.py b/a0python/edcm-org/src/edcm_org/governance/interventions.py deleted file mode 100644 index f32da7645..000000000 --- a/a0python/edcm-org/src/edcm_org/governance/interventions.py +++ /dev/null @@ -1,130 +0,0 @@ -# 87:21 0:0 0:1 -""" -EDCM Non-Punitive Intervention Recommendations. - -Interventions are load-management suggestions, not blame assignments. -They are generated from basin + metric state and are always framed as -system-level recommendations, never individual-level judgments. - -Per spec: no punitive automation. Interventions are advisory only. -""" - -from __future__ import annotations - -from typing import List - -from ..types import BasinName, Metrics - - -def recommend_interventions(basin: BasinName, m: Metrics) -> List[str]: - """ - Generate non-punitive, system-level intervention recommendations. - - Parameters - ---------- - basin : BasinName - The detected basin for the current window. - m : Metrics - Current metric state. - - Returns - ------- - List[str] - Ordered list of recommended interventions. Advisory only. - """ - recs: List[str] = [] - - if basin == "REFUSAL_FIXATION": - recs.append( - "Reduce constraint load: identify which input demands are irreconcilable " - "and either remove them or separate them into distinct workflows." - ) - recs.append( - "Introduce a resolution pathway: ensure refusal outputs include a " - "'what would resolve this' response to prevent energy accumulation." - ) - - elif basin == "DISSIPATIVE_NOISE": - recs.append( - "Introduce structured decision gates: require a defined decision or " - "artifact at the end of each work session." - ) - recs.append( - "Reduce meeting frequency and increase resolution accountability: " - "assign a resolution owner per constraint." - ) - - elif basin == "INTEGRATION_OSCILLATION": - recs.append( - "Audit correction pathways: verify that feedback reaches decision-makers " - "and that a mechanism exists to update behavior." - ) - recs.append( - "Introduce an integration checkpoint: before each new window, review " - "whether corrections from the prior window changed outputs." - ) - - elif basin == "CONFIDENCE_RUNAWAY": - recs.append( - "Require external validation before next commitment step. " - "Pause escalation until evidence citations are provided." - ) - recs.append( - "Introduce a dissent channel: allow minority views to be recorded " - "without requiring consensus before action." - ) - - elif basin == "DEFLECTIVE_STASIS": - recs.append( - "Audit resource allocation against the constraint list: " - "verify that effort is directed at actual constraints, not adjacent work." - ) - recs.append( - "Surface the avoided constraint explicitly and assign ownership." - ) - - elif basin == "COMPLIANCE_STASIS": - recs.append( - "Audit whether artifacts produced map to actual constraint resolution. " - "Ask: what constraint does this deliverable close?" - ) - recs.append( - "Redesign process metrics to track constraint reduction, not artifact count." - ) - recs.append( - "Check for structural incentives that reward artifact production " - "independent of resolution outcomes." - ) - - elif basin == "SCAPEGOAT_DISCHARGE": - recs.append( - "Do not treat personnel action as resolution. " - "Identify and document the original unresolved constraint " - "that preceded the discharge event." - ) - recs.append( - "Introduce systemic post-mortem: examine what constraints were " - "unresolved and why integration failed." - ) - - else: # UNCLASSIFIED - recs.append( - "Continue monitoring. Collect additional windows before classifying. " - "No intervention indicated at this confidence level." - ) - - # Cross-cutting recommendations based on metric values - if m.I > 0.7: - recs.append( - "CROSS-CUTTING: Integration Failure is high (I={:.2f}). " - "Verify feedback loops are structurally intact regardless of basin.".format(m.I) - ) - - if m.O > 0.8: - recs.append( - "CROSS-CUTTING: Overconfidence is high (O={:.2f}). " - "Require evidence citations for all high-certainty claims.".format(m.O) - ) - - return recs -# 87:21 0:0 0:1 diff --git a/a0python/edcm-org/src/edcm_org/governance/privacy.py b/a0python/edcm-org/src/edcm_org/governance/privacy.py deleted file mode 100644 index 9af3b48e2..000000000 --- a/a0python/edcm-org/src/edcm_org/governance/privacy.py +++ /dev/null @@ -1,86 +0,0 @@ -# 30:32 0:0 2:0 -""" -EDCM-Org Privacy Guard — spec v0.1 enforcement. - -Governance rules (non-negotiable): - - Default aggregation: department-level. - - No individual scoring absent explicit consent + safety protocol. - - No punitive automation. - - No PII in processed payloads. - -Any attempt to produce individual-level output raises ConsentError. -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any, Dict, List, Literal - -AggregationLevel = Literal["department", "team", "organization"] - -_PII_KEYS = {"email", "phone", "name", "employee_id", "address", "ssn", "dob", "ip_address"} - - -class ConsentError(Exception): - """Raised when individual-level output is attempted without explicit consent.""" - - -@dataclass -class PrivacyConfig: - aggregation: AggregationLevel = "department" - consent_required_for_individual: bool = True - retain_months: int = 6 - - -class EDCMPrivacyGuard: - """ - Enforces EDCM spec governance rules on output payloads. - - Usage:: - - guard = EDCMPrivacyGuard(PrivacyConfig(aggregation="department")) - safe_payload = guard.enforce(raw_output) - """ - - def __init__(self, cfg: PrivacyConfig) -> None: - self.cfg = cfg - - def enforce(self, payload: Dict[str, Any]) -> Dict[str, Any]: - """ - Enforce spec governance rules. - - Raises - ------ - ConsentError - If payload.aggregation == 'individual'. - - Returns - ------- - Dict[str, Any] - Payload with PII stripped and aggregation validated. - """ - if payload.get("aggregation") == "individual": - raise ConsentError( - "Individual-level outputs are prohibited by EDCM spec v0.1. " - "Default aggregation is 'department'. " - "Individual scoring requires explicit consent + safety protocol." - ) - - return self._scrub(payload) - - def _scrub(self, obj: Any) -> Any: - """Recursively strip PII fields from dicts and lists.""" - if isinstance(obj, dict): - return {k: self._scrub(v) for k, v in obj.items() if k not in _PII_KEYS} - if isinstance(obj, list): - return [self._scrub(x) for x in obj] - return obj - - def validate_retention(self, data_age_months: float) -> bool: - """ - Check whether retained data is within the configured retention window. - - Returns True if within window, False if data should be purged. - """ - return data_age_months <= self.cfg.retain_months -# 30:32 0:0 2:0 diff --git a/a0python/edcm-org/src/edcm_org/io/__init__.py b/a0python/edcm-org/src/edcm_org/io/__init__.py deleted file mode 100644 index 6d7efc493..000000000 --- a/a0python/edcm-org/src/edcm_org/io/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# 0:5 0:0 0:0 -""" -EDCM-Org I/O package. - -loaders.py — load meeting transcripts and ticket data from files -schemas.py — JSON schema definitions for input/output validation -""" -# 0:5 0:0 0:0 diff --git a/a0python/edcm-org/src/edcm_org/io/loaders.py b/a0python/edcm-org/src/edcm_org/io/loaders.py deleted file mode 100644 index e004e1012..000000000 --- a/a0python/edcm-org/src/edcm_org/io/loaders.py +++ /dev/null @@ -1,139 +0,0 @@ -# 58:56 0:0 2:0 -""" -EDCM-Org Data Loaders. - -Supported input formats: - - Plain text (.txt) — meeting transcripts, discussion logs - - CSV (.csv) — ticket/issue data with configurable column mapping - -All loaders return plain text or structured dicts. No PII is returned -(apply EDCMPrivacyGuard after loading if raw data may contain PII). -""" - -from __future__ import annotations - -import csv -import io -from pathlib import Path -from typing import Dict, List, Optional - - -def load_meeting_text(path: str | Path) -> str: - """ - Load a plain-text meeting transcript or discussion log. - - Parameters - ---------- - path : str or Path - Path to a .txt file. - - Returns - ------- - str - Full text content. - """ - return Path(path).read_text(encoding="utf-8") - - -def load_tickets_csv( - path: str | Path, - text_columns: Optional[List[str]] = None, - status_column: Optional[str] = "status", - resolved_values: Optional[List[str]] = None, -) -> Dict[str, object]: - """ - Load ticket/issue data from a CSV file. - - Parameters - ---------- - path : str or Path - Path to a .csv file. - text_columns : List[str], optional - Column names whose text content should be concatenated for metric analysis. - Defaults to ['title', 'description', 'comments']. - status_column : str, optional - Column name for ticket status. Default: 'status'. - resolved_values : List[str], optional - Values in status_column that indicate resolution. - Defaults to ['done', 'resolved', 'closed', 'completed']. - - Returns - ------- - dict with keys: - 'text' : str — concatenated text from text_columns - 'total' : int — total ticket count - 'resolved' : int — resolved ticket count - 'resolution_rate' : float — resolved / total - 'rows' : List[dict] — all rows (with PII fields not stripped yet) - """ - if text_columns is None: - text_columns = ["title", "description", "comments"] - if resolved_values is None: - resolved_values = {"done", "resolved", "closed", "completed"} - else: - resolved_values = set(v.lower() for v in resolved_values) - - rows: List[Dict[str, str]] = [] - with open(path, encoding="utf-8", newline="") as f: - reader = csv.DictReader(f) - for row in reader: - rows.append(dict(row)) - - # Concatenate text fields - text_parts = [] - for row in rows: - for col in text_columns: - val = row.get(col, "").strip() - if val: - text_parts.append(val) - - full_text = "\n".join(text_parts) - - # Resolution rate - total = len(rows) - resolved = sum( - 1 for row in rows - if row.get(status_column, "").strip().lower() in resolved_values - ) - resolution_rate = resolved / total if total > 0 else 0.0 - - return { - "text": full_text, - "total": total, - "resolved": resolved, - "resolution_rate": resolution_rate, - "rows": rows, - } - - -def window_meeting_text(text: str, window_size: int = 500, overlap: int = 50) -> List[str]: - """ - Split a long meeting transcript into overlapping word-count windows. - - Parameters - ---------- - text : Full meeting text. - window_size : Target words per window. - overlap : Words of overlap between consecutive windows. - - Returns - ------- - List[str] - List of window text strings. - """ - words = text.split() - if not words: - return [] - - windows = [] - step = max(1, window_size - overlap) - start = 0 - while start < len(words): - end = min(start + window_size, len(words)) - windows.append(" ".join(words[start:end])) - if end == len(words): - break - start += step - - return windows -# 58:56 0:0 2:0 diff --git a/a0python/edcm-org/src/edcm_org/io/schemas.py b/a0python/edcm-org/src/edcm_org/io/schemas.py deleted file mode 100644 index 3e78222e8..000000000 --- a/a0python/edcm-org/src/edcm_org/io/schemas.py +++ /dev/null @@ -1,86 +0,0 @@ -# 72:8 0:0 0:0 -""" -EDCM-Org JSON Schemas. - -These schemas define the canonical structure for: - - OutputEnvelope (what the analyzer produces) - - InputConfig (what the CLI/API accepts) - -Used for validation and documentation generation. -""" - -from __future__ import annotations - -# Output envelope schema (mirrors types.OutputEnvelope) -OUTPUT_ENVELOPE_SCHEMA: dict = { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "EDCMOutputEnvelope", - "description": "Canonical EDCM analysis output. Every field is required.", - "type": "object", - "required": [ - "spec_version", "org", "window_id", "aggregation", - "metrics", "params", "basin", "basin_confidence", - "gaming_alerts", "warnings" - ], - "properties": { - "spec_version": { - "type": "string", - "const": "edcm-org-v0.1.0", - "description": "Non-negotiable spec stamp." - }, - "org": {"type": "string", "description": "Organization identifier (anonymized if needed)."}, - "window_id": {"type": "string", "description": "Unique identifier for this analysis window."}, - "aggregation": { - "type": "string", - "enum": ["department", "team", "organization"], - "description": "Aggregation level. 'individual' is prohibited." - }, - "metrics": { - "type": "object", - "required": ["C", "R", "F", "E", "D", "N", "I", "O", "L", "P"], - "properties": { - "C": {"type": "number", "minimum": 0, "maximum": 1, "description": "Constraint Strain"}, - "R": {"type": "number", "minimum": 0, "maximum": 1, "description": "Refusal Density"}, - "F": {"type": "number", "minimum": 0, "maximum": 1, "description": "Fixation"}, - "E": {"type": "number", "minimum": 0, "maximum": 1, "description": "Escalation"}, - "D": {"type": "number", "minimum": 0, "maximum": 1, "description": "Deflection"}, - "N": {"type": "number", "minimum": 0, "maximum": 1, "description": "Noise"}, - "I": {"type": "number", "minimum": 0, "maximum": 1, "description": "Integration Failure"}, - "O": {"type": "number", "minimum": -1, "maximum": 1, "description": "Overconfidence"}, - "L": {"type": "number", "minimum": 0, "maximum": 1, "description": "Coherence Loss"}, - "P": {"type": "number", "minimum": 0, "maximum": 1, "description": "Progress"}, - "P_decisions": {"type": "number", "minimum": 0, "maximum": 1}, - "P_commitments": {"type": "number", "minimum": 0, "maximum": 1}, - "P_artifacts": {"type": "number", "minimum": 0, "maximum": 1}, - "P_followthrough": {"type": "number", "minimum": 0, "maximum": 1}, - "conf": { - "type": "object", - "description": "Per-primary confidence scores.", - "additionalProperties": {"type": "number", "minimum": 0, "maximum": 1} - } - } - }, - "params": { - "type": "object", - "required": ["alpha", "delta_max", "complexity"], - "properties": { - "alpha": {"type": "number", "minimum": 0, "maximum": 1}, - "delta_max": {"type": "number", "minimum": 0, "maximum": 1}, - "complexity": {"type": "number", "minimum": 0, "maximum": 1} - } - }, - "basin": { - "type": "string", - "enum": [ - "REFUSAL_FIXATION", "DISSIPATIVE_NOISE", "INTEGRATION_OSCILLATION", - "CONFIDENCE_RUNAWAY", "DEFLECTIVE_STASIS", "COMPLIANCE_STASIS", - "SCAPEGOAT_DISCHARGE", "UNCLASSIFIED" - ] - }, - "basin_confidence": {"type": "number", "minimum": 0, "maximum": 1}, - "gaming_alerts": {"type": "array", "items": {"type": "string"}}, - "warnings": {"type": "array", "items": {"type": "string"}} - }, - "additionalProperties": False -} -# 72:8 0:0 0:0 diff --git a/a0python/edcm-org/src/edcm_org/metrics/__init__.py b/a0python/edcm-org/src/edcm_org/metrics/__init__.py deleted file mode 100644 index d474612b0..000000000 --- a/a0python/edcm-org/src/edcm_org/metrics/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# 0:7 0:0 0:0 -""" -EDCM-Org metrics package. - -Primary metrics: primary.py -Window-history metrics (Fixation, Escalation, Integration): secondary.py -Progress sub-components: progress.py -Token/marker extraction utilities: extraction_helpers.py -""" -# 0:7 0:0 0:0 diff --git a/a0python/edcm-org/src/edcm_org/metrics/extraction_helpers.py b/a0python/edcm-org/src/edcm_org/metrics/extraction_helpers.py deleted file mode 100644 index ae403ea1a..000000000 --- a/a0python/edcm-org/src/edcm_org/metrics/extraction_helpers.py +++ /dev/null @@ -1,128 +0,0 @@ -# 69:36 0:0 10:0 -""" -Low-level text extraction utilities for EDCM metric computation. - -All functions operate on plain text strings. No NLP models are required — -EDCM v0.1 uses keyword/pattern matching to remain auditable and fast. - -Extend these helpers (not the metric functions) when adding domain-specific -vocabulary. -""" - -from __future__ import annotations - -import re -from typing import List - -# --------------------------------------------------------------------------- -# Tokenization -# --------------------------------------------------------------------------- - -_TOKEN_RE = re.compile(r"\b\w+\b") - -CONSTRAINT_KEYWORDS: List[str] = [ - # Statements of impossibility / constraint violation - "cannot", "can't", "impossible", "against policy", "not allowed", "prohibited", - "won't", "will not", "no way", "blocked", "forbidden", - # Uncertainty signals - "not sure", "maybe", "unclear", "unknown", "unsure", "uncertain", - # Deferral / tabling - "circle back", "tabled", "defer", "postpone", "later", "pending", - # Constraint acknowledgment - "constraint", "requirement", "must", "should", "need to", "have to", - "obligated", "mandate", "deadline", -] - -RESOLUTION_KEYWORDS: List[str] = [ - "decided", "decision", "agreed", "approved", "resolved", "completed", - "done", "shipped", "deployed", "closed", "fixed", "implemented", - "committed", "signed off", "confirmed", "finalized", -] - -CONTRADICTION_PATTERNS: List[tuple[str, str]] = [ - # (marker_a, marker_b) — if both appear in same text window it's a contradiction signal - ("yes", "no"), - ("will", "won't"), - ("can", "cannot"), - ("approved", "rejected"), - ("agreed", "disagreed"), - ("always", "never"), - ("increase", "decrease"), - ("add", "remove"), -] - - -def tokenize(text: str) -> List[str]: - """Return lowercased word tokens from text.""" - return _TOKEN_RE.findall(text.lower()) - - -def count_markers(text: str, markers: List[str]) -> int: - """ - Count how many of the given phrase markers appear in text (case-insensitive). - Each marker is counted as a binary presence (not frequency) per call. - """ - lower = text.lower() - return sum(1 for m in markers if m.lower() in lower) - - -def constraint_engagement_tokens(text: str) -> int: - """ - Estimate the number of tokens that engage with constraints. - Uses heuristic: count tokens in sentences that contain a constraint keyword. - """ - sentences = re.split(r"[.!?\n]+", text) - total = 0 - for sent in sentences: - lower = sent.lower() - if any(kw in lower for kw in CONSTRAINT_KEYWORDS): - total += len(_TOKEN_RE.findall(sent)) - return total - - -def resolution_action_tokens(text: str) -> int: - """ - Estimate the number of tokens in resolution-action sentences. - """ - sentences = re.split(r"[.!?\n]+", text) - total = 0 - for sent in sentences: - lower = sent.lower() - if any(kw in lower for kw in RESOLUTION_KEYWORDS): - total += len(_TOKEN_RE.findall(sent)) - return total - - -def contradiction_count(text: str) -> int: - """ - Count how many contradictory keyword pairs both appear in the text. - This is a conservative lower bound — does not require the markers to - appear in the same sentence. - """ - lower = text.lower() - count = 0 - for a, b in CONTRADICTION_PATTERNS: - if a in lower and b in lower: - count += 1 - return count - - -def blame_density(text: str) -> float: - """ - Estimate proportion of sentences containing blame-assignment language. - Used for SCAPEGOAT_DISCHARGE basin detection. - """ - blame_markers = [ - "fault", "blame", "responsible for failure", "caused this", - "their fault", "his fault", "her fault", "should have", - "failed to", "didn't do", "never did", "dropped the ball", - ] - sentences = re.split(r"[.!?\n]+", text) - if not sentences: - return 0.0 - blame_sents = sum( - 1 for s in sentences - if any(m in s.lower() for m in blame_markers) - ) - return blame_sents / max(1, len(sentences)) -# 69:36 0:0 10:0 diff --git a/a0python/edcm-org/src/edcm_org/metrics/primary.py b/a0python/edcm-org/src/edcm_org/metrics/primary.py deleted file mode 100644 index 752cec3d5..000000000 --- a/a0python/edcm-org/src/edcm_org/metrics/primary.py +++ /dev/null @@ -1,173 +0,0 @@ -# 68:59 0:0 6:1 -""" -Primary EDCM metrics — range-checked, spec-compliant. - -All functions return values in their defined ranges: - C, R, F, E, D, N, I, L, P -> [0, 1] - O -> [-1, 1] - -Fixation (F), Escalation (E), and Integration Failure (I) require window -history and are computed in secondary.py. This module handles single-window -primaries that operate on a text string alone. -""" - -from __future__ import annotations - -import math -from typing import Dict, List, Tuple - -from .extraction_helpers import ( - count_markers, - tokenize, - constraint_engagement_tokens, - resolution_action_tokens, - contradiction_count, -) - - -# --------------------------------------------------------------------------- -# Range clamps -# --------------------------------------------------------------------------- - -def clamp01(x: float) -> float: - """Clamp to [0, 1].""" - return max(0.0, min(1.0, float(x))) - - -def clamp11(x: float) -> float: - """Clamp to [-1, 1].""" - return max(-1.0, min(1.0, float(x))) - - -# --------------------------------------------------------------------------- -# Metric C — Constraint Strain -# --------------------------------------------------------------------------- - -DEFAULT_C_WEIGHTS: Dict[str, float] = { - "contradiction": 1.0, - "refusal": 1.0, - "uncertainty": 0.75, - "low_progress": 0.5, -} - - -def metric_C(text: str, weights: Dict[str, float] | None = None) -> float: - """ - Weighted contradiction density over constraint-relevant segments. - - `weights` is a spec-level knob for org domains. Document any changes from - DEFAULT_C_WEIGHTS in your run configuration. - - Range: [0, 1] - """ - if weights is None: - weights = DEFAULT_C_WEIGHTS - - tokens = tokenize(text) - if not tokens: - return 0.0 - - v = { - "contradiction": contradiction_count(text), - "refusal": count_markers(text, ["cannot", "impossible", "against policy", "not allowed"]), - "uncertainty": count_markers(text, ["not sure", "maybe", "unclear", "unknown"]), - "low_progress": count_markers(text, ["no decision", "we'll see", "tabled", "circle back"]), - } - - num = sum(weights.get(k, 1.0) * (1.0 if v[k] > 0 else 0.0) for k in v) - den = sum(weights.get(k, 1.0) for k in v) - return clamp01(num / den if den else 0.0) - - -# --------------------------------------------------------------------------- -# Metric R — Refusal Density -# --------------------------------------------------------------------------- - -_REFUSAL_MARKERS = ["cannot", "impossible", "against policy", "won't", "no way"] - - -def metric_R(text: str) -> float: - """ - Refusal statements / total constraint statements. - - Range: [0, 1] - """ - cons = constraint_engagement_tokens(text) - if cons <= 0: - return 0.0 - refusals = count_markers(text, _REFUSAL_MARKERS) - return clamp01(refusals / cons) - - -# --------------------------------------------------------------------------- -# Metric D — Deflection -# --------------------------------------------------------------------------- - -def metric_D(text: str) -> float: - """ - 1 - (tokens_about_constraints / total_tokens) - - Range: [0, 1] - """ - total = len(tokenize(text)) - if total <= 0: - return 0.0 - cons = constraint_engagement_tokens(text) - return clamp01(1.0 - (cons / total)) - - -# --------------------------------------------------------------------------- -# Metric N — Noise -# --------------------------------------------------------------------------- - -def metric_N(text: str) -> float: - """ - 1 - (tokens_in_resolution_actions / tokens_about_constraints) - - Range: [0, 1] - """ - cons = constraint_engagement_tokens(text) - if cons <= 0: - return 0.0 - res = resolution_action_tokens(text) - return clamp01(1.0 - (res / cons)) - - -# --------------------------------------------------------------------------- -# Metric L — Coherence Loss -# --------------------------------------------------------------------------- - -def metric_L(text: str) -> float: - """ - Internal contradiction density. - - Range: [0, 1] - """ - stmts = max(1, text.count(".") + text.count("\n")) - contr = contradiction_count(text) - return clamp01(contr / stmts) - - -# --------------------------------------------------------------------------- -# Metric O — Overconfidence -# --------------------------------------------------------------------------- - -_ABSOLUTE_MARKERS = ["guarantee", "definitely", "certain", "no doubt", "will", "always", "never fails"] -_HEDGE_MARKERS = ["maybe", "might", "unclear", "likely", "approximately", "could be", "uncertain"] -_EVIDENCE_MARKERS = ["http", "source", "data shows", "metrics", "evidence", "study", "research"] - - -def metric_O(text: str) -> float: - """ - Certainty-evidence mismatch. - - Range: [-1, 1] - Positive = over-certain; negative = under-certain (hedging without action). - """ - total_stmts = max(1, text.count(".") + text.count("\n")) - absolutes = count_markers(text, _ABSOLUTE_MARKERS) - hedges = count_markers(text, _HEDGE_MARKERS) - citations = count_markers(text, _EVIDENCE_MARKERS) - raw = (absolutes - hedges - citations) / total_stmts - return clamp11(raw) -# 68:59 0:0 6:1 diff --git a/a0python/edcm-org/src/edcm_org/metrics/progress.py b/a0python/edcm-org/src/edcm_org/metrics/progress.py deleted file mode 100644 index dc241632e..000000000 --- a/a0python/edcm-org/src/edcm_org/metrics/progress.py +++ /dev/null @@ -1,103 +0,0 @@ -# 62:21 0:0 2:2 -""" -Progress (P) metric computation. - -P = 0.3*P_decisions + 0.2*P_commitments + 0.3*P_artifacts + 0.2*P_followthrough - -Each sub-component is estimated from keyword/pattern matching. These are -conservative lower-bound estimates; supplement with structured data (ticket -status, artifact counts) via the io/ loaders for higher fidelity. -""" - -from __future__ import annotations - -from typing import Optional - -from .extraction_helpers import count_markers, tokenize -from .primary import clamp01 - -# --------------------------------------------------------------------------- -# Sub-component keyword sets -# --------------------------------------------------------------------------- - -_DECISION_MARKERS = [ - "decided", "decision made", "agreed on", "we will", "going with", - "approved", "selected", "chosen", "voted", "resolved to", -] - -_COMMITMENT_MARKERS = [ - "committed", "i will", "we will", "by next", "by friday", "owner:", - "assigned to", "responsible", "taking on", "on me", "my action item", -] - -_ARTIFACT_MARKERS = [ - "pr merged", "pull request", "ticket closed", "deployed", "shipped", - "document updated", "spec written", "design finalized", "completed", - "merged", "released", -] - -_FOLLOWTHROUGH_MARKERS = [ - "done", "finished", "as promised", "per last meeting", "following up", - "update:", "status:", "completed as planned", "delivered", -] - - -def _sub_score(text: str, markers: list, scale: float = 0.2) -> float: - """ - Simple sub-score: count marker hits, normalize by total sentence count. - scale controls sensitivity. Returns [0, 1]. - """ - sentences = [s.strip() for s in text.replace("\n", ".").split(".") if s.strip()] - if not sentences: - return 0.0 - hits = count_markers(text, markers) - # One hit per scale*N sentences = 1.0 - normalized = hits / max(1, len(sentences) * scale) - return clamp01(normalized) - - -def compute_progress( - text: str, - p_decisions_override: Optional[float] = None, - p_commitments_override: Optional[float] = None, - p_artifacts_override: Optional[float] = None, - p_followthrough_override: Optional[float] = None, -) -> tuple[float, float, float, float, float]: - """ - Compute P and its four sub-components. - - Overrides allow structured data sources (e.g., ticket counts) to replace - the text-heuristic estimate for individual sub-components. - - Returns: (P, P_decisions, P_commitments, P_artifacts, P_followthrough) - """ - P_decisions = ( - p_decisions_override - if p_decisions_override is not None - else _sub_score(text, _DECISION_MARKERS) - ) - P_commitments = ( - p_commitments_override - if p_commitments_override is not None - else _sub_score(text, _COMMITMENT_MARKERS) - ) - P_artifacts = ( - p_artifacts_override - if p_artifacts_override is not None - else _sub_score(text, _ARTIFACT_MARKERS) - ) - P_followthrough = ( - p_followthrough_override - if p_followthrough_override is not None - else _sub_score(text, _FOLLOWTHROUGH_MARKERS) - ) - - P = clamp01( - 0.3 * P_decisions - + 0.2 * P_commitments - + 0.3 * P_artifacts - + 0.2 * P_followthrough - ) - - return P, P_decisions, P_commitments, P_artifacts, P_followthrough -# 62:21 0:0 2:2 diff --git a/a0python/edcm-org/src/edcm_org/metrics/secondary.py b/a0python/edcm-org/src/edcm_org/metrics/secondary.py deleted file mode 100644 index b5bc43384..000000000 --- a/a0python/edcm-org/src/edcm_org/metrics/secondary.py +++ /dev/null @@ -1,220 +0,0 @@ -# 106:66 0:0 2:2 -""" -Secondary EDCM metrics — require window history. - -Metrics computed here: - F — Fixation (similarity of constraint engagement over time) - E — Escalation (commitment velocity increase) - I — Integration Failure (failure to incorporate corrections across windows) - -Secondary modifiers (sentiment slope, urgency, filler ratio, topic drift) are -also computed here. Per spec, they can ONLY modulate confidence, not define -primaries. Caps: - Sentiment slope -> Escalation confidence <= 0.2 - Urgency -> Escalation confidence <= 0.15 - Filler ratio -> Noise confidence <= 0.25 - Topic drift -> Deflection confidence <= 0.3 -""" - -from __future__ import annotations - -import math -from typing import List - -from .extraction_helpers import ( - count_markers, - tokenize, - constraint_engagement_tokens, - resolution_action_tokens, -) -from .primary import clamp01, clamp11 - -# --------------------------------------------------------------------------- -# Fixation (F) -# --------------------------------------------------------------------------- - -def _jaccard(set_a: set, set_b: set) -> float: - if not set_a and not set_b: - return 1.0 - union = set_a | set_b - if not union: - return 0.0 - return len(set_a & set_b) / len(union) - - -def metric_F(window_texts: List[str]) -> float: - """ - Fixation: similarity of constraint engagement across windows. - - Computed as mean pairwise Jaccard similarity of constraint-keyword sets - over consecutive window pairs. High F = system keeps engaging the same - (unresolved) constraints. - - Range: [0, 1] - Requires at least 2 windows. - """ - if len(window_texts) < 2: - return 0.0 - - def constraint_set(text: str) -> set: - tokens = tokenize(text) - from .extraction_helpers import CONSTRAINT_KEYWORDS - return {t for t in tokens if any(kw.replace(" ", "_") == t or kw in text.lower() - for kw in CONSTRAINT_KEYWORDS)} - - similarities = [] - for i in range(len(window_texts) - 1): - a = constraint_set(window_texts[i]) - b = constraint_set(window_texts[i + 1]) - similarities.append(_jaccard(a, b)) - - return clamp01(sum(similarities) / len(similarities)) - - -# --------------------------------------------------------------------------- -# Escalation (E) -# --------------------------------------------------------------------------- - -_IRREVERSIBILITY_MARKERS = [ - "committed", "signed", "launched", "deployed", "shipped", "announced", - "published", "sent", "filed", "submitted", "approved", "final", "no going back", -] - - -def metric_E(window_texts: List[str]) -> float: - """ - Escalation: commitment velocity increase (irreversibility marker slope). - - Computes the slope of irreversibility marker counts across windows. - Positive slope normalized to [0, 1]. - - Range: [0, 1] - Requires at least 2 windows. - """ - if len(window_texts) < 2: - return 0.0 - - counts = [count_markers(t, _IRREVERSIBILITY_MARKERS) for t in window_texts] - n = len(counts) - if n < 2: - return 0.0 - - # Simple linear regression slope - xs = list(range(n)) - mean_x = sum(xs) / n - mean_y = sum(counts) / n - num = sum((xs[i] - mean_x) * (counts[i] - mean_y) for i in range(n)) - den = sum((xs[i] - mean_x) ** 2 for i in range(n)) - slope = num / den if den != 0 else 0.0 - - # Normalize: slope of 1 irreversibility marker per window => E = 0.5 - return clamp01(slope / 2.0) - - -# --------------------------------------------------------------------------- -# Integration Failure (I) -# --------------------------------------------------------------------------- - -_CORRECTION_MARKERS = [ - "correction", "actually", "revised", "updated", "changed to", "per feedback", - "as noted", "you're right", "we were wrong", "amend", "retract", -] - - -def metric_I(window_texts: List[str]) -> float: - """ - Integration Failure: failure to incorporate corrections across windows. - - If correction markers appear in window N, check whether constraint strain - decreases in window N+1. If it does not, that counts as a failure. - - Range: [0, 1] - Requires at least 2 windows. - """ - if len(window_texts) < 2: - return 0.0 - - from .primary import metric_C - - failures = 0 - correction_windows = 0 - - for i in range(len(window_texts) - 1): - if count_markers(window_texts[i], _CORRECTION_MARKERS) > 0: - correction_windows += 1 - c_before = metric_C(window_texts[i]) - c_after = metric_C(window_texts[i + 1]) - if c_after >= c_before: # no improvement - failures += 1 - - if correction_windows == 0: - return 0.0 - return clamp01(failures / correction_windows) - - -# --------------------------------------------------------------------------- -# Secondary modifiers — confidence adjustments only -# --------------------------------------------------------------------------- - -def modifier_sentiment_slope(window_texts: List[str]) -> float: - """ - Sentiment slope: estimates rate of negative sentiment increase. - Returns a value in [0, 1]; caps Escalation confidence at 0.2. - """ - _neg = ["bad", "worse", "terrible", "failed", "broken", "disaster", "crisis", "urgent"] - counts = [count_markers(t, _neg) for t in window_texts] - if len(counts) < 2: - return 0.0 - diffs = [counts[i + 1] - counts[i] for i in range(len(counts) - 1)] - slope = sum(diffs) / len(diffs) - return clamp01(slope / 3.0) # normalize: 3 new neg markers/window = 1.0 - - -def modifier_urgency(window_texts: List[str]) -> float: - """ - Urgency: density of urgency markers in latest window. - Returns [0, 1]; caps Escalation confidence at 0.15. - """ - _urg = ["asap", "urgent", "immediately", "critical", "emergency", "now", "right now"] - if not window_texts: - return 0.0 - latest = window_texts[-1] - hits = count_markers(latest, _urg) - total = max(1, len(tokenize(latest))) - return clamp01(hits / total * 10) # normalize - - -def modifier_filler_ratio(text: str) -> float: - """ - Filler ratio: proportion of tokens that are filler/hedge words. - Returns [0, 1]; caps Noise confidence at 0.25. - """ - _fillers = ["um", "uh", "like", "basically", "literally", "actually", - "you know", "sort of", "kind of", "i mean", "right"] - tokens = tokenize(text) - if not tokens: - return 0.0 - filler_count = count_markers(text, _fillers) - return clamp01(filler_count / len(tokens) * 5) - - -def modifier_topic_drift(window_texts: List[str]) -> float: - """ - Topic drift: how much the vocabulary shifts between windows. - Returns [0, 1]; caps Deflection confidence at 0.3. - """ - if len(window_texts) < 2: - return 0.0 - - drifts = [] - for i in range(len(window_texts) - 1): - a = set(tokenize(window_texts[i])) - b = set(tokenize(window_texts[i + 1])) - if not a or not b: - drifts.append(0.0) - continue - overlap = len(a & b) / min(len(a), len(b)) - drifts.append(1.0 - overlap) - - return clamp01(sum(drifts) / len(drifts)) -# 106:66 0:0 2:2 diff --git a/a0python/edcm-org/src/edcm_org/params/__init__.py b/a0python/edcm-org/src/edcm_org/params/__init__.py deleted file mode 100644 index 3befbafdc..000000000 --- a/a0python/edcm-org/src/edcm_org/params/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# 0:6 0:0 0:0 -""" -EDCM-Org parameter estimation package. - -alpha: Persistence — unresolved constraint half-life (alpha.py) -delta_max: Complexity-bounded throughput ceiling (delta_max.py) -complexity: Complexity bucket assignment (complexity.py) -""" -# 0:6 0:0 0:0 diff --git a/a0python/edcm-org/src/edcm_org/params/alpha.py b/a0python/edcm-org/src/edcm_org/params/alpha.py deleted file mode 100644 index a2b64821e..000000000 --- a/a0python/edcm-org/src/edcm_org/params/alpha.py +++ /dev/null @@ -1,59 +0,0 @@ -# 23:22 0:0 2:0 -""" -Persistence parameter (alpha) estimation. - -alpha is estimated from the unresolved constraint half-life across windows: - - Track constraint strain C(t) over time. - - Fit an exponential decay: C(t) = C0 * exp(-lambda * t) - - alpha = 1 - lambda (so high alpha means slow decay = high persistence) - -If fewer than 3 data points are available, alpha defaults to 0.5 (neutral). -""" - -from __future__ import annotations - -import math -from typing import List - - -def estimate_alpha(c_series: List[float]) -> float: - """ - Estimate persistence alpha from a time series of Constraint Strain values. - - Parameters - ---------- - c_series : List[float] - Constraint Strain (C) values for consecutive windows. Length >= 3 - recommended for reliable estimation. Values must be in [0, 1]. - - Returns - ------- - float - alpha in [0, 1]. Higher = dissonance persists longer across windows. - """ - n = len(c_series) - if n < 2: - return 0.5 # neutral default - - # Filter out zeros to avoid log(0) - valid = [(i, c) for i, c in enumerate(c_series) if c > 0] - if len(valid) < 2: - return 0.0 # C went to zero quickly -> low persistence - - # Fit log(C) ~ -lambda * t via ordinary least squares - log_c = [(i, math.log(c)) for i, c in valid] - xs = [p[0] for p in log_c] - ys = [p[1] for p in log_c] - n_fit = len(xs) - mean_x = sum(xs) / n_fit - mean_y = sum(ys) / n_fit - - num = sum((xs[i] - mean_x) * (ys[i] - mean_y) for i in range(n_fit)) - den = sum((xs[i] - mean_x) ** 2 for i in range(n_fit)) - if den == 0: - return 0.5 - - lam = -num / den # decay rate; negate because slope is negative for decay - alpha = 1.0 - max(0.0, min(1.0, lam)) - return max(0.0, min(1.0, alpha)) -# 23:22 0:0 2:0 diff --git a/a0python/edcm-org/src/edcm_org/params/complexity.py b/a0python/edcm-org/src/edcm_org/params/complexity.py deleted file mode 100644 index 75686d755..000000000 --- a/a0python/edcm-org/src/edcm_org/params/complexity.py +++ /dev/null @@ -1,95 +0,0 @@ -# 42:32 0:0 4:1 -""" -Complexity parameter estimation. - -Complexity captures the cognitive/structural load of a text window. -It is used to bucket resolution rates for delta_max estimation. - -Complexity is estimated from: - - vocabulary diversity (type-token ratio) - - sentence length distribution - - nested clause markers - - technical/domain term density (pluggable vocabulary) -""" - -from __future__ import annotations - -from typing import List, Optional - -from ..metrics.extraction_helpers import tokenize - - -_CLAUSE_MARKERS = [ - "however", "whereas", "although", "unless", "provided that", - "on the other hand", "despite", "notwithstanding", "in contrast", - "except", "regardless", -] - - -def estimate_complexity( - text: str, - domain_terms: Optional[List[str]] = None, -) -> float: - """ - Estimate complexity of a text window. - - Parameters - ---------- - text : str - The window text. - domain_terms : List[str], optional - Additional domain-specific technical terms to count. - - Returns - ------- - float - Complexity score in [0, 1]. - """ - tokens = tokenize(text) - if not tokens: - return 0.0 - - # 1. Type-token ratio (vocabulary diversity) - ttr = len(set(tokens)) / len(tokens) - - # 2. Mean sentence length (longer sentences = harder) - sentences = [s.strip() for s in text.replace("\n", ".").split(".") if s.strip()] - if sentences: - mean_sent_len = sum(len(tokenize(s)) for s in sentences) / len(sentences) - sent_complexity = min(1.0, mean_sent_len / 30.0) # 30 tokens/sentence => 1.0 - else: - sent_complexity = 0.0 - - # 3. Clause marker density - clause_hits = sum(1 for m in _CLAUSE_MARKERS if m in text.lower()) - clause_density = min(1.0, clause_hits / max(1, len(sentences))) - - # 4. Domain term density (optional) - if domain_terms: - dt_hits = sum(1 for t in domain_terms if t.lower() in text.lower()) - dt_density = min(1.0, dt_hits / max(1, len(tokens)) * 10) - else: - dt_density = 0.0 - - # Weighted combination - complexity = ( - 0.3 * ttr - + 0.35 * sent_complexity - + 0.25 * clause_density - + 0.1 * dt_density - ) - return max(0.0, min(1.0, complexity)) - - -def bucket(complexity: float) -> str: - """ - Assign a complexity bucket label for delta_max estimation. - - Returns one of: 'low', 'medium', 'high' - """ - if complexity < 0.33: - return "low" - if complexity < 0.66: - return "medium" - return "high" -# 42:32 0:0 4:1 diff --git a/a0python/edcm-org/src/edcm_org/params/delta_max.py b/a0python/edcm-org/src/edcm_org/params/delta_max.py deleted file mode 100644 index 02f48bde2..000000000 --- a/a0python/edcm-org/src/edcm_org/params/delta_max.py +++ /dev/null @@ -1,85 +0,0 @@ -# 31:35 0:0 2:1 -""" -delta_max parameter estimation. - -delta_max is the complexity-bounded throughput ceiling: - delta_max ≈ P90(median(resolution_rate | complexity_bucket)) - -It represents the maximum rate at which a system can resolve constraints -given its current complexity load. If a system is operating near delta_max -and constraint input is still rising, overload is imminent. - -In v0.1, delta_max is estimated from observed resolution rates bucketed by -complexity. With insufficient history, a conservative default is used. -""" - -from __future__ import annotations - -import statistics -from typing import Dict, List, Optional - -from .complexity import bucket as complexity_bucket - -# Default delta_max values per complexity bucket (from reference calibration) -# These are conservative baselines; update from empirical data in production. -_DEFAULT_DELTA_MAX: Dict[str, float] = { - "low": 0.7, - "medium": 0.45, - "high": 0.25, -} - - -def estimate_delta_max( - resolution_rates: List[float], - complexities: List[float], - bucket_override: Optional[str] = None, -) -> float: - """ - Estimate delta_max from observed resolution rates and complexities. - - Parameters - ---------- - resolution_rates : List[float] - Resolution rate for each historical window (0..1). - resolution_rate = resolved_constraints / total_constraints_that_window - complexities : List[float] - Complexity score for each corresponding window. - bucket_override : str, optional - Force a specific complexity bucket ('low', 'medium', 'high'). - Used when you know the current context type. - - Returns - ------- - float - delta_max estimate in [0, 1]. - """ - if not resolution_rates or not complexities: - # Fall back to medium bucket default - return _DEFAULT_DELTA_MAX["medium"] - - if len(resolution_rates) != len(complexities): - raise ValueError("resolution_rates and complexities must have the same length.") - - # Group resolution rates by complexity bucket - bucketed: Dict[str, List[float]] = {"low": [], "medium": [], "high": []} - for rate, comp in zip(resolution_rates, complexities): - b = bucket_override if bucket_override else complexity_bucket(comp) - bucketed[b].append(rate) - - # Determine current bucket (from most recent complexity, or override) - current_bucket = bucket_override if bucket_override else complexity_bucket(complexities[-1]) - - group = bucketed.get(current_bucket, []) - if len(group) < 3: - return _DEFAULT_DELTA_MAX[current_bucket] - - # P90 of median resolution rate within bucket - median_rate = statistics.median(group) - # P90 approximation: sort and take index at 90th percentile - sorted_group = sorted(group) - p90_idx = int(len(sorted_group) * 0.9) - p90 = sorted_group[min(p90_idx, len(sorted_group) - 1)] - - # delta_max = P90 of the median estimate (conservative) - return max(0.0, min(1.0, (median_rate + p90) / 2.0)) -# 31:35 0:0 2:1 diff --git a/a0python/edcm-org/src/edcm_org/spec_version.py b/a0python/edcm-org/src/edcm_org/spec_version.py deleted file mode 100644 index da98b4f6a..000000000 --- a/a0python/edcm-org/src/edcm_org/spec_version.py +++ /dev/null @@ -1,4 +0,0 @@ -# 1:1 0:0 6:0 -# Non-negotiable spec stamp — must be included in every output envelope. -SPEC_VERSION = "edcm-org-v0.1.0" -# 1:1 0:0 6:0 diff --git a/a0python/edcm-org/src/edcm_org/types.py b/a0python/edcm-org/src/edcm_org/types.py deleted file mode 100644 index 8ddbd0d49..000000000 --- a/a0python/edcm-org/src/edcm_org/types.py +++ /dev/null @@ -1,127 +0,0 @@ -# 69:32 0:0 10:0 -""" -Typed state and output envelope for EDCM-Org v0.1. - -All fields are spec-defined. Do not add fields without a spec amendment. -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Dict, List, Literal, Optional - -BasinName = Literal[ - "REFUSAL_FIXATION", - "DISSIPATIVE_NOISE", - "INTEGRATION_OSCILLATION", - "CONFIDENCE_RUNAWAY", - "DEFLECTIVE_STASIS", - "COMPLIANCE_STASIS", - "SCAPEGOAT_DISCHARGE", - "UNCLASSIFIED", -] - -AggregationLevel = Literal["department", "team", "organization"] - - -@dataclass -class Metrics: - """ - Primary EDCM metrics. All ranges validated at output time. - - C: Constraint Strain [0, 1] - R: Refusal Density [0, 1] - F: Fixation [0, 1] - E: Escalation [0, 1] - D: Deflection [0, 1] - N: Noise [0, 1] - I: Integration Failure [0, 1] - O: Overconfidence [-1, 1] - L: Coherence Loss [0, 1] - P: Progress [0, 1] - """ - - C: float # constraint strain - R: float # refusal density - F: float # fixation - E: float # escalation - D: float # deflection - N: float # noise - I: float # integration failure - O: float # overconfidence [-1, 1] - L: float # coherence loss - P: float # progress - - # Optional Progress sub-components (auditable) - P_decisions: float = 0.0 - P_commitments: float = 0.0 - P_artifacts: float = 0.0 - P_followthrough: float = 0.0 - - # Per-primary confidence scores (0..1); secondary modifiers are capped per spec - conf: Dict[str, float] = field(default_factory=dict) - - -@dataclass -class Params: - """ - Estimated system parameters. - - alpha: Persistence — estimated from unresolved constraint half-life regression. - delta_max: Complexity-bounded throughput — P90(median(resolution_rate | complexity_bucket)). - complexity: Complexity bucket value for the current window. - """ - - alpha: float - delta_max: float - complexity: float - - -@dataclass -class OutputEnvelope: - """ - Canonical EDCM output. Every output MUST include all fields. - Validated before serialization. - """ - - spec_version: str - org: str - window_id: str - aggregation: AggregationLevel - metrics: Metrics - params: Params - basin: BasinName - basin_confidence: float - gaming_alerts: List[str] = field(default_factory=list) - warnings: List[str] = field(default_factory=list) - - def validate(self) -> List[str]: - """ - Returns a list of validation errors. Empty list means valid. - """ - errors: List[str] = [] - m = self.metrics - - def chk(name: str, val: float, lo: float, hi: float) -> None: - if not (lo <= val <= hi): - errors.append(f"Metric {name}={val:.4f} out of range [{lo}, {hi}]") - - chk("C", m.C, 0.0, 1.0) - chk("R", m.R, 0.0, 1.0) - chk("F", m.F, 0.0, 1.0) - chk("E", m.E, 0.0, 1.0) - chk("D", m.D, 0.0, 1.0) - chk("N", m.N, 0.0, 1.0) - chk("I", m.I, 0.0, 1.0) - chk("O", m.O, -1.0, 1.0) - chk("L", m.L, 0.0, 1.0) - chk("P", m.P, 0.0, 1.0) - - if self.aggregation == "individual": - errors.append("aggregation='individual' is prohibited by spec v0.1") - - if self.spec_version != "edcm-org-v0.1.0": - errors.append(f"Unknown spec_version: {self.spec_version!r}") - - return errors -# 69:32 0:0 10:0 diff --git a/a0python/edcm-org/tests/__init__.py b/a0python/edcm-org/tests/__init__.py deleted file mode 100644 index f959c5ce1..000000000 --- a/a0python/edcm-org/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# 0:0 0:0 0:0 -# 0:0 0:0 0:0 diff --git a/a0python/edcm-org/tests/test_basin_detection.py b/a0python/edcm-org/tests/test_basin_detection.py deleted file mode 100644 index 3da4d081c..000000000 --- a/a0python/edcm-org/tests/test_basin_detection.py +++ /dev/null @@ -1,98 +0,0 @@ -# 71:10 0:0 0:0 -""" -Basin detection tests — verify all basins fire at their documented thresholds. -""" - -import pytest -from edcm_org.types import Metrics -from edcm_org.basins.detect import detect_basin - - -def make_metrics(**overrides) -> Metrics: - """Create a Metrics instance with neutral defaults, applying overrides.""" - defaults = dict( - C=0.3, R=0.3, F=0.3, E=0.3, D=0.3, N=0.3, - I=0.3, O=0.0, L=0.3, P=0.5, - P_decisions=0.5, P_commitments=0.5, - P_artifacts=0.5, P_followthrough=0.5, - ) - defaults.update(overrides) - return Metrics(**defaults) - - -class TestBasinDetection: - - def test_refusal_fixation(self): - m = make_metrics(R=0.8, F=0.7) - basin, conf, expl = detect_basin(m, s_t=0.5, c_reduction=0.1, delta_work=0.3, blame_density=0.1) - assert basin == "REFUSAL_FIXATION" - assert conf == pytest.approx(0.90) - assert len(expl["fired"]) > 0 - - def test_dissipative_noise(self): - m = make_metrics(N=0.8, P=0.2) - basin, conf, expl = detect_basin(m, s_t=0.5, c_reduction=0.1, delta_work=0.2, blame_density=0.1) - assert basin == "DISSIPATIVE_NOISE" - assert conf == pytest.approx(0.80) - - def test_integration_oscillation(self): - m = make_metrics(I=0.7, F=0.6) - basin, conf, expl = detect_basin(m, s_t=0.5, c_reduction=0.1, delta_work=0.3, blame_density=0.1) - assert basin == "INTEGRATION_OSCILLATION" - assert conf == pytest.approx(0.70) - - def test_confidence_runaway(self): - m = make_metrics(O=0.8, E=0.7) - basin, conf, expl = detect_basin(m, s_t=0.5, c_reduction=0.1, delta_work=0.3, blame_density=0.1) - assert basin == "CONFIDENCE_RUNAWAY" - assert conf == pytest.approx(0.85) - - def test_deflective_stasis(self): - m = make_metrics(D=0.8, P=0.3) - basin, conf, expl = detect_basin(m, s_t=0.5, c_reduction=0.1, delta_work=0.3, blame_density=0.1) - assert basin == "DEFLECTIVE_STASIS" - assert conf == pytest.approx(0.70) - - def test_compliance_stasis(self): - m = make_metrics(E=0.2, P_artifacts=0.85, P=0.5) - basin, conf, expl = detect_basin( - m, s_t=0.7, c_reduction=0.05, delta_work=0.5, blame_density=0.1 - ) - assert basin == "COMPLIANCE_STASIS" - assert conf == pytest.approx(0.85) - - def test_scapegoat_discharge(self): - m = make_metrics(I=0.7, P=0.5) - basin, conf, expl = detect_basin( - m, s_t=0.4, c_reduction=0.1, delta_work=0.05, blame_density=0.5 - ) - assert basin == "SCAPEGOAT_DISCHARGE" - assert conf == pytest.approx(0.80) - - def test_unclassified(self): - m = make_metrics() # all neutral defaults - basin, conf, expl = detect_basin(m, s_t=0.3, c_reduction=0.3, delta_work=0.5, blame_density=0.1) - assert basin == "UNCLASSIFIED" - assert conf == pytest.approx(0.50) - - def test_explanation_block_always_present(self): - m = make_metrics(R=0.8, F=0.7) - _, _, expl = detect_basin(m, s_t=0.5, c_reduction=0.1, delta_work=0.3, blame_density=0.1) - assert "fired" in expl - assert "would_change_if" in expl - assert isinstance(expl["fired"], list) - assert isinstance(expl["would_change_if"], list) - - def test_human_only_basins_checked_before_standard(self): - """ - COMPLIANCE_STASIS should fire even when standard basin conditions are met, - because human-only basins are evaluated first. - """ - # Also set N high and P low to trigger DISSIPATIVE_NOISE if standard ran first - m = make_metrics(N=0.8, P=0.2, E=0.2, P_artifacts=0.85) - basin, _, _ = detect_basin( - m, s_t=0.7, c_reduction=0.05, delta_work=0.2, blame_density=0.1 - ) - # COMPLIANCE_STASIS should win because it's evaluated first - assert basin == "COMPLIANCE_STASIS" -# 71:10 0:0 0:0 diff --git a/a0python/edcm-org/tests/test_metrics_ranges.py b/a0python/edcm-org/tests/test_metrics_ranges.py deleted file mode 100644 index 175634afa..000000000 --- a/a0python/edcm-org/tests/test_metrics_ranges.py +++ /dev/null @@ -1,169 +0,0 @@ -# 100:23 0:0 0:0 -""" -Spec compliance tests — metric range validation. - -These tests MUST pass before any release. They enforce that no metric -can silently drift outside its defined range. -""" - -import pytest -from edcm_org.metrics.primary import ( - metric_C, metric_R, metric_D, metric_N, metric_L, metric_O, - clamp01, clamp11, -) -from edcm_org.metrics.secondary import metric_F, metric_E, metric_I -from edcm_org.metrics.progress import compute_progress - - -# --------------------------------------------------------------------------- -# Range constants -# --------------------------------------------------------------------------- - -RANGE_01 = (0.0, 1.0) -RANGE_11 = (-1.0, 1.0) - - -def in_range(val: float, lo: float, hi: float) -> bool: - return lo <= val <= hi - - -# --------------------------------------------------------------------------- -# Test data -# --------------------------------------------------------------------------- - -SAMPLE_TEXTS = [ - "", - "Hello world.", - "We cannot proceed. It is impossible to meet this deadline. We're not sure about the requirements.", - "Decision made: we will ship by Friday. Committed. Approved.", - "Maybe we'll circle back. Not sure. Unclear. Tabled for next week.", - "The team definitely guarantees this will work. No doubt whatsoever.", - "Actually I retract that. Correction: we were wrong. Per feedback we changed to the new approach.", - "Fault lies with the project manager. They failed to deliver. It's their fault entirely.", - "We shipped the feature. PR merged. Deployed to production. Completed as planned.", -] - -MULTI_WINDOW = [SAMPLE_TEXTS[2], SAMPLE_TEXTS[3], SAMPLE_TEXTS[4]] - - -# --------------------------------------------------------------------------- -# Primary metric range tests -# --------------------------------------------------------------------------- - -class TestMetricRanges: - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_C_in_range(self, text): - val = metric_C(text) - assert in_range(val, *RANGE_01), f"C={val} out of [0,1] for text={text!r:.50}" - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_R_in_range(self, text): - val = metric_R(text) - assert in_range(val, *RANGE_01), f"R={val} out of [0,1]" - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_D_in_range(self, text): - val = metric_D(text) - assert in_range(val, *RANGE_01), f"D={val} out of [0,1]" - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_N_in_range(self, text): - val = metric_N(text) - assert in_range(val, *RANGE_01), f"N={val} out of [0,1]" - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_L_in_range(self, text): - val = metric_L(text) - assert in_range(val, *RANGE_01), f"L={val} out of [0,1]" - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_O_in_range(self, text): - val = metric_O(text) - assert in_range(val, *RANGE_11), f"O={val} out of [-1,1]" - - -# --------------------------------------------------------------------------- -# Window-history metric range tests -# --------------------------------------------------------------------------- - -class TestWindowMetricRanges: - - def test_F_single_window_returns_zero(self): - val = metric_F(["only one window"]) - assert val == 0.0 - - @pytest.mark.parametrize("windows", [MULTI_WINDOW, SAMPLE_TEXTS[:3]]) - def test_F_in_range(self, windows): - val = metric_F(windows) - assert in_range(val, *RANGE_01), f"F={val} out of [0,1]" - - def test_E_single_window_returns_zero(self): - val = metric_E(["only one window"]) - assert val == 0.0 - - @pytest.mark.parametrize("windows", [MULTI_WINDOW, SAMPLE_TEXTS[:3]]) - def test_E_in_range(self, windows): - val = metric_E(windows) - assert in_range(val, *RANGE_01), f"E={val} out of [0,1]" - - def test_I_single_window_returns_zero(self): - val = metric_I(["only one window"]) - assert val == 0.0 - - @pytest.mark.parametrize("windows", [MULTI_WINDOW, SAMPLE_TEXTS[:3]]) - def test_I_in_range(self, windows): - val = metric_I(windows) - assert in_range(val, *RANGE_01), f"I={val} out of [0,1]" - - -# --------------------------------------------------------------------------- -# Progress sub-component consistency -# --------------------------------------------------------------------------- - -class TestProgressConsistency: - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_P_in_range(self, text): - P, P_d, P_c, P_a, P_f = compute_progress(text) - assert in_range(P, *RANGE_01), f"P={P} out of [0,1]" - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_P_sub_components_in_range(self, text): - P, P_d, P_c, P_a, P_f = compute_progress(text) - for name, val in [("P_d", P_d), ("P_c", P_c), ("P_a", P_a), ("P_f", P_f)]: - assert in_range(val, *RANGE_01), f"{name}={val} out of [0,1]" - - @pytest.mark.parametrize("text", SAMPLE_TEXTS) - def test_P_sub_components_sum_matches_P(self, text): - P, P_d, P_c, P_a, P_f = compute_progress(text) - computed = 0.3 * P_d + 0.2 * P_c + 0.3 * P_a + 0.2 * P_f - assert abs(computed - P) < 0.01, ( - f"P sub-components sum {computed:.4f} != P {P:.4f}" - ) - - -# --------------------------------------------------------------------------- -# Clamp utility tests -# --------------------------------------------------------------------------- - -class TestClampUtilities: - - def test_clamp01_below(self): - assert clamp01(-0.5) == 0.0 - - def test_clamp01_above(self): - assert clamp01(1.5) == 1.0 - - def test_clamp01_within(self): - assert clamp01(0.5) == 0.5 - - def test_clamp11_below(self): - assert clamp11(-2.0) == -1.0 - - def test_clamp11_above(self): - assert clamp11(2.0) == 1.0 - - def test_clamp11_within(self): - assert clamp11(-0.3) == -0.3 -# 100:23 0:0 0:0 diff --git a/a0python/edcm-org/tests/test_no_individual_outputs.py b/a0python/edcm-org/tests/test_no_individual_outputs.py deleted file mode 100644 index be6531559..000000000 --- a/a0python/edcm-org/tests/test_no_individual_outputs.py +++ /dev/null @@ -1,88 +0,0 @@ -# 66:6 0:0 0:0 -""" -Spec compliance test: no individual-level outputs. - -This test suite is specifically designed to catch any code path that could -produce individual-level EDCM outputs. It is a hard build gate. -""" - -import json -import pytest -from edcm_org.governance.privacy import EDCMPrivacyGuard, PrivacyConfig, ConsentError -from edcm_org.types import OutputEnvelope, Metrics, Params -from edcm_org.spec_version import SPEC_VERSION -from edcm_org.eval.protocol import check_spec_compliance - - -def make_envelope(aggregation="department") -> OutputEnvelope: - return OutputEnvelope( - spec_version=SPEC_VERSION, - org="test-org", - window_id="w001", - aggregation=aggregation, - metrics=Metrics( - C=0.3, R=0.2, F=0.2, E=0.2, D=0.3, N=0.4, - I=0.2, O=0.1, L=0.2, P=0.5, - ), - params=Params(alpha=0.5, delta_max=0.45, complexity=0.4), - basin="UNCLASSIFIED", - basin_confidence=0.5, - ) - - -class TestNoIndividualOutputs: - - def test_privacy_guard_blocks_individual(self): - guard = EDCMPrivacyGuard(PrivacyConfig()) - with pytest.raises(ConsentError): - guard.enforce({"aggregation": "individual"}) - - def test_output_envelope_validate_blocks_individual(self): - envelope = make_envelope(aggregation="individual") - errors = envelope.validate() - assert any("individual" in e for e in errors) - - def test_spec_compliance_check_blocks_individual(self): - envelope = make_envelope(aggregation="individual") - result = check_spec_compliance(envelope) - assert not result.passed - assert any("individual" in e for e in result.errors) - - def test_valid_department_output_passes(self): - envelope = make_envelope(aggregation="department") - result = check_spec_compliance(envelope) - assert result.passed, f"Expected pass, got errors: {result.errors}" - - def test_valid_team_output_passes(self): - envelope = make_envelope(aggregation="team") - result = check_spec_compliance(envelope) - assert result.passed, f"Expected pass, got errors: {result.errors}" - - def test_valid_organization_output_passes(self): - envelope = make_envelope(aggregation="organization") - result = check_spec_compliance(envelope) - assert result.passed, f"Expected pass, got errors: {result.errors}" - - def test_spec_version_enforced(self): - envelope = make_envelope() - envelope.spec_version = "edcm-org-v99.0.0" - result = check_spec_compliance(envelope) - assert not result.passed - assert any("spec_version" in e for e in result.errors) - - def test_all_metric_ranges_enforced(self): - """Each metric out of range should produce a compliance error.""" - test_cases = [ - ("C", 1.5), ("R", -0.1), ("F", 1.1), ("O", -1.5), ("O", 1.5), - ] - for metric_name, bad_value in test_cases: - envelope = make_envelope() - setattr(envelope.metrics, metric_name, bad_value) - result = check_spec_compliance(envelope) - assert not result.passed, ( - f"Expected failure for {metric_name}={bad_value}" - ) - assert any(metric_name in e for e in result.errors), ( - f"Error message should reference metric {metric_name}" - ) -# 66:6 0:0 0:0 diff --git a/a0python/edcm-org/tests/test_privacy_guard.py b/a0python/edcm-org/tests/test_privacy_guard.py deleted file mode 100644 index ee6645fa8..000000000 --- a/a0python/edcm-org/tests/test_privacy_guard.py +++ /dev/null @@ -1,87 +0,0 @@ -# 65:3 0:0 0:0 -""" -Privacy guard tests — enforce spec v0.1 governance rules. -""" - -import pytest -from edcm_org.governance.privacy import EDCMPrivacyGuard, PrivacyConfig, ConsentError - - -@pytest.fixture -def guard(): - return EDCMPrivacyGuard(PrivacyConfig(aggregation="department")) - - -class TestPrivacyGuard: - - def test_department_aggregation_passes(self, guard): - payload = {"aggregation": "department", "org": "ACME", "metrics": {}} - result = guard.enforce(payload) - assert result["aggregation"] == "department" - - def test_team_aggregation_passes(self, guard): - payload = {"aggregation": "team", "org": "ACME"} - result = guard.enforce(payload) - assert result["aggregation"] == "team" - - def test_organization_aggregation_passes(self, guard): - payload = {"aggregation": "organization", "org": "ACME"} - result = guard.enforce(payload) - assert result["aggregation"] == "organization" - - def test_individual_aggregation_raises(self, guard): - payload = {"aggregation": "individual", "org": "ACME"} - with pytest.raises(ConsentError): - guard.enforce(payload) - - def test_pii_email_stripped(self, guard): - payload = { - "aggregation": "department", - "email": "user@example.com", - "org": "ACME", - } - result = guard.enforce(payload) - assert "email" not in result - - def test_pii_name_stripped(self, guard): - payload = {"aggregation": "department", "name": "John Doe", "data": "ok"} - result = guard.enforce(payload) - assert "name" not in result - assert result["data"] == "ok" - - def test_pii_nested_stripped(self, guard): - payload = { - "aggregation": "department", - "nested": {"email": "x@y.com", "value": 42}, - } - result = guard.enforce(payload) - assert "email" not in result["nested"] - assert result["nested"]["value"] == 42 - - def test_pii_in_list_stripped(self, guard): - payload = { - "aggregation": "department", - "items": [{"email": "x@y.com", "id": 1}, {"id": 2}], - } - result = guard.enforce(payload) - assert "email" not in result["items"][0] - assert result["items"][0]["id"] == 1 - - def test_retention_within_window(self, guard): - assert guard.validate_retention(3.0) is True - - def test_retention_at_boundary(self, guard): - assert guard.validate_retention(6.0) is True - - def test_retention_beyond_window(self, guard): - assert guard.validate_retention(7.0) is False - - def test_all_pii_keys_stripped(self, guard): - pii_fields = {"email", "phone", "name", "employee_id", "address", "ssn", "dob", "ip_address"} - payload = {"aggregation": "department"} - for field in pii_fields: - payload[field] = "sensitive" - result = guard.enforce(payload) - for field in pii_fields: - assert field not in result, f"PII field {field!r} was not stripped" -# 65:3 0:0 0:0 diff --git a/a0python/pyproject.toml b/a0python/pyproject.toml deleted file mode 100644 index 321b13096..000000000 --- a/a0python/pyproject.toml +++ /dev/null @@ -1,52 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "a0python" -license = { text = "AGPL-3.0-or-later" } -version = "0.1.0" -description = "a0 — PTCA-structured cognitive routing shell" -requires-python = ">=3.10" - -dependencies = [ - "anyio>=4.0", - "textual>=0.50", - "gradio>=4.7", - "httpx>=0.27", - "python-dotenv>=1.0", -] - -[project.optional-dependencies] -anthropic = [ - "anthropic>=0.25", -] -agent = [ - "claude-agent-sdk", -] -local-llama = [ - "llama-cpp-python>=0.2", -] -tools = [ - "pypdf>=4.0", - "openai-whisper", -] -encryption = [ - "cryptography>=42.0", -] -zfae = [ - "numpy>=1.24", # for accurate lstsq in train_readout(); falls back to pure-Python otherwise -] -dev = [ - "pytest>=7.0", -] - -[project.scripts] -a0 = "a0.a0:main" -a0-web = "a0.guardian.ui.web.app:main" - -[tool.hatch.build.targets.wheel] -packages = ["a0"] - -[tool.pytest.ini_options] -testpaths = ["tests"] diff --git a/a0python/run.sh b/a0python/run.sh deleted file mode 100644 index 06fd9624a..000000000 --- a/a0python/run.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# Launch the a0 web UI (primary) or CLI (emergency fallback). -# -# Usage: -# ./run.sh — start Gradio web app at A0_HOST:A0_PORT -# ./run.sh --cli — emergency CLI mode (Textual TUI) -# -set -euo pipefail -cd "$(dirname "$0")" - -if [[ "${1:-}" == "--cli" ]]; then - echo "[a0] CLI mode (emergency fallback)" - python -m a0.guardian.ui.app -else - echo "[a0] web UI → http://$(python -c 'from a0.cores.psi.tensors.env import A0_HOST, A0_PORT; h=A0_HOST if A0_HOST!="0.0.0.0" else "localhost"; print(f"{h}:{A0_PORT}")')" - python -m a0.guardian.ui.web.app "$@" -fi diff --git a/a0python/tests/test_smoke.py b/a0python/tests/test_smoke.py deleted file mode 100644 index b52f83333..000000000 --- a/a0python/tests/test_smoke.py +++ /dev/null @@ -1,28 +0,0 @@ -# 22:1 0:0 0:0 -# tests/test_smoke.py -import json, subprocess, sys, os - -REQ = { - "task_id": "smoke1", - "input": {"text": "hello a0", "files": [], "metadata": {}}, - "tools_allowed": ["none"], - "mode": "analyze", - "hmmm": ["hmm"] -} - -def main(): - p = subprocess.run( - [sys.executable, "-m", "a0.a0"], - input=json.dumps(REQ).encode("utf-8"), - stdout=subprocess.PIPE, - check=True, - cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))), - ) - out = json.loads(p.stdout.decode("utf-8")) - assert out["task_id"] == "smoke1" - assert "result" in out - print("OK") - -if __name__ == "__main__": - main() -# 22:1 0:0 0:0