feat(governance): 定義 Agent 主動溝通學習契約
All checks were successful
CD Pipeline / tests (push) Successful in 1m23s
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / build-and-deploy (push) Successful in 4m33s
CD Pipeline / post-deploy-checks (push) Successful in 1m32s

This commit is contained in:
Your Name
2026-06-11 11:52:45 +08:00
parent ccf872131c
commit 8c11af7c19
9 changed files with 1400 additions and 0 deletions

View File

@@ -49,6 +49,9 @@ from src.services.ai_agent_automation_backlog_snapshot import (
from src.services.ai_agent_automation_inventory_snapshot import (
load_latest_ai_agent_automation_inventory_snapshot,
)
from src.services.ai_agent_communication_learning_contract import (
load_latest_ai_agent_communication_learning_contract,
)
from src.services.ai_agent_deployment_layout import (
load_latest_ai_agent_deployment_layout,
)
@@ -524,6 +527,33 @@ async def get_agent_deployment_layout() -> dict[str, Any]:
) from exc
@router.get(
"/agent-communication-learning-contract",
response_model=dict[str, Any],
summary="取得 AI Agent 主動溝通與學習契約",
description=(
"讀取最新已提交的 OpenClaw / Hermes / NemoTron 主動溝通、學習、記錄、MCP 與 RAG 契約;"
"此端點不啟動 worker、不建立 DB migration、不送 Telegram、不安裝 SDK、不呼叫付費服務、"
"不修改生產路由或主機。"
),
)
async def get_agent_communication_learning_contract() -> dict[str, Any]:
"""Return the latest read-only AI Agent communication learning contract."""
try:
return await asyncio.to_thread(load_latest_ai_agent_communication_learning_contract)
except FileNotFoundError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(exc),
) from exc
except (json.JSONDecodeError, ValueError) as exc:
logger.error("ai_agent_communication_learning_contract_invalid", error=str(exc))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="AI Agent 主動溝通與學習契約無效",
) from exc
@router.get(
"/runtime-surface-inventory",
response_model=dict[str, Any],

View File

@@ -0,0 +1,146 @@
"""
AI Agent communication and learning contract snapshot.
Loads the latest committed, read-only contract for OpenClaw, Hermes, and
NemoTron proactive communication, learning, recording, MCP, RAG, and
intelligence service boundaries. This module never starts workers, writes
database migrations, sends Telegram messages, installs SDKs, calls paid
providers, or changes production routes.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from src.services.snapshot_paths import default_evaluations_dir
_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__))
_SNAPSHOT_PATTERN = "ai_agent_communication_learning_contract_*.json"
_SCHEMA_VERSION = "ai_agent_communication_learning_contract_v1"
def load_latest_ai_agent_communication_learning_contract(
evaluations_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the newest committed AI Agent communication learning contract."""
directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
if not candidates:
raise FileNotFoundError(
f"no AI Agent communication learning contract snapshots found in {directory}"
)
latest = candidates[-1]
with latest.open(encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict):
raise ValueError(f"{latest}: expected JSON object")
_require_schema(payload, _SCHEMA_VERSION, str(latest))
_require_read_only_contract(payload, str(latest))
_require_rollup_consistency(payload, str(latest))
_require_agent_boundaries(payload, str(latest))
_require_frontend_redaction(payload, str(latest))
return payload
def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None:
actual = payload.get("schema_version")
if actual != expected:
raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}")
def _require_read_only_contract(payload: dict[str, Any], label: str) -> None:
program_status = payload.get("program_status") or {}
if program_status.get("read_only_mode") is not True:
raise ValueError(f"{label}: program_status.read_only_mode must be true")
if program_status.get("runtime_authority") != "contract_only_no_runtime_worker":
raise ValueError(f"{label}: runtime_authority must stay contract_only_no_runtime_worker")
boundaries = payload.get("approval_boundaries") or {}
blocked_flags = {
"runtime_worker_allowed",
"db_migration_allowed",
"telegram_direct_send_allowed",
"paid_external_service_allowed",
"secret_plaintext_allowed",
"autonomous_host_mutation_allowed",
"production_route_change_allowed",
"sdk_installation_allowed",
}
allowed = sorted(flag for flag in blocked_flags if boundaries.get(flag) is not False)
if allowed:
raise ValueError(f"{label}: approval boundaries must remain false: {allowed}")
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
rollups = payload.get("rollups") or {}
expected_counts = {
"agent_lane_count": len(payload.get("agent_lanes") or []),
"mcp_stack_count": len(payload.get("mcp_stack") or []),
"rag_layer_count": len(payload.get("rag_memory_stack") or []),
"learning_loop_count": len(payload.get("learning_loops") or []),
"intelligence_service_count": len(payload.get("intelligence_services") or []),
"rollout_task_count": len(payload.get("rollout_tasks") or []),
}
mismatched = {
key: {"expected": expected, "actual": rollups.get(key)}
for key, expected in expected_counts.items()
if rollups.get(key) != expected
}
if mismatched:
raise ValueError(f"{label}: rollup counts must match payload sections: {mismatched}")
rollout_tasks = payload.get("rollout_tasks") or []
blocked_task_ids = sorted(
task.get("task_id")
for task in rollout_tasks
if task.get("status") in {"planned", "blocked"}
and (
"approval" in str(task.get("next_gate", "")).lower()
or "gate" in str(task.get("next_gate", "")).lower()
)
)
if sorted(rollups.get("blocked_task_ids") or []) != blocked_task_ids:
raise ValueError(f"{label}: rollups.blocked_task_ids must match gated rollout tasks")
optional_service_ids = sorted(
service.get("id")
for service in payload.get("intelligence_services") or []
if service.get("status") in {"optional_candidate", "deferred_candidate"}
)
if sorted(rollups.get("optional_service_ids") or []) != optional_service_ids:
raise ValueError(f"{label}: rollups.optional_service_ids must match optional services")
def _require_agent_boundaries(payload: dict[str, Any], label: str) -> None:
lanes = payload.get("agent_lanes") or []
lane_ids = {lane.get("agent_id") for lane in lanes}
required_lanes = {"openclaw", "hermes", "nemotron"}
if not required_lanes.issubset(lane_ids):
raise ValueError(f"{label}: missing required agent lanes: {sorted(required_lanes - lane_ids)}")
unsafe_lanes = [
lane.get("agent_id")
for lane in lanes
if not lane.get("blocked_actions")
or "secret_plaintext_read" not in set(lane.get("blocked_actions") or [])
]
if unsafe_lanes:
raise ValueError(f"{label}: agent lanes must block secret plaintext read: {unsafe_lanes}")
nemotron = next((lane for lane in lanes if lane.get("agent_id") == "nemotron"), {})
nemotron_blocked = set(nemotron.get("blocked_actions") or [])
if "production_route_change" not in nemotron_blocked:
raise ValueError(f"{label}: Nemotron must remain blocked from production route changes")
def _require_frontend_redaction(payload: dict[str, Any], label: str) -> None:
redaction = ((payload.get("communication_plane") or {}).get("frontend_redaction") or {})
if redaction.get("operator_conversation_display_allowed") is not False:
raise ValueError(f"{label}: operator conversation display must stay false")
if redaction.get("agent_private_reasoning_display_allowed") is not False:
raise ValueError(f"{label}: agent private reasoning display must stay false")