org_level_loop.py 3.1 KB
"""U-017: Org-level Loop closed loop (architecture doc §11 MVP acceptance
criterion 4) -- ties U-015 (root-cause judgment) + U-016 (routing) + real
Coder/Validator calls into one function: given a task sitting at
'validating' with a known bug, autonomously locate root cause, route to
the right Org Agent, drive the fix, and re-validate back to 'done'.
"""

from src import bug_record, org_loop_route, org_node_trigger, root_cause_trace

_ROUTE_FOR_CATEGORY = {"coder": "fast_lane", "planning": "full_replan"}


def run_fix_loop(conn, task_id, target_folder_path, bug_description, context, fix_prompt, verify_prompt, timeout=300):
    """Returns {"root_cause_category", "route", "verified", "final_status",
    "verify_response"}. Raises root_cause_trace.InconclusiveEscalateError if
    the 3 perspectives don't reach 2/3 agreement (same as U-015 -- this
    function doesn't add its own fallback for that, matching 04-P's own
    "升级人工" answer to disagreement).

    **F (2026-08-06, docs/done/verifier-fan-out-jury-design.md)**: root
    cause judgment now runs as a real Work Graph fan-out (each perspective
    is its own Work Node with its own clean context) instead of three
    sequential Python closures calling org_node_trigger.trigger_org_node --
    see root_cause_trace.judge_root_cause_via_work_graph. The fixer/verify
    calls below are unchanged flat trigger_org_node calls; only root-cause
    judgment moved."""
    judgment = root_cause_trace.judge_root_cause_via_work_graph(
        conn, task_id, target_folder_path, bug_description, context, timeout=timeout,
    )
    category = judgment["root_cause_category"]
    route = _ROUTE_FOR_CATEGORY.get(category, "full_replan")

    bug_record.create_bug_record(
        conn, task_id=task_id, description=bug_description,
        root_cause_category=category, root_cause_detail=judgment["root_cause_detail"],
        suggested_route=route,
    )
    org_loop_route.route_org_loop(conn, task_id, route)

    fixer_node = "coder" if route == "fast_lane" else "planner"
    org_node_trigger.trigger_org_node(fixer_node, fix_prompt, target_folder_path, timeout=timeout)

    verify_response = org_node_trigger.trigger_org_node("validator", verify_prompt, target_folder_path, timeout=timeout)
    verified = "PASS" in verify_response.upper()

    if verified:
        # A real Work Graph would move coding/planning_brd_prd_sdd back to
        # 'validating' once the fixer's own Work Graph completes (U-011/012/013,
        # same mechanism U-014 uses) -- no separate controller exists for that
        # hop, so this mirrors it directly rather than inventing one just for
        # this Unit.
        conn.execute("UPDATE task SET status = 'validating' WHERE task_id = ?", (task_id,))
        conn.commit()
        org_loop_route.close_task_done(conn, task_id)
        final_status = "done"
    else:
        final_status = conn.execute("SELECT status FROM task WHERE task_id = ?", (task_id,)).fetchone()[0]

    return {
        "root_cause_category": category,
        "route": route,
        "verified": verified,
        "final_status": final_status,
        "verify_response": verify_response,
    }