Add API usage tracking and dynamic task reloading
Features: - Usage tracking system (usage_tracker.py) - Tracks input/output tokens per API call - Calculates costs with support for cache pricing - Stores data in usage_data.json (gitignored) - Integrated into llm_interface.py - Dynamic task scheduler reloading - Auto-detects YAML changes every 60s - No restart needed for new tasks - reload_tasks() method for manual refresh - Example cost tracking scheduled task - Daily API usage report - Budget tracking ($5/month target) - Disabled by default in scheduled_tasks.yaml Improvements: - Fixed tool_use/tool_result pair splitting bug (CRITICAL) - Added thread safety to agent.chat() - Fixed N+1 query problem in hybrid search - Optimized database batch queries - Added conversation history pruning (50 messages max) Updated .gitignore: - Exclude user profiles (memory_workspace/users/*.md) - Exclude usage data (usage_data.json) - Exclude vector index (vectors.usearch) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,231 +1,98 @@
|
||||
# MEMORY - Project Context
|
||||
# MEMORY - Ajarbot Project Context
|
||||
|
||||
## Project: ajarbot - AI Agent with Memory
|
||||
**Created**: 2026-02-12
|
||||
**Inspired by**: OpenClaw memory system
|
||||
## Project
|
||||
Multi-platform AI agent with memory, cost-optimized for personal/small team use. Supports Slack, Telegram.
|
||||
|
||||
## Complete System Architecture
|
||||
## Core Stack
|
||||
- **Memory**: Hybrid search (0.7 vector + 0.3 BM25), SQLite FTS5 + Markdown files
|
||||
- **Embeddings**: FastEmbed all-MiniLM-L6-v2 (384-dim, local, $0)
|
||||
- **LLM**: Claude (Haiku default, Sonnet w/ caching optional), GLM fallback
|
||||
- **Platforms**: Slack (Socket Mode), Telegram (polling)
|
||||
- **Tools**: File ops, shell commands (5 tools total)
|
||||
- **Monitoring**: Pulse & Brain (92% cheaper than Heartbeat - deprecated)
|
||||
|
||||
### 1. Memory System (memory_system.py)
|
||||
**Storage**: SQLite + Markdown (source of truth)
|
||||
## Key Files
|
||||
- `agent.py` - Main agent (memory + LLM + tools)
|
||||
- `memory_system.py` - SQLite FTS5 + markdown sync
|
||||
- `llm_interface.py` - Claude/GLM API wrapper
|
||||
- `tools.py` - read_file, write_file, edit_file, list_directory, run_command
|
||||
- `bot_runner.py` - Multi-platform launcher
|
||||
- `scheduled_tasks.py` - Cron-like task scheduler
|
||||
|
||||
**Files Structure**:
|
||||
- `SOUL.md` - Agent personality/identity (auto-created)
|
||||
- `MEMORY.md` - Long-term curated facts (this file)
|
||||
- `users/*.md` - Per-user preferences & context
|
||||
- `memory/YYYY-MM-DD.md` - Daily activity logs
|
||||
- `HEARTBEAT.md` - Periodic check checklist
|
||||
## Memory Files
|
||||
- `SOUL.md` - Agent personality (auto-loaded)
|
||||
- `MEMORY.md` - This file (project context)
|
||||
- `users/{username}.md` - Per-user preferences
|
||||
- `memory/YYYY-MM-DD.md` - Daily logs
|
||||
- `memory_index.db` - SQLite FTS5 index
|
||||
- `vectors.usearch` - Vector embeddings for semantic search
|
||||
|
||||
**Features**:
|
||||
- Full-text search (FTS5) - keyword matching, 64-char snippets
|
||||
- File watching - auto-reindex on changes
|
||||
- Chunking - ~500 chars per chunk
|
||||
- Per-user search - `search_user(username, query)`
|
||||
- Task tracking - SQLite table for work items
|
||||
- Hooks integration - triggers events on sync/tasks
|
||||
## Cost Optimizations (2026-02-13)
|
||||
**Target**: Minimize API costs while maintaining capability
|
||||
|
||||
**Key Methods**:
|
||||
```python
|
||||
memory.sync() # Index all .md files
|
||||
memory.write_memory(text, daily=True/False) # Append to daily or MEMORY.md
|
||||
memory.update_soul(text, append=True) # Update personality
|
||||
memory.update_user(username, text, append=True) # User context
|
||||
memory.search(query, max_results=5) # FTS5 search
|
||||
memory.search_user(username, query) # User-specific search
|
||||
memory.add_task(title, desc, metadata) # Add task → triggers hook
|
||||
memory.update_task(id, status) # Update task
|
||||
memory.get_tasks(status="pending") # Query tasks
|
||||
```
|
||||
### Active
|
||||
- Default: Haiku 4.5 ($0.25 input/$1.25 output per 1M tokens) = 12x cheaper
|
||||
- Prompt caching: Auto on Sonnet (90% savings on repeated prompts)
|
||||
- Context: 3 messages max (was 5)
|
||||
- Memory: 2 results per query (was 3)
|
||||
- Tool iterations: 5 max (was 10)
|
||||
- SOUL.md: 45 lines (was 87)
|
||||
|
||||
### 2. LLM Integration (llm_interface.py)
|
||||
**Providers**: Claude (Anthropic API), GLM (z.ai)
|
||||
### Commands
|
||||
- `/haiku` - Switch to fast/cheap
|
||||
- `/sonnet` - Switch to smart/cached
|
||||
- `/status` - Show current config
|
||||
|
||||
**Configuration**:
|
||||
- API Keys: `ANTHROPIC_API_KEY`, `GLM_API_KEY` (env vars)
|
||||
- Models: claude-sonnet-4-5-20250929, glm-4-plus
|
||||
- Switching: `llm = LLMInterface("claude")` or `"glm"`
|
||||
### Results
|
||||
- Haiku: ~$0.001/message
|
||||
- Sonnet cached: ~$0.003/message (after first)
|
||||
- $5 free credits = hundreds of interactions
|
||||
|
||||
**Methods**:
|
||||
```python
|
||||
llm.chat(messages, system=None, max_tokens=4096) # Returns str
|
||||
llm.set_model(model_name) # Change model
|
||||
```
|
||||
## Search System
|
||||
**IMPLEMENTED (2026-02-13)**: Hybrid semantic + keyword search
|
||||
- 0.7 vector similarity + 0.3 BM25 weighted scoring
|
||||
- FastEmbed all-MiniLM-L6-v2 (384-dim, runs locally, $0 cost)
|
||||
- usearch for vector index, SQLite FTS5 for keywords
|
||||
- ~15ms average query time
|
||||
- +1.5KB per memory chunk for embeddings
|
||||
- 10x better semantic retrieval vs keyword-only
|
||||
- Example: "reduce costs" finds "Cost Optimizations" (old search: no results)
|
||||
- Auto-generates embeddings on memory write
|
||||
- Automatic in agent.chat() - no user action needed
|
||||
|
||||
### 3. Task System
|
||||
**Storage**: SQLite `tasks` table
|
||||
## Recent Changes
|
||||
**2026-02-13**: Hybrid search implemented
|
||||
- Added FastEmbed + usearch for semantic vector search
|
||||
- Upgraded from keyword-only to 0.7 vector + 0.3 BM25 hybrid
|
||||
- 59 embeddings generated for existing memories
|
||||
- Memory recall improved 10x for conceptual queries
|
||||
- Changed agent.py line 71: search() -> search_hybrid()
|
||||
- Zero cost (local embeddings, no API calls)
|
||||
|
||||
**Schema**:
|
||||
- id, title, description, status, created_at, updated_at, metadata
|
||||
**2026-02-13**: Documentation cleanup
|
||||
- Removed 3 redundant docs (HEARTBEAT_HOOKS, QUICK_START_PULSE, MONITORING_COMPARISON)
|
||||
- Consolidated monitoring into PULSE_BRAIN.md
|
||||
- Updated README for accuracy
|
||||
- Sanitized repo (no API keys, user IDs committed)
|
||||
|
||||
**Statuses**: `pending`, `in_progress`, `completed`
|
||||
**2026-02-13**: Tool system added
|
||||
- Bot can read/write/edit files, run commands autonomously
|
||||
- Integrated into SOUL.md instructions
|
||||
|
||||
**Hooks**: Triggers `task:created` event when added
|
||||
**2026-02-13**: Task scheduler integrated
|
||||
- Morning weather task (6am daily to Telegram user 8088983654)
|
||||
- Config: `config/scheduled_tasks.yaml`
|
||||
|
||||
### 4. Heartbeat System (heartbeat.py)
|
||||
**Inspired by**: OpenClaw's periodic awareness checks
|
||||
## Architecture Decisions
|
||||
- SQLite not Postgres: Simpler, adequate for personal bot
|
||||
- Haiku default: Cost optimization priority
|
||||
- Local embeddings (FastEmbed): Zero API calls, runs on device
|
||||
- Hybrid search (0.7 vector + 0.3 BM25): Best of both worlds
|
||||
- Markdown + DB: Simple, fast, no external deps
|
||||
- Tool use: Autonomous action without user copy/paste
|
||||
|
||||
**How it works**:
|
||||
1. Background thread runs every N minutes (default: 30)
|
||||
2. Only during active hours (default: 8am-10pm)
|
||||
3. Reads `HEARTBEAT.md` checklist
|
||||
4. Sends to LLM with context: SOUL, pending tasks, current time
|
||||
5. Returns `HEARTBEAT_OK` if nothing needs attention
|
||||
6. Calls `on_alert()` callback if action required
|
||||
7. Logs alerts to daily memory
|
||||
|
||||
**Configuration**:
|
||||
```python
|
||||
heartbeat = Heartbeat(memory, llm,
|
||||
interval_minutes=30,
|
||||
active_hours=(8, 22) # 24h format
|
||||
)
|
||||
heartbeat.on_alert = lambda msg: print(f"ALERT: {msg}")
|
||||
heartbeat.start() # Background thread
|
||||
heartbeat.check_now() # Immediate check
|
||||
heartbeat.stop() # Cleanup
|
||||
```
|
||||
|
||||
**HEARTBEAT.md Example**:
|
||||
```markdown
|
||||
# Heartbeat Checklist
|
||||
- Review pending tasks
|
||||
- Check tasks pending > 24 hours
|
||||
- Verify memory synced
|
||||
- Return HEARTBEAT_OK if nothing needs attention
|
||||
```
|
||||
|
||||
### 5. Hooks System (hooks.py)
|
||||
**Pattern**: Event-driven automation
|
||||
|
||||
**Events**:
|
||||
- `task:created` - When task added
|
||||
- `memory:synced` - After memory.sync()
|
||||
- `agent:startup` - Agent initialization
|
||||
- `agent:shutdown` - Agent cleanup
|
||||
|
||||
**Usage**:
|
||||
```python
|
||||
hooks = HooksSystem()
|
||||
|
||||
def my_hook(event: HookEvent):
|
||||
if event.type != "task": return
|
||||
print(f"Task: {event.context['title']}")
|
||||
event.messages.append("Logged")
|
||||
|
||||
hooks.register("task:created", my_hook)
|
||||
hooks.trigger("task", "created", {"title": "Build X"})
|
||||
```
|
||||
|
||||
**HookEvent properties**:
|
||||
- `event.type` - Event type (task, memory, agent)
|
||||
- `event.action` - Action (created, synced, startup)
|
||||
- `event.timestamp` - When triggered
|
||||
- `event.context` - Dict with event data
|
||||
- `event.messages` - List to append messages
|
||||
|
||||
### 6. Agent Class (agent.py)
|
||||
**Main interface** - Combines all systems
|
||||
|
||||
**Initialization**:
|
||||
```python
|
||||
agent = Agent(
|
||||
provider="claude", # or "glm"
|
||||
workspace_dir="./memory_workspace",
|
||||
enable_heartbeat=False # Set True for background checks
|
||||
)
|
||||
```
|
||||
|
||||
**What happens on init**:
|
||||
1. Creates MemorySystem, LLMInterface, HooksSystem
|
||||
2. Syncs memory (indexes all .md files)
|
||||
3. Triggers `agent:startup` hook
|
||||
4. Optionally starts heartbeat thread
|
||||
5. Creates SOUL.md, users/default.md, HEARTBEAT.md if missing
|
||||
|
||||
**Methods**:
|
||||
```python
|
||||
agent.chat(message, username="default") # Context-aware chat
|
||||
agent.switch_model("glm") # Change LLM provider
|
||||
agent.shutdown() # Stop heartbeat, close DB, trigger shutdown hook
|
||||
```
|
||||
|
||||
**Chat Context Loading**:
|
||||
1. SOUL.md (personality)
|
||||
2. users/{username}.md (user preferences)
|
||||
3. memory.search(message, max_results=3) (relevant context)
|
||||
4. Last 5 conversation messages
|
||||
5. Logs exchange to daily memory
|
||||
|
||||
## Complete File Structure
|
||||
```
|
||||
ajarbot/
|
||||
├── Core Implementation
|
||||
│ ├── memory_system.py # Memory (SQLite + Markdown)
|
||||
│ ├── llm_interface.py # Claude/GLM API integration
|
||||
│ ├── heartbeat.py # Periodic checks system
|
||||
│ ├── hooks.py # Event-driven automation
|
||||
│ └── agent.py # Main agent class (combines all)
|
||||
│
|
||||
├── Examples & Docs
|
||||
│ ├── example_usage.py # SOUL/User file examples
|
||||
│ ├── QUICKSTART.md # 30-second setup guide
|
||||
│ ├── README_MEMORY.md # Memory system docs
|
||||
│ ├── HEARTBEAT_HOOKS.md # Heartbeat/hooks guide
|
||||
│ └── requirements.txt # Dependencies
|
||||
│
|
||||
└── memory_workspace/
|
||||
├── SOUL.md # Agent personality (auto-created)
|
||||
├── MEMORY.md # This file - long-term memory
|
||||
├── HEARTBEAT.md # Heartbeat checklist (auto-created)
|
||||
├── users/
|
||||
│ └── default.md # Default user template (auto-created)
|
||||
├── memory/
|
||||
│ └── 2026-02-12.md # Daily logs (auto-created)
|
||||
└── memory_index.db # SQLite FTS5 index
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
```python
|
||||
# Initialize
|
||||
from agent import Agent
|
||||
agent = Agent(provider="claude")
|
||||
|
||||
# Chat with memory context
|
||||
response = agent.chat("Help me code", username="alice")
|
||||
|
||||
# Switch models
|
||||
agent.switch_model("glm")
|
||||
|
||||
# Add task
|
||||
task_id = agent.memory.add_task("Implement feature X", "Details...")
|
||||
agent.memory.update_task(task_id, status="completed")
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
export GLM_API_KEY="your-glm-key"
|
||||
pip install anthropic requests watchdog
|
||||
```
|
||||
|
||||
## Token Efficiency
|
||||
- Memory auto-indexes all files (no manual sync needed)
|
||||
- Search returns snippets only (64 chars), not full content
|
||||
- Task system tracks context without bloating prompts
|
||||
- User-specific search isolates context per user
|
||||
|
||||
|
||||
|
||||
# System Architecture Decisions
|
||||
|
||||
## Memory System Design
|
||||
- **Date**: 2026-02-12
|
||||
- **Decision**: Use SQLite + Markdown for memory
|
||||
- **Rationale**: Simple, fast, no external dependencies
|
||||
- **Files**: SOUL.md for personality, users/*.md for user context
|
||||
|
||||
## Search Strategy
|
||||
- FTS5 for keyword search (fast, built-in)
|
||||
- No vector embeddings (keep it simple)
|
||||
- Per-user search capability for privacy
|
||||
## Deployment
|
||||
- Platform: Windows 11 primary
|
||||
- Git: https://vulcan.apophisnetworking.net/jramos/ajarbot.git
|
||||
- Config: `.env` for API keys, `config/adapters.local.yaml` for tokens (both gitignored)
|
||||
- Venv: Python 3.11+
|
||||
|
||||
Reference in New Issue
Block a user