feat(agents): expose autonomous runtime control
Some checks failed
CD Pipeline / tests (push) Successful in 1m43s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Code Review / ai-code-review (push) Has been cancelled
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
Some checks failed
CD Pipeline / tests (push) Successful in 1m43s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Code Review / ai-code-review (push) Has been cancelled
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
This commit is contained in:
273
apps/api/src/services/ai_agent_autonomous_runtime_control.py
Normal file
273
apps/api/src/services/ai_agent_autonomous_runtime_control.py
Normal file
@@ -0,0 +1,273 @@
|
||||
"""Current AI Agent autonomous runtime control plane.
|
||||
|
||||
This read model is the current directive layer. Historical P2 snapshots can
|
||||
still describe earlier no-send / no-live states, but this payload states what
|
||||
the product should enforce now: low, medium, and high risk routes may proceed
|
||||
through controlled automation when allowlist, check-mode, verifier, rollback,
|
||||
KM, and Telegram receipts are present.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from src.core.config import settings
|
||||
from src.services.report_generation_service import (
|
||||
DAILY_REPORT_HOUR_TAIPEI,
|
||||
MONTHLY_REPORT_DAY_TAIPEI,
|
||||
MONTHLY_REPORT_HOUR_TAIPEI,
|
||||
WEEKLY_REPORT_HOUR_TAIPEI,
|
||||
WEEKLY_REPORT_WEEKDAY_TAIPEI,
|
||||
)
|
||||
|
||||
_SCHEMA_VERSION = "ai_agent_autonomous_runtime_control_v1"
|
||||
_RUNTIME_AUTHORITY = "current_owner_directive_controlled_ai_automation"
|
||||
|
||||
|
||||
def _allowed_risk_levels() -> list[str]:
|
||||
raw = str(settings.AWOOOP_ANSIBLE_CONTROLLED_APPLY_ALLOWED_RISK_LEVELS or "")
|
||||
return sorted({item.strip().lower() for item in raw.split(",") if item.strip()})
|
||||
|
||||
|
||||
def build_ai_agent_autonomous_runtime_control() -> dict[str, Any]:
|
||||
"""Build the current AI Agent autonomy control-plane readback."""
|
||||
|
||||
allowed_risks = _allowed_risk_levels()
|
||||
report_cadences = [
|
||||
{
|
||||
"cadence": "daily",
|
||||
"display_name": "日報",
|
||||
"schedule": f"每日 {DAILY_REPORT_HOUR_TAIPEI:02d}:00 台北時間",
|
||||
"worker": "report_generation_service.run_daily_report_loop",
|
||||
"telegram_gateway_delivery_enabled": True,
|
||||
"direct_bot_api_allowed": False,
|
||||
"receipt_source": "daily_report_sent log + Telegram Gateway result",
|
||||
},
|
||||
{
|
||||
"cadence": "weekly",
|
||||
"display_name": "週報",
|
||||
"schedule": (
|
||||
f"每週五 {WEEKLY_REPORT_HOUR_TAIPEI:02d}:00 台北時間"
|
||||
if WEEKLY_REPORT_WEEKDAY_TAIPEI == 4
|
||||
else f"每週 weekday={WEEKLY_REPORT_WEEKDAY_TAIPEI} {WEEKLY_REPORT_HOUR_TAIPEI:02d}:00 台北時間"
|
||||
),
|
||||
"worker": "report_generation_service.run_weekly_report_loop",
|
||||
"telegram_gateway_delivery_enabled": True,
|
||||
"direct_bot_api_allowed": False,
|
||||
"receipt_source": "weekly_report_sent log + Telegram Gateway result",
|
||||
},
|
||||
{
|
||||
"cadence": "monthly",
|
||||
"display_name": "月報",
|
||||
"schedule": f"每月 {MONTHLY_REPORT_DAY_TAIPEI} 日 {MONTHLY_REPORT_HOUR_TAIPEI:02d}:00 台北時間",
|
||||
"worker": "report_generation_service.run_monthly_report_loop",
|
||||
"telegram_gateway_delivery_enabled": True,
|
||||
"direct_bot_api_allowed": False,
|
||||
"receipt_source": "monthly_report_sent log + Telegram Gateway result",
|
||||
},
|
||||
]
|
||||
executor_receipts = [
|
||||
{
|
||||
"operation_type": "ansible_candidate_matched",
|
||||
"owner_agent": "Hermes",
|
||||
"purpose": "把修復候選寫入 executor 可認領佇列",
|
||||
"writes_runtime_state": False,
|
||||
},
|
||||
{
|
||||
"operation_type": "ansible_check_mode_executed",
|
||||
"owner_agent": "AwoooP Ansible check-mode worker",
|
||||
"purpose": "執行 ansible-playbook --check --diff 並留下乾跑收據",
|
||||
"writes_runtime_state": False,
|
||||
},
|
||||
{
|
||||
"operation_type": "ansible_apply_executed",
|
||||
"owner_agent": "AwoooP controlled apply worker",
|
||||
"purpose": "check-mode 通過後,對 allowlisted low / medium / high PlayBook 受控 apply",
|
||||
"writes_runtime_state": True,
|
||||
},
|
||||
{
|
||||
"operation_type": "incident_evidence.post_execution_state",
|
||||
"owner_agent": "post_apply_verifier",
|
||||
"purpose": "apply 後寫入 verifier 結果與 post-execution evidence",
|
||||
"writes_runtime_state": True,
|
||||
},
|
||||
{
|
||||
"operation_type": "knowledge_entries",
|
||||
"owner_agent": "Hermes",
|
||||
"purpose": "把已驗證執行沉澱成 KM / PlayBook trust 候選",
|
||||
"writes_runtime_state": True,
|
||||
},
|
||||
]
|
||||
hard_blockers = [
|
||||
"secret_token_private_key_cookie_session_auth_header_cleartext",
|
||||
"drop_truncate_restore_prune_destructive_database_operation",
|
||||
"reboot_node_drain_irreversible_firewall_or_host_lockout",
|
||||
"credentialed_exploit_or_external_active_scan",
|
||||
"new_paid_provider_cost_ceiling_or_provider_switch_without_replay_shadow_canary",
|
||||
"force_push_delete_repo_refs_or_visibility_change",
|
||||
"critical_or_break_glass_route_without_explicit_break_glass_contract",
|
||||
]
|
||||
legacy_overrides = [
|
||||
{
|
||||
"legacy_area": "report_status_board_no_live_send",
|
||||
"current_effect": "overridden",
|
||||
"new_behavior": "日報 / 週報 / 月報透過 Telegram Gateway 排程派送",
|
||||
},
|
||||
{
|
||||
"legacy_area": "report_live_delivery_owner_review_required",
|
||||
"current_effect": "overridden",
|
||||
"new_behavior": "報告派送走低/中/高風險自動化政策;critical 才 break-glass",
|
||||
},
|
||||
{
|
||||
"legacy_area": "high_risk_owner_review_queue",
|
||||
"current_effect": "overridden_for_high_non_critical",
|
||||
"new_behavior": "high 風險允許 controlled apply;critical / hard blocker 仍不自動",
|
||||
},
|
||||
{
|
||||
"legacy_area": "telegram_no_send_preview_only",
|
||||
"current_effect": "overridden",
|
||||
"new_behavior": "用 Telegram Gateway 實送報告與 actionable receipt;不直接暴露 Bot API",
|
||||
},
|
||||
]
|
||||
payload = {
|
||||
"schema_version": _SCHEMA_VERSION,
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"program_status": {
|
||||
"current_task_id": "P2-416-D1N",
|
||||
"status": "current_directive_control_plane_active",
|
||||
"runtime_authority": _RUNTIME_AUTHORITY,
|
||||
"legacy_no_send_no_live_rules_overridden": True,
|
||||
"implementation_completion_percent": 82,
|
||||
"status_note": (
|
||||
"目前有效規則:low / medium / high 風險由 AI Agent 在 allowlist、"
|
||||
"Ansible check-mode、verifier、rollback、KM 與 Telegram receipt 下受控自動處理。"
|
||||
),
|
||||
},
|
||||
"current_policy": {
|
||||
"low_risk_controlled_apply_allowed": "low" in allowed_risks,
|
||||
"medium_risk_controlled_apply_allowed": "medium" in allowed_risks,
|
||||
"high_risk_controlled_apply_allowed": "high" in allowed_risks,
|
||||
"critical_break_glass_required": True,
|
||||
"owner_review_required_for_low_medium_high": False,
|
||||
"direct_bot_api_allowed": False,
|
||||
"telegram_gateway_required": True,
|
||||
"post_apply_verifier_required": True,
|
||||
"km_learning_writeback_required": True,
|
||||
},
|
||||
"runtime_switches": {
|
||||
"ansible_check_mode_worker_enabled": bool(settings.ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER),
|
||||
"ansible_controlled_apply_enabled": bool(settings.ENABLE_AWOOOP_ANSIBLE_CONTROLLED_APPLY),
|
||||
"ansible_controlled_apply_allowed_risk_levels": allowed_risks,
|
||||
"ansible_check_mode_interval_seconds": settings.AWOOOP_ANSIBLE_CHECK_MODE_INTERVAL_SECONDS,
|
||||
"ansible_check_mode_batch_limit": settings.AWOOOP_ANSIBLE_CHECK_MODE_BATCH_LIMIT,
|
||||
"ansible_check_mode_timeout_seconds": settings.AWOOOP_ANSIBLE_CHECK_MODE_TIMEOUT_SECONDS,
|
||||
"ansible_controlled_apply_timeout_seconds": settings.AWOOOP_ANSIBLE_CONTROLLED_APPLY_TIMEOUT_SECONDS,
|
||||
},
|
||||
"agent_roles": [
|
||||
{
|
||||
"agent_id": "openclaw",
|
||||
"role": "仲裁 / hard blocker / replay-shadow-canary gate",
|
||||
"current_job": "只阻擋真正 critical 與 hard blocker,不再用身份保護舊架構",
|
||||
},
|
||||
{
|
||||
"agent_id": "hermes",
|
||||
"role": "報告 / Telegram digest / KM 與 PlayBook trust writeback",
|
||||
"current_job": "日週月報、收據摘要與 verifier 後學習沉澱",
|
||||
},
|
||||
{
|
||||
"agent_id": "nemotron",
|
||||
"role": "市場技術雷達 / no-write replay / challenger scorecard",
|
||||
"current_job": "用市場與回放數據挑戰 OpenClaw / provider / Agent 組合",
|
||||
},
|
||||
{
|
||||
"agent_id": "awooop_ansible_worker",
|
||||
"role": "executor",
|
||||
"current_job": "candidate → check-mode → controlled apply → verifier → KM",
|
||||
},
|
||||
{
|
||||
"agent_id": "telegram_ops",
|
||||
"role": "Telegram Gateway receipt",
|
||||
"current_job": "群組報告、actionable receipt、失敗告警;不展示敏感值或未脫敏資料",
|
||||
},
|
||||
],
|
||||
"report_delivery": {
|
||||
"status": "telegram_gateway_delivery_enabled",
|
||||
"cadences": report_cadences,
|
||||
},
|
||||
"controlled_executor": {
|
||||
"status": "check_mode_then_apply_enabled"
|
||||
if settings.ENABLE_AWOOOP_ANSIBLE_CONTROLLED_APPLY
|
||||
else "check_mode_only_by_config",
|
||||
"operation_receipts": executor_receipts,
|
||||
"required_flow": [
|
||||
"allowlisted_candidate",
|
||||
"ansible_check_mode_success",
|
||||
"controlled_apply",
|
||||
"post_apply_verifier",
|
||||
"auto_repair_execution_receipt",
|
||||
"km_learning_writeback",
|
||||
"telegram_receipt_or_alert",
|
||||
],
|
||||
},
|
||||
"legacy_policy_overrides": legacy_overrides,
|
||||
"hard_blockers": hard_blockers,
|
||||
"visibility_contract": {
|
||||
"frontend_displays_runtime_truth": True,
|
||||
"work_window_transcript_display_allowed": False,
|
||||
"prompt_body_display_allowed": False,
|
||||
"internal_reasoning_display_allowed": False,
|
||||
"sensitive_value_display_allowed": False,
|
||||
"telegram_unredacted_payload_display_allowed": False,
|
||||
"lan_topology_redaction_required": True,
|
||||
},
|
||||
"rollups": {
|
||||
"automated_risk_tier_count": sum(1 for risk in ("low", "medium", "high") if risk in allowed_risks),
|
||||
"hard_blocker_count": len(hard_blockers),
|
||||
"report_cadence_enabled_count": len(report_cadences),
|
||||
"telegram_gateway_delivery_enabled_count": sum(
|
||||
1 for item in report_cadences if item["telegram_gateway_delivery_enabled"]
|
||||
),
|
||||
"direct_bot_api_allowed_count": 0,
|
||||
"controlled_executor_operation_receipt_count": len(executor_receipts),
|
||||
"runtime_write_receipt_type_count": sum(
|
||||
1 for item in executor_receipts if item["writes_runtime_state"]
|
||||
),
|
||||
"legacy_policy_overridden_count": len(legacy_overrides),
|
||||
},
|
||||
}
|
||||
_validate_payload(payload)
|
||||
return payload
|
||||
|
||||
|
||||
def _validate_payload(payload: dict[str, Any]) -> None:
|
||||
if payload.get("schema_version") != _SCHEMA_VERSION:
|
||||
raise ValueError(f"schema_version must be {_SCHEMA_VERSION}")
|
||||
status = payload.get("program_status") or {}
|
||||
if status.get("runtime_authority") != _RUNTIME_AUTHORITY:
|
||||
raise ValueError(f"runtime_authority must be {_RUNTIME_AUTHORITY}")
|
||||
policy = payload.get("current_policy") or {}
|
||||
for key in (
|
||||
"low_risk_controlled_apply_allowed",
|
||||
"medium_risk_controlled_apply_allowed",
|
||||
"high_risk_controlled_apply_allowed",
|
||||
"telegram_gateway_required",
|
||||
"post_apply_verifier_required",
|
||||
"km_learning_writeback_required",
|
||||
):
|
||||
if policy.get(key) is not True:
|
||||
raise ValueError(f"current_policy.{key} must be true")
|
||||
if policy.get("owner_review_required_for_low_medium_high") is not False:
|
||||
raise ValueError("owner_review_required_for_low_medium_high must be false")
|
||||
if policy.get("direct_bot_api_allowed") is not False:
|
||||
raise ValueError("direct_bot_api_allowed must be false")
|
||||
visibility = payload.get("visibility_contract") or {}
|
||||
for key in (
|
||||
"work_window_transcript_display_allowed",
|
||||
"prompt_body_display_allowed",
|
||||
"internal_reasoning_display_allowed",
|
||||
"sensitive_value_display_allowed",
|
||||
"telegram_unredacted_payload_display_allowed",
|
||||
):
|
||||
if visibility.get(key) is not False:
|
||||
raise ValueError(f"visibility_contract.{key} must remain false")
|
||||
Reference in New Issue
Block a user