Skip to content

Commit aa1409e

Browse files
authored
feat: harden Agent Ready discovery surface
Close #27 by improving sitemap freshness, AGENTS.md structure, markdown negotiation, readable content, and v0.6.0 metadata while preserving tool-policy behavior.
1 parent f14e9c8 commit aa1409e

6 files changed

Lines changed: 143 additions & 38 deletions

File tree

assets/demo/safe-agent-demo.gif

126 KB
Loading
59.3 KB
Loading

examples/safe-agent-api/app/discovery.py

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
PUBLIC_URL = "https://safe-agent-playground.onrender.com"
22
SOURCE_URL = "https://github.com/Videirafo/AI-Agent-Production-Checklist"
3+
SOCIAL_IMAGE_URL = (
4+
"https://raw.githubusercontent.com/Videirafo/AI-Agent-Production-Checklist/"
5+
"main/assets/demo/safe-agent-playground.png"
6+
)
7+
RELEASE_VERSION = "0.6.0"
8+
LAST_MODIFIED = "2026-09-07"
39

410
LLMS_TXT = f"""# Safe Agent Playground
511
@@ -9,7 +15,7 @@
915
- [Live Playground]({PUBLIC_URL}/): interactive policy, approval, execution and audit demo
1016
- [OpenAPI]({PUBLIC_URL}/openapi.json): machine-readable API schema
1117
- [Swagger UI]({PUBLIC_URL}/docs): interactive API documentation
12-
- [Playground Markdown]({PUBLIC_URL}/playground.md): text-first description of the demo
18+
- [Playground Markdown]({PUBLIC_URL}/index.md): text-first description of the demo
1319
- [Source]({SOURCE_URL}): MIT-licensed source code and contribution workflow
1420
1521
## Safety model
@@ -49,63 +55,98 @@
4955

5056
SITEMAP_XML = f"""<?xml version="1.0" encoding="UTF-8"?>
5157
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
52-
<url><loc>{PUBLIC_URL}/</loc></url>
53-
<url><loc>{PUBLIC_URL}/docs</loc></url>
54-
<url><loc>{PUBLIC_URL}/openapi.json</loc></url>
55-
<url><loc>{PUBLIC_URL}/playground.md</loc></url>
56-
<url><loc>{PUBLIC_URL}/llms.txt</loc></url>
57-
<url><loc>{PUBLIC_URL}/AGENTS.md</loc></url>
58+
<url>
59+
<loc>{PUBLIC_URL}/</loc>
60+
<lastmod>{LAST_MODIFIED}</lastmod>
61+
</url>
5862
</urlset>
5963
"""
6064

6165
SITEMAP_MD = f"""# Safe Agent Playground Sitemap
6266
67+
## Canonical page
6368
- [Playground]({PUBLIC_URL}/)
69+
70+
## Developer resources
6471
- [OpenAPI / Swagger]({PUBLIC_URL}/docs)
6572
- [OpenAPI JSON]({PUBLIC_URL}/openapi.json)
66-
- [Markdown mirror]({PUBLIC_URL}/playground.md)
73+
- [Markdown mirror]({PUBLIC_URL}/index.md)
6774
- [LLM discovery]({PUBLIC_URL}/llms.txt)
6875
- [Agent instructions]({PUBLIC_URL}/AGENTS.md)
6976
- [Source code]({SOURCE_URL})
7077
"""
7178

7279
AGENTS_MD = f"""# AGENTS.md
7380
74-
## Purpose
75-
Safe Agent Playground demonstrates production boundaries for AI-agent tool execution.
81+
Safe Agent Playground demonstrates production boundaries for AI-agent tool execution. Core invariant: Model suggestion is not authorization. Authorization is enforced by deterministic application policy outside the model.
82+
83+
## Installation
84+
85+
Clone the source and install the example in an isolated Python environment:
86+
87+
```bash
88+
git clone {SOURCE_URL}.git
89+
cd AI-Agent-Production-Checklist/examples/safe-agent-api
90+
python -m venv .venv
91+
pip install -e \".[dev]\"
92+
```
93+
94+
Docker is also supported through the repository's `docker compose` configuration.
95+
96+
## Configuration
97+
98+
No secrets, API keys, LLM provider, external database, or signup are required. The public demo is intentionally deterministic and simulated. Do not send credentials, private customer data, proprietary payloads, or secrets.
7699
77-
## Core invariant
78-
Model suggestion is not authorization. Authorization is enforced by deterministic application policy outside the model.
100+
## Usage
79101
80-
## Public API
102+
Public endpoints:
103+
- GET {PUBLIC_URL}/
81104
- GET {PUBLIC_URL}/health
82105
- POST {PUBLIC_URL}/v1/tool-check
83106
- POST {PUBLIC_URL}/v1/run-demo
84107
- GET {PUBLIC_URL}/openapi.json
85108
86-
## Safe usage
87-
The public demo is deterministic and simulated. It performs no real notification, deletion or tenant data access. Do not send secrets, credentials, private customer data or proprietary payloads.
109+
The model may suggest an action, but the application policy decides whether execution is permitted.
88110
89-
## Contribution source
90-
{SOURCE_URL}
111+
## Examples
112+
113+
Use the browser Playground to run five reference scenarios: same-tenant read, cross-tenant denial, approval-required notification, approved notification, and blocked destructive delete.
114+
115+
For API examples and the exact schema, use {PUBLIC_URL}/docs or {PUBLIC_URL}/openapi.json.
116+
117+
## Contribution
118+
119+
Source, tests, issues, and contribution workflow: {SOURCE_URL}
91120
"""
92121

93-
PLAYGROUND_MD = f"""# Safe Agent Playground
122+
PLAYGROUND_MD = f"""---
123+
title: Safe Agent Playground
124+
description: Runnable open-source FastAPI demo for deterministic AI-agent tool authorization boundaries.
125+
canonical: {PUBLIC_URL}/
126+
version: {RELEASE_VERSION}
127+
last_modified: {LAST_MODIFIED}
128+
---
129+
130+
# Safe Agent Playground
94131
95-
Safe Agent Playground is an MIT-licensed FastAPI demo showing how agent tool execution can remain outside model authority.
132+
Safe Agent Playground is an MIT-licensed FastAPI demo showing how agent tool execution can remain outside model authority. The model may suggest an action, while deterministic application policy, tenant boundaries, and human approval decide whether execution is allowed.
96133
97134
## Live demo
135+
98136
{PUBLIC_URL}/
99137
100138
## What it demonstrates
101-
- tenant isolation
102-
- deterministic tool policies
103-
- human approval gates
104-
- destructive-action denial
105-
- structured audit events
106-
- correlation IDs
139+
140+
- tenant isolation;
141+
- deterministic tool policies;
142+
- human approval gates;
143+
- destructive-action denial;
144+
- structured audit events;
145+
- correlation IDs;
146+
- an execution boundary that does not depend on an LLM making its own authorization decision.
107147
108148
## Scenarios
149+
109150
| Scenario | Expected result |
110151
|---|---|
111152
| same-tenant read_record | allowed and executed |
@@ -115,15 +156,29 @@
115156
| delete_record | denied: destructive_tool_disabled_in_demo |
116157
117158
## Flow
118-
`model suggestion -> policy -> approval -> execution -> audit -> correlation`
159+
160+
```text
161+
model suggestion -> policy -> approval -> execution -> audit -> correlation
162+
```
119163
120164
## API
165+
121166
- [Swagger]({PUBLIC_URL}/docs)
122167
- [OpenAPI JSON]({PUBLIC_URL}/openapi.json)
123168
- [Health]({PUBLIC_URL}/health)
124169
170+
## Sitemap
171+
172+
- [Canonical Playground]({PUBLIC_URL}/)
173+
- [Agent discovery]({PUBLIC_URL}/llms.txt)
174+
- [Agent instructions]({PUBLIC_URL}/AGENTS.md)
175+
- [Human-readable sitemap]({PUBLIC_URL}/sitemap.md)
176+
125177
## Source and contributions
178+
126179
{SOURCE_URL}
127180
"""
128181

129-
HEAD_DISCOVERY_HTML = f'''\n <meta name="description" content="Runnable open-source Safe Agent Playground demonstrating tenant isolation, deterministic tool policy, human approval, audit events and correlation IDs for AI-agent execution." />\n <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large" />\n <link rel="canonical" href="{PUBLIC_URL}/" />\n <link rel="describedby" href="/llms.txt" />\n <link rel="alternate" type="text/markdown" href="/playground.md" />\n <meta property="og:title" content="Safe Agent Playground" />\n <meta property="og:description" content="Try deterministic agent-tool authorization, human approvals, tenant isolation and audit correlation in a runnable FastAPI demo." />\n <meta property="og:type" content="website" />\n <meta property="og:url" content="{PUBLIC_URL}/" />\n <meta name="twitter:card" content="summary" />\n <script type="application/ld+json">{{"@context":"https://schema.org","@type":"SoftwareApplication","name":"Safe Agent Playground","description":"Runnable open-source demo for AI-agent tool authorization boundaries.","url":"{PUBLIC_URL}/","applicationCategory":"DeveloperApplication","operatingSystem":"Web","codeRepository":"{SOURCE_URL}"}}</script>\n'''
182+
HEAD_DISCOVERY_HTML = f'''\n <meta name="description" content="Runnable open-source Safe Agent Playground demonstrating tenant isolation, deterministic tool policy, human approval, audit events and correlation IDs for AI-agent execution." />\n <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large" />\n <link rel="canonical" href="{PUBLIC_URL}/" />\n <link rel="describedby" href="/llms.txt" />\n <link rel="alternate" type="text/markdown" href="/index.md" />\n <meta property="og:title" content="Safe Agent Playground" />\n <meta property="og:description" content="Try deterministic agent-tool authorization, human approvals, tenant isolation and audit correlation in a runnable FastAPI demo." />\n <meta property="og:type" content="website" />\n <meta property="og:url" content="{PUBLIC_URL}/" />\n <meta property="og:image" content="{SOCIAL_IMAGE_URL}" />\n <meta name="twitter:card" content="summary_large_image" />\n <meta name="twitter:title" content="Safe Agent Playground" />\n <meta name="twitter:description" content="Runnable open-source demo for AI-agent tool authorization boundaries." />\n <meta name="twitter:image" content="{SOCIAL_IMAGE_URL}" />\n <script type="application/ld+json">{{"@context":"https://schema.org","@type":"SoftwareApplication","name":"Safe Agent Playground","headline":"Safe Agent Playground","description":"Runnable open-source demo for AI-agent tool authorization boundaries.","url":"{PUBLIC_URL}/","dateModified":"{LAST_MODIFIED}","softwareVersion":"{RELEASE_VERSION}","applicationCategory":"DeveloperApplication","operatingSystem":"Web","codeRepository":"{SOURCE_URL}"}}</script>\n'''
183+
184+
DISCOVERY_BODY_HTML = f'''\n <section class="card" style="margin-top:20px" aria-labelledby="why-title">\n <h2 id="why-title">Why this demo exists</h2>\n <p>Many AI-agent examples demonstrate how a model selects or calls a tool, but production systems need a separate authorization boundary. This Playground makes that boundary visible: tenant scope is checked first, sensitive actions can require human approval, destructive actions remain blocked, and every decision can be correlated with an audit event.</p>\n <h3>What to evaluate</h3>\n <p>Try an allowed same-tenant read, then change the resource tenant and observe the denial. Compare a notification request before and after human approval. Finally, try the destructive delete scenario and verify that approval alone cannot bypass a policy that disables the tool. The purpose is not to simulate intelligence; it is to make execution policy deterministic, inspectable, testable, and independent from model persuasion.</p>\n <p>Machine-readable resources are available through <a href="/openapi.json">OpenAPI</a>, <a href="/llms.txt">llms.txt</a>, <a href="/AGENTS.md">AGENTS.md</a>, and the <a href="/index.md">Markdown mirror</a>.</p>\n </section>\n'''

examples/safe-agent-api/app/main.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
from fastapi import FastAPI
1+
from fastapi import FastAPI, Request
22
from fastapi.responses import HTMLResponse, PlainTextResponse, Response
33

44
from app.discovery import (
55
AGENTS_MD,
6+
DISCOVERY_BODY_HTML,
67
HEAD_DISCOVERY_HTML,
78
LLMS_FULL_TXT,
89
LLMS_TXT,
910
PLAYGROUND_MD,
11+
RELEASE_VERSION,
1012
ROBOTS_TXT,
1113
SITEMAP_MD,
1214
SITEMAP_XML,
@@ -23,32 +25,49 @@
2325

2426
app = FastAPI(
2527
title="Safe Agent API",
26-
version="0.5.0",
28+
version=RELEASE_VERSION,
2729
description="Deterministic policy, approval and audit layer for agent tool execution demos.",
2830
)
2931

3032

31-
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
32-
def playground() -> HTMLResponse:
33+
MARKDOWN_HEADERS = {
34+
"Link": '</>; rel="canonical", </sitemap.md>; rel="sitemap"',
35+
"X-Robots-Tag": "index, follow, max-snippet:-1, max-image-preview:large",
36+
}
37+
38+
39+
@app.api_route("/", methods=["GET", "HEAD"], response_class=HTMLResponse, include_in_schema=False)
40+
def playground(request: Request) -> Response:
41+
accept = request.headers.get("accept", "").lower()
42+
if request.method == "GET" and "text/markdown" in accept:
43+
return PlainTextResponse(
44+
PLAYGROUND_MD,
45+
media_type="text/markdown",
46+
headers=MARKDOWN_HEADERS,
47+
)
48+
3349
html = PLAYGROUND_HTML.replace("</head>", f"{HEAD_DISCOVERY_HTML}</head>")
50+
html = html.replace('<p class="footer">', f'{DISCOVERY_BODY_HTML}<p class="footer">')
3451
return HTMLResponse(
3552
html,
3653
headers={
3754
"Cache-Control": "no-store",
3855
"Content-Security-Policy": (
3956
"default-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; "
40-
"connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'"
57+
"connect-src 'self'; img-src 'self' data: https://raw.githubusercontent.com; "
58+
"frame-ancestors 'none'"
4159
),
42-
"Link": '</llms.txt>; rel="describedby", </playground.md>; rel="alternate"; type="text/markdown"',
60+
"Link": '</llms.txt>; rel="describedby", </index.md>; rel="alternate"; type="text/markdown"',
4361
"Referrer-Policy": "no-referrer",
4462
"X-Content-Type-Options": "nosniff",
63+
"X-Robots-Tag": "index, follow, max-snippet:-1, max-image-preview:large",
4564
},
4665
)
4766

4867

4968
@app.get("/health")
5069
def health() -> dict[str, str]:
51-
return {"status": "ok", "service": "safe-agent-api", "version": "0.5.0"}
70+
return {"status": "ok", "service": "safe-agent-api", "version": RELEASE_VERSION}
5271

5372

5473
@app.get("/llms.txt", response_class=PlainTextResponse, include_in_schema=False)
@@ -81,12 +100,13 @@ def agents_md() -> PlainTextResponse:
81100
return PlainTextResponse(AGENTS_MD, media_type="text/markdown")
82101

83102

103+
@app.get("/index.md", response_class=PlainTextResponse, include_in_schema=False)
84104
@app.get("/playground.md", response_class=PlainTextResponse, include_in_schema=False)
85105
def playground_md() -> PlainTextResponse:
86106
return PlainTextResponse(
87107
PLAYGROUND_MD,
88108
media_type="text/markdown",
89-
headers={"Link": '</sitemap.md>; rel="sitemap"'},
109+
headers=MARKDOWN_HEADERS,
90110
)
91111

92112

examples/safe-agent-api/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "safe-agent-api"
3-
version = "0.5.0"
3+
version = "0.6.0"
44
description = "Deterministic safety policy demo for AI agent tool execution"
55
requires-python = ">=3.12"
66
dependencies = [

examples/safe-agent-api/tests/test_api.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,29 @@ def test_playground_is_public_and_self_contained() -> None:
1010
assert response.status_code == 200
1111
assert "Safe Agent Playground" in response.text
1212
assert "/v1/run-demo" in response.text
13+
assert "Why this demo exists" in response.text
1314
assert 'rel="canonical"' in response.text
1415
assert 'rel="describedby" href="/llms.txt"' in response.text
16+
assert 'property="og:title"' in response.text
1517
assert 'type="application/ld+json"' in response.text
1618
assert response.headers["x-content-type-options"] == "nosniff"
1719
assert "connect-src 'self'" in response.headers["content-security-policy"]
20+
assert response.headers["x-robots-tag"].startswith("index, follow")
21+
22+
23+
def test_head_root_is_supported() -> None:
24+
response = client.head("/")
25+
assert response.status_code == 200
26+
assert response.headers["content-type"].startswith("text/html")
27+
28+
29+
def test_root_supports_markdown_content_negotiation() -> None:
30+
response = client.get("/", headers={"Accept": "text/markdown"})
31+
assert response.status_code == 200
32+
assert response.headers["content-type"].startswith("text/markdown")
33+
assert "# Safe Agent Playground" in response.text
34+
assert "## Sitemap" in response.text
35+
assert 'rel="canonical"' in response.headers["link"]
1836

1937

2038
def test_health() -> None:
@@ -23,7 +41,7 @@ def test_health() -> None:
2341
assert response.json() == {
2442
"status": "ok",
2543
"service": "safe-agent-api",
26-
"version": "0.5.0",
44+
"version": "0.6.0",
2745
}
2846

2947

@@ -33,6 +51,7 @@ def test_discovery_documents_are_public() -> None:
3351
assert llms.headers["content-type"].startswith("text/plain")
3452
assert "# Safe Agent Playground" in llms.text
3553
assert "/openapi.json" in llms.text
54+
assert "/index.md" in llms.text
3655

3756
full = client.get("/llms-full.txt")
3857
assert full.status_code == 200
@@ -47,6 +66,9 @@ def test_discovery_documents_are_public() -> None:
4766
assert sitemap_xml.status_code == 200
4867
assert sitemap_xml.headers["content-type"].startswith("application/xml")
4968
assert "<urlset" in sitemap_xml.text
69+
assert "<lastmod>2026-09-07</lastmod>" in sitemap_xml.text
70+
assert "<loc>https://safe-agent-playground.onrender.com/</loc>" in sitemap_xml.text
71+
assert "playground.md" not in sitemap_xml.text
5072

5173
sitemap_md = client.get("/sitemap.md")
5274
assert sitemap_md.status_code == 200
@@ -55,11 +77,19 @@ def test_discovery_documents_are_public() -> None:
5577
agents = client.get("/AGENTS.md")
5678
assert agents.status_code == 200
5779
assert "Model suggestion is not authorization" in agents.text
80+
assert "## Installation" in agents.text
81+
assert "## Configuration" in agents.text
82+
assert "## Usage" in agents.text
83+
assert "## Examples" in agents.text
5884

59-
markdown = client.get("/playground.md")
85+
markdown = client.get("/index.md")
6086
assert markdown.status_code == 200
6187
assert markdown.headers["content-type"].startswith("text/markdown")
88+
assert markdown.text.startswith("---\n")
89+
assert "canonical: https://safe-agent-playground.onrender.com/" in markdown.text
90+
assert "last_modified: 2026-09-07" in markdown.text
6291
assert "## Scenarios" in markdown.text
92+
assert "## Sitemap" in markdown.text
6393

6494

6595
def test_read_is_allowed_in_same_tenant() -> None:

0 commit comments

Comments
 (0)