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 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
+ }