feat(I): I-APPR-LOOP — agent loop human-in-the-loop approval integration (require_approval + approval_tools params, ApprovalRequest creation, workstream notification, pause loop), 8 tests passing
This commit is contained in:
@@ -149,6 +149,8 @@ async def run_react_loop(
|
||||
trace_id: str | None = None,
|
||||
on_step: Callable | None = None,
|
||||
dry_run: bool = False,
|
||||
require_approval: bool = False,
|
||||
approval_tools: list[str] | None = None,
|
||||
) -> ReActResult:
|
||||
"""Execute a ReAct loop: LLM reasoning → tool execution → repeat.
|
||||
|
||||
@@ -371,6 +373,44 @@ async def run_react_loop(
|
||||
"arguments": args,
|
||||
}
|
||||
)
|
||||
elif require_approval and (approval_tools is None or tool_name in (approval_tools or [])):
|
||||
# I-APPR-LOOP: Human-in-the-Loop Approval
|
||||
# Create an ApprovalRequest and pause the loop
|
||||
try:
|
||||
from app.core.approval import create_approval_request
|
||||
from app.ai.agent_workstream import post_approval_request
|
||||
|
||||
approval = await create_approval_request(
|
||||
db=db,
|
||||
tenant_id=tenant_id,
|
||||
entity_type="agent_run",
|
||||
entity_id=agent_run_id or uuid.uuid4(),
|
||||
action=f"tool:{tool_name}",
|
||||
requested_by=user_id,
|
||||
requested_by_type="agent",
|
||||
)
|
||||
|
||||
# Post approval request to workstream
|
||||
if agent_run_id:
|
||||
await post_approval_request(
|
||||
db=db,
|
||||
tenant_id=tenant_id,
|
||||
agent_id=getattr(agent_definition, "id", uuid.uuid4()),
|
||||
approval_id=approval.id,
|
||||
action=f"Tool '{tool_name}' requires approval",
|
||||
details={"tool_name": tool_name, "arguments": args},
|
||||
)
|
||||
|
||||
# Pause the loop — return with waiting_for_approval status
|
||||
result.status = "waiting_for_approval"
|
||||
result.error = f"Tool '{tool_name}' requires human approval (request_id: {approval.id})"
|
||||
result.steps_taken = step_num
|
||||
result.final_content = f"I need approval to execute tool '{tool_name}'. Approval request {approval.id} has been created."
|
||||
logger.info("Agent loop paused for approval on tool '%s' (request: %s)", tool_name, approval.id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning("Failed to create approval request for tool '%s': %s", tool_name, e)
|
||||
observation = json.dumps({"error": f"Approval required but failed to create request: {e}"})
|
||||
else:
|
||||
observation = await _execute_tool(tool_registry, tool_name, args, tool_context)
|
||||
observations.append(observation)
|
||||
|
||||
@@ -93,3 +93,29 @@ class TestIntegrationTools:
|
||||
)
|
||||
assert result["error"] == "Instance not found"
|
||||
assert result["status"] == "not_found"
|
||||
|
||||
|
||||
# ─── I-APPR-LOOP: Agent Loop Human-in-the-Loop Approval ──────────────────────
|
||||
|
||||
|
||||
class TestAgentLoopApproval:
|
||||
"""Test the I-APPR-LOOP approval integration in run_react_loop."""
|
||||
|
||||
def test_run_react_loop_has_approval_params(self):
|
||||
"""run_react_loop has require_approval and approval_tools parameters."""
|
||||
import inspect
|
||||
from app.ai.agent_loop import run_react_loop
|
||||
|
||||
sig = inspect.signature(run_react_loop)
|
||||
assert "require_approval" in sig.parameters
|
||||
assert "approval_tools" in sig.parameters
|
||||
assert sig.parameters["require_approval"].default is False
|
||||
assert sig.parameters["approval_tools"].default is None
|
||||
|
||||
def test_react_result_has_waiting_for_approval_status(self):
|
||||
"""ReActResult supports waiting_for_approval status."""
|
||||
from app.ai.agent_loop import ReActResult
|
||||
|
||||
result = ReActResult(final_content="", status="waiting_for_approval")
|
||||
assert result.status == "waiting_for_approval"
|
||||
assert result.final_content == ""
|
||||
|
||||
Reference in New Issue
Block a user