grg 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grg/__init__.py +124 -0
- grg/agent_governor.py +375 -0
- grg/cli.py +474 -0
- grg/config/grg-config.json +90 -0
- grg/config.py +125 -0
- grg/early_warning.py +273 -0
- grg/grg-config.json +90 -0
- grg/intervention.py +426 -0
- grg/metrics.py +994 -0
- grg-0.2.1.dist-info/METADATA +758 -0
- grg-0.2.1.dist-info/RECORD +15 -0
- grg-0.2.1.dist-info/WHEEL +5 -0
- grg-0.2.1.dist-info/entry_points.txt +2 -0
- grg-0.2.1.dist-info/licenses/LICENSE +21 -0
- grg-0.2.1.dist-info/top_level.txt +1 -0
grg/__init__.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GRG (Geometric Reasoning Governor) — Real-time structural integrity monitoring for LLM inference.
|
|
3
|
+
|
|
4
|
+
A Python package for monitoring and governing the structural integrity of LLM outputs
|
|
5
|
+
during token generation using Alpha-Drift Velocity (V_alpha) tracking.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
- AlphaMomentumTracker: Core GRG class for real-time EMA-smoothed alpha tracking
|
|
9
|
+
- SGO Metrics: Structural Geometry Optimization metrics for evaluating binary-outcome systems
|
|
10
|
+
- Configuration: Profile-based configuration for different sensitivity requirements
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from grg import AlphaMomentumTracker, evaluate_structural_geometry
|
|
14
|
+
from grg.config import get_config
|
|
15
|
+
|
|
16
|
+
# Real-time GRG tracking
|
|
17
|
+
tracker = AlphaMomentumTracker()
|
|
18
|
+
for token_logprob in token_stream:
|
|
19
|
+
alpha = compute_structural_alpha(token_logprob)
|
|
20
|
+
tracker.push(alpha)
|
|
21
|
+
stats = tracker.get_stats()
|
|
22
|
+
if stats["is_collapsing"]:
|
|
23
|
+
# Trigger thermal damping or e-stop
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
# Offline SGO evaluation
|
|
27
|
+
results = evaluate_structural_geometry(positive_outcomes, negative_outcomes)
|
|
28
|
+
print(f"Structural Alpha: {results['structural_alpha']:.4f}")
|
|
29
|
+
print(f"Ranking Score: {results['ranking_score']:.4f}")
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from grg.early_warning import EarlyWarningSystem, EWIReading
|
|
33
|
+
from grg.config import get_config
|
|
34
|
+
from grg.agent_governor import (
|
|
35
|
+
AgentTrajectoryGovernor,
|
|
36
|
+
GovernorAction,
|
|
37
|
+
AgentStepTelemetry,
|
|
38
|
+
GovernorDecision,
|
|
39
|
+
)
|
|
40
|
+
from grg.metrics import (
|
|
41
|
+
RANKING_PROFILES,
|
|
42
|
+
# Trackers (GRG Core)
|
|
43
|
+
AlphaMomentumTracker,
|
|
44
|
+
StructuralTracker,
|
|
45
|
+
# Utility
|
|
46
|
+
apply_utility_transformation,
|
|
47
|
+
calculate_cofactor_matrix,
|
|
48
|
+
# Entropy & Probability
|
|
49
|
+
calculate_entropy,
|
|
50
|
+
calculate_moment_sgo_metrics,
|
|
51
|
+
# Structural Metrics
|
|
52
|
+
calculate_normalized_structure_metrics,
|
|
53
|
+
calculate_positive_alpha_penalty,
|
|
54
|
+
calculate_probability_sgo_matrix,
|
|
55
|
+
# Ranking & Diagnostics
|
|
56
|
+
calculate_ranking_score,
|
|
57
|
+
calculate_regime_aware_ideal,
|
|
58
|
+
calculate_sgo_divergence,
|
|
59
|
+
compute_distributional_concentration,
|
|
60
|
+
# Core SGO Matrix Functions
|
|
61
|
+
compute_sgo_matrix,
|
|
62
|
+
compute_structural_alpha,
|
|
63
|
+
evaluate_balance_diagnostics,
|
|
64
|
+
evaluate_cofactors_norm_diff_diagnostics,
|
|
65
|
+
# Distributional Analysis
|
|
66
|
+
evaluate_distributional_geometry,
|
|
67
|
+
evaluate_structural_geometry,
|
|
68
|
+
extract_matrix_alpha,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
from grg.intervention import (
|
|
72
|
+
CandidateBatch,
|
|
73
|
+
GenerationResult,
|
|
74
|
+
InterventionConfig,
|
|
75
|
+
InterventionSampler,
|
|
76
|
+
TokenTelemetry,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
__all__ = [
|
|
80
|
+
# Core SGO Matrix Functions
|
|
81
|
+
"compute_sgo_matrix",
|
|
82
|
+
"compute_structural_alpha",
|
|
83
|
+
"calculate_moment_sgo_metrics",
|
|
84
|
+
"evaluate_structural_geometry",
|
|
85
|
+
# Structural Metrics
|
|
86
|
+
"calculate_normalized_structure_metrics",
|
|
87
|
+
"extract_matrix_alpha",
|
|
88
|
+
"calculate_cofactor_matrix",
|
|
89
|
+
"calculate_regime_aware_ideal",
|
|
90
|
+
# Ranking & Diagnostics
|
|
91
|
+
"calculate_ranking_score",
|
|
92
|
+
"calculate_positive_alpha_penalty",
|
|
93
|
+
"evaluate_balance_diagnostics",
|
|
94
|
+
"evaluate_cofactors_norm_diff_diagnostics",
|
|
95
|
+
# Entropy & Probability
|
|
96
|
+
"calculate_entropy",
|
|
97
|
+
"calculate_probability_sgo_matrix",
|
|
98
|
+
"calculate_sgo_divergence",
|
|
99
|
+
# Distributional Analysis
|
|
100
|
+
"evaluate_distributional_geometry",
|
|
101
|
+
"compute_distributional_concentration",
|
|
102
|
+
# Utility
|
|
103
|
+
"apply_utility_transformation",
|
|
104
|
+
# Trackers (GRG Core)
|
|
105
|
+
"AlphaMomentumTracker",
|
|
106
|
+
"StructuralTracker",
|
|
107
|
+
# Ranking Profiles
|
|
108
|
+
"RANKING_PROFILES",
|
|
109
|
+
# Config
|
|
110
|
+
"get_config",
|
|
111
|
+
# Intervention Sampler
|
|
112
|
+
"InterventionSampler",
|
|
113
|
+
"InterventionConfig",
|
|
114
|
+
"GenerationResult",
|
|
115
|
+
"TokenTelemetry",
|
|
116
|
+
"CandidateBatch",
|
|
117
|
+
"EarlyWarningSystem",
|
|
118
|
+
"EWIReading",
|
|
119
|
+
# Agent Trajectory Governor
|
|
120
|
+
"AgentTrajectoryGovernor",
|
|
121
|
+
"GovernorAction",
|
|
122
|
+
"AgentStepTelemetry",
|
|
123
|
+
"GovernorDecision",
|
|
124
|
+
]
|
grg/agent_governor.py
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Trajectory Governor (GRG for Autonomous AI Agents).
|
|
3
|
+
|
|
4
|
+
Applies Structural Geometric Optimization (SGO) and Kinematic Momentum Tracking
|
|
5
|
+
to multi-turn autonomous agent trajectories.
|
|
6
|
+
|
|
7
|
+
Monitors turn-by-turn health, detects loops, dead-ends, and error cascades
|
|
8
|
+
turns in advance of budget exhaustion, and triggers targeted interventions:
|
|
9
|
+
- Hint injection (re-anchoring)
|
|
10
|
+
- State rollback (pruning failed turns)
|
|
11
|
+
- Early stop (saving wasted inference budget)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import difflib
|
|
17
|
+
import json
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import Any, Sequence
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
|
|
24
|
+
from .metrics import AlphaMomentumTracker, calculate_moment_sgo_metrics
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GovernorAction(str, Enum):
|
|
28
|
+
"""Recommended governor action for current agent turn."""
|
|
29
|
+
PROCEED = "PROCEED" # Trajectory healthy, continue normal execution
|
|
30
|
+
WARN = "WARN" # Minor degradation, log warning
|
|
31
|
+
HINT = "HINT" # Inject reflection prompt / counter-bias
|
|
32
|
+
ROLLBACK = "ROLLBACK" # Prune recent failing turns, reset state
|
|
33
|
+
ABORT = "ABORT" # Structural collapse detected, terminate trajectory
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class AgentStepTelemetry:
|
|
38
|
+
"""Telemetry captured from an agent at turn t."""
|
|
39
|
+
step_index: int
|
|
40
|
+
action_type: str = "tool_call" # "tool_call", "thought", "final_answer"
|
|
41
|
+
tool_name: str | None = None
|
|
42
|
+
tool_args: dict[str, Any] | str | None = None
|
|
43
|
+
tool_success: bool = True
|
|
44
|
+
error_message: str | None = None
|
|
45
|
+
semantic_progress: float = 0.5 # Progress towards goal in [0.0, 1.0]
|
|
46
|
+
tokens_consumed: int = 0
|
|
47
|
+
extra_metadata: dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class GovernorDecision:
|
|
52
|
+
"""Governor evaluation output for turn t."""
|
|
53
|
+
step_index: int
|
|
54
|
+
action: GovernorAction
|
|
55
|
+
alpha: float # Current structural health alpha in [0, 1]
|
|
56
|
+
v_alpha: float # Velocity of alpha change (EMA)
|
|
57
|
+
m_alpha: float # Momentum / acceleration of change (EMA)
|
|
58
|
+
horizon_turns: float # Predicted turns before health drops below floor
|
|
59
|
+
rho_pn_sq: float # SGO win/loss coupling correlation squared
|
|
60
|
+
e_p: float # Positive utility expectation
|
|
61
|
+
e_n: float # Negative utility expectation
|
|
62
|
+
repetition_score: float # Call repetition metric [0, 1]
|
|
63
|
+
message: str # Human/LLM-readable diagnostic explanation
|
|
64
|
+
suggested_hint: str | None = None
|
|
65
|
+
rollback_turns: int = 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AgentTrajectoryGovernor:
|
|
69
|
+
"""
|
|
70
|
+
Multi-Turn Agent Trajectory Governor using SGO and Momentum Kinematics.
|
|
71
|
+
|
|
72
|
+
Evaluates (P_u, N_u) utility pairs per turn:
|
|
73
|
+
- P_u (Positive Utility): Goal alignment, novelty, successful execution.
|
|
74
|
+
- N_u (Negative Utility): Execution errors, parameter looping, stagnation.
|
|
75
|
+
|
|
76
|
+
Tracks trajectory dynamics using AlphaMomentumTracker and Moment SGO metrics.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
window_size: int = 10,
|
|
82
|
+
alpha_ema: float = 0.35,
|
|
83
|
+
safety_floor: float = 0.15,
|
|
84
|
+
panic_threshold: float = -0.04,
|
|
85
|
+
repetition_threshold: float = 0.75,
|
|
86
|
+
hint_horizon: float = 4.0,
|
|
87
|
+
abort_horizon: float = 1.5,
|
|
88
|
+
min_steps_before_abort: int = 3,
|
|
89
|
+
):
|
|
90
|
+
self.window_size = window_size
|
|
91
|
+
self.alpha_ema = alpha_ema
|
|
92
|
+
self.safety_floor = safety_floor
|
|
93
|
+
self.panic_threshold = panic_threshold
|
|
94
|
+
self.repetition_threshold = repetition_threshold
|
|
95
|
+
self.hint_horizon = hint_horizon
|
|
96
|
+
self.abort_horizon = abort_horizon
|
|
97
|
+
self.min_steps_before_abort = min_steps_before_abort
|
|
98
|
+
|
|
99
|
+
self.tracker = AlphaMomentumTracker(window_size=window_size, alpha_ema=alpha_ema)
|
|
100
|
+
self.history: list[AgentStepTelemetry] = []
|
|
101
|
+
self.outcomes: list[tuple[float, float]] = [] # (P_u, N_u) pairs
|
|
102
|
+
self.decisions: list[GovernorDecision] = []
|
|
103
|
+
self.consecutive_warnings: int = 0
|
|
104
|
+
self.consecutive_hints: int = 0
|
|
105
|
+
|
|
106
|
+
def reset(self) -> None:
|
|
107
|
+
"""Reset governor state for a new agent trajectory."""
|
|
108
|
+
self.tracker = AlphaMomentumTracker(window_size=self.window_size, alpha_ema=self.alpha_ema)
|
|
109
|
+
self.history.clear()
|
|
110
|
+
self.outcomes.clear()
|
|
111
|
+
self.decisions.clear()
|
|
112
|
+
self.consecutive_warnings = 0
|
|
113
|
+
self.consecutive_hints = 0
|
|
114
|
+
|
|
115
|
+
def _normalize_args(self, args: dict[str, Any] | str | None) -> str:
|
|
116
|
+
"""Convert tool arguments to a stable string representation for exact equality."""
|
|
117
|
+
if args is None:
|
|
118
|
+
return ""
|
|
119
|
+
if isinstance(args, str):
|
|
120
|
+
return args.strip()
|
|
121
|
+
try:
|
|
122
|
+
return json.dumps(args, sort_keys=True)
|
|
123
|
+
except Exception:
|
|
124
|
+
return str(args)
|
|
125
|
+
|
|
126
|
+
def _extract_arg_values(self, args: dict[str, Any] | str | None) -> str:
|
|
127
|
+
"""Extract argument values to compare similarity without JSON key boilerplate."""
|
|
128
|
+
if args is None:
|
|
129
|
+
return ""
|
|
130
|
+
if isinstance(args, str):
|
|
131
|
+
return args.strip()
|
|
132
|
+
if isinstance(args, dict):
|
|
133
|
+
return " ".join(str(v) for v in args.values()).strip()
|
|
134
|
+
return str(args).strip()
|
|
135
|
+
|
|
136
|
+
def _compute_repetition_penalty(
|
|
137
|
+
self, current: AgentStepTelemetry, history: list[AgentStepTelemetry] | None = None
|
|
138
|
+
) -> float:
|
|
139
|
+
"""
|
|
140
|
+
Check for repetitive or oscillating tool calls in recent history.
|
|
141
|
+
Returns penalty in [0.0, 1.0].
|
|
142
|
+
"""
|
|
143
|
+
hist = self.history if history is None else history
|
|
144
|
+
if not hist or current.tool_name is None:
|
|
145
|
+
return 0.0
|
|
146
|
+
|
|
147
|
+
current_norm = self._normalize_args(current.tool_args)
|
|
148
|
+
current_vals = self._extract_arg_values(current.tool_args)
|
|
149
|
+
lookback = min(len(hist), self.window_size)
|
|
150
|
+
recent = hist[-lookback:]
|
|
151
|
+
|
|
152
|
+
max_sim = 0.0
|
|
153
|
+
exact_matches = 0
|
|
154
|
+
|
|
155
|
+
for past in reversed(recent):
|
|
156
|
+
if past is current or past.step_index == current.step_index:
|
|
157
|
+
continue
|
|
158
|
+
if past.tool_name != current.tool_name:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
past_norm = self._normalize_args(past.tool_args)
|
|
162
|
+
if current_norm == past_norm:
|
|
163
|
+
exact_matches += 1
|
|
164
|
+
max_sim = max(max_sim, 1.0)
|
|
165
|
+
else:
|
|
166
|
+
past_vals = self._extract_arg_values(past.tool_args)
|
|
167
|
+
ratio = difflib.SequenceMatcher(None, current_vals, past_vals).ratio()
|
|
168
|
+
max_sim = max(max_sim, ratio)
|
|
169
|
+
|
|
170
|
+
if exact_matches >= 2:
|
|
171
|
+
return 1.0
|
|
172
|
+
if exact_matches == 1:
|
|
173
|
+
return 0.75
|
|
174
|
+
if max_sim >= 0.90:
|
|
175
|
+
return 0.50
|
|
176
|
+
return 0.0
|
|
177
|
+
|
|
178
|
+
def _compute_step_utilities(
|
|
179
|
+
self, step: AgentStepTelemetry, rep_penalty: float
|
|
180
|
+
) -> tuple[float, float]:
|
|
181
|
+
"""
|
|
182
|
+
Derive positive (P_u) and negative (N_u) utility for this turn.
|
|
183
|
+
"""
|
|
184
|
+
# 1. Positive utility: goal progress scaled by execution success and novelty
|
|
185
|
+
base_progress = max(0.0, min(1.0, float(step.semantic_progress)))
|
|
186
|
+
if step.tool_success:
|
|
187
|
+
p_u = base_progress * (1.0 - 0.5 * rep_penalty)
|
|
188
|
+
# Extra reward for completed goal or milestone
|
|
189
|
+
if step.action_type == "final_answer":
|
|
190
|
+
p_u += 0.5
|
|
191
|
+
else:
|
|
192
|
+
# Failed tool call provides minimal positive learning signal
|
|
193
|
+
p_u = base_progress * 0.15
|
|
194
|
+
|
|
195
|
+
# 2. Negative utility: failure penalty + repetition + stall
|
|
196
|
+
n_u = 0.0
|
|
197
|
+
if not step.tool_success:
|
|
198
|
+
n_u += 1.0
|
|
199
|
+
if rep_penalty > 0:
|
|
200
|
+
n_u += rep_penalty * 1.5
|
|
201
|
+
if step.error_message:
|
|
202
|
+
n_u += 0.5
|
|
203
|
+
if step.semantic_progress < 0.15:
|
|
204
|
+
n_u += 0.25
|
|
205
|
+
|
|
206
|
+
return (max(0.0, p_u), max(0.0, n_u))
|
|
207
|
+
|
|
208
|
+
def step(self, telemetry: AgentStepTelemetry) -> GovernorDecision:
|
|
209
|
+
"""
|
|
210
|
+
Record a step and evaluate trajectory health.
|
|
211
|
+
Returns GovernorDecision with recommended action.
|
|
212
|
+
"""
|
|
213
|
+
rep_score = self._compute_repetition_penalty(telemetry, self.history)
|
|
214
|
+
p_u, n_u = self._compute_step_utilities(telemetry, rep_score)
|
|
215
|
+
self.history.append(telemetry)
|
|
216
|
+
self.outcomes.append((p_u, n_u))
|
|
217
|
+
|
|
218
|
+
# Evaluate SGO metrics over recent window
|
|
219
|
+
window_outcomes = self.outcomes[-self.window_size:]
|
|
220
|
+
if len(window_outcomes) < 2:
|
|
221
|
+
# Single observation has geometric cosine of 1.0 trivially; requires >= 2 samples
|
|
222
|
+
rho_pn_sq = 0.0
|
|
223
|
+
e_p = float(p_u)
|
|
224
|
+
e_n = float(n_u)
|
|
225
|
+
else:
|
|
226
|
+
# Build Moment SGO units: positive returns > 0, negative returns < 0
|
|
227
|
+
sgo_units: list[np.ndarray] = []
|
|
228
|
+
for p, n in window_outcomes:
|
|
229
|
+
if p > 0 and n > 0:
|
|
230
|
+
sgo_units.append(np.array([p, -n], dtype=float))
|
|
231
|
+
elif p > 0:
|
|
232
|
+
sgo_units.append(np.array([p], dtype=float))
|
|
233
|
+
elif n > 0:
|
|
234
|
+
sgo_units.append(np.array([-n], dtype=float))
|
|
235
|
+
else:
|
|
236
|
+
sgo_units.append(np.array([0.0], dtype=float))
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
sgo_metrics = calculate_moment_sgo_metrics(sgo_units)
|
|
240
|
+
rho_pn_sq = float(sgo_metrics["rho_pn_sq"])
|
|
241
|
+
e_p = float(sgo_metrics["e_p"])
|
|
242
|
+
e_n = float(sgo_metrics["e_n"])
|
|
243
|
+
except Exception:
|
|
244
|
+
rho_pn_sq = 0.0
|
|
245
|
+
e_p = float(p_u)
|
|
246
|
+
e_n = float(n_u)
|
|
247
|
+
|
|
248
|
+
# SGO Structural Alpha for Agent:
|
|
249
|
+
# Bounded in [0, 1]. High when P > N and P, N are decoupled.
|
|
250
|
+
# Degrades when N >> P or when failure co-occurs repeatedly with stall (rho_pn_sq -> 1).
|
|
251
|
+
balance_ratio = e_p / (e_p + e_n + 1e-8)
|
|
252
|
+
coupling_discount = 1.0 - 0.7 * min(1.0, rho_pn_sq)
|
|
253
|
+
raw_alpha = float(balance_ratio * coupling_discount)
|
|
254
|
+
step_alpha = max(0.0, min(1.0, raw_alpha))
|
|
255
|
+
|
|
256
|
+
# Push to kinematic tracker
|
|
257
|
+
self.tracker.push(step_alpha)
|
|
258
|
+
stats = self.tracker.get_stats(v_panic=self.panic_threshold, floor=self.safety_floor)
|
|
259
|
+
|
|
260
|
+
v_alpha = float(stats["v_alpha"])
|
|
261
|
+
m_alpha = float(stats["m_alpha"])
|
|
262
|
+
horizon = float(stats["horizon"])
|
|
263
|
+
|
|
264
|
+
# Decision Policy Logic
|
|
265
|
+
action = GovernorAction.PROCEED
|
|
266
|
+
hint: str | None = None
|
|
267
|
+
rollback_turns = 0
|
|
268
|
+
reason: list[str] = []
|
|
269
|
+
|
|
270
|
+
# Check for immediate hard loop
|
|
271
|
+
failed_repeat = (not telemetry.tool_success) and (rep_score >= 0.75)
|
|
272
|
+
if failed_repeat:
|
|
273
|
+
reason.append(f"Failing tool call '{telemetry.tool_name}' repeated.")
|
|
274
|
+
|
|
275
|
+
# Kinematic checks
|
|
276
|
+
if stats["is_collapsing"] or v_alpha < self.panic_threshold:
|
|
277
|
+
reason.append(f"Rapid health velocity collapse (V_α={v_alpha:.3f} < {self.panic_threshold}).")
|
|
278
|
+
|
|
279
|
+
if m_alpha < self.panic_threshold / 2.0 and v_alpha < 0:
|
|
280
|
+
reason.append(f"Accelerating trajectory decay (M_α={m_alpha:.3f}).")
|
|
281
|
+
|
|
282
|
+
if horizon <= self.hint_horizon and horizon < 900:
|
|
283
|
+
reason.append(f"Remaining safe horizon low (H={horizon:.1f} turns).")
|
|
284
|
+
|
|
285
|
+
# Determine GovernorAction level
|
|
286
|
+
can_abort = len(self.history) >= self.min_steps_before_abort
|
|
287
|
+
if can_abort and (
|
|
288
|
+
step_alpha < self.safety_floor
|
|
289
|
+
or (horizon <= self.abort_horizon and v_alpha < -0.05)
|
|
290
|
+
or self.consecutive_hints >= 3
|
|
291
|
+
):
|
|
292
|
+
action = GovernorAction.ABORT
|
|
293
|
+
msg = f"ABORT: Trajectory collapsed (α={step_alpha:.3f}, H={horizon:.1f}). " + " ".join(reason)
|
|
294
|
+
elif (
|
|
295
|
+
m_alpha < self.panic_threshold
|
|
296
|
+
or (rep_score >= 1.0 and not telemetry.tool_success)
|
|
297
|
+
or (self.consecutive_hints >= 1 and failed_repeat)
|
|
298
|
+
or (self.consecutive_warnings >= 2 and v_alpha < self.panic_threshold)
|
|
299
|
+
):
|
|
300
|
+
action = GovernorAction.ROLLBACK
|
|
301
|
+
rollback_turns = min(3, max(1, len(self.history) - 1))
|
|
302
|
+
msg = f"ROLLBACK: Severe stagnation or loop detected (α={step_alpha:.3f}, M_α={m_alpha:.3f}). " + " ".join(reason)
|
|
303
|
+
hint = self._build_reflection_hint(telemetry)
|
|
304
|
+
elif horizon <= self.hint_horizon or v_alpha < self.panic_threshold or failed_repeat or (not can_abort and step_alpha < self.safety_floor):
|
|
305
|
+
action = GovernorAction.HINT
|
|
306
|
+
self.consecutive_hints += 1
|
|
307
|
+
msg = f"HINT: Trajectory degrading (α={step_alpha:.3f}, V_α={v_alpha:.3f}, H={horizon:.1f}). " + " ".join(reason)
|
|
308
|
+
hint = self._build_reflection_hint(telemetry)
|
|
309
|
+
elif v_alpha < self.panic_threshold / 2.0 or rep_score >= 0.5:
|
|
310
|
+
action = GovernorAction.WARN
|
|
311
|
+
self.consecutive_warnings += 1
|
|
312
|
+
msg = f"WARN: Trajectory softening (α={step_alpha:.3f}, V_α={v_alpha:.3f}). " + " ".join(reason)
|
|
313
|
+
else:
|
|
314
|
+
action = GovernorAction.PROCEED
|
|
315
|
+
self.consecutive_warnings = 0
|
|
316
|
+
self.consecutive_hints = 0
|
|
317
|
+
msg = f"PROCEED: Trajectory healthy (α={step_alpha:.3f}, V_α={v_alpha:.3f}, H={horizon:.1f})."
|
|
318
|
+
|
|
319
|
+
decision = GovernorDecision(
|
|
320
|
+
step_index=telemetry.step_index,
|
|
321
|
+
action=action,
|
|
322
|
+
alpha=step_alpha,
|
|
323
|
+
v_alpha=v_alpha,
|
|
324
|
+
m_alpha=m_alpha,
|
|
325
|
+
horizon_turns=horizon,
|
|
326
|
+
rho_pn_sq=rho_pn_sq,
|
|
327
|
+
e_p=e_p,
|
|
328
|
+
e_n=e_n,
|
|
329
|
+
repetition_score=rep_score,
|
|
330
|
+
message=msg,
|
|
331
|
+
suggested_hint=hint,
|
|
332
|
+
rollback_turns=rollback_turns,
|
|
333
|
+
)
|
|
334
|
+
self.decisions.append(decision)
|
|
335
|
+
return decision
|
|
336
|
+
|
|
337
|
+
def _build_reflection_hint(self, step: AgentStepTelemetry) -> str:
|
|
338
|
+
"""Construct an actionable prompt reflection hint for the agent."""
|
|
339
|
+
if step.tool_name == "write_file" and step.tool_success:
|
|
340
|
+
return "File changes written successfully. Next step: call run_tests() to verify your fix."
|
|
341
|
+
|
|
342
|
+
parts = ["CRITICAL GOVERNANCE ALERT: Trajectory is degenerating."]
|
|
343
|
+
if step.tool_name:
|
|
344
|
+
parts.append(f"- Tool '{step.tool_name}' failed or repeated without forward progress.")
|
|
345
|
+
if step.error_message:
|
|
346
|
+
short_err = step.error_message[:120].strip()
|
|
347
|
+
parts.append(f"- Last error: {short_err}")
|
|
348
|
+
parts.append("- Mandate: Do NOT repeat the exact same command or query.")
|
|
349
|
+
parts.append("- Mandate: Check available files with list_files(), inspect code with read_file(), or run verification tests.")
|
|
350
|
+
return " ".join(parts)
|
|
351
|
+
|
|
352
|
+
def get_summary(self) -> dict[str, Any]:
|
|
353
|
+
"""Trajectory governance diagnostic summary."""
|
|
354
|
+
total_steps = len(self.history)
|
|
355
|
+
if total_steps == 0:
|
|
356
|
+
return {"total_steps": 0, "status": "NO_STEPS"}
|
|
357
|
+
|
|
358
|
+
alphas = [d.alpha for d in self.decisions]
|
|
359
|
+
actions = [d.action.value for d in self.decisions]
|
|
360
|
+
aborted = any(d.action == GovernorAction.ABORT for d in self.decisions)
|
|
361
|
+
rollbacks = sum(1 for d in self.decisions if d.action == GovernorAction.ROLLBACK)
|
|
362
|
+
hints = sum(1 for d in self.decisions if d.action == GovernorAction.HINT)
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
"total_steps": total_steps,
|
|
366
|
+
"final_alpha": alphas[-1] if alphas else 0.0,
|
|
367
|
+
"min_alpha": min(alphas) if alphas else 0.0,
|
|
368
|
+
"mean_alpha": float(np.mean(alphas)) if alphas else 0.0,
|
|
369
|
+
"aborted": aborted,
|
|
370
|
+
"rollback_count": rollbacks,
|
|
371
|
+
"hint_count": hints,
|
|
372
|
+
"action_counts": {
|
|
373
|
+
act: actions.count(act) for act in {a.value for a in GovernorAction}
|
|
374
|
+
},
|
|
375
|
+
}
|