feat(Phase 3): Evolver loop 排程 + 手動觸發端點 — 合併演練閘道完工
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled

- playbook_evolver.py: 新增 run_evolver_loop()(24h 無限迴圈)
- main.py: 掛載 run_evolver_loop asyncio.create_task
- api/v1/learning.py: POST /api/v1/learning/evolver/run(Phase 3 exit #6 演練端點)
- MASTER §8: 補錄 66c4eda AgentSession + 本次 Evolver 完整退出條件清單

ADR-083 Phase 3 — 2026-04-15 ogt + Claude Sonnet 4.6(亞太)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-15 21:07:56 +08:00
parent 66c4eda27a
commit 4718c7667c
4 changed files with 125 additions and 1 deletions

View File

@@ -18,7 +18,7 @@ Phase D-G P0 修正: 新增學習 API 端點
""" """
import structlog import structlog
from fastapi import APIRouter from fastapi import APIRouter, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
from src.services.learning_service import get_learning_service from src.services.learning_service import get_learning_service
@@ -125,3 +125,60 @@ async def get_recommendation(anomaly_key: str) -> RecommendationResponse:
) )
return RecommendationResponse(**recommendation) return RecommendationResponse(**recommendation)
# =============================================================================
# Evolver Admin Endpoints
# =============================================================================
class EvolverRunResponse(BaseModel):
"""Evolver 執行報告回應"""
archived_count: int
merged_count: int
skipped_count: int
archived_ids: list[str]
merged_pairs: list[list[str]] # [[dropped_id, kept_id], ...]
errors: list[str]
total_affected: int
@router.post(
"/evolver/run",
response_model=EvolverRunResponse,
summary="手動觸發 Evolver Agent",
description=(
"立即執行 Playbook Evolver低信任封存 + 休眠封存 + 相似合併。"
"需要 AIOPS_P3_EVOLVER_ENABLED=True否則返回空報告HTTP 200"
"ADR-083 Phase 3 手動演練端點。"
),
)
async def run_evolver_now() -> EvolverRunResponse:
"""
手動觸發 Evolver AgentPhase 3 exit condition #6 演練端點)
Returns:
EvolverRunResponse: 合併/封存報告
"""
try:
from src.services.playbook_evolver import run_evolver
report = await run_evolver()
except Exception as exc:
logger.exception("evolver_manual_run_failed")
raise HTTPException(status_code=500, detail=str(exc)) from exc
logger.info(
"evolver_manual_run_done",
archived=report.archived_count,
merged=report.merged_count,
skipped=report.skipped_count,
)
return EvolverRunResponse(
archived_count=report.archived_count,
merged_count=report.merged_count,
skipped_count=report.skipped_count,
archived_ids=report.archived_ids,
merged_pairs=[list(p) for p in report.merged_pairs],
errors=report.errors,
total_affected=report.total_affected,
)

View File

@@ -345,6 +345,15 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
except Exception as e: except Exception as e:
logger.warning("approval_timeout_resolver_schedule_failed", error=str(e)) logger.warning("approval_timeout_resolver_schedule_failed", error=str(e))
# ADR-083 Phase 3: Evolver Agent每日— Playbook 自動合併 + 低信任封存
# 2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 3 初始建立
try:
from src.services.playbook_evolver import run_evolver_loop
asyncio.create_task(run_evolver_loop())
logger.info("evolver_loop_scheduled", interval_sec=86400)
except Exception as e:
logger.warning("evolver_loop_schedule_failed", error=str(e))
# ADR-083 Phase 3: 知識遺忘 Job每日— 30d 未引用 KB entry 標記 archived # ADR-083 Phase 3: 知識遺忘 Job每日— 30d 未引用 KB entry 標記 archived
# 2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 3 初始建立 # 2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 3 初始建立
try: try:

View File

@@ -354,3 +354,23 @@ async def schedule_daily_evolver() -> None:
) )
except Exception: except Exception:
logger.exception("evolver_daily_failed") logger.exception("evolver_daily_failed")
DAILY_INTERVAL_SEC = 86_400 # 24h
async def run_evolver_loop() -> None:
"""
無限迴圈:每 24 小時執行一次 Evolver Agent。
在 main.py startup 以 asyncio.create_task 掛載。
ADR-083 Phase 3: Evolver 每日掃描L4×D3 + L5×D2
2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 3 初始建立
"""
while True:
try:
await schedule_daily_evolver()
except Exception as e:
logger.error("evolver_loop_error", error=str(e))
await asyncio.sleep(DAILY_INTERVAL_SEC)

View File

@@ -1559,3 +1559,41 @@ Phase 6 完成後
- [ ] Fine-tune JSONL ≥ 10 條(待 EvidenceSnapshot 累積 7 天後驗證) - [ ] Fine-tune JSONL ≥ 10 條(待 EvidenceSnapshot 累積 7 天後驗證)
**下一步:** 推 Gitea → CD 部署 → 7 天生產觀察 Phase 3 退出條件 **下一步:** 推 Gitea → CD 部署 → 7 天生產觀察 Phase 3 退出條件
---
### 2026-04-15 深夜台北Session 2 — Phase 3 AgentSession 學習接線 + Evolver Loop 補完
**commit 66c4eda** `feat(Phase 3): AgentSession 學習接線 — record_agent_session() + orchestrator 辯證訊號`
| 檔案 | 修改內容 | 對應項目 |
|------|---------|---------|
| `services/learning_service.py` | 新增 `record_agent_session()`5 Agent 辯證結果寫 Redis analytics + Critic challenges 觸發 Playbook 負向 EWMA | ADR-083 Phase 3 L7×D2 |
| `services/agent_orchestrator.py` | `run_agent_debate()` 成功後 best-effort 呼叫 `record_agent_session()`,辯證品質訊號接入飛輪 | Orchestrator 接線 |
**本次提交未推Phase 3 Evolver Loop + 手動觸發端點**
| 檔案 | 修改內容 | 對應項目 |
|------|---------|---------|
| `services/playbook_evolver.py` | 新增 `run_evolver_loop()`:無限迴圈 24h 一次,供 main.py `asyncio.create_task` 掛載 | Evolver 每日排程 |
| `main.py` | 掛載 `run_evolver_loop`(每 24h於 knowledge_decay_loop 之前 | Job 調度補完 |
| `api/v1/learning.py` | 新增 `POST /api/v1/learning/evolver/run`:手動觸發 Evolver回傳 `EvolverRunResponse`(合併/封存報告)| Phase 3 exit condition #6 演練端點 |
**Phase 3 退出條件更新(完整版):**
- [x] Root cause 1fire-and-forget → await
- [x] Root cause 2matched_playbook_id 永不填充
- [x] Root cause 3驗證結果未傳學習
- [x] 2x EWMA 負向衰減playbook_repository.py
- [x] Evolver Agentplaybook_evolver.py
- [x] Evolver Loop 每日排程run_evolver_loop + main.py
- [x] 手動觸發端點POST /api/v1/learning/evolver/run
- [x] 診斷 feedbackrecord_diagnosis_outcome
- [x] AgentSession 學習接線record_agent_session + orchestrator
- [x] 知識遺忘 Jobknowledge_decay_job.py
- [x] Fine-tune 管線finetune_exporter.py
- [ ] Evolver 合併演練 1 次成功(部署後呼叫 POST /learning/evolver/run 驗證)
- [ ] `matched_playbook_id` null 率 = 0生產驗證 7 天)
- [ ] Playbook trust_score 有 ≥ 1 筆 24h 動態更新(生產驗證)
- [ ] Fine-tune JSONL ≥ 10 條EvidenceSnapshot 累積 7 天後)
**下一步:** 推 Gitea → CD 部署 → 呼叫 `POST /api/v1/learning/evolver/run` 完成合併演練 → 7 天生產監控