The framework for building AI agents you can actually trust in production.
Not just agents that demo well β agents that remember users, recover from mistakes, escalate when stuck, and get smarter over time.
β If this saves you time, give it a star β it helps other developers find it.
Rails did it for web apps. Next.js did it for React. Glaivio does it for AI agents.
from glaivio import Agent, skill
@skill
def book_appointment(patient_name: str, patient_phone: str, date: str, time: str) -> str:
"""Book an appointment. patient_phone: use the current user's ID from context."""
# call your calendar API here
return f"Booked {patient_name} on {date} at {time}"
agent = Agent(
instructions="prompts/system.md",
skills=[book_appointment],
learn_from_feedback=True,
privacy=True,
)
agent.run(channel="whatsapp")That's it. Your agent is live on WhatsApp β with memory, PII redaction, and self-improvement.
output.mp4
pip install glaivio-aiOptional extras:
pip install "glaivio-ai[privacy]" # PII redaction & re-hydration (Presidio)
pip install "glaivio-ai[gmail]" # Gmail channel support
pip install "glaivio-ai[knowledge]" # RAG / knowledge base (ChromaDB)
pip install "glaivio-ai[openai]" # OpenAI / GPT model support
pip install "glaivio-ai[gemini]" # Google Gemini model support
pip install "glaivio-ai[ollama]" # Local models via OllamaPersistent memory β conversation history survives restarts. Zero config in development, one line to switch to Postgres in production.
Self-improvement β when a user corrects the agent, it stores the lesson and applies it to all future conversations. No prompt editing required.
Human handoff β when the agent is stuck, it notifies a human operator and holds the conversation until they take over.
PII redaction β sensitive identifiers are redacted before reaching the LLM and re-hydrated in the reply, so users still get personalised responses.
Multi-channel β the same agent runs on WhatsApp or Gmail. Each channel can have its own prompt β formal for email, concise for WhatsApp.
Multi-model β Claude, GPT, Gemini, or local models via Ollama. Swap with one param.
An AI agent is an LLM that can take actions, remember things, and talk to users through a channel. Building one from scratch means solving the same problems every time:
- Which LLM? How do I swap between them?
- How do I give it memory across conversations?
- How do I connect it to WhatsApp or email?
- How do I pass the user's identity into a tool call?
- How do I redact sensitive data before it hits the LLM?
- How do I escalate to a human when it gets stuck?
- How do I deploy it?
There are no standard answers. Every team solves these differently, from scratch, every time.
LangChain gives you the primitives β a way to call LLMs, define tools, chain them together. But you still wire everything else yourself. It's powerful, but it's not a framework. It's Lego with no instructions.
Glaivio makes the decisions for you.
| LangChain | Glaivio | |
|---|---|---|
| Define a tool | β | β |
| Swap LLM providers | β | β |
| Memory across sessions | You build it | Built in |
| WhatsApp / Gmail channels | You build it | Built in |
| User ID in every skill | You build it | Built in |
| PII redaction | You build it | One flag |
| Human handoff | You build it | One line |
| Agent self-improvement | You build it | One flag |
| Deployment | You figure it out | One command |
Web era β Rails (2004) β one way to build web apps
Frontend β Next.js (2016) β one way to build React apps
Agent era β Glaivio (2026) β one way to build AI agents
Convention over configuration β the same philosophy that made Rails dominate web development for a decade. If you want full control β use LangChain. If you want to ship in hours not weeks β use Glaivio.
ββββββββββββββββββββββββ
β prompts/system.md β β who the agent is
ββββββββββββ¬ββββββββββββ
β
ββββββββββββΌββββββββββββ
β π§ LLM β β the brain
β Claude/GPT/Gemini β decides what to do
ββββββββββββ¬ββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ
β β β
βββββββββΌβββββββββ ββββββββββββΌβββββββββββ βββββββββΌβββββββββ
β @skill β β @skill β β @skill β β the arms
β search_db() β β send_email() β β book_slot() β what it can do
βββββββββ¬βββββββββ ββββββββββββ¬βββββββββββ βββββββββ¬βββββββββ
β β β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ
β
ββββββββββββΌββββββββββββ
β π± WhatsApp / Gmail β β the mouth
ββββββββββββ¬ββββββββββββ talks to users
β
ββββββββββββΌββββββββββββ ββββββββββββββββββββββββ
β User β β π€ Human operator β
β "that's wrong, ββββββββΊβ notified when β
β I meant X not Y" β stuck β agent is confused β
ββββββββββββ¬ββββββββββββ β β
β correction β replies "learned: β
ββββββββββββΌββββββββββββ β always confirm X" β
β π‘ Self-improvement βββββββββ β
β agent gets smarter β β
β with every mistake β β
ββββββββββββββββββββββββ
pip install glaivio-ai
glaivio new my-agent
cd my-agent
cp .env.example .env # add your ANTHROPIC_API_KEY
glaivio runYour agent is running. Open prompts/system.md to change its instructions. Open skills/example.py to add capabilities.
For a full real-world example β an AI receptionist that books appointments over WhatsApp β see the full quickstart below.
- Python 3.10+
- An API key for your chosen LLM:
- Anthropic Claude (default) β
ANTHROPIC_API_KEYfrom console.anthropic.com - OpenAI GPT β
OPENAI_API_KEY, install withpip install glaivio-ai[openai] - Google Gemini β
GOOGLE_API_KEY, install withpip install glaivio-ai[gemini] - Ollama (local, free) β no API key needed, install with
pip install glaivio-ai[ollama]
- Anthropic Claude (default) β
- For WhatsApp: a Twilio account with a WhatsApp-enabled number
- For Gmail: a Google Cloud project with the Gmail API enabled
An AI receptionist that books appointments over WhatsApp.
1. Scaffold
glaivio new my-receptionist
cd my-receptionist
cp .env.example .env # add your ANTHROPIC_API_KEY2. Write your prompt β prompts/system.md
You are an AI receptionist for Bright Smile Dental.
Your job is to help patients via WhatsApp. Keep replies SHORT β this is a text message.
Max 2 sentences. Never use bullet points or markdown.
When booking: ask for name, date and time. Always call check_availability first.
If the slot is taken, offer the alternatives the tool returns.
If medical or urgent, tell them to call the office directly.3. Define your skills
# skills/check_availability.py
from glaivio import skill
@skill
def check_availability(date: str, time: str) -> str:
"""Check if a time slot is available. Always call before book_appointment.
date: YYYY-MM-DD, time: HH:MM 24h format."""
# call your calendar API here
return "Available"# skills/book_appointment.py
from glaivio import skill
@skill
def book_appointment(patient_name: str, patient_phone: str, date: str, time: str) -> str:
"""Book an appointment. Only call after check_availability confirms the slot is free.
patient_phone: use the current user's ID from context.
date: YYYY-MM-DD, time: HH:MM 24h format."""
# call your calendar API here
return f"Booked {patient_name} on {date} at {time}"4. Wire it up β agent.py
from dotenv import load_dotenv
load_dotenv()
from glaivio import Agent
from skills.check_availability import check_availability
from skills.book_appointment import book_appointment
agent = Agent(
instructions="prompts/system.md",
skills=[check_availability, book_appointment],
learn_from_feedback=True,
privacy=True,
)
if __name__ == "__main__":
agent.run(channel="whatsapp")5. Run it
glaivio run --channel whatsappFor local testing, expose your server with ngrok and point your Twilio WhatsApp sandbox webhook at:
https://<your-ngrok-id>.ngrok.io/webhook/whatsapp
Send a WhatsApp message to your Twilio number. Your agent replies.
Write your agent's instructions in plain markdown β no string literals in code:
prompts/
βββ system.md
agent = Agent(
instructions="prompts/system.md",
...
)Glaivio loads it automatically. Edit the prompt without touching agent.py.
Skills are what your agent can do. Define them with @skill:
from glaivio import skill
@skill
def book_appointment(name: str, date: str, time: str) -> str:
"""Book an appointment. date: YYYY-MM-DD, time: HH:MM."""
# your logic here β call an API, write to a DB, anything
return "Booked successfully"The docstring is what the agent reads to decide when to use the skill. Write it clearly.
Skills always know who they're talking to β Glaivio injects the current user's ID automatically:
@skill
def book_appointment(name: str, user_phone: str, date: str, time: str) -> str:
"""Book an appointment. user_phone: use the current user's ID from context."""
...No closures. No wiring. It just works.
from glaivio import Agent
agent = Agent(
instructions="prompts/system.md",
skills=[book_appointment, check_availability],
model="claude-haiku-4-5-20251001", # or "gpt-4o", "gemini-2.0-flash", "ollama/llama3"
max_messages=20, # context window per session
)Add to .env:
ANTHROPIC_API_KEY=your_key
TWILIO_ACCOUNT_SID=your_sid
TWILIO_AUTH_TOKEN=your_token
TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886
Run:
glaivio run --channel whatsappFor local testing, expose your server with ngrok:
ngrok http 8000Then set the webhook URL in your Twilio WhatsApp sandbox:
https://<your-ngrok-id>.ngrok.io/webhook/whatsapp
Add to .env:
TWILIO_ACCOUNT_SID=your_sid
TWILIO_AUTH_TOKEN=your_token
Run:
glaivio run --channel smsPoint your Twilio number's Messaging webhook at:
https://<your-ngrok-id>.ngrok.io/webhook/sms
When a client calls and nobody picks up, Glaivio sends a TCPA-compliant consent request via SMS before starting any AI conversation.
Set the Voice Status Callback on your Twilio number to:
https://<your-ngrok-id>.ngrok.io/webhook/missed-call
The flow:
- Missed call β Glaivio sends: "Hi, this is [Business]. Sorry we missed your call! Reply YES to continue by text or STOP to opt out."
- Customer replies YES β AI conversation starts
- Customer replies STOP β opted out permanently, no further messages sent
Built-in guards prevent spam:
- Calls under 5 seconds are ignored (robocalls, accidental dials)
- Rate limited to one SMS per caller per 24 hours (persisted in Postgres if available)
- Opted-out numbers are never contacted again
Consent state is persisted in glaivio_sms_consent (Postgres) or in-memory as fallback.
For local testing, disable guards with:
glaivio run --channel sms --devInstall the extra dependency:
pip install glaivio-ai[gmail]Set up a Google Cloud project, enable the Gmail API, and download your OAuth credentials.json. Add to .env:
GMAIL_CREDENTIALS_FILE=credentials.json
GMAIL_POLL_INTERVAL=30
GMAIL_TARGET_EMAIL=support@yourcompany.com # optional β filter by recipient address
Run:
glaivio run --channel gmailThe first run opens a browser for OAuth. After that the token is cached and it runs silently. The agent uses LLM classification to decide which emails to handle β it reads its own instructions to determine what's relevant.
Each channel can have its own prompt. If prompts/gmail.md exists, Glaivio appends it to your base instructions automatically:
prompts/
βββ system.md β shared instructions (who the agent is)
βββ whatsapp.md β short replies, no markdown
βββ gmail.md β formal tone, full sentences, sign-off
Or set the default channel in .env:
GLAIVIO_CHANNEL=whatsapp
Run multiple clients from a single deployment. Each client gets their own number, prompt, and name β routed automatically by Twilio's To number.
clients.yaml:
"whatsapp:+447911111111":
name: bright-smile
instructions: prompts/bright-smile.md
"+14155551234":
name: QuickCool HVAC
instructions: prompts/quickcool.md
base: prompts/system.mdagent.py:
from glaivio import MultiAgent
from glaivio.memory import PostgresMemory
agent = MultiAgent(
config="clients.yaml",
skills=[check_availability, book_appointment],
memory=PostgresMemory(url=os.getenv("DATABASE_URL")),
)
agent.run(channel="sms")Use base in clients.yaml to share a generic prompt across all clients, with client-specific details appended:
"+14155551234":
name: QuickCool HVAC
instructions: prompts/quickcool.md # client-specific details
base: prompts/system.md # shared behaviourAt runtime Glaivio combines them: system.md + quickcool.md + sms.md (channel prompt).
prompts/
βββ system.md β shared behaviour (role, flow, rules)
βββ quickcool.md β client details (name, phone, hours)
βββ sms.md β channel formatting (auto-appended)
Zero config by default β conversation history lives in memory, works immediately.
For production, switch to Postgres:
import os
from glaivio import Agent
from glaivio.memory import PostgresMemory
agent = Agent(
instructions="prompts/system.md",
memory=PostgresMemory(url=os.getenv("DATABASE_URL")),
)Add DATABASE_URL to your .env and run glaivio migrate once to create the tables. History now survives restarts and works across multiple instances.
Drop files in and the agent searches them automatically:
from glaivio.knowledge import Knowledge
agent = Agent(
instructions="prompts/system.md",
knowledge=Knowledge(["./faqs.md", "./pricing.pdf", "./policies.txt"]),
)Supports .txt, .md, .pdf. Requires pip install glaivio-ai[knowledge].
When the agent can't handle something, escalate to a human:
from glaivio.handoff import handoff_to_human
agent = Agent(
instructions="prompts/system.md",
on_confusion=handoff_to_human(notify="whatsapp:+447911111111"),
)The agent detects confusion, notifies your team via WhatsApp, and holds the conversation until a human takes over.
Glaivio uses a redact & re-hydrate workflow powered by Microsoft Presidio:
- Redact β sensitive identifiers (NHS numbers, NI numbers, DOBs, emails) are replaced with placeholders before the message reaches the LLM
- Process β the LLM sees
[NHS_NUMBER_1]instead of real data - Re-hydrate β placeholders are swapped back in the LLM's reply before it reaches the user
Names and phone numbers are intentionally not redacted so booking skills work correctly.
agent = Agent(
instructions="prompts/system.md",
privacy=True,
)pip install "glaivio-ai[privacy]"
python -m spacy download en_core_web_lgYou'll see exactly what's happening in your logs:
[Glaivio] π Redacted: 'AB123456C' β '[NI_NUMBER_1]'
[Glaivio] π Sending to LLM: my NI number is [NI_NUMBER_1]
[Glaivio] π Restored: '[NI_NUMBER_1]' β 'AB123456C'
Every conversation turn is persisted to Postgres β raw message, redacted message, skill calls, and reply. Requires PostgresMemory.
SELECT user_id, raw_message, redacted_message, pii_redacted, skill_calls, reply, created_at
FROM glaivio_audit
ORDER BY created_at DESC;| Column | Description |
|---|---|
raw_message |
Exact message received from the user |
redacted_message |
Message sent to the LLM (PII replaced) |
pii_redacted |
Whether any PII was detected and redacted |
skill_calls |
JSON array of skills called and their arguments |
reply |
Final reply sent back to the user |
Run glaivio migrate to create all tables automatically.
Missed call rate limiting is tracked in glaivio_missed_calls:
SELECT from_number, to_number, sent_at
FROM glaivio_missed_calls
ORDER BY sent_at DESC;SMS consent state is tracked in glaivio_sms_consent:
SELECT from_number, to_number, status, updated_at
FROM glaivio_sms_consent
ORDER BY updated_at DESC;| Status | Meaning |
|---|---|
pending |
Consent request sent, waiting for YES/STOP |
consented |
Customer replied YES β AI conversation active |
opted_out |
Customer replied STOP β never contact again |
The agent learns from user corrections automatically:
agent = Agent(
instructions="prompts/system.md",
skills=[book_appointment],
learn_from_feedback=True,
)When a user says "that's wrong, I said Tuesday not Wednesday" β the agent extracts the correction, stores it, and applies it to all future conversations. No prompt editing required.
Glaivio agents work in any language β just write your prompts in the language you want the agent to respond in.
<!-- prompts/system.md -->
Eres un asistente para una clΓnica dental en Madrid.
Responde siempre en espaΓ±ol, de forma concisa y amable.No configuration needed. The agent responds in whatever language the prompt is written in β Spanish, French, Romanian, Arabic, anything.
For channel-specific tone in a different language, write prompts/whatsapp.md or prompts/gmail.md in the same language as your system prompt.
Extract structured data from natural language:
from pydantic import BaseModel
from glaivio import extract
class BookingRequest(BaseModel):
name: str
date: str # YYYY-MM-DD
time: str # HH:MM
booking = extract(BookingRequest, from_message="I need Tuesday 10am, I'm John Smith")
# β BookingRequest(name="John Smith", date="2026-03-25", time="10:00")| Prefix | Provider | Example |
|---|---|---|
claude- |
Anthropic | claude-haiku-4-5-20251001 |
gpt- |
OpenAI | gpt-4o |
gemini- |
gemini-2.0-flash |
|
ollama/ |
Local (Ollama) | ollama/llama3 |
Test your agent like you test your code:
# tests/test_booking.py
from glaivio.testing import eval, EvalCase
@eval
def test_booking(agent):
return [
EvalCase("I want Tuesday 10am", "booked", "basic booking"),
EvalCase("Cancel my appointment", "cancelled", "cancellation"),
EvalCase("Do you accept BUPA?", "bupa", "insurance FAQ"),
]glaivio test
# β 3/3 passed β
Change your instructions and run again β regressions are caught automatically.
glaivio deployGenerates a Dockerfile, docker-compose.yml, and railway.toml.
railway login
railway upDone. Your agent is live.
glaivio new my-app # scaffold a project
glaivio run # start the agent
glaivio run --channel whatsapp # start on a specific channel
glaivio run --channel sms --dev # SMS channel with guards disabled (local testing)
glaivio run --reset # clear all conversation history before starting
glaivio generate skill BookAppointment # generate a skill stub
glaivio migrate # run database migrations (Postgres only)
glaivio test # run evaluations
glaivio deploy # generate Railway deployment files
glaivio deploy --target render # generate Render deployment files
glaivio deploy --target fly # generate Fly.io deployment filesv0.2 β Memory & persistence β
- Postgres memory β conversation history survives restarts
- One-command database setup β tables created automatically, no manual SQL
- Session tracking β per-user metadata (channel, message count, last seen)
- Instructions in markdown β prompts live in files, not code
- User ID injection β skills always know who they're talking to
- Self-improvement β agent learns from user corrections automatically
- Human handoff β escalate to operator when confused, operator teaches agent
- Gmail channel β polls inbox, LLM classification, replies in-thread
- Channel-specific prompts β different tone per channel, zero config
v0.3 β Observability
- Token usage tracking per session, per user, per channel
- Cost dashboard β see exactly what each conversation costs
- Structured logs β every message, skill call, and result in one place
- Twilio webhook signature verification
v0.4 β Human handover (full loop)
- Improved confusion detection β smarter signals beyond keyword matching
- Full handover UI β operator sees full conversation history before taking over
- Handover analytics β how often does the agent get stuck, on what topics
- Automatic resume after operator resolves the conversation
v0.5 β Self-learning
- Feedback learning v2 β agent detects corrections across more signals
- Rule conflict resolution β when two learned rules contradict each other
- Learning dashboard β see what the agent has learned, edit or remove rules
v0.6 β Conscious / unconscious memory
- Two-tier memory system inspired by how humans think:
- Conscious β recent conversation, active context (already in v0.2)
- Unconscious β long-term facts about the user, retrieved semantically when relevant
- User profile store β agent remembers preferences, past interactions, stated facts
- Smart context injection β only pull in what's relevant to the current message
- Memory decay β old facts fade unless reinforced
v0.7 β Skills library
- Built-in skills for common integrations β calendars, payments, CRMs, messaging
- Import skills from community marketplaces β one line to add a pre-built skill
v1.0 β Glaivio Cloud
- One command deploy to the cloud
- Hosted memory, observability, and token dashboard out of the box
- No infrastructure to manage
β If you find this useful, a star goes a long way β it helps more developers find the project.
Have an idea or want to contribute? Open an issue or read the contributing guide.
MIT