annator-command-center / tools /agent_guidance_canvas_tool.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 10)
a37e6db verified
Raw
History Blame Contribute Delete
15.6 kB
"""
Agent Guidance Canvas Tool
Provides real-time agent operation broadcasting to canvas for transparency
and visibility into what agents are doing.
Features:
- Operation start/update/complete broadcasting
- Real-time progress tracking
- Context explanations (what/why/next)
- Operation logging
- Full governance integration
"""
from datetime import datetime
import json
import logging
from typing import Any, Dict, List, Optional
import uuid
from sqlalchemy.orm import Session
from core.agent_context_resolver import AgentContextResolver
from core.agent_governance_service import AgentGovernanceService
from core.models import AgentExecution, AgentOperationTracker, AgentRegistry, CanvasAudit
from core.websockets import manager as ws_manager
logger = logging.getLogger(__name__)
# Feature flags
import os
AGENT_GUIDANCE_ENABLED = os.getenv("AGENT_GUIDANCE_ENABLED", "true").lower() == "true"
EMERGENCY_GOVERNANCE_BYPASS = os.getenv("EMERGENCY_GOVERNANCE_BYPASS", "false").lower() == "true"
class AgentGuidanceSystem:
"""
Real-time agent operation broadcasting system for canvas visibility.
Provides transparency into agent operations with live progress tracking,
contextual explanations, and operation logs.
"""
def __init__(self, db: Session):
self.db = db
self.resolver = AgentContextResolver(db)
self.governance = AgentGovernanceService(db)
async def start_operation(
self,
user_id: str,
agent_id: str,
operation_type: str,
context: Dict[str, Any],
total_steps: Optional[int] = None,
metadata: Optional[Dict[str, Any]] = None
) -> str:
"""
Start a new agent operation and broadcast to canvas.
Args:
user_id: User ID to broadcast to
agent_id: Agent ID performing the operation
operation_type: Type of operation (integration_connect, browser_automate, etc.)
context: Context dict with what/why/next explanations
total_steps: Total number of steps (if known)
metadata: Optional metadata about the operation
Returns:
operation_id: Unique operation ID for tracking
"""
if not AGENT_GUIDANCE_ENABLED:
return str(uuid.uuid4())
try:
# Generate operation ID
operation_id = str(uuid.uuid4())
# Governance check
agent = None
governance_check = None
if AGENT_GUIDANCE_ENABLED and not EMERGENCY_GOVERNANCE_BYPASS:
agent = self.db.query(AgentRegistry).filter(
AgentRegistry.id == agent_id
).first()
if agent:
# Check if agent can present operations (INTERN+)
governance_check = self.governance.can_perform_action(
agent_id=agent_id,
action_type="present_canvas",
require_approval=False
)
if not governance_check.get("allowed"):
logger.warning(
f"Agent {agent_id} not allowed to start operation: "
f"{governance_check.get('reason')}"
)
# Create tracker but don't broadcast - operation will be blocked
return {
"success": False,
"error": f"Operation not allowed: {governance_check.get('reason')}",
"governance_check": governance_check
}
# Get workspace_id
workspace_id = "default"
if agent and hasattr(agent, 'workspace_id') and agent.workspace_id:
workspace_id = agent.workspace_id
# Create operation tracker
tracker = AgentOperationTracker(
id=str(uuid.uuid4()),
agent_id=agent_id,
user_id=user_id,
workspace_id=workspace_id,
operation_type=operation_type,
operation_id=operation_id,
current_step="Initializing",
total_steps=total_steps,
current_step_index=0,
status="running",
progress=0,
what_explanation=context.get("what", ""),
why_explanation=context.get("why", ""),
next_steps=context.get("next", ""),
operation_metadata=metadata or {},
logs=[]
)
self.db.add(tracker)
self.db.commit()
# Create audit entry
await self._create_audit(
agent_id=agent_id,
user_id=user_id,
operation_id=operation_id,
action="start_operation",
governance_check_passed=governance_check.get("allowed") if governance_check else True,
metadata={
"operation_type": operation_type,
"context": context
}
)
# Broadcast to canvas
await ws_manager.broadcast(
f"user:{user_id}",
{
"type": "canvas:update",
"data": {
"action": "present",
"component": "agent_operation_tracker",
"data": {
"operation_id": operation_id,
"agent_id": agent_id,
"agent_name": agent.name if agent else "Agent",
"operation_type": operation_type,
"status": "running",
"current_step": "Initializing",
"total_steps": total_steps,
"current_step_index": 0,
"progress": 0,
"context": {
"what": context.get("what", ""),
"why": context.get("why", ""),
"next": context.get("next", "")
},
"metadata": metadata or {},
"logs": [],
"started_at": datetime.utcnow().isoformat()
}
}
}
)
logger.info(
f"Started operation {operation_id} for agent {agent_id}, "
f"user {user_id}"
)
return operation_id
except Exception as e:
logger.error(f"Failed to start operation: {e}")
return str(uuid.uuid4())
async def update_step(
self,
user_id: str,
operation_id: str,
step: str,
progress: Optional[int] = None,
add_log: Optional[Dict[str, Any]] = None
):
"""
Update operation step and progress.
Args:
user_id: User ID
operation_id: Operation ID
step: Current step description
progress: Progress percentage (0-100)
add_log: Optional log entry to add
"""
if not AGENT_GUIDANCE_ENABLED:
return
try:
# Get tracker
tracker = self.db.query(AgentOperationTracker).filter(
AgentOperationTracker.operation_id == operation_id
).first()
if not tracker:
logger.warning(f"Operation {operation_id} not found")
return
# Update tracker
tracker.current_step = step
tracker.current_step_index += 1
if progress is not None:
tracker.progress = min(100, max(0, progress))
# Add log if provided
if add_log:
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"level": add_log.get("level", "info"),
"message": add_log.get("message", "")
}
tracker.logs.append(log_entry)
self.db.commit()
# Calculate progress if not provided
if progress is None and tracker.total_steps:
progress = int((tracker.current_step_index / tracker.total_steps) * 100)
tracker.progress = progress
self.db.commit()
# Broadcast update
await ws_manager.broadcast(
f"user:{user_id}",
{
"type": "canvas:update",
"data": {
"action": "update",
"component": "agent_operation_tracker",
"operation_id": operation_id,
"updates": {
"current_step": step,
"current_step_index": tracker.current_step_index,
"progress": tracker.progress,
"logs": tracker.logs if add_log else None
}
}
}
)
logger.debug(f"Updated operation {operation_id}: {step} ({tracker.progress}%)")
except Exception as e:
logger.error(f"Failed to update operation step: {e}")
async def update_context(
self,
user_id: str,
operation_id: str,
what: Optional[str] = None,
why: Optional[str] = None,
next_steps: Optional[str] = None
):
"""
Update operation context explanations.
Args:
user_id: User ID
operation_id: Operation ID
what: What agent is doing
why: Why agent is doing this
next_steps: What happens next
"""
if not AGENT_GUIDANCE_ENABLED:
return
try:
# Get tracker
tracker = self.db.query(AgentOperationTracker).filter(
AgentOperationTracker.operation_id == operation_id
).first()
if not tracker:
logger.warning(f"Operation {operation_id} not found")
return
# Update context
if what:
tracker.what_explanation = what
if why:
tracker.why_explanation = why
if next_steps:
tracker.next_steps = next_steps
self.db.commit()
# Broadcast update
await ws_manager.broadcast(
f"user:{user_id}",
{
"type": "canvas:update",
"data": {
"action": "update",
"component": "agent_operation_tracker",
"operation_id": operation_id,
"updates": {
"context": {
"what": tracker.what_explanation,
"why": tracker.why_explanation,
"next": tracker.next_steps
}
}
}
}
)
logger.debug(f"Updated context for operation {operation_id}")
except Exception as e:
logger.error(f"Failed to update operation context: {e}")
async def complete_operation(
self,
user_id: str,
operation_id: str,
status: str = "completed",
final_message: Optional[str] = None
):
"""
Mark operation as completed or failed.
Args:
user_id: User ID
operation_id: Operation ID
status: Final status (completed or failed)
final_message: Optional final message
"""
if not AGENT_GUIDANCE_ENABLED:
return
try:
# Get tracker
tracker = self.db.query(AgentOperationTracker).filter(
AgentOperationTracker.operation_id == operation_id
).first()
if not tracker:
logger.warning(f"Operation {operation_id} not found")
return
# Update tracker
tracker.status = status
tracker.completed_at = datetime.utcnow()
tracker.progress = 100 if status == "completed" else tracker.progress
if final_message:
tracker.current_step = final_message
self.db.commit()
# Broadcast update
await ws_manager.broadcast(
f"user:{user_id}",
{
"type": "canvas:update",
"data": {
"action": "update",
"component": "agent_operation_tracker",
"operation_id": operation_id,
"updates": {
"status": status,
"progress": tracker.progress,
"current_step": final_message or tracker.current_step,
"completed_at": tracker.completed_at.isoformat()
}
}
}
)
logger.info(f"Completed operation {operation_id} with status {status}")
except Exception as e:
logger.error(f"Failed to complete operation: {e}")
async def add_log_entry(
self,
user_id: str,
operation_id: str,
level: str,
message: str
):
"""
Add a log entry to the operation.
Args:
user_id: User ID
operation_id: Operation ID
level: Log level (info, warning, error)
message: Log message
"""
if not AGENT_GUIDANCE_ENABLED:
return
await self.update_step(
user_id=user_id,
operation_id=operation_id,
step=None, # Don't update step
add_log={
"level": level,
"message": message
}
)
async def _create_audit(
self,
agent_id: str,
user_id: str,
operation_id: str,
action: str,
governance_check_passed: bool,
metadata: Dict[str, Any]
):
"""
Create canvas audit entry for governance tracking.
"""
try:
audit = CanvasAudit(
id=str(uuid.uuid4()),
workspace_id="default",
agent_id=agent_id,
agent_execution_id=None,
user_id=user_id,
canvas_id=None,
session_id=None,
component_type="agent_operation_tracker",
component_name="agent_guidance",
action=action,
audit_metadata={
"operation_id": operation_id,
**metadata
},
governance_check_passed=governance_check_passed
)
self.db.add(audit)
self.db.commit()
except Exception as e:
logger.error(f"Failed to create audit: {e}")
# Singleton instance helper
def get_agent_guidance_system(db: Session) -> AgentGuidanceSystem:
"""Get or create agent guidance system instance."""
return AgentGuidanceSystem(db)