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:
2026-02-13 23:38:44 -07:00
parent ab3a5afd59
commit 8afff96bb5
16 changed files with 1096 additions and 244 deletions

View File

@@ -39,6 +39,7 @@ class SlackAdapter(BaseAdapter):
super().__init__(config)
self.app: Optional[AsyncApp] = None
self.handler: Optional[AsyncSocketModeHandler] = None
self._username_cache: Dict[str, str] = {} # user_id -> username
@property
def platform_name(self) -> str:
@@ -255,7 +256,15 @@ class SlackAdapter(BaseAdapter):
}
async def _get_username(self, user_id: str) -> str:
"""Get username from user ID."""
"""Get username from user ID, with caching to avoid excessive API calls.
Sanitizes the returned username to contain only alphanumeric,
hyphens, and underscores (matching memory_system validation rules).
"""
# Check cache first
if user_id in self._username_cache:
return self._username_cache[user_id]
if not self.app:
return user_id
@@ -263,13 +272,20 @@ class SlackAdapter(BaseAdapter):
result = await self.app.client.users_info(user=user_id)
user = result["user"]
profile = user.get("profile", {})
return (
raw_username = (
profile.get("display_name")
or profile.get("real_name")
or user.get("name")
or user_id
)
# Sanitize: replace spaces/special chars with underscores
sanitized = "".join(
c if c.isalnum() or c in "-_" else "_" for c in raw_username
)
self._username_cache[user_id] = sanitized
return sanitized
except SlackApiError:
self._username_cache[user_id] = user_id
return user_id
@staticmethod