| name | waku-agent-assistant |
| description | Local-first personal AI agent with harness, loop, memory, and eval pillars |
| triggers | ["build a personal AI assistant","set up waku agent","implement AI agent memory system","create local-first AI agent","add semantic memory to agent","run waku agent dashboard","build agent with SQLite memory","implement agent eval harness"] |
Waku Agent Assistant
Skill by ara.so — AI Agent Skills collection.
What Waku Agent Does
Waku is a local-first personal AI assistant built on four core pillars:
- Harness: Gateway interface (CLI, Telegram, voice, web dashboard)
- Loop: ~95 lines of plain Python reasoning loop (LLM ↔ tools)
- Memory: Three-layer system (semantic facts, episodic events, procedural skills) in SQLite
- Eval/LLM-Ops: Built-in deterministic tests and LLM-as-judge evaluation with release gates
Your memory lives in a single state.db SQLite file that you own and can inspect. No frameworks hiding the implementation.
Installation
pip install waku-agent
git clone https://github.com/ShenSeanChen/waku-agent
cd waku-agent
uv venv && uv pip install -e .
cp .env.example .env
Configuration
Create .env file with your chosen provider (only one key needed):
WAKU_PROVIDER=anthropic
ANTHROPIC_API_KEY=your_key_here
TELEGRAM_BOT_TOKEN=your_bot_token
TAVILY_API_KEY=your_tavily_key
Supported providers: Anthropic (Claude), OpenAI, Gemini, DeepSeek, MiniMax, Kimi, GLM, OpenRouter, OpenCode Zen, OpenCode Go.
Key Commands
waku
waku dashboard
uv run waku
uv run waku dashboard
make run
make dashboard
Architecture Overview
Gateway → Working Memory → LLM Loop → Tools → Reply
↑ ↓
Retrieval Gate ← Memory (state.db)
↓
Consolidation
Core Components
- Gateway (
waku/gateway/): Multiple input channels
- Session (
waku/runtime/session.py): Working memory per turn
- Agent Loop (
waku/loop/agent.py): Reasoning and tool execution
- Memory (
waku/memory/): Three-pillar storage system
- Tools (
waku/tools/): Calendar, notes, search, messaging
- Ops (
waku/ops/): Tracing, eval, release gates
Memory System
Semantic Memory (Facts)
from waku.memory.semantic import SemanticMemory
mem = SemanticMemory()
mem.save_fact("Alex prefers morning meetings", tags=["preferences", "scheduling"])
results = mem.search("Alex meeting time")
Episodic Memory (Events)
from waku.memory.episodic import EpisodicMemory
ep_mem = EpisodicMemory()
ep_mem.save_event(
description="Tennis game with Raj",
timestamp="2026-08-05T08:00:00",
metadata={"location": "Park courts"}
)
recent = ep_mem.get_recent_events(days=7)
Procedural Memory (Skills)
Skills live in skills/*.md files and .waku/SOUL.md:
# SOUL.md - Your agent's personality and core instructions
## Identity
You are Waku, a helpful personal assistant.
## Communication Style
- Be concise and friendly
- Ask clarifying questions when needed
## Capabilities
- Calendar management
- Note-taking
- Web search
Working with the Agent Loop
The loop is in waku/loop/agent.py (~95 lines):
from waku.loop.agent import run_agent_loop
from waku.runtime.session import Session
session = Session(user_id="demo")
response = run_agent_loop(
user_message="Schedule tennis with Raj on Saturday at 8am",
session=session
)
print(response)
The loop does:
- Calls LLM with messages and available tools
- Executes any tool calls
- Appends results back to messages
- Repeats until LLM returns text (no more tool calls)
Building Custom Tools
Tools are Python functions with docstrings describing their purpose:
from waku.tools.base import tool
@tool
def calculate_tax(amount: float, rate: float) -> dict:
"""Calculate tax on an amount.
Args:
amount: The base amount in dollars
rate: Tax rate as decimal (e.g., 0.08 for 8%)
Returns:
dict with 'total', 'tax', 'base' keys
"""
tax = amount * rate
return {
"base": amount,
"tax": tax,
"total": amount + tax
}
Register it:
from waku.tools import register_tool
register_tool(calculate_tax)
Retrieval Gate
The gate decides whether to retrieve memory for a turn:
from waku.memory.retrieval_gate import should_retrieve
should_retrieve("What's 2 + 2?")
should_retrieve("When is my meeting with Alex?")
Check gate decisions in the dashboard Ops tab or the Overview gate bar.
Graph Workflows
For structured multi-step tasks, use graph workflows (waku/graph/):
from waku.graph.triage import run_triage_workflow
result = run_triage_workflow(
user_message="Search for World Cup games and add them to my calendar",
session=session
)
Dashboard Usage
waku dashboard
Dashboard Tabs
- Overview: Architecture diagram, costs, latency, gate metrics
- Gateway: Unified conversation across all input channels
- Loop: Turn-by-turn execution with tool calls and tokens
- Graph: Workflow topology visualization
- Memory: Browse semantic facts, episodes, skills
- Tools: Available tools and their results
- Data: Live SQLite browser for
state.db
- Ops: Eval history, gate decisions, traces
Chat in Dashboard
The chat dock (right side) supports:
- Text input
- Voice input
- New conversation
- Message history
- Multi-channel tagging (shows if message came from CLI, Telegram, etc.)
Evaluation System
Deterministic Tests
from waku.memory.semantic import SemanticMemory
def test_fact_storage():
mem = SemanticMemory()
mem.save_fact("Test fact")
results = mem.search("Test")
assert len(results) > 0
assert "Test fact" in results[0]["content"]
Run tests:
pytest evals/deterministic/
LLM-as-Judge Evals
SCENARIOS = [
{
"input": "Remember that Alex prefers morning meetings",
"expected_behavior": "Should save a semantic fact about Alex's preference",
"judge_prompt": "Did the agent store this preference in memory?"
}
]
Run judge evals:
python evals/judge/run_judge.py
Common Patterns
Multi-Tool Coordination
"Search for Python conferences in 2026 and add them to my calendar"
Memory Consolidation
After every N chat turns, Waku consolidates episodic memory into semantic facts:
from waku.memory.consolidation import consolidate_memory
consolidate_memory(session)
Cross-Channel Conversations
Start a conversation in CLI, continue in dashboard, respond via Telegram — all tracked in one thread:
$ waku
You: Schedule tennis on Saturday
Waku: What time?
You: 8am please
You: /status
Waku: Your Saturday 8am tennis game is confirmed.
Inspecting Memory
Via Dashboard
Data tab → facts or episodes table → browse or run SQL:
SELECT * FROM facts WHERE content LIKE '%Alex%';
Via Code
import sqlite3
conn = sqlite3.connect(".waku/state.db")
cursor = conn.cursor()
cursor.execute("SELECT * FROM facts")
for row in cursor.fetchall():
print(row)
cursor.execute("SELECT * FROM facts WHERE content MATCH 'meeting'")
Via File System
cat .waku/MEMORY.md
cat .waku/SOUL.md
ls skills/
Troubleshooting
"No API key found"
Set one provider key in .env:
WAKU_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...
Memory not persisting
Check that .waku/state.db exists and is writable:
ls -la .waku/
sqlite3 .waku/state.db "SELECT COUNT(*) FROM facts;"
Dashboard won't start
Port 7777 already in use:
lsof -i :7777
Agent not using tools
Check tool registration in waku/tools/__init__.py and verify tools appear in dashboard Tools tab.
Gate always skipping retrieval
Check gate threshold in waku/memory/retrieval_gate.py:
GATE_THRESHOLD = 0.5
Advanced: Custom Gateway
Add a new input channel:
from waku.runtime.session import Session
from waku.loop.agent import run_agent_loop
def handle_slack_message(user_id: str, message: str):
session = Session(user_id=user_id, channel="slack")
response = run_agent_loop(message, session)
return response
File Structure
waku-agent/
├── waku/
│ ├── gateway/ # CLI, Telegram, dashboard, voice
│ ├── loop/ # agent.py (main loop), models.py (LLM adapters)
│ ├── graph/ # Structured workflows
│ ├── memory/ # semantic/, episodic/, procedural/, consolidation
│ ├── tools/ # Built-in tools (calendar, notes, search)
│ ├── runtime/ # session.py (working memory)
│ └── ops/ # tracing.py, release_gate.py
├── evals/
│ ├── deterministic/ # Pytest-based tests
│ └── judge/ # LLM-as-judge scenarios
├── skills/ # Procedural memory (.md files)
├── .waku/
│ ├── state.db # SQLite database (your memory)
│ ├── SOUL.md # Agent personality
│ └── MEMORY.md # Human-readable memory mirror
└── .env # API keys (gitignored)
Resources
License
MIT License — code you own and can modify freely.