Approval Engine

Approval Engine

Callback-based approval flow for gating destructive or sensitive operations. Part of the unified CodeGraphHarness service harness.

Overview

The approval engine decides whether operations (autofix application, pattern fixes, command execution) require explicit user confirmation before proceeding. It supports three policies:

Policy Behavior
auto Auto-approve whitelisted actions, prompt for others
ask_always Require approval for all actions
read_only Decline all write operations automatically

Architecture

User/CLI/IDE  ←→  ApprovalEngine  ←→  Harness  ←→  Analysis Modules
                        │
                   ┌────┴────┐
                   │ Policies │
                   ├──────────┤
                   │ auto     │  ← Check whitelist, then prompt
                   │ ask      │  ← Always prompt
                   │ read_only│  ← Always decline writes
                   └──────────┘

API Reference

ApprovalRequest

@dataclass
class ApprovalRequest:
    id: str                     # Unique request ID (UUID)
    thread_id: str              # Session thread ID
    action_type: str            # e.g., 'apply_autofix', 'command_execution'
    details: Dict[str, Any]     # Arbitrary action details
    status: str                 # 'pending' | 'accepted' | 'declined' | 'cancelled'

ApprovalDecision

@dataclass
class ApprovalDecision:
    decision: str               # 'accept' | 'accept_for_session' | 'decline' | 'cancel'
    reason: Optional[str]       # Optional reason text
    scope: Optional[str]        # Scope for 'accept_for_session'

ApprovalEngine

class ApprovalEngine:
    def __init__(self, config: HarnessConfig):
        """Initialize with harness configuration."""

    async def request_approval(
        self,
        action_type: str,
        details: Dict,
        thread_id: str,
    ) -> ApprovalDecision:
        """
        Request approval for an action. Blocks until resolved.

        Auto-approves if action_type is in the whitelist or was
        previously approved for this session.

        Args:
            action_type: Type of action (e.g., 'apply_autofix')
            details: Action context (file, line, description)
            thread_id: Current session thread

        Returns:
            ApprovalDecision with the user's decision
        """

    def resolve_approval(self, request_id: str, decision: ApprovalDecision) -> None:
        """
        Resolve a pending approval request (called by UI/CLI).

        If decision is 'accept_for_session', all future requests of the
        same action_type in this thread are auto-approved.
        """

    def get_pending_request_ids(self) -> list:
        """Return IDs of all pending approval requests."""

    def clear_session_approvals(self, thread_id: str) -> None:
        """Clear session-scoped auto-approvals on session end."""

Configuration

# config.yaml
harness:
  session_dir: ./data/sessions
  max_threads: 100
  thread_ttl_hours: 168            # 7 days
  approval:
    default_policy: auto             # auto | ask_always | read_only
    auto_approve:                    # Actions auto-approved (no prompt)
      - codegraph_query
      - codegraph_find_callers
      - codegraph_find_callees
      - codegraph_explain
    require_approval:                # Actions that always require approval
      - apply_autofix
      - apply_pattern_fix
      - command_execution

Usage

Request Approval

from src.harness import get_harness

harness = get_harness()

decision = await harness.approval.request_approval(
    action_type="apply_autofix",
    details={
        "file": "main.c",
        "line": 42,
        "fix": "Replace strcpy with strncpy",
        "cwe": "CWE-120"
    },
    thread_id="session_abc"
)

if decision.decision == "accept":
    apply_fix(...)
elif decision.decision == "decline":
    log(f"Fix rejected: {decision.reason}")

Resolve from UI

# Called by CLI/TUI/IDE when user makes a decision
harness.approval.resolve_approval(
    request_id="req_abc123",
    decision=ApprovalDecision(decision="accept")
)

Session-Scoped Approval

When a user selects accept_for_session, all subsequent requests of the same action_type within the same thread are auto-approved:

# User approves once for the whole session
decision = ApprovalDecision(decision="accept_for_session")
harness.approval.resolve_approval(request_id, decision)

# Subsequent autofix requests auto-approved (no prompt)
decision = await harness.approval.request_approval(
    action_type="apply_autofix",  # Same type → auto-approved
    details={...},
    thread_id="session_abc"       # Same thread
)
# decision.decision == "accept" (automatic)

Decision Flow

request_approval(action_type, details, thread_id)
    
    ├─ Is action_type in auto_approve list?
       └─ YES  return accept immediately
    
    ├─ Is action_type in session_approvals[thread_id]?
       └─ YES  return accept (session-scoped)
    
    ├─ Is default_policy == read_only?
       └─ YES  return decline("read_only policy")
    
    └─ Create pending request, await user decision
        ├─ accept               apply action
        ├─ accept_for_session   apply + remember for session
        ├─ decline              skip action
        └─ cancel               abort operation

Default Action Classification

Auto-Approved (read-only) Requires Approval (write)
codegraph_query apply_autofix
codegraph_find_callers apply_pattern_fix
codegraph_find_callees command_execution
codegraph_explain

Integration Points

The approval engine is used by: - Autofix (src/analysis/autofix/) — applying security fixes to source code - Pattern fixes (src/analysis/patterns/) — applying structural pattern rewrites - File editing (src/editing/) — AST-based code modifications - MCP tools — write operations via MCP server - ACP sessions — IDE-initiated operations


Module: src/harness/ Last updated: February 2026