Files
cleveragents-core/benchmarks/decision_correction_bench.py
freemo 33a5adcfee
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 17s
CI / build (pull_request) Successful in 22s
CI / typecheck (pull_request) Successful in 28s
CI / security (pull_request) Successful in 33s
CI / integration_tests (pull_request) Successful in 2m25s
CI / unit_tests (pull_request) Successful in 5m43s
CI / docker (pull_request) Successful in 58s
CI / benchmark-regression (pull_request) Successful in 17m48s
CI / coverage (pull_request) Successful in 19m18s
feat(M4.2): Correction service with revert/append BFS + dry-run
Adds CorrectionRequest, CorrectionResult, CorrectionMode,
CorrectionPatch, CorrectionDryRunReport, CorrectionNotFoundError,
and CorrectionConflictError domain models.

Implements CorrectionService with BFS-based revert (marks decisions
as rolled back and restores via inverse changes) and append mode
(spawns a child correction plan).  Includes request_correction()
with dry-run support and dispatch_correction() convenience method.

33 Behave scenarios, 8 Robot smoke tests, ASV benchmark suite,
and reference documentation.

Ref: Day-14 Rebaseline – M4.2 Decision-correction flows [Jeff]
2026-02-22 16:40:06 +00:00

174 lines
6.1 KiB
Python

"""Airspeed Velocity benchmarks for decision correction flows.
Measures BFS subtree traversal, impact analysis, dry-run report
generation, and revert execution overhead across varying tree sizes.
"""
from __future__ import annotations
from cleveragents.application.services.correction_service import CorrectionService
from cleveragents.domain.models.core.correction import (
CorrectionMode,
CorrectionStatus,
)
# ---------------------------------------------------------------------------
# Tree generators
# ---------------------------------------------------------------------------
_PLAN_ID = "01HGZ6FE0AQDYTR4BXVQZ6EA00"
def _linear_tree(root: str, depth: int) -> dict[str, list[str]]:
"""Build a linear chain of *depth* nodes starting at *root*."""
tree: dict[str, list[str]] = {}
current = root
for i in range(1, depth):
child = f"{root}_c{i}"
tree[current] = [child]
current = child
return tree
def _binary_tree(root: str, depth: int) -> dict[str, list[str]]:
"""Build a complete binary tree of *depth* levels rooted at *root*."""
tree: dict[str, list[str]] = {}
level = [root]
counter = 0
for _ in range(depth - 1):
next_level: list[str] = []
for node in level:
left = f"{root}_n{counter}"
right = f"{root}_n{counter + 1}"
counter += 2
tree[node] = [left, right]
next_level.extend([left, right])
level = next_level
return tree
# ---------------------------------------------------------------------------
# BFS traversal benchmarks
# ---------------------------------------------------------------------------
class BfsTraversalSuite:
"""Benchmark BFS subtree computation at varying tree sizes."""
def setup(self) -> None:
"""Prepare trees and service instances."""
self.svc = CorrectionService()
self.small_tree = _linear_tree("S", 5) # 5 nodes
self.medium_tree = _binary_tree("M", 5) # 31 nodes
self.large_tree = _binary_tree("L", 8) # 255 nodes
def time_bfs_small_tree(self) -> None:
"""BFS on a 5-node linear chain."""
self.svc._compute_affected_subtree("S", self.small_tree)
def time_bfs_medium_tree(self) -> None:
"""BFS on a 31-node binary tree (depth 5)."""
self.svc._compute_affected_subtree("M", self.medium_tree)
def time_bfs_large_tree(self) -> None:
"""BFS on a 255-node binary tree (depth 8)."""
self.svc._compute_affected_subtree("L", self.large_tree)
# ---------------------------------------------------------------------------
# Impact analysis benchmarks
# ---------------------------------------------------------------------------
class ImpactAnalysisSuite:
"""Benchmark full impact analysis including risk classification."""
def setup(self) -> None:
"""Prepare service and correction requests."""
self.svc = CorrectionService()
self.small_tree = _linear_tree("S", 3)
self.medium_tree = _binary_tree("M", 4)
self.large_tree = _binary_tree("L", 6)
self.req_small = self.svc.request_correction(
_PLAN_ID, "S", CorrectionMode.REVERT
)
self.req_medium = self.svc.request_correction(
_PLAN_ID, "M", CorrectionMode.REVERT
)
self.req_large = self.svc.request_correction(
_PLAN_ID, "L", CorrectionMode.REVERT
)
def time_impact_small(self) -> None:
"""Impact analysis on a 3-node chain."""
# Reset status so analyze_impact can run again
self.svc._corrections[
self.req_small.correction_id
].status = CorrectionStatus.PENDING
self.svc.analyze_impact(self.req_small.correction_id, self.small_tree)
def time_impact_medium(self) -> None:
"""Impact analysis on a 15-node binary tree."""
self.svc._corrections[
self.req_medium.correction_id
].status = CorrectionStatus.PENDING
self.svc.analyze_impact(self.req_medium.correction_id, self.medium_tree)
def time_impact_large(self) -> None:
"""Impact analysis on a 63-node binary tree."""
self.svc._corrections[
self.req_large.correction_id
].status = CorrectionStatus.PENDING
self.svc.analyze_impact(self.req_large.correction_id, self.large_tree)
# ---------------------------------------------------------------------------
# Dry-run report benchmarks
# ---------------------------------------------------------------------------
class DryRunReportSuite:
"""Benchmark dry-run report generation."""
def setup(self) -> None:
"""Prepare service and requests."""
self.svc = CorrectionService()
self.tree = _binary_tree("DR", 5)
self.req = self.svc.request_correction(_PLAN_ID, "DR", CorrectionMode.REVERT)
def time_dry_run_report(self) -> None:
"""Generate dry-run report for a 31-node tree."""
self.svc._corrections[self.req.correction_id].status = CorrectionStatus.PENDING
self.svc.generate_dry_run_report(self.req.correction_id, self.tree)
# ---------------------------------------------------------------------------
# Revert execution benchmarks
# ---------------------------------------------------------------------------
class RevertExecutionSuite:
"""Benchmark revert execution overhead."""
def _fresh_request(self, target: str) -> str:
"""Create a fresh correction request and return its ID."""
req = self.svc.request_correction(_PLAN_ID, target, CorrectionMode.REVERT)
return req.correction_id
def setup(self) -> None:
"""Prepare service and trees."""
self.svc = CorrectionService()
self.small_tree = _linear_tree("RS", 3)
self.medium_tree = _binary_tree("RM", 4)
def time_revert_small(self) -> None:
"""Revert execution on a 3-node chain."""
cid = self._fresh_request("RS")
self.svc.execute_revert(cid, self.small_tree)
def time_revert_medium(self) -> None:
"""Revert execution on a 15-node binary tree."""
cid = self._fresh_request("RM")
self.svc.execute_revert(cid, self.medium_tree)