canopy-258m-r3 / miniswardbower /agents /flow_reasoning_refiner.py
psikosen's picture
Update to v6: Tri-Engine Swarm Coordinator, URL Invariance, Thought Bus & State Verification
ed79a7f verified
Raw History Blame Contribute Delete
6.69 kB
"""
Flow Reasoning Engine with Fixed-Point Forcing (FPF).
Synthesized from:
"Flow Reasoning Models: Turning Flows Into Efficient Recurrent Reasoners"
(Georgia Tech / MIT / MIT-IBM / IBM Research, Sep 2026).
Treats reasoning as iterative solution refinement toward a stable attractor.
Rather than committing irrevocably to one-shot greedy decisions, candidate
browser actions and spatial grounding coordinates undergo recurrent flow
refinement over held states:
s^{(k+1)} = Refine(s^{(k)}, Observation)
until converging to a globally consistent fixed point.
"""
from __future__ import annotations
import math
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Tuple
from miniswardbower.core.schemas import BrowserAction, BrowserActionType, InteractiveElement, PrunedAXTree
@dataclass
class FlowRefinementState:
"""Represents the recurrent held-state evolving toward a stable attractor."""
step_index: int
candidate_action: BrowserAction
spatial_coords: Tuple[float, float]
confidence: float
residual_norm: float = 1.0
converged: bool = False
iterations_run: int = 0
refinement_trace: List[Dict[str, Any]] = field(default_factory=list)
class FlowReasoningRefiner:
"""
Recurrent Flow Reasoner with Fixed-Point Iteration for Browser Grounding & Actions.
Iteratively refines candidate coordinates and parameter schemas to prevent cascade failures.
"""
def __init__(
self,
max_refinement_depth: int = 4,
convergence_threshold: float = 0.05,
damping_factor: float = 0.65,
):
self.max_depth = max_refinement_depth
self.threshold = convergence_threshold
self.damping = damping_factor
@staticmethod
def _find_element(tree: Optional[PrunedAXTree], target_id: Optional[str]) -> Optional[InteractiveElement]:
if not tree or not target_id:
return None
for el in tree.elements:
if el.id == target_id or el.selector == target_id:
return el
return None
def refine_action(
self,
initial_action: BrowserAction,
tree: Optional[PrunedAXTree] = None,
viewport_dims: Tuple[int, int] = (1280, 900),
) -> FlowRefinementState:
"""
Runs recurrent fixed-point refinement on candidate action and coordinates.
Converges candidate toward the optimal, collision-free attractor.
"""
t0 = time.perf_counter()
vw, vh = viewport_dims
# Extract initial spatial coordinates
cur_x, cur_y = 0.0, 0.0
if getattr(initial_action, "coords", None):
cur_x, cur_y = float(initial_action.coords[0]), float(initial_action.coords[1])
elif tree and initial_action.target:
target_el = self._find_element(tree, initial_action.target)
if target_el and target_el.bbox:
bx, by, bw, bh = target_el.bbox
cur_x, cur_y = bx + bw / 2.0, by + bh / 2.0
else:
cur_x, cur_y = float(vw / 2.0), float(vh / 2.0)
else:
cur_x, cur_y = float(vw / 2.0), float(vh / 2.0)
cur_action = initial_action.model_copy(deep=True)
cur_confidence = 0.50
state = FlowRefinementState(
step_index=0,
candidate_action=cur_action,
spatial_coords=(cur_x, cur_y),
confidence=cur_confidence,
)
trace = []
# Recurrent refinement loop
for depth in range(1, self.max_depth + 1):
prev_x, prev_y = cur_x, cur_y
prev_conf = cur_confidence
# 1. Attractor gradient from DOM layout
target_el: Optional[InteractiveElement] = None
if tree and cur_action.target:
target_el = self._find_element(tree, cur_action.target)
attractor_x, attractor_y = prev_x, prev_y
target_valid = False
if target_el:
target_valid = True
if target_el.bbox:
bx, by, bw, bh = target_el.bbox
attractor_x = bx + bw / 2.0
attractor_y = by + bh / 2.0
# Type mismatch refinement (e.g. TYPE requested on non-editable tag)
if cur_action.op == BrowserActionType.TYPE:
if target_el.tag not in ("input", "textarea") and target_el.element_type not in ("textbox", "search"):
# Search for adjacent editable child/sibling in tree
for alt_el in tree.elements:
if alt_el.tag in ("input", "textarea") and alt_el.bbox:
cur_action.target = alt_el.id
cur_action.selector = alt_el.selector
abx, aby, abw, abh = alt_el.bbox
attractor_x = abx + abw / 2.0
attractor_y = aby + abh / 2.0
break
# 2. Viewport boundary attraction (keep coordinates within visible viewport)
attractor_x = max(10.0, min(float(vw - 10.0), attractor_x))
attractor_y = max(10.0, min(float(vh - 10.0), attractor_y))
# 3. Fixed-point update with damping
dx = (attractor_x - cur_x) * self.damping
dy = (attractor_y - cur_y) * self.damping
cur_x += dx
cur_y += dy
# 4. Confidence refinement
if target_valid:
cur_confidence = min(0.99, cur_confidence + 0.15)
else:
cur_confidence = max(0.20, cur_confidence - 0.10)
# Residual norm
delta_dist = math.sqrt(dx * dx + dy * dy)
residual = delta_dist / max(1.0, math.sqrt(float(vw * vw + vh * vh)))
trace.append({
"depth": depth,
"coords": (round(cur_x, 2), round(cur_y, 2)),
"residual": round(residual, 5),
"confidence": round(cur_confidence, 3),
})
if residual < self.threshold:
state.converged = True
state.iterations_run = depth
state.residual_norm = residual
break
if not state.converged:
state.iterations_run = self.max_depth
state.residual_norm = residual
cur_action.coords = (cur_x, cur_y)
state.candidate_action = cur_action
state.spatial_coords = (cur_x, cur_y)
state.confidence = cur_confidence
state.refinement_trace = trace
return state