Skip to content

Quick Start

  1. Clone and install

    Terminal window
    git clone https://github.com/sasy-labs/sasy-demo.git
    cd sasy-demo
    make setup
  2. Configure your keys

    Terminal window
    cp .env.example .env

    The demo uses three API keys. Edit .env and 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_KEYoptional. Only needed if you run make translate and 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)

The repo includes a pre-built Datalog policy (policy.dl). Run the demo to see enforcement in action:

Terminal window
make demo

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.

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 logging
from 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")

Translation 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:

Terminal window
make upload-translated # ships output/airline_policy.dl
make demo-translated # runs scenarios against it

See Translate a Policy for the full API and the tau2 airline benchmark for empirical results.

Here’s how to wire the SDK into your own agent code.

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 configure
from sasy.auth.hooks import APIKeyAuthHook
configure(
url="sasy.fly.dev:443",
auth_hook=APIKeyAuthHook(api_key="..."),
)
from sasy.policy import upload_policy_file
resp = upload_policy_file("policy.dl")
print("Uploaded!" if resp.accepted else resp.error_output)

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 json
from 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.

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 sasy
sasy.instrument()

sasy.instrument() currently patches:

  • httpx and requests — outbound HTTP requests are recorded into the graph and proxied through SASY for credential injection and policy checks.
  • Langroid — ChatAgent and Task messages, 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 sasy
import 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.