Quota switching updates the main agent's model selection but never updates
SubagentManager.self.model, so any spawn() call without an explicit model
parameter fell back to Opus. Defaulting to Sonnet fixes this — explicit
overrides (e.g. model="claude-haiku-4-5") still take precedence.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Child subagents (origin_channel="subagent") no longer announce to Telegram;
results are stored in _task_results[task_id] instead
- New WaitForSubagentsTool: blocks via asyncio.gather until all specified
task IDs complete, returns collected results for orchestrator synthesis
- spawn() return message now includes Task ID prominently for collection
- Fixes: orchestrator spawning N workers caused N+1 Telegram messages
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Registers SpawnTool in _run_subagent so subagents can spawn child
subagents. Removes the "cannot spawn" restriction from the system prompt.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Writes rate_limits.json after every API call with weekly/5h utilization
- Writes api_headers.jsonl with raw headers for analysis
- Upgrades quota fallback model from Sonnet 4.5 to 4.6
Deploy to site-packages (gateway loads from there, not /app/):
docker cp to /usr/local/lib/python3.12/site-packages/nanobot/providers/
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Changed printf-style (%s/%d) to loguru format ({}) in 3 log statements
- Consolidated response logging into a single line showing stop_reason,
tool_calls count, thinking chars, and token usage
- Added tool count to request logging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The system message handler was using self.model instead of the
quota-selected model, bypassing the Opus/Sonnet switching logic.
Also added debug logging for model selection and thinking_budget.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement intelligent model selection to manage 7-day Opus quota burn rate:
- Add _select_model_based_on_quota() method to AgentLoop
- Reads rate limit data from memory/rate_limits.json
- Calculates expected vs actual quota usage (100%/168h = 0.595% per hour)
- If actual > expected × 1.17 (17% overage), downgrades to Sonnet
- If actual ≤ expected, uses Opus
- Caches decision for 5 minutes to minimize file I/O
- Add /quota slash command to display real-time quota status
- Shows current usage vs expected usage
- Shows hours until weekly reset
- Shows selected model and burn rate multiplier
- Main agent now calls _select_model_based_on_quota() before each conversation
- Heartbeat subagent unaffected (explicitly uses claude-sonnet-4-20250514)
This replaces the wrong approach from PR #5 which throttled heartbeat
frequency instead of switching the main agent's model.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Consolidation was failing because max_tokens defaulted to 4096,
causing Haiku's response to be truncated mid-JSON (finish_reason=max_tokens).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: consolidation was calling Opus 4.6 with 10k thinking budget
on 50-80 message prompts. The 300s httpx timeout killed every request
(all failures were exactly 5 minutes after start). Consolidation is just
summarization — Haiku with no thinking handles it in seconds.
Also adds per-call thinking_budget override to the provider interface
so callers can disable thinking for lightweight tasks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auto-deploy is now handled by Watchtower on Unraid, which polls
for new images every 5 minutes for labeled containers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allows triggering a deploy via Gitea API. SSHes to Unraid to pull
latest image and restart the nanobot container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
REGISTRY env var is just the hostname without scheme. Docker actions
handle this automatically, but curl needs the full URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Runs after push builds and daily at 03:00 UTC. Keeps :latest and
:buildcache, deletes old SHA-tagged images via Gitea packages API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add pull_request trigger to build workflow
- Skip push and cache-to on PRs (build-only validation)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove "# nanobot" branding and "You are nanobot" from context.py
- Remove "You are a helpful AI assistant" personality line
- Remove fake "required" Claude Code system prefix from OAuth provider
- Identity is now fully customizable via IDENTITY.md in workspace
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Homebrew refuses to run as root in Docker containers.
Replace all brew installs with:
- GitHub release binaries (gogcli, goplaces, himalaya, obsidian-cli)
- go install (songsee)
- npm (gemini-cli)
- uv tool (openai-whisper)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nanobot uses loguru, not stdlib logging. Switch to loguru so
thinking/usage logs actually appear in container output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logs thinking block presence, character count, and token usage
in API responses. Also logs request parameters including thinking
budget configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Anthropic API returns a signature field in thinking blocks that
must be replayed in subsequent turns. Store full thinking blocks
(including signatures) instead of just the text content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of maintaining a brittle alias dict mapping model names to
dated API IDs, simply normalize dots to hyphens. The Anthropic API
accepts both claude-sonnet-4-5 and dated variants like
claude-sonnet-4-5-20250929, so no alias table is needed. This lets
users write "claude-sonnet-4.5" or "claude-sonnet-4-5" interchangeably.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds configurable thinking_budget in agent defaults. When >0, sends
the thinking parameter to the API with the specified token budget.
Handles API constraints: forces temperature=1, auto-bumps max_tokens
if it's below the thinking budget, preserves thinking blocks in
message history for multi-turn conversations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The agent loop produces messages in OpenAI format (role:tool, tool_calls
array) but the Anthropic API expects its own format (tool_use content
blocks in assistant messages, tool_result blocks in user messages).
This caused 400 errors whenever the bot tried to use tools like
web_search, because the follow-up message with tool results was
malformed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow "*" in the allowFrom list to explicitly permit all senders,
as an alternative to the empty-list-means-allow-all behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add .github/workflows/build.yml to auto-build and push to Gitea registry
- Change Dockerfile.oauth CMD from "status" to "gateway" for persistent container
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add MODEL_ALIASES dict to resolve short model names (e.g. claude-sonnet-4)
to dated API IDs (e.g. claude-sonnet-4-20250514). Includes claude-opus-4-6.
Add Dockerfile.oauth overlay extending birdxs/nanobot:latest for fast builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>