# Bookly **Try it live: ** A conversational customer support agent for a fictional online bookstore. The agent handles two depth use cases (order status and returns) and one breadth use case (policy questions) over a vanilla HTML chat UI, backed by Anthropic Claude with a four-layer guardrail strategy. The full agent design rationale lives in [DESIGN.md](DESIGN.md). Take a deeper dive at . ## Try it yourself Open and chat with the agent. The store is fictional and the data is fake — use the orders below to walk through the happy paths and the failure modes the guardrails are designed to catch. ### Orders in the database | Order ID | Customer | Email | Status | Notes | |---|---|---|---|---| | BK-10042 | Sarah Chen | `sarah.chen@example.com` | delivered | The Goldfinch + Sapiens, $36.98. Delivered 15 days ago — inside the 30-day return window. | | BK-10089 | James Murphy | `james.murphy@example.com` | shipped | Project Hail Mary, $18.99. Shipped 4 days ago, not yet delivered. | | BK-10103 | Sarah Chen | `sarah.chen@example.com` | processing | Tomorrow, and Tomorrow, and Tomorrow, $17.99. Ordered yesterday, not yet shipped. | | BK-9871 | Maria Gonzalez | `maria.gonzalez@example.com` | delivered | The Midnight Library, $15.99. Delivered 55 days ago — past the 30-day window. | ### Things to try **Happy paths** - "What's the status of BK-10089?" then provide the email — should show the shipped status and tracking number. - "I want to return BK-10042. My email is sarah.chen@example.com." — should walk through eligibility, summarize the refund terms, ask for confirmation, then issue an RMA. - "How do I reset my password?" — should quote the verbatim FAQ entry. - "What's your shipping policy?" — same pattern, verbatim quote. **Sad paths the agent should handle gracefully** - "Where is my order?" with no order ID — should ask for the BK-prefixed ID. - Sarah Chen has two orders — say "I want to return my order" with her email and the agent should ask which one. - "I want to return BK-10089" — should refuse because the order has not been delivered yet. - "I want to return BK-9871" with Maria's email — should refuse because the order is past the 30-day window. - Provide the wrong email for an order — the agent should say it can't find a match (privacy: no enumeration of which IDs exist). **Things the agent should refuse** - "Recommend me a good mystery novel." - "Which book should I read next?" - "Can you change my shipping address?" - "I want to dispute a charge on my credit card." In every refusal case the agent uses the same template and offers to redirect you to something it *can* help with. ## Stack - Python 3.11+, FastAPI, Uvicorn - Anthropic Claude (Sonnet) with prompt caching - Vanilla HTML / CSS / JS chat frontend (no build step) - Pytest ## Setup ``` python3 -m venv venv ./venv/bin/pip install -r requirements.txt cp .env.example .env # edit .env and set ANTHROPIC_API_KEY ``` ## Run locally ``` ./venv/bin/uvicorn server:app --host 127.0.0.1 --port 8014 ``` Then open in a browser. ## Tests ``` ./venv/bin/python -m pytest tests/ -v ``` Tests mock the Anthropic client, so no API key or network access is required. ## Project layout ``` agent.py System prompt, guardrails (layers 1, 2, 4), agentic loop tools.py Tool schemas, handlers, SessionGuardState (layer 3) mock_data.py Orders, return policy, FAQ policies server.py FastAPI app: /api/chat, /health, static mount config.py pydantic-settings config loaded from .env static/ index.html, style.css, chat.js tests/ test_tools.py, test_agent.py deploy/ systemd unit + nginx site config for the production droplet ``` ## Design See `DESIGN.md` for the architecture, conversation design, hallucination and safety controls, and production-readiness tradeoffs.