Keni — Production AI Agent with Native Anthropic Tool-Use
Conversational AI agent built with the Anthropic SDK's native agentic loop — no orchestration frameworks. Runs live on this portfolio with multi-tier identity, persistent memory, Google Calendar/Gmail tools, Telegram integration, and a resilient multi-provider LLM fallback chain.
What it is
Keni is a production conversational agent embedded in this portfolio. It answers questions about my work, schedules meetings, drafts emails, and adapts its behavior based on who is asking. It is deployed on Google Cloud Run and handles requests from both a web chat widget and a Telegram bot.
The core design decision: no orchestration framework. The agentic loop is implemented directly against the Anthropic SDK, making the tool-use contract explicit and the system fully auditable.
Agentic Architecture — Native Tool-Use Loop
Instead of delegating control to LangGraph or LangChain, the agent owns its own loop:
1. Build context: system prompt (tier-scoped) + conversation history + tool schemas
↓
2. Call Claude → response is one of:
├─ [{type: "text"}] → final answer, loop ends
└─ [{type: "tool_use", name: "...", input: {...}}] → tool invocation requested
↓
3. Execute the tool locally (calendar, email, memory, search)
↓
4. Append to history:
- assistant turn: the tool_use content blocks
- user turn: [{type: "tool_result", tool_use_id: "...", content: "..."}]
↓
5. Back to step 1 with the updated history
↓
6. Safety cap at 10 iterations → forces a graceful exit
This pattern keeps the orchestration logic in plain Python — no hidden graph state, no magic middleware.
Tools available to the agent
| Tool | What it does |
|---|---|
check_availability |
Queries Google Calendar for free slots |
create_event |
Books a meeting and sends invite |
get_events |
Lists upcoming events in a range |
send_email |
Drafts and sends email via Gmail API |
search_web |
Retrieves context for open-ended questions |
get_memory |
Reads user-specific facts from Supabase |
save_memory |
Persists new facts across sessions |
Each tool is a typed Python function with a JSON schema definition injected into every API call. Claude decides autonomously which tools to call, in what order, and when to stop.
Multi-tier Identity
The agent's behavior changes based on who is on the other side of the conversation:
| Tier | How it authenticates | What changes |
|---|---|---|
| PUBLIC | none | Portfolio Q&A, basic scheduling |
| CLIENT | OTP via email | Extended professional context |
| FAMILY | OTP via email | Personal tools + family-specific knowledge |
| OWNER | Passphrase | Full tool access, no guardrails |
The system prompt is built dynamically from the tier. No branching logic inside the agent — the prompt itself scopes what Keni knows and what it can do.
Persistent Memory
Keni maintains a per-user memory backed by Supabase:
- Profiles: inferred facts about the user (role, preferences, goals)
- Long-term facts: explicitly saved across sessions
- Recent messages: rolling window for continuity
On each turn, relevant memories are retrieved and injected into the prompt context. New facts are saved automatically when the agent decides to call save_memory.
Multi-provider Fallback Chain
LLM_PROVIDER_CHAIN = anthropic, openai, groq
If Anthropic is unavailable, the agent retries transparently with OpenAI (gpt-4o-mini) and then Groq (llama-3.3-70b-versatile). The same tool schema format is used across providers — swapping the LLM requires zero changes to the agent logic.
Audio (Telegram voice notes) is transcribed via Whisper on Groq or OpenAI before the text enters the conversation loop.
Platforms
- Web: chat widget embedded in this site, session identity via signed HMAC cookie
- Telegram: webhook endpoint receiving text and voice messages from a real bot
Both platforms share the same agent core. Routing and session resolution are the only platform-specific layers.
Architecture
domain/ ← entities and port protocols (no external imports)
application/ ← KeniAgent: loop, prompt builder, tool dispatch
adapters/ ← calendar, email, memory, transcription, LLM providers
interfaces/ ← FastAPI routes (web + Telegram), Jinja2 templates
Hexagonal layout keeps the agent core independent of infrastructure. Swapping the calendar provider or the LLM requires touching only the adapter layer.
Stack
- LLM: Anthropic Claude Sonnet (primary) · OpenAI · Groq (fallback)
- Audio STT: Whisper via Groq or OpenAI
- Backend: Python 3.12, FastAPI, Pydantic v2
- Identity / DB: Supabase (PostgreSQL + RLS + OTP)
- Integrations: Google Calendar API, Gmail API, Telegram Bot API
- Infra: Docker multi-stage, Google Cloud Run