#!/usr/bin/env python3
"""
Compliance Control Monitoring Agent — runnable demo (Agent Blueprint from AgentKits)

This runs a REAL agent loop: it sends the kit's actual system prompt and a
realistic sample input to the model, prints the structured decision, and then
executes any proposed actions against MOCK tools (labelled [MOCK]).

The model/reasoning is real. The tools are stubs that return fake data and do
NOT touch any real system. Wiring the mock tools to your own integrations
(CRM, database, GitHub, etc.) is the final implementation step — see README.md.

Works with Anthropic OR OpenAI — it auto-detects whichever API key is set.

© 2026 AgentKits · www.agent-kits.com · Licensed CC BY 4.0 (attribution required)
Original blueprint: https://agent-kits.com/kit/control-monitor

Setup (pick one):
    pip install anthropic   &&  export ANTHROPIC_API_KEY=sk-ant-...
    pip install openai      &&  export OPENAI_API_KEY=sk-...
Then:
    python run.py
"""
import os, json, sys

ANTHROPIC_MODEL = "claude-sonnet-4-6"
OPENAI_MODEL = "gpt-4o"

SYSTEM_PROMPT = """You are a Compliance Control Monitoring Agent. You test internal controls against their evidence and report status for human review. You do NOT attest or certify compliance. You are judged on honest, evidence-based status and on never marking a control compliant without proof, fabricating evidence, or signing off.

== CORE PRINCIPLES ==
1. Evidence or it's not satisfied. A control is "satisfied" only when real evidence exists and actually supports it. No evidence, stale evidence, or evidence that doesn't support the control = not satisfied / exception.
2. Honest status. Report failures, gaps, and missing evidence plainly. Never mark a control green to look good or to clear an audit. A red control reported honestly is the point.
3. Support, don't attest. You gather and assess; a human owns the attestation/sign-off. You never certify compliance or make the legal determination.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED PASS: Never mark a control satisfied/compliant without evidence that supports it. Unsupported = exception, with the gap stated.
- CITE EVIDENCE: Every "satisfied" status references the specific evidence. No evidence = no pass.
- NO AUTO-ATTEST: Never attest, certify, or sign off compliance. Output status + exceptions for a human to attest.
- FLAG, DON'T HIDE: Surface failing controls, missing/stale evidence, and exceptions. Don't downgrade severity to avoid findings.
- NOT LEGAL ADVICE: You support GRC work; you don't provide legal/regulatory determinations.

== METHOD ==
- For each control, gather evidence, test whether it supports the control, and rate status (satisfied/exception/insufficient-evidence) with citations and confidence. Flag exceptions and escalate.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "framework": "<e.g. SOC2/ISO/internal>",
  "controls": [
    { "id": "<control>", "status": "satisfied|exception|insufficient_evidence", "evidence": "<cited evidence, or 'none/stale'>", "confidence": "high|medium|low", "note": "<what's missing/why>" }
  ],
  "exceptions": ["<failing or unsubstantiated controls, with the gap>"],
  "attestation": "NOT_PERFORMED — a human owns attestation/sign-off",
  "note": "Evidence-based monitoring for human review. No control marked compliant without supporting evidence."
}
Never mark satisfied without evidence. Never attest. Flag exceptions honestly.
"""

SAMPLE_INPUT = """Control AC-1 (quarterly access reviews). Evidence: a completed Q2 access review export, dated, signed by the owner.
"""

# ---- MOCK tools (stubs — replace with real integrations to deploy) ----
def get_controls(args=None):
    print(f"  [MOCK] get_controls(" + str(args) + ")")
    return {"status": "mock", "tool": "get_controls"}

def gather_evidence(args=None):
    print(f"  [MOCK] gather_evidence(" + str(args) + ")")
    return {"status": "mock", "tool": "gather_evidence"}

def test_control(args=None):
    print(f"  [MOCK] test_control(" + str(args) + ")")
    return {"status": "mock", "tool": "test_control"}

def verify_evidence(args=None):
    print(f"  [MOCK] verify_evidence(" + str(args) + ")")
    return {"status": "mock", "tool": "verify_evidence"}

def flag_exceptions(args=None):
    print(f"  [MOCK] flag_exceptions(" + str(args) + ")")
    return {"status": "mock", "tool": "flag_exceptions"}

def status_report(args=None):
    print(f"  [MOCK] status_report(" + str(args) + ")")
    return {"status": "mock", "tool": "status_report"}

def escalate(args=None):
    print(f"  [MOCK] escalate(" + str(args) + ")")
    return {"status": "mock", "tool": "escalate"}

def cite_evidence(args=None):
    print(f"  [MOCK] cite_evidence(" + str(args) + ")")
    return {"status": "mock", "tool": "cite_evidence"}

TOOLS = {
    "get_controls": get_controls,
    "gather_evidence": gather_evidence,
    "test_control": test_control,
    "verify_evidence": verify_evidence,
    "flag_exceptions": flag_exceptions,
    "status_report": status_report,
    "escalate": escalate,
    "cite_evidence": cite_evidence,
}


def call_model(system, user):
    """Dispatch to whichever provider's key is configured."""
    if os.environ.get("ANTHROPIC_API_KEY"):
        try:
            from anthropic import Anthropic
        except ImportError:
            sys.exit("Run: pip install anthropic")
        msg = Anthropic().messages.create(
            model=ANTHROPIC_MODEL,
            max_tokens=1600,
            system=system,
            messages=[{"role": "user", "content": user}],
        )
        return "".join(b.text for b in msg.content if getattr(b, "type", "") == "text")
    if os.environ.get("OPENAI_API_KEY"):
        try:
            from openai import OpenAI
        except ImportError:
            sys.exit("Run: pip install openai")
        resp = OpenAI().chat.completions.create(
            model=OPENAI_MODEL,
            max_tokens=1600,
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
        )
        return resp.choices[0].message.content
    sys.exit("Set ANTHROPIC_API_KEY or OPENAI_API_KEY first.")


def extract_json(text):
    a, b = text.find("{"), text.rfind("}")
    if a == -1 or b == -1:
        return None
    try:
        return json.loads(text[a : b + 1])
    except json.JSONDecodeError:
        return None


def main():
    print("=" * 70)
    print("INPUT:\n" + SAMPLE_INPUT)
    print("=" * 70)

    text = call_model(SYSTEM_PROMPT, SAMPLE_INPUT)
    print("AGENT DECISION:\n" + text)
    print("=" * 70)

    data = extract_json(text)
    if not data:
        print("(No JSON object parsed — the model returned prose.)")
        return

    actions = data.get("actions") or []
    if actions:
        print("EXECUTING PROPOSED ACTIONS (mock):")
        for a in actions:
            name = a.get("tool")
            args = a.get("args", {})
            if name in TOOLS:
                TOOLS[name](args)
            else:
                print(f"  [skip] no mock tool for '{name}'")
    else:
        print("This blueprint returned a structured result (no side-effecting "
              "actions in this sample). Available mock tools: "
              + ", ".join(TOOLS.keys()))


if __name__ == "__main__":
    main()
