# Waku Agent Assistant

> Local-first personal AI agent with harness, loop, memory, and eval pillars

- Skill: `aradotso/waku-agent-assistant` (Agent Skill)
- Install (CLI): `npx skillmds@latest add aradotso/waku-agent-assistant`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aradotso/waku-agent-assistant/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: aradotso (https://skillmd.com/u/aradotso)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aradotso/waku-agent-assistant

---


# Waku Agent Assistant

> Skill by [ara.so](https://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

```bash
# Quick install (use pre-built package)
pip install waku-agent

# Development install (clone and modify)
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):

```bash
# Choose one provider:
WAKU_PROVIDER=anthropic  # default, or: openai, gemini, deepseek, openrouter
ANTHROPIC_API_KEY=your_key_here

# Optional: Telegram integration
TELEGRAM_BOT_TOKEN=your_bot_token

# Optional: Web search capability
TAVILY_API_KEY=your_tavily_key
```

Supported providers: Anthropic (Claude), OpenAI, Gemini, DeepSeek, MiniMax, Kimi, GLM, OpenRouter, OpenCode Zen, OpenCode Go.

## Key Commands

```bash
# Terminal chat interface
waku

# Web dashboard (localhost:7777)
waku dashboard

# Run with uv (no venv activation needed)
uv run waku
uv run waku dashboard

# Development shortcuts
make run         # terminal interface
make dashboard   # web interface
```

## Architecture Overview

```
Gateway → Working Memory → LLM Loop → Tools → Reply
                ↑                        ↓
         Retrieval Gate ← Memory (state.db)
                              ↓
                        Consolidation
```

### Core Components

1. **Gateway** (`waku/gateway/`): Multiple input channels
2. **Session** (`waku/runtime/session.py`): Working memory per turn
3. **Agent Loop** (`waku/loop/agent.py`): Reasoning and tool execution
4. **Memory** (`waku/memory/`): Three-pillar storage system
5. **Tools** (`waku/tools/`): Calendar, notes, search, messaging
6. **Ops** (`waku/ops/`): Tracing, eval, release gates

## Memory System

### Semantic Memory (Facts)

```python
from waku.memory.semantic import SemanticMemory

mem = SemanticMemory()

# Save a fact
mem.save_fact("Alex prefers morning meetings", tags=["preferences", "scheduling"])

# Search facts
results = mem.search("Alex meeting time")
# Returns: [{"content": "Alex prefers morning meetings", "tags": [...], ...}]
```

### Episodic Memory (Events)

```python
from waku.memory.episodic import EpisodicMemory

ep_mem = EpisodicMemory()

# Save an event
ep_mem.save_event(
    description="Tennis game with Raj",
    timestamp="2026-08-05T08:00:00",
    metadata={"location": "Park courts"}
)

# Retrieve recent events
recent = ep_mem.get_recent_events(days=7)
```

### Procedural Memory (Skills)

Skills live in `skills/*.md` files and `.waku/SOUL.md`:

```markdown
# 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):

```python
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)  # Agent's final reply after tool calls
```

The loop does:
1. Calls LLM with messages and available tools
2. Executes any tool calls
3. Appends results back to messages
4. Repeats until LLM returns text (no more tool calls)

## Building Custom Tools

Tools are Python functions with docstrings describing their purpose:

```python
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:

```python
from waku.tools import register_tool

register_tool(calculate_tax)
```

## Retrieval Gate

The gate decides whether to retrieve memory for a turn:

```python
from waku.memory.retrieval_gate import should_retrieve

# Simple query that doesn't need context
should_retrieve("What's 2 + 2?")  # → False

# Query that needs memory lookup
should_retrieve("When is my meeting with Alex?")  # → True
```

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/`):

```python
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
)

# The workflow will:
# 1. Classify the intent (search + calendar)
# 2. Execute search tool multiple times
# 3. Parse results
# 4. Create calendar events for each game
```

## Dashboard Usage

```bash
waku dashboard
# Opens http://localhost:7777
```

### 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

```python
# evals/deterministic/test_memory.py
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:

```bash
pytest evals/deterministic/
```

### LLM-as-Judge Evals

```python
# evals/judge/scenarios.py
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:

```bash
python evals/judge/run_judge.py
```

## Common Patterns

### Multi-Tool Coordination

```python
# Agent automatically chains tools:
"Search for Python conferences in 2026 and add them to my calendar"

# Execution flow:
# 1. search_web("Python conferences 2026")
# 2. search_web("PyCon 2026 dates")
# 3. create_event("PyCon", "2026-04-15")
# 4. create_event("EuroPython", "2026-07-20")
# ... (multiple iterations in one turn)
```

### Memory Consolidation

After every N chat turns, Waku consolidates episodic memory into semantic facts:

```python
from waku.memory.consolidation import consolidate_memory

# Runs automatically, but you can trigger manually:
consolidate_memory(session)

# Converts patterns like:
# Episodes: "Meeting with Alex (9am)", "Call with Alex (10am)"
# → Fact: "Alex prefers morning communication"
```

### Cross-Channel Conversations

Start a conversation in CLI, continue in dashboard, respond via Telegram — all tracked in one thread:

```bash
# Terminal
$ waku
You: Schedule tennis on Saturday
Waku: What time?

# Dashboard (localhost:7777)
You: 8am please

# Telegram
You: /status
Waku: Your Saturday 8am tennis game is confirmed.
```

## Inspecting Memory

### Via Dashboard

**Data** tab → `facts` or `episodes` table → browse or run SQL:

```sql
SELECT * FROM facts WHERE content LIKE '%Alex%';
```

### Via Code

```python
import sqlite3

conn = sqlite3.connect(".waku/state.db")
cursor = conn.cursor()

# View all facts
cursor.execute("SELECT * FROM facts")
for row in cursor.fetchall():
    print(row)

# Full-text search
cursor.execute("SELECT * FROM facts WHERE content MATCH 'meeting'")
```

### Via File System

```bash
cat .waku/MEMORY.md  # Human-readable mirror of state.db
cat .waku/SOUL.md    # Agent personality and instructions
ls skills/           # Procedural skills (*.md)
```

## Troubleshooting

### "No API key found"

Set one provider key in `.env`:

```bash
WAKU_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...
```

### Memory not persisting

Check that `.waku/state.db` exists and is writable:

```bash
ls -la .waku/
sqlite3 .waku/state.db "SELECT COUNT(*) FROM facts;"
```

### Dashboard won't start

Port 7777 already in use:

```bash
# Find process using port
lsof -i :7777
# Kill it or change port in waku/gateway/dashboard_server.py
```

### 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`:

```python
# Adjust sensitivity (0.0 = always retrieve, 1.0 = never retrieve)
GATE_THRESHOLD = 0.5  # Default
```

## Advanced: Custom Gateway

Add a new input channel:

```python
# waku/gateway/slack.py
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  # Send back to Slack
```

## 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

- [GitHub Repository](https://github.com/ShenSeanChen/waku-agent)
- [20-min Code Walkthrough](https://www.youtube.com/watch?v=rvRyBhILrls)
- [Architecture Diagrams](docs/whiteboards/) (editable .excalidraw files)
- [Full Architecture Docs](docs/architecture.md)

## License

MIT License — code you own and can modify freely.

