feat(F): F-PERM permissions, F-APPR approval, F-AIUSE metadata, F-TRANS transparency, F-DATA-POL data policy, F-OVERSIGHT decision record, F-DRY dry-run, F-AUDIT audit log
Check Cross-Plugin Imports / check (push) Has been cancelled

- F-PERM: app/ai/agent_permissions.py (230 lines) — AgentPermissionContext, resolve_agent_permissions(), filter_visible_agents(), check_agent_execute_permission(), optimistic locking
- F-APPR: app/core/approval.py (160 lines) + app/routes/approvals.py (305 lines) + migration 0123 — ApprovalRequest model, CRUD API, approve/reject/expire
- F-AIUSE: app/ai/ai_use_case.py (156 lines) — AIUseCaseMetadata Pydantic model, validate_ai_use_case()
- F-TRANS: app/ai/transparency.py (60 lines) — mark_as_ai_generated(), is_ai_participant()
- F-DATA-POL: app/ai/data_policy.py (210 lines) — enforce_data_policy() with SENSITIVE_FIELDS + provider compliance
- F-OVERSIGHT: app/ai/oversight.py (108 lines) — DecisionRecord, create_decision_record()
- F-DRY: agent_loop.py updated with dry_run parameter
- F-AUDIT: agent_loop.py updated with audit log for tool calls
- agent_routes.py: AI use case metadata endpoints added
- main.py: approval routes registered
- All Python compile checks pass
This commit is contained in:
Agent Zero
2026-08-17 16:57:50 +02:00
parent dbeadd8ab1
commit 638e3f3e1e
11 changed files with 1455 additions and 1 deletions
+55 -1
View File
@@ -148,6 +148,7 @@ async def run_react_loop(
timeout_seconds: int = 300,
trace_id: str | None = None,
on_step: Callable | None = None,
dry_run: bool = False,
) -> ReActResult:
"""Execute a ReAct loop: LLM reasoning → tool execution → repeat.
@@ -164,6 +165,9 @@ async def run_react_loop(
timeout_seconds: Overall timeout (default 300).
trace_id: Optional trace ID for correlation.
on_step: Optional async callback fired after each step.
dry_run: When True, tool execution is simulated — tool handlers are
NOT called. A mock result is returned instead and steps are still
logged with real LLM cost.
Returns:
ReActResult with final content, steps, cost, and status.
@@ -193,6 +197,38 @@ async def run_react_loop(
"db": db,
}
# Audit helper — records every tool call in the audit log.
async def _audit_tool_call(
step_number: int,
tool_name: str,
arguments: dict[str, Any],
result: str,
cost_usd: float,
) -> None:
"""Create an audit log entry for a single tool call."""
try:
from app.core.audit import log_audit
await log_audit(
db=db,
tenant_id=tenant_id,
user_id=user_id,
action="agent.tool_call",
entity_type="agent_run",
entity_id=agent_run_id,
details={
"agent_run_id": str(agent_run_id) if agent_run_id else None,
"step_number": step_number,
"tool_name": tool_name,
"arguments": arguments,
"result": result[:2000],
"cost_usd": cost_usd,
"dry_run": dry_run,
},
)
except Exception:
logger.exception("Failed to audit tool call '%s'", tool_name)
for step_num in range(1, max_steps + 1):
# ── Timeout check ──
elapsed = time.monotonic() - start_time
@@ -327,9 +363,27 @@ async def run_react_loop(
args = {}
logger.warning("Invalid JSON arguments for tool '%s': %s", tool_name, tc["arguments"])
observation = await _execute_tool(tool_registry, tool_name, args, tool_context)
if dry_run:
observation = json.dumps(
{
"dry_run": True,
"would_execute": tool_name,
"arguments": args,
}
)
else:
observation = await _execute_tool(tool_registry, tool_name, args, tool_context)
observations.append(observation)
# Audit every tool call (real or simulated)
await _audit_tool_call(
step_number=step_num,
tool_name=tool_name,
arguments=args,
result=observation,
cost_usd=cost_usd / len(tool_calls) if tool_calls else cost_usd,
)
# Feed tool result back into conversation
full_messages.append({
"role": "tool",