commits
anthropic-proxy, auth-adapter, and litellm no longer expose ports
externally. They communicate only via Docker internal network.
This prevents unauthorized external access (e.g., the mysterious
"What's 1 + 1?" requests hitting anthropic-proxy in prod).
OAuth setup now requires SSH tunnel:
ssh -L 4001:localhost:4001 root@SERVER
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added documentation about the LiteLLM bug that causes TypeError when
Letta sends tools=null during summarization, and the fix using pinned
version v1.80.9.dev6.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes TypeError in filter_web_search_deployments when Letta sends
tools=null during summarization requests.
Fix: https://github.com/BerriAI/litellm/commit/7c2e2111
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Call getOrCreateAgent() during startup to ensure tools are synced
with correct webhook URLs before any messages arrive.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Detach and re-attach all tools when finding existing agent.
This ensures tools have the correct webhook URLs after importing
an agent from a different environment (dev -> prod).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The app's WAL file was overriding the copied database. Now we:
- Stop the app container first
- Remove remote WAL/SHM files
- Copy the database
- Restart app before Letta import (which needs the container running)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add append_copy_suffix: false to prevent _copy suffix on import
- Delete both original and _copy variants before importing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changed importFile call to pass object with file property instead of
positional argument: `importFile({ file })` not `importFile(file, {})`
- Use snake_case `agent_ids` in response (matches Letta SDK types)
- Use Blob instead of File for upload (simpler, matches SDK docs)
- Copy export file to container-mounted data dir instead of /tmp
- Use exportFile/importFile APIs (replacing deprecated serialized methods)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Migrates local development data to production:
- SQLite database (assistant.db)
- Letta agent state (exports to .af file and imports on server)
Usage: ./infra/migrate-data.sh
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sets the command menu programmatically via setMyCommands, keeping
BotFather in sync with actual handlers. Removes unused /dump, /focus,
/wins commands until those features are implemented.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Container runs as bun user (UID 1000), so uploaded files need to be
owned by that user to be readable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The SYSTEM_PROMPT.md file is gitignored (contains personal info), so
infra/deploy.sh now uploads it to the server during initial deployment.
Also mounts the prompts directory in docker-compose.prod.yml so the
container can access the system prompt at runtime.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The SYSTEM_PROMPT.md file is gitignored (contains personal info) so it
needs to be mounted into the container rather than copied at build time.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, webhook mode only logged the URL but never actually called
setWebhook. This meant the secret_token was never configured, causing
Telegram to send updates without the X-Telegram-Bot-Api-Secret-Token
header, which the app then rejected with 401 Unauthorized.
Now registerWebhook() is called on startup in webhook mode, ensuring
the webhook is properly configured with the secret token.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Letta server runs in secure mode requiring authentication.
Added LETTA_SERVER_PASSWORD config and pass it as apiKey to the client.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The app container runs as user bun (UID 1000) but the cloned
data/ directory is owned by root, causing SQLite to fail.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Backup .env to timestamped file before git pull
- Wait for services to be healthy after docker compose up
- Only prune images if no services are unhealthy/exited
- Use separate build and up commands for better control
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Dockerfile.anthropic-proxy:
- Pin rust:latest → rust:1.92.0
- Pin debian:latest → debian:trixie-slim (Debian 13)
- Add DEBIAN_FRONTEND=noninteractive
Dockerfile:
- Add DEBIAN_FRONTEND=noninteractive
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- deploy.sh: single-command server provisioning with Hetzner, Cloudflare DNS,
Tailscale, Docker, and GitHub Actions deploy key setup
- teardown.sh: clean removal of server and DNS records
- cloud-init.yaml.tmpl: server initialization template
- sync-prompt.sh: quick system prompt sync without full deploy
- secrets.env.example: template for required secrets
Security improvements:
- Tailscale auth key written to file instead of command line (avoids logs)
- Deploy key saved to file with chmod 600 (not printed to stdout)
- IPv6 address properly extracted from /64 prefix
- Host key captured after first SSH connection
Also updates documentation and .gitignore for infra secrets.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add comment to docker-compose.yml documenting Tailscale access
- Add "Letta Access via Tailscale" section to DEPLOY.md
- Include Letta ADE connection instructions and API examples
- Update architecture diagram to show Letta's Tailscale access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add prompts/SYSTEM_PROMPT.md.example as template
- Add src/prompts.ts to load and inject tools at runtime
- Update bot.ts to use loadSystemPrompt() for agent creation
- Gitignore prompts/SYSTEM_PROMPT.md (contains personal info)
- Update README with setup instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Tailscale installation to DEPLOY.md server setup
- Update monitoring section to use Tailscale IP:19999
- Remove netdata from Caddyfile (no public exposure)
- Expose netdata port 19999 in docker-compose.prod.yml
- Update architecture diagram to show Tailscale access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add SECURE and LETTA_SERVER_PASSWORD env vars to letta service
- Add letta.assistant.mosphere.at to Caddyfile.example
- Add LETTA_SERVER_PASSWORD to .env.example
When LETTA_SERVER_PASSWORD is set, Letta enables SECURE mode and
requires the password for API access. This allows connecting via
Letta Cloud ADE at https://app.letta.com → Self-Hosted Server.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update README.md and DEPLOY.md to reflect the current architecture
with auth-adapter between LiteLLM and anthropic-proxy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add start_interval: 2s to all health checks (checks every 2s during
startup instead of waiting 30s, reduces total startup from ~2min to ~20s)
- Use env_file: .env for litellm, app, and netdata services
- Move app service from main compose to prod-only
- Remove commented-out app section from main compose
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
LiteLLM's os.environ/ substitution doesn't work in extra_headers,
causing "Authentication required" errors. The auth-adapter middleware
translates Bearer tokens to x-api-key headers reliably.
Changes:
- Restore src/auth-adapter.ts for header translation
- Route LiteLLM through auth-adapter -> anthropic-proxy
- Add volume for anthropic-proxy session persistence
- Simplify litellm-config.yaml (use api_key, not extra_headers)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(detect): add Haiku 4.5 based overwhelm and brain dump detection
Replace crude regex-only detection with Claude Haiku 4.5 classification:
- Quick regex pre-filter triggers Haiku only when patterns detected
- Haiku classifies: overwhelm, brain_dump, self_bullying, urgency
- Brain dumps are parsed and saved to DB in parallel
- Detection context prepended to message for Opus to respond appropriately
Flow: regex trigger -> Haiku classify -> (if brain_dump) Haiku parse + save
* docs: update README and CLAUDE.md for Haiku detection
- Add Haiku 4.5 detection to architecture diagram
- Add HAIKU_MODEL to environment variables
- Add detect.ts and wins.ts to project structure
- Mark M3 (detection) and M4 (tiny wins) as complete
- Document dual-model setup (Opus for agent, Haiku for detection)
* fix(detect): harden Haiku parsing
* fix(detect): preserve flags and avoid userId=0
* fix(detect): make parsed item saves atomic
---------
Co-authored-by: Claude <noreply@anthropic.com>
* docs(deploy): simplify deployment without Coolify
Replace Coolify with plain Docker Compose + Caddy + GitHub Actions:
- Debian 13 with Docker installed directly
- Caddy for reverse proxy and auto-SSL
- GitHub Actions for auto-deploy on push to main
- Install mosh, git, and gh CLI for easier server management
Much simpler setup with fewer moving parts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs(deploy): add production configs and simplify instructions
- Add docker-compose.prod.yml with Caddy + Netdata
- Add deploy.sh script for auto-deploy
- Add .github/workflows/deploy.yml for CI/CD
- Add Caddyfile.example with basic auth for Netdata
- Update .env.example with NETDATA_CLAIM_TOKEN
- Update .gitignore to exclude Caddyfile (contains secrets)
- Simplify DEPLOY.md to reference existing files
- Remove step numbers from headings
- Use vim instead of nano
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Debian 13 doesn't include ufw by default
- Docker bypasses ufw/iptables rules anyway
- Hetzner Cloud Firewall works at network level, cleaner solution
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Debian has a faster package manager (apt vs dnf) and is more minimal.
Debian 13 "Trixie" released August 2025, currently at 13.2.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove auth-adapter middleware
LiteLLM can handle header translation natively using extra_headers.
This eliminates the need for a separate middleware service.
- Configure LiteLLM to pass x-api-key and Accept-Encoding headers
- Remove auth-adapter service from docker-compose
- Update docs to reflect simplified architecture
* revert: restore auth-adapter middleware
Address bugbot review concern about os.environ/ syntax in
extra_headers not being reliably processed by LiteLLM.
The auth-adapter middleware is a proven approach for:
- Translating Bearer tokens to x-api-key headers
- Adding Accept-Encoding: identity to prevent gzip issues
While LiteLLM docs claim os.environ/ works for "ANY value",
there are reported issues with nested dictionary values.
Keeping auth-adapter ensures reliable header translation.
* refactor: remove auth-adapter middleware
Use LiteLLM's native extra_headers with os.environ/ substitution.
Investigation confirmed this is safe:
- LiteLLM docs: os.environ/ works for "ANY value on config.yaml"
- Source code: _check_for_os_environ_vars recursively processes dicts
- extra_headers values are processed like any other config value
This eliminates a separate Docker service while maintaining the same
header translation (Bearer -> x-api-key, Accept-Encoding: identity).
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(wins): add delete_tiny_win and get_wins_by_day tools
Add two new Letta tools for the tiny wins system:
- delete_tiny_win: Allows deleting a win by ID if recorded by mistake
- get_wins_by_day: Get wins for a specific day with timestamps
- Supports "today", "yesterday", or YYYY-MM-DD date format
- Returns wins with time of day, category breakdown
- Human-readable date labels (e.g., "Monday, Dec 9")
The existing createdAt timestamps are now exposed in the API results,
enabling per-day breakdown views for tracking progress over time.
* docs: add tiny wins documentation to README and CLAUDE.md
- Add Features section to README with tiny wins tool reference
- Document all 4 wins tools (record, delete, get_wins_by_day, get_wins_summary)
- Add wins.ts to project structure
- Mark M4 milestone as complete
- Add Tiny Wins Tools section to CLAUDE.md for AI agent reference
* fix(wins): use local timezone for date string formatting
Fix timezone bug where dateStr used UTC via toISOString() but date
boundaries used local time. In positive UTC offset zones, early morning
queries could return a dateStr from the previous day.
Added formatLocalDate() helper that formats dates as YYYY-MM-DD using
local timezone components (getFullYear, getMonth, getDate) instead of
converting to UTC.
* fix(wins): validate date format and handle whitespace consistently
Address two bugbot review comments:
1. Invalid YYYY-MM-DD dates: Added isNaN(start.getTime()) check to
reject dates like "2024-13-45" that match the regex pattern but
create Invalid Date objects. Falls back to today on invalid dates.
2. Inconsistent whitespace handling: Changed regex to use periodLower
(trimmed input) instead of raw period, so " 2024-12-15" is handled
the same as "2024-12-15" rather than falling back to today.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(deploy): add Dockerfile and Coolify deployment guide
- Add multi-stage Dockerfile for production Bun app
- Add .dockerignore to optimize build context
- Add comprehensive DEPLOY.md with Hetzner CX33 + Coolify setup
- Fix TOOL_WEBHOOK_URL in docker-compose.yml for Linux/production
* fix(deploy): set correct ownership on data directory
Fix permissions issue where /app/data was created as root but container
runs as non-root 'bun' user. Now correctly sets bun:bun ownership before
switching users.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Previously, new tools were only attached when creating a new agent.
Now existing agents get any missing tools attached automatically,
preserving agent memory while adding new capabilities.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add wins table and tools for tracking small accomplishments:
- record_tiny_win: Save wins with category/magnitude, get celebratory feedback
- get_wins_summary: View win history, streaks, and patterns
Helps users with ADHD celebrate small victories and build momentum.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /reset command to bot commands list
- Add cleanup-agents.ts to project structure
- Improve Telegram troubleshooting with 409 error note
- Note that hot reload handles bot restart automatically
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use import.meta.hot.dispose() to stop the Telegram bot polling before
module replacement during hot reload. This prevents the "terminated by
other getUpdates request" error when developing with `bun --hot`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes:
- Single agent named 'adhd-support-agent' instead of per-user agents
- Agent is found by name on startup, created only if not found
- Added /reset command to delete and recreate the agent
- Added scripts/cleanup-agents.ts to bulk delete old agents
Usage:
bun run scripts/cleanup-agents.ts # Dry run - list agents
bun run scripts/cleanup-agents.ts --delete # Delete all except adhd-support-agent
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
AGENTS.md:
- Added "Letta Tool Registration (Critical!)" section
- Documents json_schema format (flat, not OpenAI nested)
- Explains explicit schema requirement on create AND update
- Shows Python function signature best practices
- Covers tool attachment timing and existing tool handling
README.md:
- Added troubleshooting section for "Tools not working"
- Includes commands to verify tool schemas in Letta
- Documents how to debug tool webhook calls
- Shows how to delete/recreate agents if needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The parser now handles unstructured brain dumps like:
"i have to send an email to hr. i'm finally sending..."
Extracts tasks from:
- Intent phrases: "need to X", "have to X", "gotta X", "should X"
- "Going to X", "gonna X"
- "Finally X" (procrastination indicator!)
Plus existing support for:
- Bullet points and numbered lists
- Action verbs at line starts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Now shows in console:
- 📤 Messages sent to agent
- 📨 Agent response breakdown with message types
- 🔧 TOOL CALL with name and args
- ✅ TOOL RETURN with status and result
- 💬 ASSISTANT messages
- 🧠 REASONING messages
- 🔧 TOOL WEBHOOK when our handlers receive calls
Makes debugging tool calls much easier.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Consolidates multiple debugging iterations into working tool registration.
## What was fixed
1. **json_schema format**: Letta uses a flat format, NOT OpenAI's nested format
- Wrong: `{type: 'function', function: {name, parameters}}`
- Right: `{name, description, parameters}`
2. **Schema must be explicit**: Letta's "auto-extraction from Python source"
doesn't work reliably. Always pass json_schema explicitly in both
create AND update calls.
3. **Python function signatures**: Generate explicit typed parameters
`def tool(arg: str)` instead of `def tool(**kwargs)` so Letta knows
what arguments to pass.
4. **Tool attachment timing**: Tools must be attached AFTER agent creation
when using letta-free model workaround (tool_ids in create doesn't work).
5. **Update existing tools**: Check for existing tools by name and update
them instead of failing on duplicate registration.
## Files changed
- src/tools/dispatcher.ts: Generate proper Python signatures + json_schema
- src/letta.ts: Update existing tools, include json_schema in PATCH
- src/bot.ts: Attach tools after agent creation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add TOOL_WEBHOOK_URL config for Docker→host communication
- Add POST /tools/:name webhook endpoint to handle Letta tool calls
- Register tools with Letta at startup (registerTools in letta.ts)
- Attach tool IDs to agents when created
- Update Python stubs to use embedded webhook URL
- Add human memory block with user ID for tool context
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Mark M2: Tools + Items as complete
- Update project structure to include db/ and tools/ directories
- Add drizzle.config.ts to file listing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Dependencies for SQLite database layer:
- drizzle-orm: TypeScript ORM
- drizzle-kit: Migration tooling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Exports all tool modules and re-exports dispatcher utilities:
- Tool definitions for Letta registration
- Tool handlers for local dispatch
- Registry and conversion functions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Retrieves open/in_progress items for context loading:
- Filters by item type (task, brain_dump, subtask)
- Supports limit parameter for pagination
- User-scoped results via userId
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CRUD operations for task items:
- save_item: Creates new items (brain_dump, task, subtask)
- update_item: Updates status, priority, content, parent
- User isolation via userId on all operations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Breaks complex tasks into actionable subtasks:
- Parses numbered lists, bullets, commas, conjunctions
- Handles multi-line and single-line input
- Returns array of suggested subtasks
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extracts tasks and ideas from free-form text using heuristics:
- Recognizes task prefixes (-, *, TODO, numbers)
- Identifies action verbs indicating tasks
- Returns structured items array for agent processing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ToolRegistry class for managing tool handlers
- dispatchTool() for routing calls to handlers
- toLettaToolCreate() generates Python webhook stubs for Letta
- Supports user context (Telegram user ID) for isolation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add database schema and infrastructure for storing tasks, brain dumps, and subtasks. Uses bun:sqlite for optimal Bun runtime integration and Drizzle ORM for type-safe queries.
Key additions:
- src/db/schema.ts: Items table with hierarchical task support
- src/db/index.ts: Database initialization with auto-migration
- src/db/migrations/: Initial migration and metadata
- src/db/schema.test.ts: Comprehensive database tests
- drizzle.config.ts: Drizzle Kit configuration
- .gitignore: Exclude database files, keep .gitkeep
The items table supports:
- Multiple item types (brain_dump, task, subtask)
- Status tracking (open, in_progress, done, archived)
- Priority levels (0-4, aligned with beads)
- Hierarchical relationships via parentId
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
anthropic-proxy, auth-adapter, and litellm no longer expose ports
externally. They communicate only via Docker internal network.
This prevents unauthorized external access (e.g., the mysterious
"What's 1 + 1?" requests hitting anthropic-proxy in prod).
OAuth setup now requires SSH tunnel:
ssh -L 4001:localhost:4001 root@SERVER
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The app's WAL file was overriding the copied database. Now we:
- Stop the app container first
- Remove remote WAL/SHM files
- Copy the database
- Restart app before Letta import (which needs the container running)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changed importFile call to pass object with file property instead of
positional argument: `importFile({ file })` not `importFile(file, {})`
- Use snake_case `agent_ids` in response (matches Letta SDK types)
- Use Blob instead of File for upload (simpler, matches SDK docs)
- Copy export file to container-mounted data dir instead of /tmp
- Use exportFile/importFile APIs (replacing deprecated serialized methods)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sets the command menu programmatically via setMyCommands, keeping
BotFather in sync with actual handlers. Removes unused /dump, /focus,
/wins commands until those features are implemented.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The SYSTEM_PROMPT.md file is gitignored (contains personal info), so
infra/deploy.sh now uploads it to the server during initial deployment.
Also mounts the prompts directory in docker-compose.prod.yml so the
container can access the system prompt at runtime.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, webhook mode only logged the URL but never actually called
setWebhook. This meant the secret_token was never configured, causing
Telegram to send updates without the X-Telegram-Bot-Api-Secret-Token
header, which the app then rejected with 401 Unauthorized.
Now registerWebhook() is called on startup in webhook mode, ensuring
the webhook is properly configured with the secret token.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Backup .env to timestamped file before git pull
- Wait for services to be healthy after docker compose up
- Only prune images if no services are unhealthy/exited
- Use separate build and up commands for better control
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Dockerfile.anthropic-proxy:
- Pin rust:latest → rust:1.92.0
- Pin debian:latest → debian:trixie-slim (Debian 13)
- Add DEBIAN_FRONTEND=noninteractive
Dockerfile:
- Add DEBIAN_FRONTEND=noninteractive
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- deploy.sh: single-command server provisioning with Hetzner, Cloudflare DNS,
Tailscale, Docker, and GitHub Actions deploy key setup
- teardown.sh: clean removal of server and DNS records
- cloud-init.yaml.tmpl: server initialization template
- sync-prompt.sh: quick system prompt sync without full deploy
- secrets.env.example: template for required secrets
Security improvements:
- Tailscale auth key written to file instead of command line (avoids logs)
- Deploy key saved to file with chmod 600 (not printed to stdout)
- IPv6 address properly extracted from /64 prefix
- Host key captured after first SSH connection
Also updates documentation and .gitignore for infra secrets.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add comment to docker-compose.yml documenting Tailscale access
- Add "Letta Access via Tailscale" section to DEPLOY.md
- Include Letta ADE connection instructions and API examples
- Update architecture diagram to show Letta's Tailscale access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add prompts/SYSTEM_PROMPT.md.example as template
- Add src/prompts.ts to load and inject tools at runtime
- Update bot.ts to use loadSystemPrompt() for agent creation
- Gitignore prompts/SYSTEM_PROMPT.md (contains personal info)
- Update README with setup instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Tailscale installation to DEPLOY.md server setup
- Update monitoring section to use Tailscale IP:19999
- Remove netdata from Caddyfile (no public exposure)
- Expose netdata port 19999 in docker-compose.prod.yml
- Update architecture diagram to show Tailscale access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add SECURE and LETTA_SERVER_PASSWORD env vars to letta service
- Add letta.assistant.mosphere.at to Caddyfile.example
- Add LETTA_SERVER_PASSWORD to .env.example
When LETTA_SERVER_PASSWORD is set, Letta enables SECURE mode and
requires the password for API access. This allows connecting via
Letta Cloud ADE at https://app.letta.com → Self-Hosted Server.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add start_interval: 2s to all health checks (checks every 2s during
startup instead of waiting 30s, reduces total startup from ~2min to ~20s)
- Use env_file: .env for litellm, app, and netdata services
- Move app service from main compose to prod-only
- Remove commented-out app section from main compose
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
LiteLLM's os.environ/ substitution doesn't work in extra_headers,
causing "Authentication required" errors. The auth-adapter middleware
translates Bearer tokens to x-api-key headers reliably.
Changes:
- Restore src/auth-adapter.ts for header translation
- Route LiteLLM through auth-adapter -> anthropic-proxy
- Add volume for anthropic-proxy session persistence
- Simplify litellm-config.yaml (use api_key, not extra_headers)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(detect): add Haiku 4.5 based overwhelm and brain dump detection
Replace crude regex-only detection with Claude Haiku 4.5 classification:
- Quick regex pre-filter triggers Haiku only when patterns detected
- Haiku classifies: overwhelm, brain_dump, self_bullying, urgency
- Brain dumps are parsed and saved to DB in parallel
- Detection context prepended to message for Opus to respond appropriately
Flow: regex trigger -> Haiku classify -> (if brain_dump) Haiku parse + save
* docs: update README and CLAUDE.md for Haiku detection
- Add Haiku 4.5 detection to architecture diagram
- Add HAIKU_MODEL to environment variables
- Add detect.ts and wins.ts to project structure
- Mark M3 (detection) and M4 (tiny wins) as complete
- Document dual-model setup (Opus for agent, Haiku for detection)
* fix(detect): harden Haiku parsing
* fix(detect): preserve flags and avoid userId=0
* fix(detect): make parsed item saves atomic
---------
Co-authored-by: Claude <noreply@anthropic.com>
* docs(deploy): simplify deployment without Coolify
Replace Coolify with plain Docker Compose + Caddy + GitHub Actions:
- Debian 13 with Docker installed directly
- Caddy for reverse proxy and auto-SSL
- GitHub Actions for auto-deploy on push to main
- Install mosh, git, and gh CLI for easier server management
Much simpler setup with fewer moving parts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs(deploy): add production configs and simplify instructions
- Add docker-compose.prod.yml with Caddy + Netdata
- Add deploy.sh script for auto-deploy
- Add .github/workflows/deploy.yml for CI/CD
- Add Caddyfile.example with basic auth for Netdata
- Update .env.example with NETDATA_CLAIM_TOKEN
- Update .gitignore to exclude Caddyfile (contains secrets)
- Simplify DEPLOY.md to reference existing files
- Remove step numbers from headings
- Use vim instead of nano
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove auth-adapter middleware
LiteLLM can handle header translation natively using extra_headers.
This eliminates the need for a separate middleware service.
- Configure LiteLLM to pass x-api-key and Accept-Encoding headers
- Remove auth-adapter service from docker-compose
- Update docs to reflect simplified architecture
* revert: restore auth-adapter middleware
Address bugbot review concern about os.environ/ syntax in
extra_headers not being reliably processed by LiteLLM.
The auth-adapter middleware is a proven approach for:
- Translating Bearer tokens to x-api-key headers
- Adding Accept-Encoding: identity to prevent gzip issues
While LiteLLM docs claim os.environ/ works for "ANY value",
there are reported issues with nested dictionary values.
Keeping auth-adapter ensures reliable header translation.
* refactor: remove auth-adapter middleware
Use LiteLLM's native extra_headers with os.environ/ substitution.
Investigation confirmed this is safe:
- LiteLLM docs: os.environ/ works for "ANY value on config.yaml"
- Source code: _check_for_os_environ_vars recursively processes dicts
- extra_headers values are processed like any other config value
This eliminates a separate Docker service while maintaining the same
header translation (Bearer -> x-api-key, Accept-Encoding: identity).
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(wins): add delete_tiny_win and get_wins_by_day tools
Add two new Letta tools for the tiny wins system:
- delete_tiny_win: Allows deleting a win by ID if recorded by mistake
- get_wins_by_day: Get wins for a specific day with timestamps
- Supports "today", "yesterday", or YYYY-MM-DD date format
- Returns wins with time of day, category breakdown
- Human-readable date labels (e.g., "Monday, Dec 9")
The existing createdAt timestamps are now exposed in the API results,
enabling per-day breakdown views for tracking progress over time.
* docs: add tiny wins documentation to README and CLAUDE.md
- Add Features section to README with tiny wins tool reference
- Document all 4 wins tools (record, delete, get_wins_by_day, get_wins_summary)
- Add wins.ts to project structure
- Mark M4 milestone as complete
- Add Tiny Wins Tools section to CLAUDE.md for AI agent reference
* fix(wins): use local timezone for date string formatting
Fix timezone bug where dateStr used UTC via toISOString() but date
boundaries used local time. In positive UTC offset zones, early morning
queries could return a dateStr from the previous day.
Added formatLocalDate() helper that formats dates as YYYY-MM-DD using
local timezone components (getFullYear, getMonth, getDate) instead of
converting to UTC.
* fix(wins): validate date format and handle whitespace consistently
Address two bugbot review comments:
1. Invalid YYYY-MM-DD dates: Added isNaN(start.getTime()) check to
reject dates like "2024-13-45" that match the regex pattern but
create Invalid Date objects. Falls back to today on invalid dates.
2. Inconsistent whitespace handling: Changed regex to use periodLower
(trimmed input) instead of raw period, so " 2024-12-15" is handled
the same as "2024-12-15" rather than falling back to today.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(deploy): add Dockerfile and Coolify deployment guide
- Add multi-stage Dockerfile for production Bun app
- Add .dockerignore to optimize build context
- Add comprehensive DEPLOY.md with Hetzner CX33 + Coolify setup
- Fix TOOL_WEBHOOK_URL in docker-compose.yml for Linux/production
* fix(deploy): set correct ownership on data directory
Fix permissions issue where /app/data was created as root but container
runs as non-root 'bun' user. Now correctly sets bun:bun ownership before
switching users.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Previously, new tools were only attached when creating a new agent.
Now existing agents get any missing tools attached automatically,
preserving agent memory while adding new capabilities.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add wins table and tools for tracking small accomplishments:
- record_tiny_win: Save wins with category/magnitude, get celebratory feedback
- get_wins_summary: View win history, streaks, and patterns
Helps users with ADHD celebrate small victories and build momentum.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /reset command to bot commands list
- Add cleanup-agents.ts to project structure
- Improve Telegram troubleshooting with 409 error note
- Note that hot reload handles bot restart automatically
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use import.meta.hot.dispose() to stop the Telegram bot polling before
module replacement during hot reload. This prevents the "terminated by
other getUpdates request" error when developing with `bun --hot`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes:
- Single agent named 'adhd-support-agent' instead of per-user agents
- Agent is found by name on startup, created only if not found
- Added /reset command to delete and recreate the agent
- Added scripts/cleanup-agents.ts to bulk delete old agents
Usage:
bun run scripts/cleanup-agents.ts # Dry run - list agents
bun run scripts/cleanup-agents.ts --delete # Delete all except adhd-support-agent
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
AGENTS.md:
- Added "Letta Tool Registration (Critical!)" section
- Documents json_schema format (flat, not OpenAI nested)
- Explains explicit schema requirement on create AND update
- Shows Python function signature best practices
- Covers tool attachment timing and existing tool handling
README.md:
- Added troubleshooting section for "Tools not working"
- Includes commands to verify tool schemas in Letta
- Documents how to debug tool webhook calls
- Shows how to delete/recreate agents if needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The parser now handles unstructured brain dumps like:
"i have to send an email to hr. i'm finally sending..."
Extracts tasks from:
- Intent phrases: "need to X", "have to X", "gotta X", "should X"
- "Going to X", "gonna X"
- "Finally X" (procrastination indicator!)
Plus existing support for:
- Bullet points and numbered lists
- Action verbs at line starts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Now shows in console:
- 📤 Messages sent to agent
- 📨 Agent response breakdown with message types
- 🔧 TOOL CALL with name and args
- ✅ TOOL RETURN with status and result
- 💬 ASSISTANT messages
- 🧠 REASONING messages
- 🔧 TOOL WEBHOOK when our handlers receive calls
Makes debugging tool calls much easier.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Consolidates multiple debugging iterations into working tool registration.
## What was fixed
1. **json_schema format**: Letta uses a flat format, NOT OpenAI's nested format
- Wrong: `{type: 'function', function: {name, parameters}}`
- Right: `{name, description, parameters}`
2. **Schema must be explicit**: Letta's "auto-extraction from Python source"
doesn't work reliably. Always pass json_schema explicitly in both
create AND update calls.
3. **Python function signatures**: Generate explicit typed parameters
`def tool(arg: str)` instead of `def tool(**kwargs)` so Letta knows
what arguments to pass.
4. **Tool attachment timing**: Tools must be attached AFTER agent creation
when using letta-free model workaround (tool_ids in create doesn't work).
5. **Update existing tools**: Check for existing tools by name and update
them instead of failing on duplicate registration.
## Files changed
- src/tools/dispatcher.ts: Generate proper Python signatures + json_schema
- src/letta.ts: Update existing tools, include json_schema in PATCH
- src/bot.ts: Attach tools after agent creation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add TOOL_WEBHOOK_URL config for Docker→host communication
- Add POST /tools/:name webhook endpoint to handle Letta tool calls
- Register tools with Letta at startup (registerTools in letta.ts)
- Attach tool IDs to agents when created
- Update Python stubs to use embedded webhook URL
- Add human memory block with user ID for tool context
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CRUD operations for task items:
- save_item: Creates new items (brain_dump, task, subtask)
- update_item: Updates status, priority, content, parent
- User isolation via userId on all operations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extracts tasks and ideas from free-form text using heuristics:
- Recognizes task prefixes (-, *, TODO, numbers)
- Identifies action verbs indicating tasks
- Returns structured items array for agent processing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ToolRegistry class for managing tool handlers
- dispatchTool() for routing calls to handlers
- toLettaToolCreate() generates Python webhook stubs for Letta
- Supports user context (Telegram user ID) for isolation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add database schema and infrastructure for storing tasks, brain dumps, and subtasks. Uses bun:sqlite for optimal Bun runtime integration and Drizzle ORM for type-safe queries.
Key additions:
- src/db/schema.ts: Items table with hierarchical task support
- src/db/index.ts: Database initialization with auto-migration
- src/db/migrations/: Initial migration and metadata
- src/db/schema.test.ts: Comprehensive database tests
- drizzle.config.ts: Drizzle Kit configuration
- .gitignore: Exclude database files, keep .gitkeep
The items table supports:
- Multiple item types (brain_dump, task, subtask)
- Status tracking (open, in_progress, done, archived)
- Priority levels (0-4, aligned with beads)
- Hierarchical relationships via parentId
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>