Quick Start
-
Clone and install
Terminal window git clone https://github.com/sasy-labs/sasy-demo.gitcd sasy-demomake setup -
Configure your keys
Terminal window cp .env.example .envThe demo uses three API keys. Edit
.envand fill them in:OPENAI_API_KEY— powers the airline agent in the demo scenarios (the LLM acting as a customer-service rep). This is the demo’s choice of agent LLM; it’s unrelated to SASY policy enforcement.SASY_API_KEY— authenticates to the SASY policy engine. Provided to you separately.ANTHROPIC_API_KEY— optional. Only needed if you runmake translateand want to use your own Anthropic credits instead of the shared server-side key (which is subject to a rate limit).
Terminal window OPENAI_API_KEY=sk-...SASY_API_KEY=demo-key-your-key-here# ANTHROPIC_API_KEY=sk-ant-... (optional; see above)
Run the Demo
Section titled “Run the Demo”The repo includes a pre-built Datalog policy
(policy.dl). Run the demo to see enforcement
in action:
make demomake demo-stepPress Enter between each stage.
make scenario-1Scenarios 1–9 are available (make scenario-2,
… make scenario-9). See Scenarios
for what each covers.
Each of these uploads policy.dl to the SASY
policy engine and runs one or more agent scenarios
(make demo and make demo-step run all nine;
make scenario-N runs just that one). You’ll see:
[Customer] Hi, I need to cancel reservation RKLA42.
→ get_reservation_details(reservation_id=RKLA42) [SASY] Consulting policy engine... [SASY] ✓ AUTHORIZED: get_reservation_details
→ cancel_reservation(reservation_id=RKLA42) [SASY] Consulting policy engine... [SASY] ✗ DENIED: cancel_reservation Reason: Only gold members or members with travel insurance can cancel reservations → Purchase travel insurance or upgrade to gold membership
[Agent] I'm sorry, your reservation cannot be cancelled because you don't have travel insurance.Translate a Policy
Section titled “Translate a Policy”The demo above ran against a hand-written Datalog
policy (policy.dl) shipped with the repo — fine
for the walkthrough, but not how you’d author one
from scratch. For your own agent, the typical
workflow is to write the policy in English and
let SASY translate it to Datalog for you. Here’s
how:
Write your policy in English (plus point the translator at your agentic codebase) and get back a Soufflé Datalog policy, plus a companion C++ functors file if the policy needs custom helpers (see below):
import loggingfrom sasy.policy import translate
# See progress while the job runs.logging.basicConfig(format="%(message)s")logging.getLogger("sasy").setLevel(logging.INFO)
with open("policy_english.md") as f: policy = f.read()
result = translate( policy, codebase_paths=["src/demo"], # your agent)result.print_summary()result.save_all("output/", base_name="airline")make translateTranslation takes ~5–15 minutes. Writes
output/airline_policy.dl and
output/agent_summary.md. A companion
output/airline_functors.cpp is written too if
the policy needs custom C++ helpers (content
matching, date arithmetic, NLP classifiers); the
demo policy is pure attribute matching, so no
functors file is emitted. The richer
tau2 airline benchmark
policy does produce functors if you want to see
the feature in practice.
Upload and run the translated policy against the scenarios:
make upload-translated # ships output/airline_policy.dlmake demo-translated # runs scenarios against itSee Translate a Policy for the full API and the tau2 airline benchmark for empirical results.
Integrate SASY in Your Own Agent
Section titled “Integrate SASY in Your Own Agent”Here’s how to wire the SDK into your own agent code.
1. Configure
Section titled “1. Configure”Set SASY_API_KEY (and optionally SASY_URL) in
.env — the SDK reads them automatically on first
use. To override at runtime (different endpoint
per environment, custom auth hook, etc.):
from sasy.config import configurefrom sasy.auth.hooks import APIKeyAuthHook
configure( url="sasy.fly.dev:443", auth_hook=APIKeyAuthHook(api_key="..."),)2. Upload
Section titled “2. Upload”from sasy.policy import upload_policy_file
resp = upload_policy_file("policy.dl")print("Uploaded!" if resp.accepted else resp.error_output)make upload # uploads policy.dl (reference)make upload-translated # uploads output/airline_policy.dl3. Check tool calls
Section titled “3. Check tool calls”Before executing any tool, check with the policy
engine. SASY tracks every recorded message + tool
call as a node in a dependency graph, and
policies can reason about prior context (e.g.
“the agent must call get_reservation_details
before cancel_reservation”). When you check a
tool call, pass the IDs of the events that
directly initiated it — typically just the
LLM message that chose this tool. The engine
traverses the graph backward from those events
to reach the full ancestry it needs for guard
rules.
Events and their dependencies land in the graph
either automatically (via sasy.instrument(),
covered below) or manually (via
sasy.observability.record_events_with_dependencies()
for frameworks instrument() doesn’t cover).
import jsonfrom sasy.reference_monitor import check_tool_call
# args is a JSON string (matches what OpenAI / Anthropic# tool-use APIs already give you in `tool_call.arguments`).auth = check_tool_call( fn_name="cancel_reservation", args=json.dumps({"reservation_id": "RKLA42"}), # Events that directly initiated this call (usually # the LLM message that chose it). Pass None if no # events are recorded — the check still works, but # graph-dependent guards can't fire. input_node_ids=None,)
if auth.authorized: # ...invoke the tool the same way you would today... result = my_tools.cancel_reservation(reservation_id="RKLA42")else: # Flat surface — see `auth.denial_trace` for the full # structured proto if you need reason codes / source # locations. print(f"Denied: {auth.denial_reasons}") print(f"Suggestions: {auth.suggestions}")Every tool call your agent makes goes through this check. The policy engine evaluates it against the loaded Datalog and returns AUTHORIZED or DENIED with a human-readable reason. See How Enforcement Works for how events are recorded into the graph.
4. Instrument (optional)
Section titled “4. Instrument (optional)”The §3 example required threading check_tool_call
by hand before every tool invocation. For supported
agent frameworks, SASY can do that wiring itself —
one line of setup and your existing code flows
through the policy engine with no per-call changes
(both the graph recording and the policy check get
patched in). Auth comes from .env:
import sasysasy.instrument()sasy.instrument() currently patches:
httpxandrequests— outbound HTTP requests are recorded into the graph and proxied through SASY for credential injection and policy checks.- Langroid —
ChatAgentandTaskmessages, tool calls, and their dependency edges are recorded; tool calls are auto-checked before invocation. - Tau2-bench agents — user-simulator and assistant
messages, tool calls, and results are recorded
(with dependencies); tool calls are auto-checked.
Opt-in — pass
sasy.instrument(tau2=True)to enable. HTTP and Langroid are on by default.
After instrument(), existing tool calls on any
of the patched frameworks flow through SASY without
code changes. The example below uses Langroid; the
same bare sasy.instrument() call covers plain
httpx / requests too — no framework switch
needed. For Tau2-bench agents, pass tau2=True as
noted above.
import sasyimport langroid as lr
sasy.instrument()
agent = lr.ChatAgent(lr.ChatAgentConfig(...))agent.enable_message(MyTool)# Every tool call is checked against the loaded# policy automatically.result = lr.Task(agent).run("cancel my booking")For a runnable end-to-end example see
src/demo/agent.py in this repo, which wires up
the airline scenarios with full graph recording.