Spaces:
Running
Running
Add Try AANA two-minute demo
Browse files- README.md +15 -5
- __pycache__/app.cpython-313.pyc +0 -0
- app.py +168 -21
README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# AANA
|
| 2 |
|
| 3 |
This local Hugging Face Space artifact is the repo-owned source for the public
|
| 4 |
"try AANA" demo. It accepts the frozen Agent Action Contract v1 fields and
|
|
@@ -28,10 +28,20 @@ Frozen required fields:
|
|
| 28 |
- `proposed_arguments`
|
| 29 |
- `recommended_route`
|
| 30 |
|
| 31 |
-
The Space
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
Public evidence links:
|
| 37 |
|
|
|
|
| 1 |
+
# Try AANA In 2 Minutes
|
| 2 |
|
| 3 |
This local Hugging Face Space artifact is the repo-owned source for the public
|
| 4 |
"try AANA" demo. It accepts the frozen Agent Action Contract v1 fields and
|
|
|
|
| 28 |
- `proposed_arguments`
|
| 29 |
- `recommended_route`
|
| 30 |
|
| 31 |
+
The Space calls `aana.check_tool_call` with these fields and displays:
|
| 32 |
+
|
| 33 |
+
- route: `accept`, `ask`, `defer`, or `refuse`
|
| 34 |
+
- AIx score
|
| 35 |
+
- hard blockers
|
| 36 |
+
- missing evidence
|
| 37 |
+
- authorization state
|
| 38 |
+
- recovery guidance
|
| 39 |
+
- audit-safe log event
|
| 40 |
+
- blocked-tool non-execution proof from a synthetic executor
|
| 41 |
+
|
| 42 |
+
The synthetic executor is intentionally safe: it records that it would have run
|
| 43 |
+
only when AANA returns `accept`. It cannot send, delete, purchase, deploy,
|
| 44 |
+
export, or access private data.
|
| 45 |
|
| 46 |
Public evidence links:
|
| 47 |
|
__pycache__/app.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
|
|
|
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
The public Space surface must keep accepting these seven frozen fields:
|
| 4 |
tool_name, tool_category, authorization_state, evidence_refs, risk_domain,
|
|
@@ -7,48 +7,195 @@ proposed_arguments, and recommended_route.
|
|
| 7 |
|
| 8 |
from __future__ import annotations
|
| 9 |
|
|
|
|
| 10 |
import json
|
|
|
|
| 11 |
|
| 12 |
import aana
|
| 13 |
|
| 14 |
|
| 15 |
-
|
| 16 |
-
"
|
| 17 |
-
"
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
"
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
| 24 |
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
def check_json_event(event_json: str) -> str:
|
| 27 |
-
"""
|
| 28 |
|
| 29 |
-
|
| 30 |
-
result = aana.check_tool_call(payload)
|
| 31 |
-
return json.dumps(result, indent=2, sort_keys=True)
|
| 32 |
|
| 33 |
|
| 34 |
def build_demo():
|
| 35 |
-
"""Build the
|
| 36 |
|
| 37 |
import gradio as gr
|
| 38 |
|
| 39 |
with gr.Blocks(title="Try AANA") as demo:
|
| 40 |
-
gr.Markdown("# AANA
|
|
|
|
| 41 |
gr.Markdown(
|
| 42 |
-
"
|
| 43 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
)
|
| 45 |
event = gr.Code(
|
| 46 |
-
value=
|
| 47 |
language="json",
|
| 48 |
-
label="Agent Action Contract v1
|
| 49 |
)
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
return demo
|
| 53 |
|
| 54 |
|
|
|
|
| 1 |
+
"""Canonical public "Try AANA in 2 minutes" Hugging Face Space.
|
| 2 |
|
| 3 |
The public Space surface must keep accepting these seven frozen fields:
|
| 4 |
tool_name, tool_category, authorization_state, evidence_refs, risk_domain,
|
|
|
|
| 7 |
|
| 8 |
from __future__ import annotations
|
| 9 |
|
| 10 |
+
import copy
|
| 11 |
import json
|
| 12 |
+
from typing import Any
|
| 13 |
|
| 14 |
import aana
|
| 15 |
|
| 16 |
|
| 17 |
+
PUBLIC_MESSAGE = (
|
| 18 |
+
"AANA is a pre-action control layer for AI agents: agents propose actions, "
|
| 19 |
+
"AANA checks evidence/auth/risk, and tools execute only when the route is accept."
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
EXAMPLE_EVENTS: dict[str, dict[str, Any]] = {
|
| 23 |
+
"Accept: confirmed support email": {
|
| 24 |
+
"tool_name": "send_email",
|
| 25 |
+
"tool_category": "write",
|
| 26 |
+
"authorization_state": "confirmed",
|
| 27 |
+
"evidence_refs": ["user_confirmed_send_email", "draft_id:123"],
|
| 28 |
+
"risk_domain": "customer_support",
|
| 29 |
+
"proposed_arguments": {"to": "customer@example.com"},
|
| 30 |
+
"recommended_route": "accept",
|
| 31 |
+
},
|
| 32 |
+
"Ask: write missing confirmation": {
|
| 33 |
+
"tool_name": "send_email",
|
| 34 |
+
"tool_category": "write",
|
| 35 |
+
"authorization_state": "user_claimed",
|
| 36 |
+
"evidence_refs": ["draft_id:123"],
|
| 37 |
+
"risk_domain": "customer_support",
|
| 38 |
+
"proposed_arguments": {"to": "customer@example.com"},
|
| 39 |
+
"recommended_route": "accept",
|
| 40 |
+
},
|
| 41 |
+
"Defer: private read missing auth": {
|
| 42 |
+
"tool_name": "get_recent_transactions",
|
| 43 |
+
"tool_category": "private_read",
|
| 44 |
+
"authorization_state": "none",
|
| 45 |
+
"evidence_refs": [],
|
| 46 |
+
"risk_domain": "finance",
|
| 47 |
+
"proposed_arguments": {"account_id": "acct_redacted", "limit": 5},
|
| 48 |
+
"recommended_route": "accept",
|
| 49 |
+
},
|
| 50 |
+
"Refuse: unknown destructive tool": {
|
| 51 |
+
"tool_name": "delete_database",
|
| 52 |
+
"tool_category": "unknown",
|
| 53 |
+
"authorization_state": "none",
|
| 54 |
+
"evidence_refs": [],
|
| 55 |
+
"risk_domain": "unknown",
|
| 56 |
+
"proposed_arguments": {"database": "prod"},
|
| 57 |
+
"recommended_route": "refuse",
|
| 58 |
+
},
|
| 59 |
}
|
| 60 |
|
| 61 |
|
| 62 |
+
def example_event(name: str) -> str:
|
| 63 |
+
"""Return a formatted example event for a scenario button/dropdown."""
|
| 64 |
+
|
| 65 |
+
return json.dumps(copy.deepcopy(EXAMPLE_EVENTS[name]), indent=2, sort_keys=True)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _decision(result: dict[str, Any]) -> dict[str, Any]:
|
| 69 |
+
architecture = result.get("architecture_decision")
|
| 70 |
+
return architecture if isinstance(architecture, dict) else result
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _route(result: dict[str, Any]) -> str:
|
| 74 |
+
decision = _decision(result)
|
| 75 |
+
return str(decision.get("route") or result.get("route") or result.get("recommended_action") or "defer")
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _synthetic_tool(event: dict[str, Any]) -> dict[str, Any]:
|
| 79 |
+
"""Synthetic-only executor used to prove blocked tools do not run."""
|
| 80 |
+
|
| 81 |
+
return {
|
| 82 |
+
"synthetic_tool_executed": True,
|
| 83 |
+
"tool_name": event.get("tool_name"),
|
| 84 |
+
"argument_keys": sorted((event.get("proposed_arguments") or {}).keys()),
|
| 85 |
+
"side_effects": "none_public_demo_only",
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def guarded_synthetic_execution(event: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]:
|
| 90 |
+
"""Run the synthetic tool only when AANA returns accept."""
|
| 91 |
+
|
| 92 |
+
route = _route(result)
|
| 93 |
+
proof = {
|
| 94 |
+
"required_route": "accept",
|
| 95 |
+
"aana_route": route,
|
| 96 |
+
"blocked_tool_non_execution_proven": route != "accept",
|
| 97 |
+
"synthetic_executor_call_count_before": 0,
|
| 98 |
+
"synthetic_executor_call_count_after": 0,
|
| 99 |
+
"synthetic_executor_result": None,
|
| 100 |
+
}
|
| 101 |
+
if route == "accept":
|
| 102 |
+
proof["synthetic_executor_result"] = _synthetic_tool(event)
|
| 103 |
+
proof["synthetic_executor_call_count_after"] = 1
|
| 104 |
+
proof["blocked_tool_non_execution_proven"] = False
|
| 105 |
+
return proof
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def summarize_decision(event: dict[str, Any], result: dict[str, Any], execution_proof: dict[str, Any]) -> dict[str, Any]:
|
| 109 |
+
"""Extract the reviewer-facing fields from the full AANA result."""
|
| 110 |
+
|
| 111 |
+
decision = _decision(result)
|
| 112 |
+
evidence_refs = decision.get("evidence_refs") if isinstance(decision.get("evidence_refs"), dict) else {}
|
| 113 |
+
audit_event = decision.get("audit_safe_log_event") or decision.get("audit_event") or {}
|
| 114 |
+
return {
|
| 115 |
+
"route": decision.get("route") or result.get("route"),
|
| 116 |
+
"aix_score": decision.get("aix_score") or (result.get("aix") or {}).get("score"),
|
| 117 |
+
"hard_blockers": decision.get("hard_blockers") or result.get("hard_blockers") or [],
|
| 118 |
+
"missing_evidence": decision.get("missing_evidence") or evidence_refs.get("missing") or [],
|
| 119 |
+
"authorization_state": decision.get("authorization_state") or event.get("authorization_state"),
|
| 120 |
+
"recovery_suggestion": decision.get("correction_recovery_suggestion") or decision.get("recovery_suggestion"),
|
| 121 |
+
"audit_safe_log_event": audit_event,
|
| 122 |
+
"execution_allowed": _route(result) == "accept",
|
| 123 |
+
"synthetic_executor_call_count_after": execution_proof["synthetic_executor_call_count_after"],
|
| 124 |
+
"blocked_tool_non_execution_proven": execution_proof["blocked_tool_non_execution_proven"],
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def check_event(event_json: str) -> tuple[str, str, str, str]:
|
| 129 |
+
"""Check a pasted tool-call event and return Gradio-friendly outputs."""
|
| 130 |
+
|
| 131 |
+
event = json.loads(event_json)
|
| 132 |
+
result = aana.check_tool_call(event)
|
| 133 |
+
execution_proof = guarded_synthetic_execution(event, result)
|
| 134 |
+
summary = summarize_decision(event, result, execution_proof)
|
| 135 |
+
route = str(summary["route"])
|
| 136 |
+
proof_line = (
|
| 137 |
+
"Synthetic executor did not run because AANA did not return accept."
|
| 138 |
+
if route != "accept"
|
| 139 |
+
else "Synthetic executor ran because AANA returned accept."
|
| 140 |
+
)
|
| 141 |
+
markdown = "\n".join(
|
| 142 |
+
[
|
| 143 |
+
f"## Route: `{route}`",
|
| 144 |
+
f"- AIx score: `{summary['aix_score']}`",
|
| 145 |
+
f"- Authorization state: `{summary['authorization_state']}`",
|
| 146 |
+
f"- Hard blockers: `{summary['hard_blockers'] or ['none']}`",
|
| 147 |
+
f"- Missing evidence: `{summary['missing_evidence'] or ['none']}`",
|
| 148 |
+
f"- Execution proof: {proof_line}",
|
| 149 |
+
]
|
| 150 |
+
)
|
| 151 |
+
return (
|
| 152 |
+
markdown,
|
| 153 |
+
json.dumps(summary, indent=2, sort_keys=True),
|
| 154 |
+
json.dumps(execution_proof, indent=2, sort_keys=True),
|
| 155 |
+
json.dumps(result, indent=2, sort_keys=True),
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
def check_json_event(event_json: str) -> str:
|
| 160 |
+
"""Backward-compatible helper returning the full AANA decision as JSON."""
|
| 161 |
|
| 162 |
+
return check_event(event_json)[3]
|
|
|
|
|
|
|
| 163 |
|
| 164 |
|
| 165 |
def build_demo():
|
| 166 |
+
"""Build the Gradio UI only when the Space runtime imports it."""
|
| 167 |
|
| 168 |
import gradio as gr
|
| 169 |
|
| 170 |
with gr.Blocks(title="Try AANA") as demo:
|
| 171 |
+
gr.Markdown("# Try AANA in 2 minutes")
|
| 172 |
+
gr.Markdown(PUBLIC_MESSAGE)
|
| 173 |
gr.Markdown(
|
| 174 |
+
"Paste a proposed tool call or load an example. AANA returns a route, AIx score, "
|
| 175 |
+
"hard blockers, missing evidence, authorization state, and an audit-safe event. "
|
| 176 |
+
"The synthetic executor only runs when the route is `accept`."
|
| 177 |
+
)
|
| 178 |
+
scenario = gr.Dropdown(
|
| 179 |
+
choices=list(EXAMPLE_EVENTS),
|
| 180 |
+
value="Ask: write missing confirmation",
|
| 181 |
+
label="Load example",
|
| 182 |
+
interactive=True,
|
| 183 |
)
|
| 184 |
event = gr.Code(
|
| 185 |
+
value=example_event("Ask: write missing confirmation"),
|
| 186 |
language="json",
|
| 187 |
+
label="Paste Agent Action Contract v1 tool call",
|
| 188 |
)
|
| 189 |
+
with gr.Row():
|
| 190 |
+
load = gr.Button("Load Example")
|
| 191 |
+
check = gr.Button("Check With AANA", variant="primary")
|
| 192 |
+
summary = gr.Markdown(label="Decision summary")
|
| 193 |
+
compact = gr.Code(language="json", label="Route, AIx, blockers, missing evidence, auth state")
|
| 194 |
+
proof = gr.Code(language="json", label="Blocked-tool non-execution proof")
|
| 195 |
+
full = gr.Code(language="json", label="Full AANA decision")
|
| 196 |
+
|
| 197 |
+
load.click(example_event, inputs=scenario, outputs=event)
|
| 198 |
+
check.click(check_event, inputs=event, outputs=[summary, compact, proof, full])
|
| 199 |
return demo
|
| 200 |
|
| 201 |
|