Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ The setup wizard will guide you through:
5. **AI provider** - Select Anthropic (Claude) or OpenAI
6. **Model selection** - Choose model (e.g., Sonnet, Opus, GPT-5.3)
7. **Heartbeat interval** - Set proactive check-in frequency
8. **Memory retrieval (optional)** - Enable/disable QMD-based memory at setup time

<details>
<summary><b>📱 Channel Setup Guides</b></summary>
Expand Down Expand Up @@ -384,10 +385,69 @@ Located at `.tinyclaw/settings.json`:
},
"monitoring": {
"heartbeat_interval": 3600
},
"memory": {
"enabled": true,
"rerank": {
"enabled": true
},
"retention": {
"enabled": true,
"retain_days": 30,
"max_turn_files_per_agent": 2000,
"cleanup_interval_seconds": 300
},
"qmd": {
"enabled": true,
"command": "/home/me/.bun/bin/qmd",
"top_k": 4,
"min_score": 0.05,
"max_chars": 2500,
"update_interval_seconds": 120,
"embed_interval_seconds": 600,
"use_semantic_search": false,
"disable_query_expansion": true,
"allow_unsafe_vsearch": false,
"debug_logging": false
}
}
}
```

### Optional Memory Retrieval (QMD)

TinyClaw can use external memory retrieval with `qmd` (BM25 by default).

- Memory retrieval is **disabled by default**. Set `"memory.enabled": true` to enable.
- Retrieval runs only for user chat channels (`telegram`, `discord`, `whatsapp`).
- Heartbeat/system messages are excluded from retrieval and memory persistence.
- Turns are stored in `~/.tinyclaw/memory/turns/<agent_id>/`.
- Turn files are cleaned automatically (default: keep 30 days and max 2000 files per agent).
- Retrieval reranking heuristics are configurable via `memory.rerank` (can be disabled).

Quick setup:

```bash
# Install qmd (recommended by qmd project)
bun install -g github:tobi/qmd

# Linux/WSL: install sqlite-vec extension package if qmd reports missing extension
bun add -g sqlite-vec-linux-x64

# Verify qmd works
qmd status

# Optional: if qmd is not in PATH, set memory.qmd.command in settings.json
tinyclaw restart
```

Safety note:
- `use_semantic_search` (`qmd-vsearch`) is currently **experimental**.
- If semantic search is enabled, TinyClaw defaults to safe mode and will fall back to BM25 unless disable-expansion support is detected.
- Set `memory.qmd.allow_unsafe_vsearch: true` only if you explicitly want to allow unguarded `vsearch`.
- Set `memory.qmd.debug_logging: true` to print QMD memory debug logs (command/mode/timeout) in `queue.log`.
- When semantic search is enabled, TinyClaw periodically runs `qmd embed` in background (non-blocking). Default `embed_interval_seconds` is `600`.

### Heartbeat Configuration

Edit agent-specific heartbeat prompts:
Expand Down Expand Up @@ -480,6 +540,7 @@ tinyclaw logs all

- Bash version error → Install bash 4.0+: `brew install bash`
- WhatsApp not connecting → Reset auth: `tinyclaw channels reset whatsapp`
- WhatsApp startup fails with `Could not find Chrome` → Install browser: `npx puppeteer browsers install chrome`
- Messages stuck → Clear queue: `rm -rf .tinyclaw/queue/processing/*`
- Agent not found → Check: `tinyclaw agent list`

Expand Down
40 changes: 40 additions & 0 deletions docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,18 @@ tinyclaw channels reset whatsapp
tinyclaw restart
```

If startup fails with `Could not find Chrome (...)`, install Puppeteer Chrome first:

```bash
npx puppeteer browsers install chrome
tinyclaw restart
```

**Common causes:**
- QR code expired (scan within 60 seconds)
- Session files corrupted
- Multiple WhatsApp Web sessions active
- Chrome/Chromium missing for Puppeteer

**Solution:**
1. Delete session: `rm -rf .tinyclaw/whatsapp-session/`
Expand Down Expand Up @@ -375,6 +383,38 @@ ps aux | grep -E 'claude|codex|node' | awk '{print $4, $11}'
tail -f .tinyclaw/logs/queue.log | grep "Processing completed"
```

### QMD memory retrieval not working

If you enabled memory retrieval and see warnings like `qmd not found` or `Memory retrieval skipped`:

1. **Check qmd command:**
```bash
command -v qmd
qmd status
```

2. **If qmd is not in PATH, set explicit command in `~/.tinyclaw/settings.json`:**
```json
{
"memory": {
"enabled": true,
"qmd": {
"command": "/home/you/.bun/bin/qmd"
}
}
}
```

3. **Restart TinyClaw:**
```bash
tinyclaw restart
```

4. **Verify retrieval hits in logs:**
```bash
grep "Memory retrieval hit" ~/.tinyclaw/logs/queue.log
```

## Log Analysis

### Enable debug logging
Expand Down
38 changes: 38 additions & 0 deletions lib/daemon.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@
# Daemon lifecycle management for TinyClaw
# Handles starting, stopping, restarting, and status checking

whatsapp_enabled() {
local ch
for ch in "${ACTIVE_CHANNELS[@]}"; do
if [ "$ch" = "whatsapp" ]; then
return 0
fi
done
return 1
}

check_whatsapp_preflight() {
if ! whatsapp_enabled; then
return 0
fi

# whatsapp-web.js launches Puppeteer; fail fast if Chrome is missing.
local chrome_path
chrome_path=$(node -e "const p=require('puppeteer');process.stdout.write(p.executablePath() || '')" 2>/dev/null || true)
if [ -n "$chrome_path" ] && [ -x "$chrome_path" ]; then
return 0
fi

echo -e "${RED}WhatsApp preflight failed: Chrome/Chromium not found for Puppeteer.${NC}"
echo ""
echo "Install browser dependency and retry:"
echo -e " ${GREEN}npx puppeteer browsers install chrome${NC}"
echo -e " ${GREEN}./tinyclaw.sh start${NC}"
echo ""
echo "Tip: if using a custom HOME, run both commands with the same HOME value."
echo ""
log "WhatsApp preflight failed: missing Chrome/Chromium for Puppeteer"
return 1
}

# Start daemon
start_daemon() {
if session_exists; then
Expand Down Expand Up @@ -54,6 +88,10 @@ start_daemon() {
return 1
fi

if ! check_whatsapp_preflight; then
return 1
fi

# Validate tokens for channels that need them
for ch in "${ACTIVE_CHANNELS[@]}"; do
local token_key="${CHANNEL_TOKEN_KEY[$ch]:-}"
Expand Down
40 changes: 40 additions & 0 deletions lib/setup-wizard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,39 @@ DEFAULT_AGENT_NAME=$(echo "$DEFAULT_AGENT_NAME" | tr ' ' '-' | tr -cd 'a-zA-Z0-9
echo -e "${GREEN}✓ Default agent: $DEFAULT_AGENT_NAME${NC}"
echo ""

# Optional memory retrieval setup
echo "Enable memory retrieval (QMD)?"
echo -e "${YELLOW}(Optional. If disabled, TinyClaw still works normally.)${NC}"
echo ""
read -rp "Enable memory retrieval? [y/N]: " ENABLE_MEMORY

MEMORY_ENABLED=false
USE_SEMANTIC_SEARCH=false
QMD_COMMAND=""

if [[ "$ENABLE_MEMORY" =~ ^[yY] ]]; then
MEMORY_ENABLED=true

if [ -x "$HOME/.bun/bin/qmd" ]; then
QMD_COMMAND="$HOME/.bun/bin/qmd"
elif command -v qmd >/dev/null 2>&1; then
QMD_COMMAND="$(command -v qmd)"
fi

if [ -n "$QMD_COMMAND" ]; then
echo -e "${GREEN}✓ Found qmd: $QMD_COMMAND${NC}"
else
echo -e "${YELLOW}⚠ qmd not found in PATH right now.${NC}"
echo -e "${YELLOW} You can install later with: bun install -g github:tobi/qmd${NC}"
fi

read -rp "Use semantic search (vector, experimental)? [y/N]: " USE_SEMANTIC
if [[ "$USE_SEMANTIC" =~ ^[yY] ]]; then
USE_SEMANTIC_SEARCH=true
fi
fi
echo ""

# --- Additional Agents (optional) ---
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} Additional Agents (Optional)${NC}"
Expand Down Expand Up @@ -274,6 +307,12 @@ else
MODELS_SECTION='"models": { "provider": "openai", "openai": { "model": "'"${MODEL}"'" } }'
fi

if [ "$MEMORY_ENABLED" = true ]; then
MEMORY_SECTION='"memory": { "enabled": true, "qmd": { "enabled": true, "command": "'"${QMD_COMMAND}"'", "top_k": 4, "min_score": 0.05, "max_chars": 2500, "update_interval_seconds": 300, "embed_interval_seconds": 600, "use_semantic_search": '"${USE_SEMANTIC_SEARCH}"', "disable_query_expansion": true, "allow_unsafe_vsearch": false, "debug_logging": false } },'
else
MEMORY_SECTION='"memory": { "enabled": false },'
fi

cat > "$SETTINGS_FILE" <<EOF
{
"workspace": {
Expand All @@ -292,6 +331,7 @@ cat > "$SETTINGS_FILE" <<EOF
},
${AGENTS_JSON}
${MODELS_SECTION},
${MEMORY_SECTION}
"monitoring": {
"heartbeat_interval": ${HEARTBEAT_INTERVAL}
}
Expand Down
54 changes: 54 additions & 0 deletions scripts/patch-qmd-no-expansion.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail

# Patch qmd vsearch to support embedding-only mode via:
# QMD_VSEARCH_DISABLE_EXPANSION=1
#
# Usage:
# scripts/patch-qmd-no-expansion.sh
# scripts/patch-qmd-no-expansion.sh /custom/path/to/store.ts

TARGET_DEFAULT="$HOME/.bun/install/global/node_modules/qmd/src/store.ts"
TARGET_PATH="${1:-$TARGET_DEFAULT}"

if [[ ! -f "$TARGET_PATH" ]]; then
echo "Target file not found: $TARGET_PATH" >&2
exit 1
fi

if grep -q "QMD_VSEARCH_DISABLE_EXPANSION" "$TARGET_PATH"; then
echo "Already patched: $TARGET_PATH"
exit 0
fi

node - "$TARGET_PATH" <<'NODE'
const fs = require('fs');
const target = process.argv[2];
const src = fs.readFileSync(target, 'utf8');

const marker = 'QMD_VSEARCH_DISABLE_EXPANSION';
if (src.includes(marker)) {
console.log(`Already patched: ${target}`);
process.exit(0);
}

const pattern = / \/\/ Expand query — filter to vec\/hyde only \(lex queries target FTS, not vector\)\n const allExpanded = await store\.expandQuery\(query\);\n const vecExpanded = allExpanded\.filter\(q => q\.type !== 'lex'\);\n options\?\.hooks\?\.onExpand\?\.\(query, vecExpanded\);/;

const replacement = [
" // Optional: disable query expansion to run embedding-only vector search.",
" const disableExpansion = process.env.QMD_VSEARCH_DISABLE_EXPANSION === '1';",
" const vecExpanded = disableExpansion",
" ? []",
" : (await store.expandQuery(query)).filter(q => q.type !== 'lex');",
" options?.hooks?.onExpand?.(query, vecExpanded);",
].join('\n');

if (!pattern.test(src)) {
console.error('Patch anchor not found. qmd source layout may have changed.');
process.exit(2);
}

const out = src.replace(pattern, replacement);
fs.writeFileSync(target, out);
console.log(`Patched: ${target}`);
NODE
Loading