feat(agents): add deploy control plane loop readback
This commit is contained in:
@@ -10,12 +10,13 @@ KM, and Telegram receipts are present.
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable, Mapping
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
from src.core.config import settings
|
||||
from src.core.logging import get_logger
|
||||
from sqlalchemy import text
|
||||
from src.db.base import get_db_context
|
||||
from src.services.report_generation_service import (
|
||||
DAILY_REPORT_HOUR_TAIPEI,
|
||||
@@ -54,8 +55,8 @@ def _utc_iso(value: Any) -> str | None:
|
||||
return None
|
||||
if isinstance(value, datetime):
|
||||
if value.tzinfo is None:
|
||||
value = value.replace(tzinfo=timezone.utc)
|
||||
return value.astimezone(timezone.utc).isoformat()
|
||||
value = value.replace(tzinfo=UTC)
|
||||
return value.astimezone(UTC).isoformat()
|
||||
return str(value)
|
||||
|
||||
|
||||
@@ -229,6 +230,188 @@ def _latest_flow_closure(
|
||||
}
|
||||
|
||||
|
||||
def classify_deploy_control_plane_observation(
|
||||
*,
|
||||
run_status: str,
|
||||
is_latest_deploy_intent: bool,
|
||||
active_task_container_count: int,
|
||||
production_marker_hit: bool,
|
||||
latest_flow_closed: bool,
|
||||
runner_capacity_ok: bool,
|
||||
runner_forbidden_label_count: int,
|
||||
) -> dict[str, Any]:
|
||||
"""Classify CD/run noise into an internal PlayBook decision."""
|
||||
|
||||
normalized_status = str(run_status or "unknown").strip().lower()
|
||||
has_active_task = active_task_container_count > 0
|
||||
runner_lane_safe = runner_capacity_ok and runner_forbidden_label_count == 0
|
||||
production_truth_ok = production_marker_hit and latest_flow_closed
|
||||
|
||||
if not is_latest_deploy_intent:
|
||||
classification = "superseded_run_skip"
|
||||
action = "skip_cd_work_and_attach_to_superseded_intent"
|
||||
elif production_truth_ok and normalized_status == "success":
|
||||
classification = "deploy_succeeded_marker_hit"
|
||||
action = "close_deploy_intent_and_write_receipts"
|
||||
elif normalized_status == "running" and has_active_task and runner_lane_safe:
|
||||
classification = "running_with_controlled_task"
|
||||
action = "continue_observing_without_restarting_runner"
|
||||
elif normalized_status == "running" and not has_active_task and production_truth_ok:
|
||||
classification = "running_no_container_stale_ui"
|
||||
action = "treat_gitea_spinner_as_stale_and_keep_production_truth"
|
||||
elif normalized_status == "failure" and production_truth_ok:
|
||||
classification = "failed_run_superseded_by_marker_hit"
|
||||
action = "record_non_blocking_failure_and_keep_current_marker"
|
||||
elif normalized_status == "failure":
|
||||
classification = "real_failure_requires_playbook_repair"
|
||||
action = "open_cd_repair_playbook_with_target_selector_and_verifier"
|
||||
elif not runner_lane_safe:
|
||||
classification = "runner_lane_guardrail_violation"
|
||||
action = "fail_closed_runner_lane_and_open_repair_playbook"
|
||||
else:
|
||||
classification = "waiting_for_controlled_observation"
|
||||
action = "wait_for_mcp_observation_or_deploy_intent_update"
|
||||
|
||||
return {
|
||||
"schema_version": "ai_agent_deploy_control_plane_decision_v1",
|
||||
"classification": classification,
|
||||
"action": action,
|
||||
"inputs": {
|
||||
"run_status": normalized_status,
|
||||
"is_latest_deploy_intent": is_latest_deploy_intent,
|
||||
"active_task_container_count": max(0, active_task_container_count),
|
||||
"production_marker_hit": production_marker_hit,
|
||||
"latest_flow_closed": latest_flow_closed,
|
||||
"runner_capacity_ok": runner_capacity_ok,
|
||||
"runner_forbidden_label_count": max(0, runner_forbidden_label_count),
|
||||
},
|
||||
"internal_writeback": {
|
||||
"mcp_event_type": "deploy_run_observation",
|
||||
"rag_context_required": True,
|
||||
"km_writeback_required": True,
|
||||
"playbook_route_required": True,
|
||||
"log_projection_required": True,
|
||||
"telegram_receipt_required": classification in {
|
||||
"deploy_succeeded_marker_hit",
|
||||
"real_failure_requires_playbook_repair",
|
||||
"runner_lane_guardrail_violation",
|
||||
},
|
||||
},
|
||||
"safety_boundary": {
|
||||
"reads_raw_sessions": False,
|
||||
"reads_secret_values": False,
|
||||
"opens_legacy_runner": False,
|
||||
"uses_force_push": False,
|
||||
"writes_runtime_state": classification in {
|
||||
"deploy_succeeded_marker_hit",
|
||||
"real_failure_requires_playbook_repair",
|
||||
"runner_lane_guardrail_violation",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _control_plane_integration() -> dict[str, Any]:
|
||||
classifier_examples = [
|
||||
classify_deploy_control_plane_observation(
|
||||
run_status="success",
|
||||
is_latest_deploy_intent=True,
|
||||
active_task_container_count=0,
|
||||
production_marker_hit=True,
|
||||
latest_flow_closed=True,
|
||||
runner_capacity_ok=True,
|
||||
runner_forbidden_label_count=0,
|
||||
),
|
||||
classify_deploy_control_plane_observation(
|
||||
run_status="running",
|
||||
is_latest_deploy_intent=True,
|
||||
active_task_container_count=0,
|
||||
production_marker_hit=True,
|
||||
latest_flow_closed=True,
|
||||
runner_capacity_ok=True,
|
||||
runner_forbidden_label_count=0,
|
||||
),
|
||||
classify_deploy_control_plane_observation(
|
||||
run_status="failure",
|
||||
is_latest_deploy_intent=True,
|
||||
active_task_container_count=0,
|
||||
production_marker_hit=False,
|
||||
latest_flow_closed=False,
|
||||
runner_capacity_ok=True,
|
||||
runner_forbidden_label_count=0,
|
||||
),
|
||||
]
|
||||
return {
|
||||
"schema_version": "ai_agent_autonomous_runtime_internal_loop_v1",
|
||||
"status": "mcp_rag_km_playbook_log_control_loop_declared",
|
||||
"purpose": (
|
||||
"把 Gitea run、runner lane、production marker、browser smoke 與 executor receipt "
|
||||
"先收斂成內部事件,再由 PlayBook decision 推進或跳過。"
|
||||
),
|
||||
"mcp_sensors": [
|
||||
{
|
||||
"sensor_id": "gitea_actions_run_observer",
|
||||
"normalized_event": "RunObservation",
|
||||
"raw_secret_access_allowed": False,
|
||||
},
|
||||
{
|
||||
"sensor_id": "controlled_runner_lane_observer",
|
||||
"normalized_event": "RunnerLaneState",
|
||||
"raw_runner_token_access_allowed": False,
|
||||
},
|
||||
{
|
||||
"sensor_id": "production_marker_observer",
|
||||
"normalized_event": "ProductionTruthSnapshot",
|
||||
"raw_session_access_allowed": False,
|
||||
},
|
||||
{
|
||||
"sensor_id": "browser_smoke_observer",
|
||||
"normalized_event": "FrontendTruthSnapshot",
|
||||
"raw_conversation_access_allowed": False,
|
||||
},
|
||||
],
|
||||
"rag_context_queries": [
|
||||
"runner_pressure_buildkit_stockplatform_collision",
|
||||
"controlled_cd_lane_capacity_label_guardrails",
|
||||
"autonomous_runtime_marker_receipt_contract",
|
||||
],
|
||||
"playbook_decision_classes": [
|
||||
"deploy_succeeded_marker_hit",
|
||||
"running_with_controlled_task",
|
||||
"running_no_container_stale_ui",
|
||||
"superseded_run_skip",
|
||||
"failed_run_superseded_by_marker_hit",
|
||||
"real_failure_requires_playbook_repair",
|
||||
"runner_lane_guardrail_violation",
|
||||
],
|
||||
"km_writeback_contract": {
|
||||
"knowledge_entry_path_type": "deploy_control_plane_decision:<deploy_intent_id>",
|
||||
"required_refs": [
|
||||
"deploy_intent_id",
|
||||
"target_sha",
|
||||
"gitea_run_id",
|
||||
"production_marker",
|
||||
"latest_flow_closure",
|
||||
"runner_lane_state",
|
||||
],
|
||||
"stores_raw_logs": False,
|
||||
"stores_secret_values": False,
|
||||
},
|
||||
"log_projection_contract": {
|
||||
"timeline_event_type": "ai_agent_deploy_control_plane_decision",
|
||||
"logbook_projection": "summary_only_after_verifier",
|
||||
"raw_html_or_long_log_allowed": False,
|
||||
},
|
||||
"classifier_examples": classifier_examples,
|
||||
"rollups": {
|
||||
"mcp_sensor_count": 4,
|
||||
"rag_context_query_count": 3,
|
||||
"playbook_decision_class_count": 7,
|
||||
"classifier_example_count": len(classifier_examples),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def build_runtime_receipt_readback_from_rows(
|
||||
*,
|
||||
project_id: str = _DEFAULT_PROJECT_ID,
|
||||
@@ -483,9 +666,10 @@ def build_ai_agent_autonomous_runtime_control() -> dict[str, Any]:
|
||||
"new_behavior": "用 Telegram Gateway 實送報告與 actionable receipt;不直接暴露 Bot API",
|
||||
},
|
||||
]
|
||||
control_plane_integration = _control_plane_integration()
|
||||
payload = {
|
||||
"schema_version": _SCHEMA_VERSION,
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"generated_at": datetime.now(UTC).isoformat(),
|
||||
"program_status": {
|
||||
"current_task_id": "P2-416-D1N",
|
||||
"status": "current_directive_control_plane_active",
|
||||
@@ -565,6 +749,7 @@ def build_ai_agent_autonomous_runtime_control() -> dict[str, Any]:
|
||||
"telegram_receipt_or_alert",
|
||||
],
|
||||
},
|
||||
"control_plane_integration": control_plane_integration,
|
||||
"legacy_policy_overrides": legacy_overrides,
|
||||
"hard_blockers": hard_blockers,
|
||||
"visibility_contract": {
|
||||
@@ -589,6 +774,10 @@ def build_ai_agent_autonomous_runtime_control() -> dict[str, Any]:
|
||||
1 for item in executor_receipts if item["writes_runtime_state"]
|
||||
),
|
||||
"legacy_policy_overridden_count": len(legacy_overrides),
|
||||
"mcp_sensor_count": control_plane_integration["rollups"]["mcp_sensor_count"],
|
||||
"rag_context_query_count": control_plane_integration["rollups"]["rag_context_query_count"],
|
||||
"playbook_decision_class_count": control_plane_integration["rollups"]["playbook_decision_class_count"],
|
||||
"deploy_control_classifier_example_count": control_plane_integration["rollups"]["classifier_example_count"],
|
||||
},
|
||||
}
|
||||
_attach_runtime_receipt_readback(
|
||||
|
||||
Reference in New Issue
Block a user