diff --git a/.forgejo/workflows/ci-smoke.yaml b/.forgejo/workflows/ci-smoke.yaml deleted file mode 100644 index bd44c56..0000000 --- a/.forgejo/workflows/ci-smoke.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Canonical CI smoke template (tier 1 routing drill). -# Copy to: .forgejo/workflows/ci-smoke.yaml in consumer repos. -name: CI Smoke - -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - host-smoke: - runs-on: self-hosted - steps: - - name: Routing probe (host runner) - run: | - set -eu - echo "repository=${GITHUB_REPOSITORY:-unknown}" - echo "sha=${GITHUB_SHA:-unknown}" - echo "runner=${RUNNER_NAME:-unknown}" - uname -a - - container-smoke: - runs-on: ubuntu-latest - steps: - - name: Routing probe (container label) - run: | - set -eu - echo "container-smoke ok for ${GITHUB_REPOSITORY:-unknown}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index bb49267..72e1476 100644 --- a/.gitignore +++ b/.gitignore @@ -177,4 +177,3 @@ cython_debug/ # PyPI configuration file .pypirc -*.swp diff --git a/Makefile b/Makefile deleted file mode 100644 index 7d78bb6..0000000 --- a/Makefile +++ /dev/null @@ -1,42 +0,0 @@ -# phase-memory development and install targets -ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) - -.DEFAULT_GOAL := help - -.PHONY: help sync install install-editable check test report-7d report - -help: - @echo "phase-memory make targets" - @echo "" - @echo " make install # install phase-memory CLI (uv tool, no venv)" - @echo " make sync # create .venv and editable install (dev)" - @echo " make install-editable # alias for make sync" - @echo " make check # verify import and print version" - @echo " make test # run pytest" - @echo " make report-7d # federated activity report (last 7 days)" - @echo " make report # federated report (REPORT_DAYS=7 default)" - @echo "" - @echo "For ops-warden integration, prefer: cd ../ops-warden && make install-all" - -sync: - uv venv --allow-existing - uv pip install -e "$(ROOT)" - -install-editable: sync - -install: - uv tool install . --force - -check: - @PYTHONPATH="$(ROOT)src" python3 -c "import phase_memory; print('phase-memory', phase_memory.__version__, 'ok')" - -test: - PYTHONPATH="$(ROOT)src" python3 -m pytest - -REPORT_DAYS ?= 7 - -report-7d: - PYTHONPATH="$(ROOT)src" python3 -m phase_memory.cli report --days 7 --format summary - -report: - PYTHONPATH="$(ROOT)src" python3 -m phase_memory.cli report --days $(REPORT_DAYS) --format summary \ No newline at end of file diff --git a/README.md b/README.md index 7bb3af5..7322f9d 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,7 @@ Adjacent repositories own nearby but separate concerns: ## Quick Start ```bash -make test -``` - -Install for local development or ops-warden integration: - -```bash -make install # global CLI: phase-memory, phase-memory-service -make sync # dev: editable install into .venv -# or from ops-warden: make install-all +python3 -m pytest ``` The default test suite uses only deterministic local fixtures. @@ -55,25 +47,8 @@ PYTHONPATH=src python3 -m phase_memory.cli store import --store .phase-memory-lo When installed, the package exposes the same commands as `phase-memory`. Commands emit JSON runtime envelopes by default and accept `--format summary` -for a concise human-readable view. Profile, graph, and store import/export -commands are dry-run planning operations; they do not mutate durable memory -stores by default. - -## Management Reports - -Inspect activity across every discovered phase-memory store (ops-warden -coordination store, registry entries, and `PHASE_MEMORY_STORE_PATHS`): - -```bash -phase-memory stores list --format summary -phase-memory report --days 7 --format summary -phase-memory report --store ops-warden-default --days 30 -make report-7d -``` - -Discovery honours `WARDEN_MEMORY_STORE`, `PHASE_MEMORY_REGISTRY`, and -`PHASE_MEMORY_STORE_PATHS`. Reports are metadata-only and never include secret -values. +for a concise human-readable view. All current commands are dry-run planning +operations; they do not mutate durable memory stores. ## Local Runtime diff --git a/docs/operator-readiness-runbook.md b/docs/operator-readiness-runbook.md index 05f8432..9581563 100644 --- a/docs/operator-readiness-runbook.md +++ b/docs/operator-readiness-runbook.md @@ -55,20 +55,6 @@ Routes: - `POST /operations/{operation}` - `POST /operations` with `{"operation": "...", "payload": {...}}` -## Cross-Store Activity - -Use the phase-memory CLI as the canonical cross-store activity check: - -```bash -phase-memory stores list --format summary -phase-memory report --days 7 --format summary -make report-7d -``` - -Federated reports aggregate ops-warden coordination episodes and local graph -store events/audit lines for the requested window. Drill into one store with -`phase-memory report --store --days N`. - ## Readiness Checks Before accepting traffic: diff --git a/docs/ops-warden-memory-contract.md b/docs/ops-warden-memory-contract.md deleted file mode 100644 index ee4010f..0000000 --- a/docs/ops-warden-memory-contract.md +++ /dev/null @@ -1,80 +0,0 @@ -# Ops-Warden Cross-Runtime Memory Contract - -Updated: 2026-07-02 - -## Purpose - -phase-memory provides a shared experiential substrate for ops-warden across: - -- `warden worker` ticks with llm-connect / OpenRouter inference -- Coding agent sessions (Claude Code, Codex, Grok, future agents) -- Direct operator CLI use - -All runtimes use one canonical store and the same redaction rules. - -## Canonical Store - -| Setting | Default | -| --- | --- | -| Path | `~/.local/share/warden/memory/` | -| Override | `WARDEN_MEMORY_STORE` | -| Opt-out | `WARDEN_MEMORY=0` | - -Layout: - -- `metadata.json` — store schema and profile id -- `events.jsonl` — append-only session episodes - -## Session Kinds - -| Runtime | `session_kind` | Selection | -| --- | --- | --- | -| Worker | `warden.worker` | `WARDEN_SESSION_KIND=warden.worker` during worker ticks | -| Operator CLI | `warden.operator` | default when `WARDEN_AGENT_ID` is unset | -| Agent session | `warden.agent.` | `WARDEN_AGENT_ID=claude|codex|grok|...` | - -## Event Schema - -Schema: `phase_memory.ops_warden.session_event.v1` - -Required metadata fields: - -- `session_kind`, `command`, `outcome` (`resolved|escalated|skipped`) -- `need_fingerprint` (digest only — never the raw need text when sensitive) -- `route_id`, `agent_id`, `session_id`, `diagnostic_codes` - -Rejected writes containing token-like values or raw URLs. - -## Activation - -`activate_ops_warden_memory` returns: - -- stabilized route matches (verified repeats) -- recent episodes filtered by session kind / need fingerprint -- `llm_calls_avoided` when stabilized memory covers the need - -Rigid memory (charter, allowlist) is import-only and never promoted from episodes. - -## Default Behavior - -Memory is enabled by default (`WARDEN_MEMORY=1`). ops-warden implicitly calls -`ensure_memory_context` on every command; explicit `warden memory activate` is -optional introspection only. - -## Agent Session Orientation - -Coding agents should: - -1. Set `WARDEN_AGENT_ID` to their runtime id (claude, codex, grok, or future). -2. Use normal `warden route` / `warden access` commands; memory loads and episodes - are recorded automatically. - -## Evaluation - -Ops-warden memory quality is measured by: - -- `routing_repeat_accuracy` -- `cross_runtime_continuity` -- `llm_calls_avoided_count` - -Use `evaluation_trend_regression_gate` when promoting worker or routing changes. \ No newline at end of file diff --git a/registry/capabilities/capability.memory.phase-planning.md b/registry/capabilities/capability.memory.phase-planning.md deleted file mode 100644 index bfd825f..0000000 --- a/registry/capabilities/capability.memory.phase-planning.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -id: capability.memory.phase-planning -name: Profile-Driven Memory Phase Planning (phase-memory) -summary: Interprets Markitect memory profiles as runtime plans, modeling memory phases and producing deterministic - dry-run actions for retention, refresh, compaction, stabilization, and activation. -owner: phase-memory -status: draft -domain: communication -tags: -- memory -- agentic -- planning -maturity: - discovery: - current: D3 - target: D5 - confidence: medium - rationale: README explicitly documents the repo's boundary against adjacent repos (markitect-tool - owns profile/graph/event contracts; kontextual-engine owns durable storage/retrieval; infospace-bench - owns pilots/evaluation) and states the first slice is local-first, dependency-light, dry-run only. - availability: - current: A1 - target: A3 - confidence: medium - rationale: Python package (`phase-memory`), pip-installable; explicitly does not launch a service - or mutate durable memory stores by default in this first slice. -external_evidence: - completeness: - level: C1 - confidence: low - basis: scope_vs_intent_and_consumer_expectations - satisfied_expectations: - - memory phase modeling (retention, refresh, compaction, stabilization, activation) - - deterministic dry-run action generation - - explicit boundary docs vs three adjacent repos - broken_expectations: [] - out_of_scope_expectations: [] - reliability: - level: R0 - confidence: low - basis: consumer_quality_signals - known_reliability_risks: - - first slice is dry-run only; does not yet mutate durable stores -discovery: - intent: Interpret Markitect memory profiles as runtime plans and produce deterministic dry-run actions - for memory-phase transitions, as the operating layer between profile definition (markitect-tool) and - durable storage (kontextual-engine). - includes: - - memory phase modeling and deterministic dry-run planning for retention/refresh/compaction/stabilization/activation - excludes: - - memory profile/graph/event contract definition (markitect-tool) - - durable storage and permission-aware retrieval (kontextual-engine) - - concrete pilots and evaluation (infospace-bench) - assumptions: [] - use_cases: [] - research_memos: [] -availability: - current_level: A1 - target_level: A3 - current_artifacts: - - Python package (`phase-memory`) - target_artifacts: [] - consumption_modes: - - library import -relations: - depends_on: [] - supports: [] - related_to: [] -evidence: - documentation: - - README.md - tests: - - tests/ - consumer_feedback: [] - bug_reports: [] - incidents: [] -consumer_guidance: - recommended_for: - - agentic systems needing deterministic memory-phase planning distinct from storage or profile-contract - concerns - not_recommended_for: - - needs for durable memory mutation today (first slice is dry-run only) - known_limitations: - - local-first, dependency-light first slice; no service or durable mutation yet -promotion_history: [] ---- - -# Profile-Driven Memory Phase Planning (phase-memory) - -## Overview - -`phase-memory` is the profile-driven memory operating layer for agentic systems: it interprets Markitect memory profiles as runtime plans and produces deterministic dry-run actions for memory phases (retention, refresh, compaction, stabilization, activation), explicitly bounded against adjacent repos that own contracts (markitect-tool), durable storage (kontextual-engine), and evaluation (infospace-bench). - -## Assessment notes - -### Discovery - -README explicitly documents the repo's boundary against adjacent repos (markitect-tool owns profile/graph/event contracts; kontextual-engine owns durable storage/retrieval; infospace-bench owns pilots/evaluation) and states the first slice is local-first, dependency-light, dry-run only. - -### Availability - -Python package (`phase-memory`), pip-installable; explicitly does not launch a service or mutate durable memory stores by default in this first slice. - -### Completeness - -First-pass honest assessment from the REUSE-WP-0017 coverage campaign -(reuse-surface). No external consumer feedback exists yet; levels reflect -scope-vs-intent documentation quality, not internal code quality. - -### Reliability - -No production consumer telemetry exists yet; reliability level is -intentionally conservative pending REUSE-WP-0019 reuse-telemetry evidence. - -## Promotion checklist - -- [x] ID follows `capability..` pattern -- [x] Maturity enums match `specs/CapabilityMaturityStandard.md` -- [x] `external_evidence` is populated separately from `maturity` -- [ ] Relations reference valid capability IDs (none yet) -- [x] Index entry added in `registry/indexes/capabilities.yaml` diff --git a/registry/indexes/capabilities.yaml b/registry/indexes/capabilities.yaml index 373f4b4..f944e47 100644 --- a/registry/indexes/capabilities.yaml +++ b/registry/indexes/capabilities.yaml @@ -1,19 +1,4 @@ version: 1 -updated: '2026-07-06' +updated: '2026-06-16' domain: helix_forge -capabilities: -- id: capability.memory.phase-planning - name: Profile-Driven Memory Phase Planning (phase-memory) - summary: Interprets Markitect memory profiles as runtime plans, modeling memory phases and producing - deterministic dry-run actions for retention, refresh, compaction, stabilization, and activation. - vector: D3 / A1 / C1 / R0 - domain: communication - status: draft - owner: phase-memory - path: registry/capabilities/capability.memory.phase-planning.md - tags: - - memory - - agentic - - planning - consumption_modes: - - library import +capabilities: [] diff --git a/src/phase_memory/__init__.py b/src/phase_memory/__init__.py index 50b2ccd..33e4a43 100644 --- a/src/phase_memory/__init__.py +++ b/src/phase_memory/__init__.py @@ -126,39 +126,6 @@ from .pilot import ( managed_deployment_pilot_report, write_live_pilot_evidence, ) -from .management import ( - FEDERATED_REPORT_SCHEMA, - STORE_LIST_SCHEMA, - STORE_REGISTRY_SCHEMA, - build_federated_report, - build_store_list, - classify_store, - discover_memory_stores, -) -from .ops_warden import ( - KNOWN_AGENT_IDS, - OPS_WARDEN_ACTIVATION_SCHEMA, - OPS_WARDEN_ADAPTER_PACK_NAME, - OPS_WARDEN_MEMORY_STATUS_SCHEMA, - OPS_WARDEN_PROFILE_ID, - OPS_WARDEN_RUNTIME_SCHEMA, - OPS_WARDEN_SESSION_EVENT_SCHEMA, - OpsWardenMemoryStore, - activate_ops_warden_memory, - build_session_event, - default_memory_store_path, - memory_enabled, - ops_warden_adapter_pack, - ops_warden_coordination_profile, - ops_warden_evaluation_metrics, - ops_warden_evaluation_report, - record_session_event, - resolve_session_kind, - session_kind_for_agent, - stabilized_route_match, - validate_memory_write, - validate_ops_warden_profile, -) from .planner import plan_profile_execution from .runtime import PhaseMemoryRuntime from .troubleshooting import ( @@ -188,9 +155,6 @@ __all__ = [ "FakeExternalPolicyGateway", "FakeExternalSemanticIndex", "FakeKontextualRuntimeRegistry", - "FEDERATED_REPORT_SCHEMA", - "STORE_LIST_SCHEMA", - "STORE_REGISTRY_SCHEMA", "FakeMarkitectPackageCompiler", "FakeTelemetryAuditSink", "LiveShapedKontextualEventLog", @@ -230,15 +194,7 @@ __all__ = [ "LIVE_PILOT_REPORT_SCHEMA", "LocalMarkitectValidator", "OptionalMarkitectValidator", - "OPS_WARDEN_ACTIVATION_SCHEMA", - "OPS_WARDEN_ADAPTER_PACK_NAME", - "OPS_WARDEN_MEMORY_STATUS_SCHEMA", - "OPS_WARDEN_PROFILE_ID", - "OPS_WARDEN_RUNTIME_SCHEMA", - "OPS_WARDEN_SESSION_EVENT_SCHEMA", - "OpsWardenMemoryStore", "PHASE_MEMORY_CREDENTIAL_NEEDS", - "KNOWN_AGENT_IDS", "abandon_path", "branch_path", "compact_path", @@ -278,23 +234,7 @@ __all__ = [ "package_request_from_selection", "package_response_envelope", "WordCountTokenEstimator", - "activate_ops_warden_memory", "activation_quality_report", - "build_federated_report", - "build_session_event", - "build_store_list", - "default_memory_store_path", - "memory_enabled", - "ops_warden_adapter_pack", - "ops_warden_coordination_profile", - "ops_warden_evaluation_metrics", - "ops_warden_evaluation_report", - "record_session_event", - "resolve_session_kind", - "session_kind_for_agent", - "stabilized_route_match", - "validate_memory_write", - "validate_ops_warden_profile", "plan_neighborhood_activation", "retrieve_graph_neighborhood", "select_event_path", @@ -312,9 +252,7 @@ __all__ = [ "WARDEN_ACCESS_NEED", "WARDEN_ROUTE_FIND_QUERY", "build_service_binding", - "classify_store", "create_wsgi_app", - "discover_memory_stores", "health_report", "resolve_credentialed_environ", "resolve_runtime_adapters", diff --git a/src/phase_memory/cli.py b/src/phase_memory/cli.py index e720d1e..6420729 100644 --- a/src/phase_memory/cli.py +++ b/src/phase_memory/cli.py @@ -9,7 +9,6 @@ from pathlib import Path from typing import Any, Sequence from .adapters import FileBackedMemoryGraphStore, JsonlAuditSink, JsonlMemoryEventLog -from .management import build_federated_report, build_store_list from .runtime import PhaseMemoryRuntime @@ -81,33 +80,6 @@ def build_parser() -> argparse.ArgumentParser: _add_format(store_repair) store_repair.set_defaults(func=_store_repair) - stores = subparsers.add_parser("stores", help="Federated store discovery") - stores_subparsers = stores.add_subparsers(dest="stores_command") - stores_list = stores_subparsers.add_parser( - "list", - help="List discovered phase-memory stores (defaults, registry, PHASE_MEMORY_STORE_PATHS)", - ) - _add_format(stores_list) - stores_list.set_defaults(func=_stores_list) - - report = subparsers.add_parser( - "report", - help="Activity report across discovered stores for the last N days", - epilog=( - "Discovery: ops-warden default store, PHASE_MEMORY_REGISTRY, " - "PHASE_MEMORY_STORE_PATHS (colon-separated). Set WARDEN_MEMORY_STORE " - "to override the ops-warden store path." - ), - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - report.add_argument("--days", type=int, default=7, help="Look back N days (default: 7)") - report.add_argument( - "--store", - help="Focus on one store by registry store_id or absolute path", - ) - _add_format(report) - report.set_defaults(func=_report) - return parser @@ -176,14 +148,6 @@ def _store_repair(args: argparse.Namespace, runtime: PhaseMemoryRuntime) -> dict return _runtime_for_store(args.store).repair_diagnostics(source_ref=str(args.store)) -def _stores_list(args: argparse.Namespace, runtime: PhaseMemoryRuntime) -> dict[str, Any]: - return build_store_list() - - -def _report(args: argparse.Namespace, runtime: PhaseMemoryRuntime) -> dict[str, Any]: - return build_federated_report(days=args.days, focus_store_id=args.store, focus_store_path=args.store) - - def _missing_command(args: argparse.Namespace, runtime: PhaseMemoryRuntime) -> dict[str, Any]: raise SystemExit("Missing command. Use --help for usage.") @@ -236,36 +200,6 @@ def _print_summary(envelope: dict[str, Any]) -> None: if activation: print(f"selected_nodes={len(activation.get('selected_node_ids', []))}") print(f"omitted={len(activation.get('omitted', []))}") - if envelope.get("schema_version") == "phase_memory.management.store_list.v1": - print(f"stores={envelope.get('store_count', 0)}") - for store in envelope.get("stores", ()): - label = store.get("label") or store.get("store_id") - print(f"- {label} [{store.get('store_kind')}] {store.get('path')}") - if envelope.get("schema_version") == "phase_memory.management.federated_report.v1": - aggregate = envelope.get("aggregate") or {} - print( - f"window={envelope.get('window_start')}..{envelope.get('window_end')} " - f"stores={envelope.get('store_count', 0)}" - ) - print( - f"episodes={aggregate.get('episode_count', 0)} " - f"audit={aggregate.get('audit_event_count', 0)} " - f"active_stores={aggregate.get('active_store_count', 0)}" - ) - for store in envelope.get("stores", ()): - print( - f"- {store.get('store_id')} [{store.get('store_kind')}] " - f"episodes={store.get('episode_count', 0)} " - f"audit={store.get('audit_event_count', 0)} " - f"last={store.get('last_activity_at') or '—'}" - ) - detail = envelope.get("store_detail") - if detail: - print(f"detail={detail.get('store_id')} schema={detail.get('schema_version') or '—'}") - if detail.get("repair_diagnostics"): - print(f"repair_diagnostics={len(detail.get('repair_diagnostics', []))}") - if detail.get("episode_timeline"): - print(f"timeline={len(detail.get('episode_timeline', []))}") if __name__ == "__main__": diff --git a/src/phase_memory/management.py b/src/phase_memory/management.py deleted file mode 100644 index 20d8887..0000000 --- a/src/phase_memory/management.py +++ /dev/null @@ -1,713 +0,0 @@ -"""Federated store discovery and activity reporting.""" - -from __future__ import annotations - -import json -import os -from dataclasses import dataclass, field -from datetime import datetime, timedelta, timezone -from pathlib import Path -from typing import Any, Mapping - -from .adapters import LOCAL_STORE_SCHEMA, FileBackedMemoryGraphStore, JsonlAuditSink, JsonlMemoryEventLog -from .models import Diagnostic -from .ops_warden import OPS_WARDEN_PROFILE_ID, OPS_WARDEN_RUNTIME_SCHEMA, OpsWardenMemoryStore, default_memory_store_path -from .utils import parse_iso_datetime, stable_digest, utc_now_iso - -STORE_REGISTRY_SCHEMA = "phase_memory.management.store_registry.v1" -STORE_LIST_SCHEMA = "phase_memory.management.store_list.v1" -FEDERATED_REPORT_SCHEMA = "phase_memory.management.federated_report.v1" - -STORE_KIND_OPS_WARDEN = "ops_warden_coordination" -STORE_KIND_LOCAL_GRAPH = "local_graph" - -DEFAULT_OPS_WARDEN_STORE_ID = "ops-warden-default" -TOP_ROUTE_IDS = 10 -DETAIL_EPISODE_LIMIT = 20 - - -@dataclass(frozen=True) -class MemoryStoreDescriptor: - store_id: str - path: Path - store_kind: str - profile_id: str = "" - label: str = "" - source: str = "discovered" - - -def default_registry_path(environ: Mapping[str, str] | None = None) -> Path: - environ = environ or os.environ - override = str(environ.get("PHASE_MEMORY_REGISTRY") or "").strip() - if override: - return Path(override).expanduser() - xdg_data = str(environ.get("XDG_DATA_HOME") or "").strip() - base = Path(xdg_data).expanduser() if xdg_data else Path.home() / ".local" / "share" - return base / "phase-memory" / "stores.json" - - -def classify_store(path: str | Path) -> str | None: - root = Path(path).expanduser().resolve() - local_metadata = root / "phase-memory.json" - if local_metadata.is_file(): - try: - data = json.loads(local_metadata.read_text(encoding="utf-8")) - except json.JSONDecodeError: - return None - if str(data.get("schema_version") or "") == LOCAL_STORE_SCHEMA: - return STORE_KIND_LOCAL_GRAPH - - ops_metadata = root / "metadata.json" - if ops_metadata.is_file(): - try: - data = json.loads(ops_metadata.read_text(encoding="utf-8")) - except json.JSONDecodeError: - return None - if str(data.get("schema_version") or "") == OPS_WARDEN_RUNTIME_SCHEMA: - return STORE_KIND_OPS_WARDEN - return None - - -def load_store_registry( - path: str | Path | None = None, - environ: Mapping[str, str] | None = None, -) -> dict[str, Any]: - registry_path = Path(path).expanduser() if path is not None else default_registry_path(environ) - diagnostics: list[dict[str, Any]] = [] - if not registry_path.is_file(): - return { - "schema_version": STORE_REGISTRY_SCHEMA, - "valid": True, - "registry_path": str(registry_path), - "entries": [], - "diagnostics": diagnostics, - } - try: - payload = json.loads(registry_path.read_text(encoding="utf-8")) - except json.JSONDecodeError as exc: - return { - "schema_version": STORE_REGISTRY_SCHEMA, - "valid": False, - "registry_path": str(registry_path), - "entries": [], - "diagnostics": [ - Diagnostic( - "warn", - "corrupt_store_registry", - "Store registry file is not valid JSON.", - str(registry_path), - {"error": str(exc)}, - ).to_dict() - ], - } - if not isinstance(payload, dict): - diagnostics.append( - Diagnostic( - "warn", - "invalid_store_registry_shape", - "Store registry must be a JSON object.", - str(registry_path), - ).to_dict() - ) - return { - "schema_version": STORE_REGISTRY_SCHEMA, - "valid": False, - "registry_path": str(registry_path), - "entries": [], - "diagnostics": diagnostics, - } - - schema = str(payload.get("schema_version") or "") - if schema and schema != STORE_REGISTRY_SCHEMA: - diagnostics.append( - Diagnostic( - "warn", - "unknown_store_registry_schema", - "Store registry declares an unknown schema version.", - str(registry_path), - {"schema_version": schema}, - ).to_dict() - ) - - entries: list[dict[str, Any]] = [] - for item in payload.get("stores") or (): - if not isinstance(item, dict): - continue - store_path = str(item.get("path") or "").strip() - store_kind = str(item.get("store_kind") or "").strip() - store_id = str(item.get("store_id") or "").strip() - if not store_path or not store_kind or not store_id: - diagnostics.append( - Diagnostic( - "warn", - "incomplete_registry_entry", - "Registry entry must include store_id, path, and store_kind.", - store_id or store_path or "unknown", - ).to_dict() - ) - continue - entries.append(item) - return { - "schema_version": STORE_REGISTRY_SCHEMA, - "valid": not any(item.get("severity") == "error" for item in diagnostics), - "registry_path": str(registry_path), - "entries": entries, - "diagnostics": diagnostics, - } - - -def _store_id_for_path(path: Path, environ: Mapping[str, str] | None = None) -> str: - resolved = path.expanduser().resolve() - default_ops = default_memory_store_path(environ).expanduser().resolve() - if resolved == default_ops: - return DEFAULT_OPS_WARDEN_STORE_ID - return f"store:{stable_digest(str(resolved))}" - - -def _descriptor_from_path( - path: Path, - *, - store_kind: str, - source: str, - profile_id: str = "", - label: str = "", - store_id: str = "", - environ: Mapping[str, str] | None = None, -) -> MemoryStoreDescriptor: - resolved = path.expanduser().resolve() - return MemoryStoreDescriptor( - store_id=store_id or _store_id_for_path(resolved, environ), - path=resolved, - store_kind=store_kind, - profile_id=profile_id, - label=label, - source=source, - ) - - -def discover_memory_stores( - environ: Mapping[str, str] | None = None, - *, - registry_path: str | Path | None = None, - extra_paths: tuple[str | Path, ...] = (), -) -> tuple[list[MemoryStoreDescriptor], list[dict[str, Any]]]: - environ = environ or os.environ - diagnostics: list[dict[str, Any]] = [] - by_path: dict[str, MemoryStoreDescriptor] = {} - - def add_descriptor(descriptor: MemoryStoreDescriptor) -> None: - key = str(descriptor.path) - existing = by_path.get(key) - if existing is None: - by_path[key] = descriptor - return - if descriptor.source == "registry" and existing.source != "registry": - by_path[key] = descriptor - - default_ops = default_memory_store_path(environ) - kind = classify_store(default_ops) - if kind: - profile_id = OPS_WARDEN_PROFILE_ID if kind == STORE_KIND_OPS_WARDEN else "" - add_descriptor( - _descriptor_from_path( - default_ops, - store_kind=kind, - source="default", - profile_id=profile_id, - label="ops-warden coordination store", - store_id=DEFAULT_OPS_WARDEN_STORE_ID, - environ=environ, - ) - ) - - registry = load_store_registry(registry_path, environ=environ) - diagnostics.extend(registry.get("diagnostics", ())) - registry_file = Path(str(registry.get("registry_path") or "")).expanduser() - for entry in registry.get("entries", ()): - entry_path = Path(str(entry.get("path") or "")).expanduser() - if not entry_path.is_absolute() and registry_file.is_file(): - entry_path = (registry_file.parent / entry_path).resolve() - if not entry_path.is_dir(): - diagnostics.append( - Diagnostic( - "warn", - "registry_store_missing", - "Registry entry path does not exist.", - str(entry.get("store_id") or entry_path), - {"path": str(entry_path)}, - ).to_dict() - ) - continue - declared_kind = str(entry.get("store_kind") or "") - actual_kind = classify_store(entry_path) - if actual_kind and actual_kind != declared_kind: - diagnostics.append( - Diagnostic( - "warn", - "registry_store_kind_mismatch", - "Registry store_kind does not match on-disk classification.", - str(entry.get("store_id") or entry_path), - {"declared": declared_kind, "actual": actual_kind}, - ).to_dict() - ) - store_kind = actual_kind or declared_kind - if not store_kind: - continue - add_descriptor( - _descriptor_from_path( - entry_path, - store_kind=store_kind, - source="registry", - profile_id=str(entry.get("profile_id") or ""), - label=str(entry.get("label") or ""), - store_id=str(entry.get("store_id") or ""), - environ=environ, - ) - ) - - env_paths = str(environ.get("PHASE_MEMORY_STORE_PATHS") or "").strip() - path_candidates = [item.strip() for item in env_paths.split(":") if item.strip()] - path_candidates.extend(str(item) for item in extra_paths) - for candidate in path_candidates: - candidate_path = Path(candidate).expanduser() - if not candidate_path.is_dir(): - continue - store_kind = classify_store(candidate_path) - if not store_kind: - continue - add_descriptor( - _descriptor_from_path( - candidate_path, - store_kind=store_kind, - source="env", - environ=environ, - ) - ) - - stores = sorted(by_path.values(), key=lambda item: (item.store_kind, item.store_id)) - return stores, diagnostics - - -def resolve_store_reference( - reference: str, - stores: list[MemoryStoreDescriptor], - environ: Mapping[str, str] | None = None, -) -> MemoryStoreDescriptor | None: - ref = str(reference or "").strip() - if not ref: - return None - for store in stores: - if ref == store.store_id or ref == str(store.path): - return store - candidate = Path(ref).expanduser() - if candidate.is_dir(): - store_kind = classify_store(candidate) - if store_kind: - return _descriptor_from_path(candidate, store_kind=store_kind, source="explicit", environ=environ) - for store in stores: - if candidate.resolve() == store.path: - return store - return None - - -def compute_report_window( - *, - days: int, - window_end: datetime | None = None, -) -> tuple[datetime, datetime]: - if days < 1: - raise ValueError("days must be at least 1") - end = window_end or datetime.now(timezone.utc) - if end.tzinfo is None: - end = end.replace(tzinfo=timezone.utc) - else: - end = end.astimezone(timezone.utc) - start = end - timedelta(days=days) - return start, end - - -def _event_in_window(timestamp: str | None, *, window_start: datetime, window_end: datetime) -> bool | None: - parsed = parse_iso_datetime(timestamp) - if parsed is None: - return None - return window_start <= parsed <= window_end - - -def _read_jsonl(path: Path) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: - if not path.is_file(): - return [], [] - events: list[dict[str, Any]] = [] - diagnostics: list[dict[str, Any]] = [] - for line_number, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1): - if not raw.strip(): - continue - try: - data = json.loads(raw) - except json.JSONDecodeError as exc: - diagnostics.append( - Diagnostic( - "warn", - "malformed_jsonl_line", - "JSONL line is not valid JSON.", - f"{path}:line:{line_number}", - {"error": str(exc)}, - ).to_dict() - ) - continue - if isinstance(data, dict): - events.append(data) - return events, diagnostics - - -def _increment(counter: dict[str, int], key: str) -> None: - normalized = key or "unknown" - counter[normalized] = counter.get(normalized, 0) + 1 - - -def _top_counter(counter: dict[str, int], *, limit: int) -> dict[str, int]: - return dict(sorted(counter.items(), key=lambda item: (-item[1], item[0]))[:limit]) - - -def aggregate_ops_warden_activity( - store: MemoryStoreDescriptor, - *, - window_start: datetime, - window_end: datetime, -) -> dict[str, Any]: - events_path = store.path / "events.jsonl" - raw_events, line_diagnostics = _read_jsonl(events_path) - diagnostics = list(line_diagnostics) - by_session_kind: dict[str, int] = {} - by_command: dict[str, int] = {} - by_outcome: dict[str, int] = {} - by_route_id: dict[str, int] = {} - episode_timeline: list[dict[str, Any]] = [] - first_activity_at = "" - last_activity_at = "" - in_window = 0 - missing_timestamp = 0 - - for event in raw_events: - timestamp = str(event.get("recorded_at") or "") - in_range = _event_in_window(timestamp, window_start=window_start, window_end=window_end) - if in_range is None: - missing_timestamp += 1 - diagnostics.append( - Diagnostic( - "warn", - "episode_missing_timestamp", - "Episode lacks a parseable recorded_at timestamp.", - str(event.get("event_id") or "unknown"), - ).to_dict() - ) - continue - if not in_range: - continue - in_window += 1 - _increment(by_session_kind, str(event.get("session_kind") or "")) - _increment(by_command, str(event.get("command") or "")) - _increment(by_outcome, str(event.get("outcome") or "")) - _increment(by_route_id, str(event.get("route_id") or "")) - if not first_activity_at or timestamp < first_activity_at: - first_activity_at = timestamp - if not last_activity_at or timestamp > last_activity_at: - last_activity_at = timestamp - episode_timeline.append( - { - "event_id": str(event.get("event_id") or ""), - "recorded_at": timestamp, - "session_kind": str(event.get("session_kind") or ""), - "command": str(event.get("command") or ""), - "route_id": str(event.get("route_id") or ""), - "outcome": str(event.get("outcome") or ""), - } - ) - - episode_timeline.sort(key=lambda item: item.get("recorded_at", ""), reverse=True) - metadata_path = store.path / "metadata.json" - schema_version = "" - if metadata_path.is_file(): - try: - schema_version = str(json.loads(metadata_path.read_text(encoding="utf-8")).get("schema_version") or "") - except json.JSONDecodeError: - pass - - return { - "store_id": store.store_id, - "store_kind": store.store_kind, - "path": str(store.path), - "profile_id": store.profile_id or OPS_WARDEN_PROFILE_ID, - "schema_version": schema_version, - "episode_count": in_window, - "audit_event_count": 0, - "missing_timestamp_count": missing_timestamp, - "first_activity_at": first_activity_at, - "last_activity_at": last_activity_at, - "by_session_kind": dict(sorted(by_session_kind.items())), - "by_command": dict(sorted(by_command.items())), - "by_outcome": dict(sorted(by_outcome.items())), - "by_route_id": _top_counter(by_route_id, limit=TOP_ROUTE_IDS), - "by_operation": {}, - "episode_timeline": episode_timeline[:DETAIL_EPISODE_LIMIT], - "repair_diagnostics": [], - "diagnostics": diagnostics, - } - - -def aggregate_local_graph_activity( - store: MemoryStoreDescriptor, - *, - window_start: datetime, - window_end: datetime, -) -> dict[str, Any]: - events_path = store.path / "events.jsonl" - audit_path = store.path / "audit.jsonl" - raw_events, event_line_diagnostics = _read_jsonl(events_path) - raw_audit, audit_line_diagnostics = _read_jsonl(audit_path) - diagnostics = list(event_line_diagnostics) - diagnostics.extend(audit_line_diagnostics) - - by_session_kind: dict[str, int] = {} - by_command: dict[str, int] = {} - by_outcome: dict[str, int] = {} - by_operation: dict[str, int] = {} - episode_timeline: list[dict[str, Any]] = [] - first_activity_at = "" - last_activity_at = "" - episode_count = 0 - audit_count = 0 - missing_timestamp = 0 - - for event in raw_events: - timestamp = str(event.get("timestamp") or event.get("recorded_at") or "") - in_range = _event_in_window(timestamp, window_start=window_start, window_end=window_end) - if in_range is None: - missing_timestamp += 1 - diagnostics.append( - Diagnostic( - "warn", - "episode_missing_timestamp", - "Event lacks a parseable timestamp.", - str(event.get("event_id") or event.get("id") or "unknown"), - ).to_dict() - ) - continue - if not in_range: - continue - episode_count += 1 - kind = str(event.get("kind") or event.get("session_kind") or "") - _increment(by_session_kind, kind) - _increment(by_command, str(event.get("command") or kind)) - _increment(by_outcome, str(event.get("outcome") or event.get("state") or "")) - if not first_activity_at or timestamp < first_activity_at: - first_activity_at = timestamp - if not last_activity_at or timestamp > last_activity_at: - last_activity_at = timestamp - episode_timeline.append( - { - "event_id": str(event.get("event_id") or event.get("id") or ""), - "recorded_at": timestamp, - "session_kind": kind, - "command": str(event.get("command") or kind), - "route_id": "", - "outcome": str(event.get("outcome") or event.get("state") or ""), - } - ) - - for event in raw_audit: - timestamp = str(event.get("timestamp") or event.get("recorded_at") or "") - in_range = _event_in_window(timestamp, window_start=window_start, window_end=window_end) - if in_range is None: - missing_timestamp += 1 - diagnostics.append( - Diagnostic( - "warn", - "audit_missing_timestamp", - "Audit event lacks a parseable timestamp.", - str(event.get("operation_id") or event.get("operation") or "unknown"), - ).to_dict() - ) - continue - if not in_range: - continue - audit_count += 1 - _increment(by_operation, str(event.get("operation") or "")) - if not first_activity_at or timestamp < first_activity_at: - first_activity_at = timestamp - if not last_activity_at or timestamp > last_activity_at: - last_activity_at = timestamp - - episode_timeline.sort(key=lambda item: item.get("recorded_at", ""), reverse=True) - graph_store = FileBackedMemoryGraphStore(store.path) - repair = graph_store.repair_diagnostics() - repair_diagnostics = [ - item.to_dict() - for item in repair - if item.severity in {"error", "warn"} - ] - schema_version = "" - try: - schema_version = str(graph_store.metadata().get("schema_version") or "") - except (json.JSONDecodeError, OSError): - pass - - return { - "store_id": store.store_id, - "store_kind": store.store_kind, - "path": str(store.path), - "profile_id": store.profile_id, - "schema_version": schema_version, - "episode_count": episode_count, - "audit_event_count": audit_count, - "missing_timestamp_count": missing_timestamp, - "first_activity_at": first_activity_at, - "last_activity_at": last_activity_at, - "by_session_kind": dict(sorted(by_session_kind.items())), - "by_command": dict(sorted(by_command.items())), - "by_outcome": dict(sorted(by_outcome.items())), - "by_route_id": {}, - "by_operation": dict(sorted(by_operation.items())), - "episode_timeline": episode_timeline[:DETAIL_EPISODE_LIMIT], - "repair_diagnostics": repair_diagnostics, - "diagnostics": diagnostics, - } - - -def aggregate_store_activity( - store: MemoryStoreDescriptor, - *, - window_start: datetime, - window_end: datetime, -) -> dict[str, Any]: - if store.store_kind == STORE_KIND_OPS_WARDEN: - return aggregate_ops_warden_activity(store, window_start=window_start, window_end=window_end) - return aggregate_local_graph_activity(store, window_start=window_start, window_end=window_end) - - -def build_store_list( - environ: Mapping[str, str] | None = None, - *, - registry_path: str | Path | None = None, - extra_paths: tuple[str | Path, ...] = (), -) -> dict[str, Any]: - stores, diagnostics = discover_memory_stores(environ, registry_path=registry_path, extra_paths=extra_paths) - return { - "schema_version": STORE_LIST_SCHEMA, - "valid": not any(item.get("severity") == "error" for item in diagnostics), - "store_count": len(stores), - "stores": [ - { - "store_id": store.store_id, - "path": str(store.path), - "store_kind": store.store_kind, - "profile_id": store.profile_id, - "label": store.label, - "source": store.source, - } - for store in stores - ], - "diagnostics": diagnostics, - } - - -def build_federated_report( - *, - days: int = 7, - focus_store_id: str | None = None, - focus_store_path: str | Path | None = None, - environ: Mapping[str, str] | None = None, - registry_path: str | Path | None = None, - extra_paths: tuple[str | Path, ...] = (), - window_end: datetime | None = None, -) -> dict[str, Any]: - environ = environ or os.environ - stores, discovery_diagnostics = discover_memory_stores( - environ, - registry_path=registry_path, - extra_paths=extra_paths, - ) - diagnostics = list(discovery_diagnostics) - - focus_reference = str(focus_store_id or focus_store_path or "").strip() - focus_store: MemoryStoreDescriptor | None = None - if focus_reference: - focus_store = resolve_store_reference(focus_reference, stores, environ=environ) - if focus_store is None: - diagnostics.append( - Diagnostic( - "error", - "unknown_store_reference", - "Requested store was not discovered and could not be classified.", - focus_reference, - ).to_dict() - ) - return { - "schema_version": FEDERATED_REPORT_SCHEMA, - "valid": False, - "window_days": days, - "window_start": "", - "window_end": "", - "focus_store_id": focus_reference, - "store_count": 0, - "aggregate": {}, - "stores": [], - "store_detail": None, - "diagnostics": diagnostics, - } - stores = [focus_store] - - window_start, window_end_dt = compute_report_window(days=days, window_end=window_end) - summaries: list[dict[str, Any]] = [] - aggregate = { - "episode_count": 0, - "audit_event_count": 0, - "active_store_count": 0, - "by_outcome": {}, - "by_session_kind": {}, - "by_operation": {}, - } - by_outcome: dict[str, int] = {} - by_session_kind: dict[str, int] = {} - by_operation: dict[str, int] = {} - - store_detail: dict[str, Any] | None = None - for store in stores: - summary = aggregate_store_activity(store, window_start=window_start, window_end=window_end_dt) - diagnostics.extend(summary.get("diagnostics", ())) - public_summary = { - key: value - for key, value in summary.items() - if key not in {"episode_timeline", "repair_diagnostics", "diagnostics"} - } - summaries.append(public_summary) - aggregate["episode_count"] += int(summary.get("episode_count") or 0) - aggregate["audit_event_count"] += int(summary.get("audit_event_count") or 0) - if int(summary.get("episode_count") or 0) or int(summary.get("audit_event_count") or 0): - aggregate["active_store_count"] += 1 - for key, value in (summary.get("by_outcome") or {}).items(): - by_outcome[key] = by_outcome.get(key, 0) + int(value) - for key, value in (summary.get("by_session_kind") or {}).items(): - by_session_kind[key] = by_session_kind.get(key, 0) + int(value) - for key, value in (summary.get("by_operation") or {}).items(): - by_operation[key] = by_operation.get(key, 0) + int(value) - if focus_store is not None and store.store_id == focus_store.store_id: - store_detail = summary - - aggregate["by_outcome"] = dict(sorted(by_outcome.items())) - aggregate["by_session_kind"] = dict(sorted(by_session_kind.items())) - aggregate["by_operation"] = dict(sorted(by_operation.items())) - - return { - "schema_version": FEDERATED_REPORT_SCHEMA, - "valid": not any(item.get("severity") == "error" for item in diagnostics), - "window_days": days, - "window_start": window_start.replace(microsecond=0).isoformat(), - "window_end": window_end_dt.replace(microsecond=0).isoformat(), - "focus_store_id": focus_store.store_id if focus_store else "", - "store_count": len(summaries), - "aggregate": aggregate, - "stores": summaries, - "store_detail": store_detail, - "diagnostics": diagnostics, - "generated_at": utc_now_iso(), - } \ No newline at end of file diff --git a/src/phase_memory/ops_warden.py b/src/phase_memory/ops_warden.py deleted file mode 100644 index 99421a1..0000000 --- a/src/phase_memory/ops_warden.py +++ /dev/null @@ -1,497 +0,0 @@ -"""ops-warden cross-runtime memory contract and activation helpers.""" - -from __future__ import annotations - -import json -import os -import re -from dataclasses import dataclass -from pathlib import Path -from typing import Any, Mapping - -from .contracts import profile_from_markitect -from .external_adapters import ExternalAdapterPack, adapter_pack_manifest -from .models import Diagnostic -from .utils import stable_digest, utc_now_iso - -OPS_WARDEN_RUNTIME_SCHEMA = "phase_memory.ops_warden.runtime.v1" -OPS_WARDEN_SESSION_EVENT_SCHEMA = "phase_memory.ops_warden.session_event.v1" -OPS_WARDEN_ACTIVATION_SCHEMA = "phase_memory.ops_warden.activation.v1" -OPS_WARDEN_MEMORY_STATUS_SCHEMA = "phase_memory.ops_warden.memory_status.v1" -OPS_WARDEN_PROFILE_ID = "ops-warden-coordination" -OPS_WARDEN_ADAPTER_PACK_NAME = "ops-warden-coordination" - -SESSION_KIND_WORKER = "warden.worker" -SESSION_KIND_OPERATOR = "warden.operator" -SESSION_KIND_AGENT_PREFIX = "warden.agent." - -KNOWN_AGENT_IDS = ("claude", "codex", "grok") - -SECRET_FIELD_PATTERN = re.compile( - r"\b(" - r"token|secret|password|api[_ ]?key|private[_ ]?key|" - r"vault[_ ]?token|npm_auth_token|client[_ ]?secret|" - r"bearer\s+[a-z0-9._-]{8,}|" - r"https?://[^\s\"']+" - r")\b", - re.IGNORECASE, -) - -_OUTCOMES = frozenset({"resolved", "escalated", "skipped"}) - - -def default_memory_store_path(environ: Mapping[str, str] | None = None) -> Path: - environ = environ or os.environ - override = str(environ.get("WARDEN_MEMORY_STORE") or "").strip() - if override: - return Path(override).expanduser() - xdg_data = str(environ.get("XDG_DATA_HOME") or "").strip() - base = Path(xdg_data).expanduser() if xdg_data else Path.home() / ".local" / "share" - return base / "warden" / "memory" - - -def memory_enabled(environ: Mapping[str, str] | None = None) -> bool: - environ = environ or os.environ - return str(environ.get("WARDEN_MEMORY", "1")).strip().lower() not in {"0", "false", "no", "off"} - - -def session_kind_for_agent(agent_id: str | None = None) -> str: - agent_id = str(agent_id or "").strip().lower() - if agent_id: - return f"{SESSION_KIND_AGENT_PREFIX}{agent_id}" - return SESSION_KIND_OPERATOR - - -def resolve_session_kind(environ: Mapping[str, str] | None = None) -> str: - environ = environ or os.environ - explicit = str(environ.get("WARDEN_SESSION_KIND") or "").strip() - if explicit: - return explicit - agent_id = str(environ.get("WARDEN_AGENT_ID") or "").strip() - return session_kind_for_agent(agent_id or None) - - -def ops_warden_coordination_profile(path: str | Path | None = None) -> dict[str, Any]: - if path is None: - path = Path(__file__).resolve().parents[2] / "tests" / "fixtures" / "ops-warden-coordination-profile.json" - return json.loads(Path(path).read_text(encoding="utf-8")) - - -def validate_ops_warden_profile(data: dict[str, Any]) -> dict[str, Any]: - result = profile_from_markitect(data) - diagnostics = list(result.diagnostics) - policy = data.get("policy") if isinstance(data.get("policy"), dict) else {} - if policy.get("secrets_allowed") is not False: - diagnostics.append( - Diagnostic( - "error", - "ops_warden_profile_secrets_not_denied", - "Ops-warden coordination profile must deny secret storage.", - "policy.secrets_allowed", - ) - ) - activation = data.get("activation") if isinstance(data.get("activation"), dict) else {} - if int(activation.get("max_items") or 0) < 1: - diagnostics.append( - Diagnostic( - "error", - "ops_warden_profile_activation_budget_missing", - "Ops-warden coordination profile must declare activation budgets.", - "activation.max_items", - ) - ) - return { - "schema_version": OPS_WARDEN_RUNTIME_SCHEMA, - "valid": not any(item.severity == "error" for item in diagnostics), - "profile_id": str(data.get("id") or ""), - "diagnostics": [item.to_dict() for item in diagnostics], - } - - -def validate_memory_write(payload: Mapping[str, Any]) -> tuple[bool, tuple[Diagnostic, ...]]: - diagnostics: list[Diagnostic] = [] - serialized = json.dumps(dict(payload), sort_keys=True) - if SECRET_FIELD_PATTERN.search(serialized): - diagnostics.append( - Diagnostic( - "error", - "ops_warden_memory_secret_field_rejected", - "Memory write appears to contain secret values or raw endpoint URLs.", - "payload", - ) - ) - outcome = str(payload.get("outcome") or "") - if outcome and outcome not in _OUTCOMES: - diagnostics.append( - Diagnostic( - "error", - "ops_warden_memory_invalid_outcome", - "Session event outcome must be resolved, escalated, or skipped.", - "outcome", - {"actual": outcome}, - ) - ) - return not diagnostics, tuple(diagnostics) - - -def need_fingerprint(need: str) -> str: - return stable_digest(str(need or "").strip().lower()) - - -@dataclass(frozen=True) -class OpsWardenMemoryStore: - root: Path - - @classmethod - def open(cls, path: str | Path | None = None, environ: Mapping[str, str] | None = None) -> OpsWardenMemoryStore: - store = cls((Path(path) if path is not None else default_memory_store_path(environ)).expanduser()) - store.ensure_layout() - return store - - def ensure_layout(self) -> None: - self.root.mkdir(parents=True, exist_ok=True) - if not self.metadata_path.exists(): - self.metadata_path.write_text( - json.dumps( - { - "schema_version": OPS_WARDEN_RUNTIME_SCHEMA, - "profile_id": OPS_WARDEN_PROFILE_ID, - "created_at": utc_now_iso(), - }, - indent=2, - sort_keys=True, - ) - + "\n", - encoding="utf-8", - ) - - @property - def metadata_path(self) -> Path: - return self.root / "metadata.json" - - @property - def events_path(self) -> Path: - return self.root / "events.jsonl" - - def list_events(self) -> list[dict[str, Any]]: - if not self.events_path.exists(): - return [] - events: list[dict[str, Any]] = [] - for line in self.events_path.read_text(encoding="utf-8").splitlines(): - if not line.strip(): - continue - data = json.loads(line) - if isinstance(data, dict): - events.append(data) - return events - - def append_event(self, event: Mapping[str, Any]) -> dict[str, Any]: - payload = dict(event) - payload.setdefault("schema_version", OPS_WARDEN_SESSION_EVENT_SCHEMA) - payload.setdefault("recorded_at", utc_now_iso()) - ok, diagnostics = validate_memory_write(payload) - if not ok: - return { - "valid": False, - "diagnostics": [item.to_dict() for item in diagnostics], - } - event_id = str(payload.get("event_id") or f"ops-warden-event:{stable_digest(payload)}") - payload["event_id"] = event_id - with self.events_path.open("a", encoding="utf-8") as handle: - handle.write(json.dumps(payload, sort_keys=True) + "\n") - return {"valid": True, "event": payload, "diagnostics": []} - - def status(self) -> dict[str, Any]: - events = self.list_events() - counts: dict[str, int] = {} - last_activation = "" - for event in events: - kind = str(event.get("session_kind") or "unknown") - counts[kind] = counts.get(kind, 0) + 1 - if event.get("command") == "memory.activate": - last_activation = str(event.get("recorded_at") or last_activation) - return { - "schema_version": OPS_WARDEN_MEMORY_STATUS_SCHEMA, - "valid": True, - "store_path": str(self.root), - "profile_id": OPS_WARDEN_PROFILE_ID, - "episode_count": len(events), - "episode_counts_by_session_kind": dict(sorted(counts.items())), - "last_activation_at": last_activation, - "diagnostics": [], - } - - -def build_session_event( - *, - command: str, - session_kind: str, - outcome: str, - need: str = "", - route_id: str = "", - agent_id: str = "", - session_id: str = "", - diagnostic_codes: list[str] | None = None, - metadata: Mapping[str, Any] | None = None, -) -> dict[str, Any]: - return { - "schema_version": OPS_WARDEN_SESSION_EVENT_SCHEMA, - "session_kind": session_kind, - "agent_id": agent_id, - "session_id": session_id, - "command": command, - "need_fingerprint": need_fingerprint(need) if need else "", - "route_id": route_id, - "outcome": outcome, - "diagnostic_codes": list(diagnostic_codes or ()), - "metadata": dict(metadata or {}), - } - - -def record_session_event( - store: OpsWardenMemoryStore | Path | str, - event: Mapping[str, Any], -) -> dict[str, Any]: - memory = store if isinstance(store, OpsWardenMemoryStore) else OpsWardenMemoryStore.open(store) - return memory.append_event(event) - - -def stabilized_route_match( - events: list[dict[str, Any]], - *, - need: str = "", - need_fingerprint_value: str = "", - min_confirmations: int = 2, -) -> dict[str, Any] | None: - fingerprint = need_fingerprint_value or (need_fingerprint(need) if need else "") - if not fingerprint: - return None - matches = [ - event - for event in events - if event.get("need_fingerprint") == fingerprint - and event.get("outcome") == "resolved" - and event.get("route_id") - ] - if len(matches) < min_confirmations: - return None - route_id = str(matches[-1].get("route_id") or "") - return { - "need_fingerprint": fingerprint, - "route_id": route_id, - "confirmations": len(matches), - "source_event_ids": [str(item.get("event_id") or "") for item in matches[-min_confirmations:]], - } - - -def activate_ops_warden_memory( - store: OpsWardenMemoryStore | Path | str, - *, - session_kind: str, - need: str = "", - session_id: str = "", -) -> dict[str, Any]: - memory = store if isinstance(store, OpsWardenMemoryStore) else OpsWardenMemoryStore.open(store) - profile = ops_warden_coordination_profile() - profile_validation = validate_ops_warden_profile(profile) - events = memory.list_events() - fingerprint = need_fingerprint(need) if need else "" - stabilized = stabilized_route_match(events, need_fingerprint_value=fingerprint) if fingerprint else None - recent = list(reversed(events))[: int(profile.get("activation", {}).get("max_items") or 8)] - selected: list[dict[str, Any]] = [] - if stabilized: - selected.append( - { - "kind": "stabilized_route", - "route_id": stabilized["route_id"], - "need_fingerprint": stabilized["need_fingerprint"], - "confirmations": stabilized["confirmations"], - } - ) - for event in recent: - if len(selected) >= int(profile.get("activation", {}).get("max_items") or 8): - break - if fingerprint and event.get("need_fingerprint") != fingerprint and event.get("session_kind") != session_kind: - continue - selected.append( - { - "kind": "episode", - "event_id": event.get("event_id", ""), - "session_kind": event.get("session_kind", ""), - "command": event.get("command", ""), - "route_id": event.get("route_id", ""), - "outcome": event.get("outcome", ""), - "diagnostic_codes": list(event.get("diagnostic_codes") or ()), - } - ) - activation = { - "schema_version": OPS_WARDEN_ACTIVATION_SCHEMA, - "id": f"ops-warden-activation:{stable_digest([session_kind, fingerprint, selected])}", - "valid": profile_validation["valid"], - "session_kind": session_kind, - "session_id": session_id, - "profile_id": OPS_WARDEN_PROFILE_ID, - "need_fingerprint": fingerprint, - "stabilized_route": stabilized, - "selected_episodes": selected, - "episode_count": len(events), - "llm_calls_avoided": bool(stabilized), - "operator_guidance": { - "worker": "Activate before Brain.plan; record after execute.", - "agent_session": "Call warden memory activate at session start.", - "operator": "WARDEN_AGENT_ID selects warden.agent. session_kind.", - }, - "diagnostics": list(profile_validation.get("diagnostics", ())), - } - memory.append_event( - build_session_event( - command="memory.activate", - session_kind=session_kind, - outcome="resolved", - session_id=session_id, - metadata={"selected_count": len(selected), "llm_calls_avoided": bool(stabilized)}, - ) - ) - return activation - - -def ops_warden_evaluation_report(scenarios_data: dict[str, Any]) -> dict[str, Any]: - scenarios = list(scenarios_data.get("scenarios") or ()) - scenario_reports: list[dict[str, Any]] = [] - metrics = { - "scenario_count": len(scenarios), - "routing_repeat_accuracy": 0.0, - "cross_runtime_continuity": 0.0, - "llm_calls_avoided_count": 0.0, - } - for scenario in scenarios: - events = list(scenario.get("events") or ()) - episode_metrics = ops_warden_evaluation_metrics(events) - expect = dict(scenario.get("expect") or {}) - diagnostics: list[dict[str, Any]] = [] - if expect.get("stabilized") and not stabilized_route_match(events, need_fingerprint_value=str(events[0].get("need_fingerprint") or "")): - diagnostics.append( - Diagnostic( - "error", - "ops_warden_stabilized_route_missing", - "Scenario expected a stabilized route match.", - "events", - ).to_dict() - ) - if expect.get("continuity") and episode_metrics["cross_runtime_continuity"] < 1.0: - diagnostics.append( - Diagnostic( - "error", - "ops_warden_cross_runtime_continuity_missing", - "Scenario expected cross-runtime continuity.", - "events", - ).to_dict() - ) - if expect.get("llm_calls_avoided") and episode_metrics["llm_calls_avoided_count"] < 1.0: - diagnostics.append( - Diagnostic( - "error", - "ops_warden_llm_avoidance_missing", - "Scenario expected an avoided llm-connect call.", - "events", - ).to_dict() - ) - scenario_reports.append( - { - "id": scenario.get("id", ""), - "metrics": episode_metrics, - "diagnostics": diagnostics, - } - ) - for key in ("routing_repeat_accuracy", "cross_runtime_continuity", "llm_calls_avoided_count"): - if key in episode_metrics: - metrics[key] = max(float(metrics[key]), float(episode_metrics[key])) - return { - "schema_version": "phase_memory.ops_warden.evaluation_report.v1", - "valid": not any(item.get("severity") == "error" for report in scenario_reports for item in report.get("diagnostics", ())), - "metrics": metrics, - "scenarios": scenario_reports, - "diagnostics": [ - item - for report in scenario_reports - for item in report.get("diagnostics", ()) - if item.get("severity") == "error" - ], - } - - -def ops_warden_evaluation_metrics(events: list[dict[str, Any]]) -> dict[str, float]: - total = float(len(events)) - resolved = float(sum(1 for event in events if event.get("outcome") == "resolved")) - escalated = float(sum(1 for event in events if event.get("outcome") == "escalated")) - activations = float(sum(1 for event in events if event.get("command") == "memory.activate")) - llm_avoided = float(sum(1 for event in events if (event.get("metadata") or {}).get("llm_calls_avoided"))) - agent_sessions = float( - sum(1 for event in events if str(event.get("session_kind") or "").startswith(SESSION_KIND_AGENT_PREFIX)) - ) - worker_sessions = float(sum(1 for event in events if event.get("session_kind") == SESSION_KIND_WORKER)) - continuity = 1.0 if agent_sessions > 0 and worker_sessions > 0 else 0.0 - return { - "episode_count": total, - "resolved_episode_count": resolved, - "escalated_episode_count": escalated, - "activation_count": activations, - "llm_calls_avoided_count": llm_avoided, - "cross_runtime_continuity": continuity, - "routing_repeat_accuracy": (resolved / total) if total else 0.0, - } - - -def ops_warden_adapter_pack() -> ExternalAdapterPack: - from .adapters import InMemoryMemoryEventLog, InMemoryMemoryGraphStore - from .external_adapters import ( - ADAPTER_CONFORMANCE_HELPERS, - FakeExternalPolicyGateway, - FakeKontextualRuntimeRegistry, - FakeMarkitectPackageCompiler, - FakeTelemetryAuditSink, - LiveShapedPermissionSemanticIndex, - ) - - class OpsWardenEventLog(InMemoryMemoryEventLog): - """Event log shape for ops-warden coordination episodes.""" - - class OpsWardenGraphStore(InMemoryMemoryGraphStore): - """Graph store shape for stabilized routing nodes.""" - - capability_requirements = { - "graph_store": ("ops-warden.coordination.graph",), - "event_log": ("ops-warden.coordination.events",), - "policy_gateway": ("ops-warden.coordination.policy",), - "audit_sink": ("ops-warden.coordination.audit",), - "package_compiler": ("ops-warden.coordination.activation",), - "semantic_index": ("ops-warden.coordination.retrieval",), - "runtime_registry": ("ops-warden.coordination.registry",), - } - ownership_boundaries = { - "graph_store": "phase-memory owns stabilized routing nodes; ops-warden owns episode ingestion", - "event_log": "phase-memory owns coordination episodes; ops-warden owns CLI/worker writers", - "policy_gateway": "ops-warden charter is rigid import-only; phase-memory never relaxes guardrails", - "audit_sink": "phase-memory owns redacted audit schema; ops-warden owns warden activity correlation", - "package_compiler": "phase-memory owns activation packages for worker and agent sessions", - "semantic_index": "phase-memory owns need-fingerprint retrieval for routing memory", - "runtime_registry": "ops-warden owns session_kind routing; phase-memory owns runtime envelopes", - } - adapters = { - "graph_store": OpsWardenGraphStore(), - "event_log": OpsWardenEventLog(), - "policy_gateway": FakeExternalPolicyGateway(), - "audit_sink": FakeTelemetryAuditSink(), - "package_compiler": FakeMarkitectPackageCompiler(), - "semantic_index": LiveShapedPermissionSemanticIndex(), - "runtime_registry": FakeKontextualRuntimeRegistry(), - } - return ExternalAdapterPack( - name=OPS_WARDEN_ADAPTER_PACK_NAME, - adapters=adapters, - capabilities=tuple(sorted({capability for values in capability_requirements.values() for capability in values})), - ownership_boundaries=ownership_boundaries, - required_conformance=dict(ADAPTER_CONFORMANCE_HELPERS), - capability_requirements=capability_requirements, - metadata={"profile_id": OPS_WARDEN_PROFILE_ID, "runtime_schema": OPS_WARDEN_RUNTIME_SCHEMA}, - ) \ No newline at end of file diff --git a/tests/fixtures/management/federated-report-aggregate.json b/tests/fixtures/management/federated-report-aggregate.json deleted file mode 100644 index 776e6a6..0000000 --- a/tests/fixtures/management/federated-report-aggregate.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "aggregate": { - "active_store_count": 2, - "audit_event_count": 1, - "by_operation": { - "audit.query": 1 - }, - "by_outcome": { - "resolved": 1, - "skipped": 1, - "unknown": 1 - }, - "by_session_kind": { - "lifecycle.apply": 1, - "warden.agent.codex": 1, - "warden.operator": 1 - }, - "episode_count": 3 - }, - "store_count": 2, - "stores": [ - { - "audit_event_count": 1, - "episode_count": 1, - "store_kind": "local_graph" - }, - { - "audit_event_count": 0, - "episode_count": 2, - "store_kind": "ops_warden_coordination" - } - ] -} \ No newline at end of file diff --git a/tests/fixtures/management/local-store/audit.jsonl b/tests/fixtures/management/local-store/audit.jsonl deleted file mode 100644 index 15f5783..0000000 --- a/tests/fixtures/management/local-store/audit.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"operation":"package.compile","operation_id":"op:audit-old","timestamp":"2026-06-02T08:00:00+00:00"} -{"operation":"audit.query","operation_id":"op:audit-in-window","timestamp":"2026-07-02T10:00:00+00:00"} \ No newline at end of file diff --git a/tests/fixtures/management/local-store/events.jsonl b/tests/fixtures/management/local-store/events.jsonl deleted file mode 100644 index 1fa5351..0000000 --- a/tests/fixtures/management/local-store/events.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"id":"event:old","kind":"path.created","schema_version":"markitect.memory.event.v1","timestamp":"2026-06-01T08:00:00+00:00"} -{"id":"event:in-window","kind":"lifecycle.apply","schema_version":"markitect.memory.event.v1","timestamp":"2026-07-02T09:00:00+00:00"} \ No newline at end of file diff --git a/tests/fixtures/management/local-store/phase-memory.json b/tests/fixtures/management/local-store/phase-memory.json deleted file mode 100644 index 0527576..0000000 --- a/tests/fixtures/management/local-store/phase-memory.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "schema_version": "phase_memory.local_store.v1" -} \ No newline at end of file diff --git a/tests/fixtures/management/ops-warden-store/events.jsonl b/tests/fixtures/management/ops-warden-store/events.jsonl deleted file mode 100644 index c6a7643..0000000 --- a/tests/fixtures/management/ops-warden-store/events.jsonl +++ /dev/null @@ -1,4 +0,0 @@ -{"agent_id":"grok","command":"route find","diagnostic_codes":[],"event_id":"ops-warden-event:old","need_fingerprint":"abc123","outcome":"resolved","recorded_at":"2026-06-01T10:00:00+00:00","route_id":"openrouter-llm-connect","schema_version":"phase_memory.ops_warden.session_event.v1","session_kind":"warden.agent.grok"} -{"agent_id":"","command":"route find","diagnostic_codes":[],"event_id":"ops-warden-event:in-window-1","need_fingerprint":"def456","outcome":"resolved","recorded_at":"2026-07-01T10:00:00+00:00","route_id":"issue-core-ingestion-api-key","schema_version":"phase_memory.ops_warden.session_event.v1","session_kind":"warden.operator"} -{"agent_id":"codex","command":"access","diagnostic_codes":[],"event_id":"ops-warden-event:in-window-2","need_fingerprint":"ghi789","outcome":"skipped","recorded_at":"2026-07-02T11:00:00+00:00","route_id":"openrouter-llm-connect","schema_version":"phase_memory.ops_warden.session_event.v1","session_kind":"warden.agent.codex"} -{"agent_id":"","command":"memory.activate","diagnostic_codes":[],"event_id":"ops-warden-event:no-ts","need_fingerprint":"","outcome":"resolved","route_id":"","schema_version":"phase_memory.ops_warden.session_event.v1","session_kind":"warden.operator"} \ No newline at end of file diff --git a/tests/fixtures/management/ops-warden-store/metadata.json b/tests/fixtures/management/ops-warden-store/metadata.json deleted file mode 100644 index 8efcf5d..0000000 --- a/tests/fixtures/management/ops-warden-store/metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "created_at": "2026-07-01T10:00:00+00:00", - "profile_id": "ops-warden-coordination", - "schema_version": "phase_memory.ops_warden.runtime.v1" -} \ No newline at end of file diff --git a/tests/fixtures/management/registry.json b/tests/fixtures/management/registry.json deleted file mode 100644 index 5ebfebd..0000000 --- a/tests/fixtures/management/registry.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "schema_version": "phase_memory.management.store_registry.v1", - "stores": [ - { - "store_id": "fixture-local-graph", - "path": "local-store", - "store_kind": "local_graph", - "profile_id": "local-dev", - "label": "Fixture local graph store", - "registered_at": "2026-07-01T10:00:00+00:00" - } - ] -} \ No newline at end of file diff --git a/tests/fixtures/ops-warden-coordination-profile.json b/tests/fixtures/ops-warden-coordination-profile.json deleted file mode 100644 index 310e854..0000000 --- a/tests/fixtures/ops-warden-coordination-profile.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "schema_version": "markitect.memory.profile.v1", - "id": "ops-warden-coordination", - "title": "Ops-Warden Coordination Memory", - "intent": "Retain routing, coordination, escalation, and pilot-feedback episodes across worker, agent session, and operator CLI runtimes.", - "memory_kinds": ["reasoning", "conversation", "knowledge", "package"], - "stores": { - "reasoning": "local-graph-store", - "conversation": "local-event-log", - "knowledge": "local-graph-store", - "package": "markitect-context-package" - }, - "limits": { - "reasoning": {"max_nodes": 200}, - "conversation": {"max_nodes": 100}, - "package": {"max_items": 8} - }, - "retention": { - "conversation": {"stale_after_days": 14, "delete_after_days": 90} - }, - "refresh": { - "trigger": "routing-catalog-or-playbook-change" - }, - "compaction": { - "strategy": "summarize-coordination-thread-after-review" - }, - "activation": { - "max_items": 8, - "max_tokens": 2000 - }, - "policy": { - "required_labels": ["coordination-local"], - "durable_writes": "review-gated", - "secrets_allowed": false - }, - "observability": { - "emit_events": true - }, - "failure": { - "missing_runtime_store": "degrade-to-dry-run" - }, - "metadata": { - "rigid_import_only": ["charter", "action_allowlist"], - "runtime_modes": ["warden.worker", "warden.operator", "warden.agent.*"] - } -} \ No newline at end of file diff --git a/tests/fixtures/ops-warden-evaluation-scenarios.json b/tests/fixtures/ops-warden-evaluation-scenarios.json deleted file mode 100644 index 2f56b38..0000000 --- a/tests/fixtures/ops-warden-evaluation-scenarios.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "schema_version": "phase_memory.evaluation.scenarios.v1", - "scenarios": [ - { - "id": "ops-warden-routing-repeat", - "events": [ - {"session_kind": "warden.agent.grok", "command": "route find", "need_fingerprint": "abc123", "route_id": "openbao-api-key", "outcome": "resolved"}, - {"session_kind": "warden.agent.grok", "command": "route find", "need_fingerprint": "abc123", "route_id": "openbao-api-key", "outcome": "resolved"}, - {"session_kind": "warden.worker", "command": "worker run", "need_fingerprint": "abc123", "route_id": "openbao-api-key", "outcome": "resolved"} - ], - "expect": {"stabilized": true, "continuity": true} - }, - { - "id": "ops-warden-cross-runtime-continuity", - "events": [ - {"session_kind": "warden.agent.claude", "command": "route find", "outcome": "resolved"}, - {"session_kind": "warden.worker", "command": "worker run", "outcome": "escalated"} - ], - "expect": {"continuity": true} - }, - { - "id": "ops-warden-llm-avoidance", - "events": [ - {"session_kind": "warden.operator", "command": "route find", "need_fingerprint": "def456", "route_id": "ops-bridge-tunnel", "outcome": "resolved"}, - {"session_kind": "warden.operator", "command": "route find", "need_fingerprint": "def456", "route_id": "ops-bridge-tunnel", "outcome": "resolved"}, - {"session_kind": "warden.worker", "command": "memory.activate", "outcome": "resolved", "metadata": {"llm_calls_avoided": true}} - ], - "expect": {"llm_calls_avoided": true} - } - ] -} \ No newline at end of file diff --git a/tests/fixtures/public-api-snapshot.json b/tests/fixtures/public-api-snapshot.json index 1f9d412..92f3f80 100644 --- a/tests/fixtures/public-api-snapshot.json +++ b/tests/fixtures/public-api-snapshot.json @@ -17,7 +17,6 @@ "EVALUATION_TREND_REGRESSION_GATE_SCHEMA", "EVALUATION_TREND_SCHEMA", "ExternalAdapterPack", - "FEDERATED_REPORT_SCHEMA", "FakeExternalEventLog", "FakeExternalGraphStore", "FakeExternalPolicyGateway", @@ -25,7 +24,6 @@ "FakeKontextualRuntimeRegistry", "FakeMarkitectPackageCompiler", "FakeTelemetryAuditSink", - "KNOWN_AGENT_IDS", "LIVE_PILOT_REPORT_SCHEMA", "LifecycleAction", "LifecycleActionKind", @@ -54,13 +52,6 @@ "MemoryPath", "MemoryPathState", "MemoryPhase", - "OPS_WARDEN_ACTIVATION_SCHEMA", - "OPS_WARDEN_ADAPTER_PACK_NAME", - "OPS_WARDEN_MEMORY_STATUS_SCHEMA", - "OPS_WARDEN_PROFILE_ID", - "OPS_WARDEN_RUNTIME_SCHEMA", - "OPS_WARDEN_SESSION_EVENT_SCHEMA", - "OpsWardenMemoryStore", "OptionalMarkitectValidator", "PHASE_MEMORY_CREDENTIAL_NEEDS", "POLICY_OPERATION_POINTS", @@ -77,8 +68,6 @@ "RuntimeConfig", "SERVICE_APP_SCHEMA", "SERVICE_BINDING_SCHEMA", - "STORE_LIST_SCHEMA", - "STORE_REGISTRY_SCHEMA", "ServiceAppConfig", "ServiceBinding", "ServiceResponse", @@ -88,15 +77,10 @@ "WARDEN_ROUTE_FIND_QUERY", "WordCountTokenEstimator", "abandon_path", - "activate_ops_warden_memory", "activation_quality_report", "adapter_pack_manifest", "branch_path", - "build_federated_report", "build_service_binding", - "build_session_event", - "build_store_list", - "classify_store", "compact_path", "create_path", "create_wsgi_app", @@ -104,8 +88,6 @@ "credentialed_drill_config_from_env", "credentialed_operator_report", "credentialed_telemetry_retention_drill", - "default_memory_store_path", - "discover_memory_stores", "evaluation_threshold_report", "evaluation_trend_artifact", "evaluation_trend_history", @@ -120,14 +102,9 @@ "make_review_record", "managed_deployment_manifest", "managed_deployment_pilot_report", - "memory_enabled", "merge_path", "missing_credentialed_adapter_env", "operator_troubleshooting_matrix", - "ops_warden_adapter_pack", - "ops_warden_coordination_profile", - "ops_warden_evaluation_metrics", - "ops_warden_evaluation_report", "package_request_from_selection", "package_response_envelope", "path_event", @@ -142,23 +119,17 @@ "plan_retention", "plan_retention_from_rules", "profile_from_markitect", - "record_session_event", "resolve_credentialed_environ", "resolve_runtime_adapters", - "resolve_session_kind", "retrieve_graph_neighborhood", "runtime_from_config", "select_event_path", "service_app_metadata", "service_binding_from_config", "service_contracts", - "session_kind_for_agent", - "stabilized_route_match", "validate_adapter_pack_manifest", "validate_managed_deployment_manifest", - "validate_memory_write", "validate_operator_troubleshooting_matrix", - "validate_ops_warden_profile", "warden_access_advisory", "warden_cli_available", "warden_credential_routing_advisory", diff --git a/tests/test_management_cli.py b/tests/test_management_cli.py deleted file mode 100644 index 1f51323..0000000 --- a/tests/test_management_cli.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Tests for federated store management and reporting.""" - -from __future__ import annotations - -import json -from datetime import datetime, timezone -from pathlib import Path - -import pytest - -from phase_memory.cli import main -from phase_memory.management import ( - FEDERATED_REPORT_SCHEMA, - STORE_KIND_LOCAL_GRAPH, - STORE_KIND_OPS_WARDEN, - build_federated_report, - build_store_list, - classify_store, - discover_memory_stores, - resolve_store_reference, -) - -FIXTURES = Path(__file__).resolve().parent / "fixtures" / "management" -WINDOW_END = datetime(2026, 7, 3, 12, 0, tzinfo=timezone.utc) - - -def _fixture_environ(tmp_path: Path) -> dict[str, str]: - registry = FIXTURES / "registry.json" - ops_store = FIXTURES / "ops-warden-store" - local_store = FIXTURES / "local-store" - return { - "WARDEN_MEMORY_STORE": str(ops_store), - "PHASE_MEMORY_REGISTRY": str(registry), - "PHASE_MEMORY_STORE_PATHS": str(local_store), - "HOME": str(tmp_path), - "XDG_DATA_HOME": str(tmp_path / "xdg"), - } - - -def test_classify_store_kinds() -> None: - assert classify_store(FIXTURES / "ops-warden-store") == STORE_KIND_OPS_WARDEN - assert classify_store(FIXTURES / "local-store") == STORE_KIND_LOCAL_GRAPH - assert classify_store(FIXTURES / "registry.json") is None - - -def test_discover_memory_stores_deduplicates_and_merges_registry(tmp_path: Path) -> None: - environ = _fixture_environ(tmp_path) - stores, diagnostics = discover_memory_stores(environ) - assert not any(item.get("severity") == "error" for item in diagnostics) - store_ids = {store.store_id for store in stores} - assert "fixture-local-graph" in store_ids - assert len({str(store.path) for store in stores}) == len(stores) - - -def test_build_federated_report_aggregate_and_detail(tmp_path: Path) -> None: - environ = _fixture_environ(tmp_path) - report = build_federated_report(days=7, environ=environ, window_end=WINDOW_END) - assert report["schema_version"] == FEDERATED_REPORT_SCHEMA - assert report["valid"] is True - assert report["store_count"] == 2 - aggregate = report["aggregate"] - assert aggregate["episode_count"] == 3 - assert aggregate["audit_event_count"] == 1 - assert aggregate["active_store_count"] == 2 - assert aggregate["by_outcome"]["resolved"] == 1 - assert aggregate["by_outcome"]["skipped"] == 1 - assert aggregate["by_session_kind"]["warden.operator"] == 1 - assert aggregate["by_operation"]["audit.query"] == 1 - - detail = build_federated_report( - days=7, - focus_store_id="fixture-local-graph", - environ=environ, - window_end=WINDOW_END, - ) - assert detail["focus_store_id"] == "fixture-local-graph" - assert detail["store_count"] == 1 - assert detail["store_detail"]["episode_count"] == 1 - assert detail["store_detail"]["audit_event_count"] == 1 - - -def test_resolve_store_reference_by_id_and_path(tmp_path: Path) -> None: - environ = _fixture_environ(tmp_path) - stores, _ = discover_memory_stores(environ) - by_id = resolve_store_reference("fixture-local-graph", stores, environ=environ) - assert by_id is not None - assert by_id.store_kind == STORE_KIND_LOCAL_GRAPH - by_path = resolve_store_reference(str(FIXTURES / "ops-warden-store"), stores, environ=environ) - assert by_path is not None - assert by_path.store_kind == STORE_KIND_OPS_WARDEN - - -def test_federated_report_aggregate_fixture(tmp_path: Path) -> None: - environ = _fixture_environ(tmp_path) - report = build_federated_report(days=7, environ=environ, window_end=WINDOW_END) - payload = { - "aggregate": report["aggregate"], - "store_count": report["store_count"], - "stores": [ - {key: store[key] for key in ("store_id", "store_kind", "episode_count", "audit_event_count")} - for store in report["stores"] - ], - } - expected = json.loads((FIXTURES / "federated-report-aggregate.json").read_text(encoding="utf-8")) - assert payload["aggregate"] == expected["aggregate"] - assert payload["store_count"] == expected["store_count"] - actual_stores = sorted(payload["stores"], key=lambda item: item["store_kind"]) - expected_stores = sorted(expected["stores"], key=lambda item: item["store_kind"]) - for actual, item in zip(actual_stores, expected_stores, strict=True): - assert actual["store_kind"] == item["store_kind"] - assert actual["episode_count"] == item["episode_count"] - assert actual["audit_event_count"] == item["audit_event_count"] - - -def test_cli_stores_list_and_report(tmp_path: Path, capsys, monkeypatch: pytest.MonkeyPatch) -> None: - environ = _fixture_environ(tmp_path) - monkeypatch.setenv("WARDEN_MEMORY_STORE", environ["WARDEN_MEMORY_STORE"]) - monkeypatch.setenv("PHASE_MEMORY_REGISTRY", environ["PHASE_MEMORY_REGISTRY"]) - monkeypatch.setenv("PHASE_MEMORY_STORE_PATHS", environ["PHASE_MEMORY_STORE_PATHS"]) - assert main(["stores", "list", "--format", "summary"]) == 0 - listed = capsys.readouterr().out - assert "Fixture local graph store" in listed - - assert main(["report", "--days", "7", "--format", "summary"]) == 0 - reported = capsys.readouterr().out - assert "episodes=" in reported - assert "active_stores=" in reported - - -def test_build_store_list_from_registry_only(tmp_path: Path) -> None: - environ = { - "WARDEN_MEMORY_STORE": str(tmp_path / "missing-ops"), - "PHASE_MEMORY_REGISTRY": str(FIXTURES / "registry.json"), - "PHASE_MEMORY_STORE_PATHS": str(FIXTURES / "local-store"), - } - listing = build_store_list(environ=environ) - assert listing["valid"] is True - assert listing["store_count"] == 1 - assert listing["stores"][0]["store_id"] == "fixture-local-graph" \ No newline at end of file diff --git a/tests/test_ops_warden_memory.py b/tests/test_ops_warden_memory.py deleted file mode 100644 index b7218c7..0000000 --- a/tests/test_ops_warden_memory.py +++ /dev/null @@ -1,150 +0,0 @@ -import json -from pathlib import Path - -from phase_memory.ops_warden import ( - OPS_WARDEN_ACTIVATION_SCHEMA, - OPS_WARDEN_MEMORY_STATUS_SCHEMA, - OPS_WARDEN_PROFILE_ID, - OPS_WARDEN_RUNTIME_SCHEMA, - OPS_WARDEN_SESSION_EVENT_SCHEMA, - activate_ops_warden_memory, - build_session_event, - memory_enabled, - ops_warden_adapter_pack, - ops_warden_coordination_profile, - ops_warden_evaluation_metrics, - record_session_event, - resolve_session_kind, - session_kind_for_agent, - stabilized_route_match, - validate_memory_write, - validate_ops_warden_profile, -) -from phase_memory.external_adapters import validate_adapter_pack_manifest - -FIXTURES = Path(__file__).parent / "fixtures" - - -def test_ops_warden_profile_validates_with_activation_budget() -> None: - profile = ops_warden_coordination_profile(FIXTURES / "ops-warden-coordination-profile.json") - validation = validate_ops_warden_profile(profile) - - assert profile["id"] == OPS_WARDEN_PROFILE_ID - assert profile["policy"]["secrets_allowed"] is False - assert validation["valid"] is True - - -def test_validate_memory_write_rejects_secret_like_payload() -> None: - ok, diagnostics = validate_memory_write( - { - "command": "access", - "outcome": "resolved", - "metadata": {"token": "super-secret-token"}, - } - ) - - assert ok is False - assert diagnostics[0].code == "ops_warden_memory_secret_field_rejected" - - -def test_session_kind_supports_known_and_future_agents() -> None: - assert session_kind_for_agent("grok") == "warden.agent.grok" - assert session_kind_for_agent(None) == "warden.operator" - assert resolve_session_kind({"WARDEN_AGENT_ID": "claude"}) == "warden.agent.claude" - assert resolve_session_kind({"WARDEN_SESSION_KIND": "warden.worker"}) == "warden.worker" - - -def test_record_and_activate_ops_warden_memory_round_trip(tmp_path) -> None: - store = tmp_path / "memory" - record_session_event( - store, - build_session_event( - command="route find", - session_kind="warden.agent.grok", - outcome="resolved", - need="openrouter api key", - route_id="openrouter-llm-connect", - agent_id="grok", - session_id="sess-1", - ), - ) - record_session_event( - store, - build_session_event( - command="route find", - session_kind="warden.agent.grok", - outcome="resolved", - need="openrouter api key", - route_id="openrouter-llm-connect", - agent_id="grok", - session_id="sess-1", - ), - ) - - activation = activate_ops_warden_memory( - store, - session_kind="warden.worker", - need="openrouter api key", - session_id="worker-1", - ) - - assert activation["schema_version"] == OPS_WARDEN_ACTIVATION_SCHEMA - assert activation["stabilized_route"]["route_id"] == "openrouter-llm-connect" - assert activation["llm_calls_avoided"] is True - - -def test_cross_runtime_continuity_is_visible_to_worker_activation(tmp_path) -> None: - store = tmp_path / "memory" - record_session_event( - store, - build_session_event( - command="route find", - session_kind="warden.agent.codex", - outcome="resolved", - need="npm token", - route_id="openbao-api-key", - agent_id="codex", - ), - ) - activation = activate_ops_warden_memory(store, session_kind="warden.worker", need="npm token") - - kinds = {item.get("session_kind") for item in activation["selected_episodes"] if item.get("kind") == "episode"} - assert "warden.agent.codex" in kinds - - -def test_ops_warden_evaluation_metrics_and_adapter_pack() -> None: - scenarios = json.loads((FIXTURES / "ops-warden-evaluation-scenarios.json").read_text(encoding="utf-8")) - events = scenarios["scenarios"][0]["events"] - metrics = ops_warden_evaluation_metrics(events) - pack = ops_warden_adapter_pack() - diagnostics = validate_adapter_pack_manifest(pack) - - assert metrics["cross_runtime_continuity"] == 1.0 - assert metrics["routing_repeat_accuracy"] > 0 - assert not any(item.severity == "error" for item in diagnostics) - assert pack.name == "ops-warden-coordination" - - -def test_stabilized_route_match_requires_confirmations() -> None: - events = [ - {"need_fingerprint": "abc", "route_id": "openbao-api-key", "outcome": "resolved"}, - {"need_fingerprint": "abc", "route_id": "openbao-api-key", "outcome": "resolved"}, - ] - - assert stabilized_route_match(events, need_fingerprint_value="abc")["route_id"] == "openbao-api-key" - assert stabilized_route_match(events[:1], need_fingerprint_value="abc") is None - - -def test_memory_enabled_honors_opt_out() -> None: - assert memory_enabled({"WARDEN_MEMORY": "1"}) is True - assert memory_enabled({"WARDEN_MEMORY": "0"}) is False - - -def test_ops_warden_evaluation_report_passes_fixture_scenarios() -> None: - from phase_memory.ops_warden import ops_warden_evaluation_report - - scenarios = json.loads((FIXTURES / "ops-warden-evaluation-scenarios.json").read_text(encoding="utf-8")) - report = ops_warden_evaluation_report(scenarios) - - assert report["valid"] is True - assert report["metrics"]["scenario_count"] == 3 \ No newline at end of file diff --git a/workplans/PMEM-WP-0016-ops-warden-cross-runtime-memory.md b/workplans/PMEM-WP-0016-ops-warden-cross-runtime-memory.md deleted file mode 100644 index 14be485..0000000 --- a/workplans/PMEM-WP-0016-ops-warden-cross-runtime-memory.md +++ /dev/null @@ -1,236 +0,0 @@ ---- -id: PMEM-WP-0016 -type: workplan -title: "Ops-Warden Cross-Runtime Memory Profile And Contracts" -domain: communication -repo: phase-memory -status: finished -owner: codex -topic_slug: phase-memory -created: "2026-07-02" -updated: "2026-07-02" -state_hub_workstream_id: "0f817f83-bb43-4e60-b3a7-b7cca0e9e6ed" ---- - -# PMEM-WP-0016: Ops-Warden Cross-Runtime Memory Profile And Contracts - -## Goal - -Define the phase-memory profile, event schemas, activation packs, and evaluation -gates that let **ops-warden learn from experience** across every runtime surface: - -- **Direct ops-warden use** — `warden worker` with llm-connect / OpenRouter - inference, scheduled ticks, and operator CLI (`route`, `access`, `sign`). -- **Coding agent sessions** — Claude Code, Codex, Grok, and future agents that - invoke warden commands during an interactive session. - -Memory must be **present** (discoverable, activatable, writable) in all three -modes without duplicating stores or leaking secrets. - -## Current Evidence - -`PMEM-WP-0015` added credential-safe pilot artifacts, ops-warden routing -advisories, evaluation trend regression gates, and redacted operator reports. -`WARDEN-WP-0020` shipped `warden worker` with `RuleBrain` and `LlmConnectBrain`, -fixed guardrails, and metadata-only audit via `warden activity`. - -The gap: no shared memory substrate connects worker ticks, agent sessions, and -operator CLI usage. Each invocation starts cold. - -## Non-Goals - -- Store secret values, tokens, endpoint URLs, or prompt bodies with credentials. -- Override ops-warden's fixed charter, action allowlist, or no-secret invariant - through learned memory. -- Own llm-connect, OpenRouter configuration, or ops-warden worker scheduling. -- Replace State Hub as the coordination transport. - -## Cross-Runtime Model - -```mermaid -flowchart LR - subgraph surfaces [Runtime surfaces] - Worker[warden worker tick] - Agent[Agent session
claude / codex / grok] - Operator[Operator CLI] - end - - subgraph contract [Shared contract] - Store[(warden memory store)] - Profile[ops-warden profile] - Activate[activation plan] - end - - Worker --> Store - Agent --> Store - Operator --> Store - Store --> Profile - Profile --> Activate -``` - -| Runtime mode | `session_kind` | Typical trigger | -| --- | --- | --- | -| `worker` | `warden.worker` | Scheduled tick, `warden worker run` | -| `agent_session` | `warden.agent.` | Coding agent invokes `warden route` / `warden access` | -| `operator` | `warden.operator` | Human shell without agent wrapper | - -All modes read/write the **same canonical store** (path owned by ops-warden, -schema owned by phase-memory). Modes differ only in `session_kind` metadata and -activation budget — not in storage location. - -## T01 - Define ops-warden memory profile - -```task -id: PMEM-WP-0016-T01 -status: done -priority: high -state_hub_task_id: "53dbc3f0-d2cb-4e0a-b558-64166ab976f3" -``` - -Author the `ops-warden-coordination` memory profile covering routing, -coordination, escalation, and pilot-feedback memory kinds. - -Acceptance: - -- Profile declares memory kinds, retention, compaction, activation budgets, and - policy (`secrets_allowed: false`, `durable_writes: review-gated`). -- Rigid memory kinds are documented as **import-only** (charter, allowlist) and - never promoted from learned episodes. -- Profile validates through existing `profile_from_markitect` / runtime planning - paths with deterministic diagnostics. -- Fixture lives at `tests/fixtures/ops-warden-coordination-profile.json`. - -## T02 - Cross-runtime memory contract - -```task -id: PMEM-WP-0016-T02 -status: done -priority: high -state_hub_task_id: "6933918e-3ae0-4e0b-aade-1fbcf88ef0a1" -``` - -Define the `phase_memory.ops_warden.runtime.v1` contract: canonical store path -resolution, `session_kind` enum, read/write envelopes, and redaction rules. - -Acceptance: - -- Contract specifies canonical store default (XDG-based, overridable via - `WARDEN_MEMORY_STORE`) without embedding secrets. -- `session_kind` covers `warden.worker`, `warden.agent.*`, and `warden.operator`. -- Write envelopes reject payloads containing secret-field patterns (tokens, raw - URLs with credentials, key material). -- Contract is documented in `docs/ops-warden-memory-contract.md` and covered by - unit tests. - -## T03 - Agent session event schema - -```task -id: PMEM-WP-0016-T03 -status: done -priority: high -state_hub_task_id: "9d436ccb-11df-49f1-82bd-eb75b1834018" -``` - -Define metadata-only event schemas for coding agent sessions (Claude, Codex, -Grok, extensible `agent_id`). - -Acceptance: - -- Events capture: `agent_id`, `session_id`, `command` (`route find`, `access`, - `worker run`, etc.), `need_fingerprint`, `route_id`, `outcome` - (`resolved|escalated|skipped`), and `diagnostic_codes`. -- Schema supports unknown future `agent_id` values without code changes. -- No event field may contain secret values or full credential payloads. -- Conformance fixtures cover at least claude, codex, and grok agent ids. - -## T04 - Activation pack for routing memory - -```task -id: PMEM-WP-0016-T04 -status: done -priority: high -state_hub_task_id: "2f1f65a3-9a82-4854-83bb-20ef5eb3bac8" -``` - -Build an ops-warden activation pack that retrieves relevant coordination -episodes before inference or routing decisions. - -Acceptance: - -- Pack retrieves stabilized routing patterns, recent fluid thread summaries, and - applicable troubleshooting rows within profile token/item budgets. -- Activation output is a bounded context package suitable for llm-connect - injection and for agent session orientation. -- Pack works without network access (local store only). -- Tests prove deterministic selection for fixture graphs with duplicate need - fingerprints. - -## T05 - Evaluation scenarios and regression gate - -```task -id: PMEM-WP-0016-T05 -status: done -priority: medium -state_hub_task_id: "ba0d35b3-2de1-483f-9761-cec5cbe3e8da" -``` - -Add evaluation scenarios and trend metrics for ops-warden memory quality across -runtime modes. - -Acceptance: - -- Scenarios cover: routing repeat accuracy, escalation precision, cross-runtime - continuity (agent session write → worker read), and OpenRouter call avoidance - when stabilized memory matches. -- Metrics integrate with `evaluation_trend_regression_gate`. -- Scenario fixture at `tests/fixtures/ops-warden-evaluation-scenarios.json`. -- Threshold report runs in default CI without live OpenRouter access. - -## T06 - Ops-warden adapter pack conformance - -```task -id: PMEM-WP-0016-T06 -status: done -priority: medium -state_hub_task_id: "d430906b-6852-4328-8bc9-ad8c79fa8b03" -``` - -Publish a live-shaped ops-warden adapter pack and conformance suite for -ops-warden to consume. - -Acceptance: - -- Adapter pack manifest declares graph store, event log, audit sink, and - activation helpers required by `WARDEN-WP-0024`. -- `validate_adapter_pack_manifest` passes for the ops-warden pack. -- Public exports and API snapshot updated if new symbols are added. -- Pack documentation explains how ops-warden depends on phase-memory without - circular repo ownership. - -## Acceptance Criteria - -- ops-warden can activate phase-memory consistently from worker, agent session, - and operator CLI modes using one canonical store. -- Learned memory improves routing suggestions without weakening security - guardrails. -- Evaluation gates can block promotion when cross-runtime continuity or routing - accuracy regresses. - -## Dependencies - -- `WARDEN-WP-0024` (ops-warden repo) — consumes the contract and adapter pack. -- `WARDEN-WP-0020` — worker brain and guardrail foundation (finished). -- `PMEM-WP-0015` — pilot evidence and regression gate patterns (finished). - -## Closure Review - -Implemented cross-runtime memory contracts and activation helpers: - -- `ops-warden-coordination` profile fixture with activation budgets and - `secrets_allowed: false`. -- `phase_memory.ops_warden.runtime.v1` contract with canonical store path, - `session_kind` coverage, and secret-field rejection. -- Agent session event schema supporting claude, codex, grok, and future agent ids. -- `activate_ops_warden_memory`, stabilized route matching, and ops-warden adapter - pack with evaluation scenarios and report helpers. -- Contract documentation in `docs/ops-warden-memory-contract.md`. \ No newline at end of file diff --git a/workplans/PMEM-WP-0017-federated-store-management-cli.md b/workplans/PMEM-WP-0017-federated-store-management-cli.md deleted file mode 100644 index 52a00e9..0000000 --- a/workplans/PMEM-WP-0017-federated-store-management-cli.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -id: PMEM-WP-0017 -type: workplan -title: "Federated Store Management CLI And Activity Reports" -domain: communication -repo: phase-memory -status: finished -owner: codex -topic_slug: phase-memory -created: "2026-07-03" -updated: "2026-07-03" -state_hub_workstream_id: "4e3fa1d9-0e9f-446f-a677-6038e153a316" ---- - -# PMEM-WP-0017: Federated Store Management CLI And Activity Reports - -## Goal - -Extend the `phase-memory` CLI into a **management interface** for every on-disk -memory store the runtime has produced — not only the ops-warden coordination -store. Operators and agents need one command to answer: - -- How much memory activity happened in the last *N* days **across all stores**? -- Which store contributed what (episodes, audit events, session kinds, outcomes)? -- What does a **single store** look like in detail for the same window? - -LLM interactivity stays out of scope here. When memory is "brought to life" -through ops-warden (worker, route/access, coding-agent sessions), chat already -happens in that surface. This workplan adds **inspectable reporting** only. - -## Implementation Update - 2026-07-03 - -Shipped federated store discovery, windowed activity aggregation, and CLI -reporting: - -- `src/phase_memory/management.py` — discovery, classification, aggregation, - `build_federated_report`, `build_store_list` -- `phase-memory stores list` and `phase-memory report [--days N] [--store …]` -- `make report-7d` and `make report REPORT_DAYS=N` -- Tests in `tests/test_management_cli.py` with `tests/fixtures/management/` - -Validation: `make test` → 117 passed, 1 skipped. - -## T01 - Store discovery and classification - -```task -id: PMEM-WP-0017-T01 -status: done -priority: high -state_hub_task_id: "7c0d7977-bd81-4aab-ab8d-5acfce7c9ba5" -``` - -Implement `phase_memory.management` with `discover_memory_stores`, -`classify_store`, and registry read path. - -## T02 - Windowed activity aggregation - -```task -id: PMEM-WP-0017-T02 -status: done -priority: high -state_hub_task_id: "f58aec01-077f-431b-8eb4-482cbd8ef200" -``` - -Aggregate ops-warden episodes and local graph events/audit for a time window. - -## T03 - Federated report envelope - -```task -id: PMEM-WP-0017-T03 -status: done -priority: high -state_hub_task_id: "e242b3b6-6c20-440e-82e7-c4e877096fe5" -``` - -`build_federated_report` returns `phase_memory.management.federated_report.v1`. - -## T04 - CLI commands - -```task -id: PMEM-WP-0017-T04 -status: done -priority: high -state_hub_task_id: "a43154ca-5fb2-4791-84d7-c92dd222e681" -``` - -`stores list` and `report` wired in `cli.py`. - -## T05 - Makefile, README, and operator runbook - -```task -id: PMEM-WP-0017-T05 -status: done -priority: medium -state_hub_task_id: "cdde3cfc-e1d5-40d2-a936-e54d6145bd79" -``` - -`make report-7d`, README management section, runbook cross-store activity. - -## T06 - Tests and evaluation hook - -```task -id: PMEM-WP-0017-T06 -status: done -priority: medium -state_hub_task_id: "19672f3e-2b95-4bae-8d76-6ac5c6cdb446" -``` - -`tests/test_management_cli.py` and management fixtures; public API snapshot updated. \ No newline at end of file