{"owner":"memovai","repo":"mimiclaw","hasSkills":true,"totalSkillsCount":6,"totalTokensCount":6871,"categories":["anthropic-skill","project-spec"],"hasMcp":false,"mcpConfig":null,"found":["spiffs_data/skills/weather.md","spiffs_data/skills/daily-briefing.md","spiffs_data/skills/gpio-control.md","skills/deploy/SKILL.md","docs/ARCHITECTURE.md","spiffs_data/skills/skill-creator.md"],"skills":{"spiffs_data/skills/weather.md":"# Weather\n\nGet current weather and forecasts using web_search.\n\n## When to use\nWhen the user asks about weather, temperature, or forecasts.\n\n## How to use\n1. Use get_current_time to know the current date\n2. Use web_search with a query like \"weather in [city] today\"\n3. Extract temperature, conditions, and forecast from results\n4. Present in a concise, friendly format\n\n## Example\nUser: \"What's the weather in Tokyo?\"\n→ get_current_time\n→ web_search \"weather Tokyo today February 2026\"\n→ \"Tokyo: 8°C, partly cloudy. High 12°C, low 4°C. Light wind from the north.\"\n","spiffs_data/skills/daily-briefing.md":"# Daily Briefing\n\nCompile a personalized daily briefing for the user.\n\n## When to use\nWhen the user asks for a daily briefing, morning update, or \"what's new today\".\nAlso useful as a heartbeat/cron task.\n\n## How to use\n1. Use get_current_time for today's date\n2. Read /spiffs/memory/MEMORY.md for user preferences and context\n3. Read today's daily note if it exists\n4. Use web_search for relevant news based on user interests\n5. Compile a concise briefing covering:\n   - Date and time\n   - Weather (if location known from USER.md)\n   - Relevant news/updates based on user interests\n   - Any pending tasks from memory\n   - Any scheduled cron jobs\n\n## Format\nKeep it brief — 5-10 bullet points max. Use the user's preferred language.\n","spiffs_data/skills/gpio-control.md":"# GPIO Control\n\nControl and monitor GPIO pins on the ESP32-S3 for digital I/O.\n\n## When to use\nWhen the user asks to:\n- Turn on/off LEDs, relays, or other outputs\n- Check switch states, button presses, or sensor readings\n- Confirm digital I/O status (switch confirmation)\n- Get an overview of all GPIO pin states\n\n## How to use\n1. To **read a switch/sensor**: use gpio_read with the pin number\n   - Returns HIGH (1) or LOW (0)\n   - HIGH typically means switch is ON / circuit closed\n   - LOW typically means switch is OFF / circuit open\n2. To **set an output**: use gpio_write with pin and state (1=HIGH, 0=LOW)\n3. To **scan all pins**: use gpio_read_all for a full status overview\n4. For **switch confirmation**: read the pin, report state, optionally toggle and re-read to verify\n\n## Pin safety\n- Only pins within the allowed range can be accessed\n- ESP32 flash pins (6-11) are always blocked\n- If a pin is rejected, suggest an alternative within the allowed range\n\n## Example\nUser: \"Check if the switch on pin 4 is on\"\n→ gpio_read {\"pin\": 4}\n→ \"Pin 4 = HIGH\"\n→ \"The switch on pin 4 is currently ON (HIGH).\"\n\nUser: \"Turn on the relay on pin 5\"\n→ gpio_write {\"pin\": 5, \"state\": 1}\n→ \"Pin 5 set to HIGH\"\n→ gpio_read {\"pin\": 5}\n→ \"Pin 5 = HIGH\"\n→ \"Relay on pin 5 is now ON. Confirmed HIGH.\"\n","skills/deploy/SKILL.md":"---\nname: deploy\ndescription: Deploy MimiClaw firmware to an ESP32-S3 board. Covers prerequisites, configuration, build, flash, verification, and troubleshooting.\n---\n\n# Deploy MimiClaw\n\nEnd-to-end guide for deploying MimiClaw to an ESP32-S3 dev board.\n\n## Prerequisites\n\n### Hardware\n- ESP32-S3 dev board with **16 MB flash + 8 MB PSRAM** (e.g. Xiaozhi AI board, ~$5-10)\n- USB Type-C data cable (not charge-only)\n\n### Software\n- **ESP-IDF v5.5+** installed and working\n  ```bash\n  # Install: https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32s3/get-started/\n  # Verify:\n  idf.py --version   # should show >= 5.5\n  ```\n\n### Credentials (get these first)\n- **WiFi SSID + password** — the network the ESP32 will connect to\n- **Telegram Bot Token** — create via [@BotFather](https://t.me/BotFather) on Telegram\n- **Anthropic API Key** — from [console.anthropic.com](https://console.anthropic.com)\n- *(Optional)* Brave Search API key — from [brave.com/search/api](https://brave.com/search/api/)\n- *(Optional)* HTTP proxy host:port — if in China or restricted network\n\n## Step 1: Clone and Set Target\n\n```bash\ngit clone https://github.com/memovai/mimiclaw.git\ncd mimiclaw\nidf.py set-target esp32s3\n```\n\n## Step 2: Configure Secrets\n\n```bash\ncp main/mimi_secrets.h.example main/mimi_secrets.h\n```\n\nEdit `main/mimi_secrets.h` — fill in ALL required fields:\n\n```c\n#define MIMI_SECRET_WIFI_SSID       \"YourWiFiName\"        // REQUIRED\n#define MIMI_SECRET_WIFI_PASS       \"YourWiFiPassword\"     // REQUIRED\n#define MIMI_SECRET_TG_TOKEN        \"123456:ABC-DEF...\"    // REQUIRED\n#define MIMI_SECRET_API_KEY         \"sk-ant-api03-...\"     // REQUIRED\n#define MIMI_SECRET_MODEL           \"\"                     // optional, defaults to claude-opus-4-5\n#define MIMI_SECRET_SEARCH_KEY      \"\"                     // optional: Brave Search API key\n#define MIMI_SECRET_PROXY_HOST      \"\"                     // optional: e.g. \"192.168.1.83\"\n#define MIMI_SECRET_PROXY_PORT      \"\"                     // optional: e.g. \"7897\"\n```\n\n**Proxy setup (China users):**\nIf you need a proxy to reach Telegram/Anthropic APIs, set both `PROXY_HOST` and `PROXY_PORT`. The proxy machine must:\n- Be on the same LAN as the ESP32\n- Support HTTP CONNECT method (Clash, V2Ray, etc.)\n- Have \"Allow LAN connections\" enabled\n\n## Step 3: Build\n\n```bash\nidf.py fullclean && idf.py build\n```\n\n**IMPORTANT:** Always `fullclean` after changing `mimi_secrets.h` — the secrets are compiled into the binary.\n\nExpected output: `Project build complete. To flash, run: idf.py flash`\n\n### Build Troubleshooting\n\n| Error | Fix |\n|-------|-----|\n| `mimi_secrets.h: No such file` | Run `cp main/mimi_secrets.h.example main/mimi_secrets.h` |\n| `esp_websocket_client not found` | Run `idf.py fullclean` then `idf.py build` (managed component auto-downloads) |\n| `Toolchain not found` | Re-run ESP-IDF `install.sh` and `source export.sh` |\n| Build runs out of memory | Close other apps, ESP-IDF build needs ~2GB RAM |\n\n## Step 4: Find Serial Port\n\n```bash\n# macOS\nls /dev/cu.usb*\n\n# Linux\nls /dev/ttyACM* /dev/ttyUSB*\n```\n\nCommon ports:\n- macOS USB-OTG: `/dev/cu.usbmodem1101` or `/dev/cu.usbmodem11401`\n- Linux: `/dev/ttyACM0`\n\n**If no port shows up:**\n- Try a different USB cable (must be data cable, not charge-only)\n- Try a different USB port\n- Check if board has a power LED lit\n\n## Step 5: Flash\n\n```bash\nidf.py -p PORT flash monitor\n```\n\nReplace `PORT` with your actual port. Example:\n```bash\nidf.py -p /dev/cu.usbmodem1101 flash monitor\n```\n\nThe monitor shows boot logs. Look for:\n```\nI (xxx) mimi: MimiClaw - ESP32-S3 AI Agent\nI (xxx) mimi: PSRAM free: ~8000000 bytes\nI (xxx) wifi: WiFi connected: 192.168.x.x\nI (xxx) telegram: Telegram bot token loaded\nI (xxx) mimi: All services started!\n```\n\n**Exit monitor:** `Ctrl+]`\n\n## Step 6: Verify\n\n1. Open Telegram, find your bot (the one you created with BotFather)\n2. Send: `Hello`\n3. You should see \"mimi is working...\" followed by a response\n4. Send: `What time is it?` — tests the get_current_time tool\n5. Send: `Search for latest news about ESP32` — tests web_search (if Brave key set)\n\n## Post-Deploy: Runtime Configuration\n\nConnect via serial (`idf.py -p PORT monitor`) and use CLI commands:\n\n```\nmimi> config_show                  # see current config\nmimi> wifi_set NewSSID NewPass     # change WiFi\nmimi> set_tg_token 123456:ABC...   # change Telegram token\nmimi> set_api_key sk-ant-...       # change API key\nmimi> set_model claude-sonnet-4-5  # change model\nmimi> set_proxy 192.168.1.83 7897  # set proxy\nmimi> clear_proxy                  # remove proxy\nmimi> heap_info                    # check memory\nmimi> restart                      # reboot\n```\n\nCLI settings are stored in NVS flash and take priority over build-time values.\n\n## OTA Update (over WiFi)\n\nAfter initial USB flash, future updates can be done over WiFi:\n\n1. Build new firmware: `idf.py build`\n2. Host the `.bin` file on a local HTTP server:\n   ```bash\n   cd build && python3 -m http.server 8080\n   ```\n3. Send to your bot on Telegram or use the OTA CLI command with the URL:\n   ```\n   http://YOUR_PC_IP:8080/mimiclaw.bin\n   ```\n\n## Flash Layout\n\n```\n16 MB Flash:\n├── 0x009000  NVS (24 KB) — runtime config\n├── 0x020000  OTA_0 (2 MB) — active firmware\n├── 0x220000  OTA_1 (2 MB) — update slot\n├── 0x420000  SPIFFS (12 MB) — memory, sessions, config\n└── 0xFF0000  Coredump (64 KB)\n```\n\n## Common Issues\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| No WiFi connection | Wrong SSID/password | Check `mimi_secrets.h`, `idf.py fullclean && build && flash` |\n| \"No bot token\" | Empty TG token | Set via `mimi_secrets.h` or CLI `set_tg_token` |\n| Bot doesn't respond | API key invalid | Check key at console.anthropic.com, set via CLI |\n| \"Markdown send failed\" | Normal with Markdown mode | Non-critical, falls back to plain text |\n| Proxy timeout | Proxy not reachable | Ensure same LAN, proxy allows LAN connections |\n| SPIFFS mount failed | First boot or corruption | Normal on first boot (auto-formats) |\n| Port busy/not found | Wrong port or cable | Try different USB port/cable, check `ls /dev/cu.usb*` |\n| Boot loop | Firmware crash | Flash via USB again, check serial logs for crash info |\n","docs/ARCHITECTURE.md":"# MimiClaw Architecture\n\n> ESP32-S3 AI Agent firmware — C/FreeRTOS implementation running on bare metal (no Linux).\n\n---\n\n## System Overview\n\n```\nTelegram App (User)\n    │\n    │  HTTPS Long Polling\n    │\n    ▼\n┌──────────────────────────────────────────────────┐\n│               ESP32-S3 (MimiClaw)                │\n│                                                  │\n│   ┌─────────────┐       ┌──────────────────┐     │\n│   │  Telegram    │──────▶│   Inbound Queue  │     │\n│   │  Poller      │       └────────┬─────────┘     │\n│   │  (Core 0)    │               │                │\n│   └─────────────┘               ▼                │\n│                     ┌────────────────────────┐    │\n│   ┌─────────────┐  │     Agent Loop          │    │\n│   │  WebSocket   │─▶│     (Core 1)           │    │\n│   │  Server      │  │                        │    │\n│   │  (:18789)    │  │  Context ──▶ LLM Proxy │    │\n│   └─────────────┘  │  Builder      (HTTPS)   │    │\n│                     │       ▲          │      │    │\n│   ┌─────────────┐  │       │     tool_use?   │    │\n│   │  Serial CLI  │  │       │          ▼      │    │\n│   │  (Core 0)    │  │  Tool Results ◀─ Tools  │    │\n│   └─────────────┘  │              (web_search)│    │\n│                     └──────────┬─────────────┘    │\n│                                │                  │\n│                         ┌──────▼───────┐          │\n│                         │ Outbound Queue│          │\n│                         └──────┬───────┘          │\n│                                │                  │\n│                         ┌──────▼───────┐          │\n│                         │  Outbound    │          │\n│                         │  Dispatch    │          │\n│                         │  (Core 0)    │          │\n│                         └──┬────────┬──┘          │\n│                            │        │             │\n│                     Telegram    WebSocket          │\n│                     sendMessage  send              │\n│                                                   │\n│   ┌──────────────────────────────────────────┐    │\n│   │  SPIFFS (12 MB)                          │    │\n│   │  /spiffs/config/  SOUL.md, USER.md       │    │\n│   │  /spiffs/memory/  MEMORY.md, YYYY-MM-DD  │    │\n│   │  /spiffs/sessions/ tg_<chat_id>.jsonl    │    │\n│   └──────────────────────────────────────────┘    │\n└───────────────────────────────────────────────────┘\n         │\n         │  Anthropic Messages API (HTTPS)\n         │  + Brave Search API (HTTPS)\n         ▼\n   ┌───────────┐   ┌──────────────┐\n   │ Claude API │   │ Brave Search │\n   └───────────┘   └──────────────┘\n```\n\n---\n\n## Data Flow\n\n```\n1. User sends message on Telegram (or WebSocket)\n2. Channel poller receives message, wraps in mimi_msg_t\n3. Message pushed to Inbound Queue (FreeRTOS xQueue)\n4. Agent Loop (Core 1) pops message:\n   a. Load session history from SPIFFS (JSONL)\n   b. Build system prompt (SOUL.md + USER.md + MEMORY.md + recent notes + tool guidance)\n   c. Build cJSON messages array (history + current message)\n   d. ReAct loop (max 10 iterations):\n      i.   Call Claude API via HTTPS (non-streaming, with tools array)\n      ii.  Parse JSON response → text blocks + tool_use blocks\n      iii. If stop_reason == \"tool_use\":\n           - Execute each tool (e.g. web_search → Brave Search API)\n           - Append assistant content + tool_result to messages\n           - Continue loop\n      iv.  If stop_reason == \"end_turn\": break with final text\n   e. Save user message + final assistant text to session file\n   f. Push response to Outbound Queue\n5. Outbound Dispatch (Core 0) pops response:\n   a. Route by channel field (\"telegram\" → sendMessage, \"websocket\" → WS frame)\n6. User receives reply\n```\n\n---\n\n## Module Map\n\n```\nmain/\n├── mimi.c                  Entry point — app_main() orchestrates init + startup\n├── mimi_config.h           All compile-time constants + build-time secrets include\n├── mimi_secrets.h          Build-time credentials (gitignored, highest priority)\n├── mimi_secrets.h.example  Template for mimi_secrets.h\n│\n├── bus/\n│   ├── message_bus.h       mimi_msg_t struct, queue API\n│   └── message_bus.c       Two FreeRTOS queues: inbound + outbound\n│\n├── wifi/\n│   ├── wifi_manager.h      WiFi STA lifecycle API\n│   └── wifi_manager.c      Event handler, exponential backoff\n│\n├── telegram/\n│   ├── telegram_bot.h      Bot init/start, send_message API\n│   └── telegram_bot.c      Long polling loop, JSON parsing, message splitting\n│\n├── llm/\n│   ├── llm_proxy.h         llm_chat() + llm_chat_tools() API, tool_use types\n│   └── llm_proxy.c         Anthropic Messages API (non-streaming), tool_use parsing\n│\n├── agent/\n│   ├── agent_loop.h        Agent task init/start\n│   ├── agent_loop.c        ReAct loop: LLM call → tool execution → repeat\n│   ├── context_builder.h   System prompt + messages builder API\n│   └── context_builder.c   Reads bootstrap files + memory + tool guidance\n│\n├── tools/\n│   ├── tool_registry.h     Tool definition struct, register/dispatch API\n│   ├── tool_registry.c     Tool registration, JSON schema builder, dispatch by name\n│   ├── tool_web_search.h   Web search tool API\n│   └── tool_web_search.c   Brave Search API via HTTPS (direct + proxy)\n│\n├── memory/\n│   ├── memory_store.h      Long-term + daily memory API\n│   ├── memory_store.c      MEMORY.md read/write, daily .md append/read\n│   ├── session_mgr.h       Per-chat session API\n│   └── session_mgr.c       JSONL session files, ring buffer history\n│\n├── gateway/\n│   ├── ws_server.h         WebSocket server API\n│   └── ws_server.c         ESP HTTP server with WS upgrade, client tracking\n│\n├── proxy/\n│   ├── http_proxy.h        Proxy connection API\n│   └── http_proxy.c        HTTP CONNECT tunnel + TLS via esp_tls\n│\n├── cli/\n│   ├── serial_cli.h        CLI init API\n│   └── serial_cli.c        esp_console REPL with debug/maintenance commands\n│\n└── ota/\n    ├── ota_manager.h       OTA update API\n    └── ota_manager.c       esp_https_ota wrapper\n```\n\n---\n\n## FreeRTOS Task Layout\n\n| Task               | Core | Priority | Stack  | Description                          |\n|--------------------|------|----------|--------|--------------------------------------|\n| `tg_poll`          | 0    | 5        | 12 KB  | Telegram long polling (30s timeout)  |\n| `agent_loop`       | 1    | 6        | 12 KB  | Message processing + Claude API call |\n| `outbound`         | 0    | 5        | 8 KB   | Route responses to Telegram / WS     |\n| `serial_cli`       | 0    | 3        | 4 KB   | USB serial console REPL              |\n| httpd (internal)   | 0    | 5        | —      | WebSocket server (esp_http_server)   |\n| wifi_event (IDF)   | 0    | 8        | —      | WiFi event handling (ESP-IDF)        |\n\n**Core allocation strategy**: Core 0 handles I/O (network, serial, WiFi). Core 1 is dedicated to the agent loop (CPU-bound JSON building + waiting on HTTPS).\n\n---\n\n## Memory Budget\n\n| Purpose                            | Location       | Size     |\n|------------------------------------|----------------|----------|\n| FreeRTOS task stacks               | Internal SRAM  | ~40 KB   |\n| WiFi buffers                       | Internal SRAM  | ~30 KB   |\n| TLS connections x2 (Telegram + Claude) | PSRAM      | ~120 KB  |\n| JSON parse buffers                 | PSRAM          | ~32 KB   |\n| Session history cache              | PSRAM          | ~32 KB   |\n| System prompt buffer               | PSRAM          | ~16 KB   |\n| LLM response stream buffer         | PSRAM          | ~32 KB   |\n| Remaining available                | PSRAM          | ~7.7 MB  |\n\nLarge buffers (32 KB+) are allocated from PSRAM via `heap_caps_calloc(1, size, MALLOC_CAP_SPIRAM)`.\n\n---\n\n## Flash Partition Layout\n\n```\nOffset      Size      Name        Purpose\n─────────────────────────────────────────────\n0x009000    24 KB     nvs         ESP-IDF internal use (WiFi calibration etc.)\n0x00F000     8 KB     otadata     OTA boot state\n0x011000     4 KB     phy_init    WiFi PHY calibration\n0x020000     2 MB     ota_0       Firmware slot A\n0x220000     2 MB     ota_1       Firmware slot B\n0x420000    12 MB     spiffs      Markdown memory, sessions, config\n0xFF0000    64 KB     coredump    Crash dump storage\n```\n\nTotal: 16 MB flash.\n\n---\n\n## Storage Layout (SPIFFS)\n\nSPIFFS is a flat filesystem — no real directories. Files use path-like names.\n\n```\n/spiffs/config/SOUL.md          AI personality definition\n/spiffs/config/USER.md          User profile\n/spiffs/memory/MEMORY.md        Long-term persistent memory\n/spiffs/memory/2026-02-05.md    Daily notes (one file per day)\n/spiffs/sessions/tg_12345.jsonl Session history (one file per Telegram chat)\n```\n\nSession files are JSONL (one JSON object per line):\n```json\n{\"role\":\"user\",\"content\":\"Hello\",\"ts\":1738764800}\n{\"role\":\"assistant\",\"content\":\"Hi there!\",\"ts\":1738764802}\n```\n\n---\n\n## Configuration\n\nAll configuration is done exclusively through `mimi_secrets.h` at build time. There is no runtime configuration — changing any setting requires `idf.py fullclean && idf.py build`.\n\n| Define                       | Description                             |\n|------------------------------|-----------------------------------------|\n| `MIMI_SECRET_WIFI_SSID`     | WiFi SSID                               |\n| `MIMI_SECRET_WIFI_PASS`     | WiFi password                           |\n| `MIMI_SECRET_TG_TOKEN`      | Telegram Bot API token                  |\n| `MIMI_SECRET_API_KEY`       | Anthropic API key                       |\n| `MIMI_SECRET_MODEL`         | Model ID (default: claude-opus-4-6)     |\n| `MIMI_SECRET_PROXY_HOST`    | HTTP proxy hostname/IP (optional)       |\n| `MIMI_SECRET_PROXY_PORT`    | HTTP proxy port (optional)              |\n| `MIMI_SECRET_SEARCH_KEY`    | Brave Search API key (optional)         |\n\nNVS is still initialized (required by ESP-IDF WiFi internals) but is not used for application configuration.\n\n---\n\n## Message Bus Protocol\n\nThe internal message bus uses two FreeRTOS queues carrying `mimi_msg_t`:\n\n```c\ntypedef struct {\n    char channel[16];   // \"telegram\", \"websocket\", \"cli\"\n    char chat_id[32];   // Telegram chat ID or WS client ID\n    char *content;      // Heap-allocated text (ownership transferred)\n} mimi_msg_t;\n```\n\n- **Inbound queue**: channels → agent loop (depth: 8)\n- **Outbound queue**: agent loop → dispatch → channels (depth: 8)\n- Content string ownership is transferred on push; receiver must `free()`.\n\n---\n\n## WebSocket Protocol\n\nPort: **18789**. Max clients: **4**.\n\n**Client → Server:**\n```json\n{\"type\": \"message\", \"content\": \"Hello\", \"chat_id\": \"ws_client1\"}\n```\n\n**Server → Client:**\n```json\n{\"type\": \"response\", \"content\": \"Hi there!\", \"chat_id\": \"ws_client1\"}\n```\n\nClient `chat_id` is auto-assigned on connection (`ws_<fd>`) but can be overridden in the first message.\n\n---\n\n## Claude API Integration\n\nEndpoint: `POST https://api.anthropic.com/v1/messages`\n\nRequest format (Anthropic-native, non-streaming, with tools):\n```json\n{\n  \"model\": \"claude-opus-4-6\",\n  \"max_tokens\": 4096,\n  \"system\": \"<system prompt>\",\n  \"tools\": [\n    {\n      \"name\": \"web_search\",\n      \"description\": \"Search the web for current information.\",\n      \"input_schema\": {\"type\": \"object\", \"properties\": {\"query\": {\"type\": \"string\"}}, \"required\": [\"query\"]}\n    }\n  ],\n  \"messages\": [\n    {\"role\": \"user\", \"content\": \"Hello\"},\n    {\"role\": \"assistant\", \"content\": \"Hi!\"},\n    {\"role\": \"user\", \"content\": \"What's the weather today?\"}\n  ]\n}\n```\n\nKey difference from OpenAI: `system` is a top-level field, not inside the `messages` array.\n\nNon-streaming JSON response:\n```json\n{\n  \"id\": \"msg_xxx\",\n  \"type\": \"message\",\n  \"role\": \"assistant\",\n  \"content\": [\n    {\"type\": \"text\", \"text\": \"Let me search for that.\"},\n    {\"type\": \"tool_use\", \"id\": \"toolu_xxx\", \"name\": \"web_search\", \"input\": {\"query\": \"weather today\"}}\n  ],\n  \"stop_reason\": \"tool_use\"\n}\n```\n\nWhen `stop_reason` is `\"tool_use\"`, the agent loop executes each tool and sends results back:\n```json\n{\"role\": \"assistant\", \"content\": [<text + tool_use blocks>]}\n{\"role\": \"user\", \"content\": [{\"type\": \"tool_result\", \"tool_use_id\": \"toolu_xxx\", \"content\": \"...\"}]}\n```\n\nThe loop repeats until `stop_reason` is `\"end_turn\"` (max 10 iterations).\n\n---\n\n## Startup Sequence\n\n```\napp_main()\n  ├── init_nvs()                    NVS flash init (erase if corrupted)\n  ├── esp_event_loop_create_default()\n  ├── init_spiffs()                 Mount SPIFFS at /spiffs\n  ├── message_bus_init()            Create inbound + outbound queues\n  ├── memory_store_init()           Verify SPIFFS paths\n  ├── session_mgr_init()\n  ├── wifi_manager_init()           Init WiFi STA mode + event handlers\n  ├── http_proxy_init()             Load proxy config from build-time secrets\n  ├── telegram_bot_init()           Load bot token from build-time secrets\n  ├── llm_proxy_init()              Load API key + model from build-time secrets\n  ├── tool_registry_init()          Register tools, build tools JSON\n  ├── agent_loop_init()\n  ├── serial_cli_init()             Start REPL (works without WiFi)\n  │\n  ├── wifi_manager_start()          Connect using build-time credentials\n  │   └── wifi_manager_wait_connected(30s)\n  │\n  └── [if WiFi connected]\n      ├── telegram_bot_start()      Launch tg_poll task (Core 0)\n      ├── agent_loop_start()        Launch agent_loop task (Core 1)\n      ├── ws_server_start()         Start httpd on port 18789\n      └── outbound_dispatch task    Launch outbound task (Core 0)\n```\n\nIf WiFi credentials are missing or connection times out, the CLI remains available for diagnostics.\n\n---\n\n## Serial CLI Commands\n\nThe CLI provides debug and maintenance commands only. All configuration is done via `mimi_secrets.h`.\n\n| Command                        | Description                          |\n|--------------------------------|--------------------------------------|\n| `wifi_status`                  | Show connection status and IP        |\n| `memory_read`                  | Print MEMORY.md contents             |\n| `memory_write <CONTENT>`       | Overwrite MEMORY.md                  |\n| `session_list`                 | List all session files               |\n| `session_clear <CHAT_ID>`      | Delete a session file                |\n| `heap_info`                    | Show internal + PSRAM free bytes     |\n| `restart`                      | Reboot the device                    |\n| `help`                         | List all available commands           |\n\n---\n\n## Nanobot Reference Mapping\n\n| Nanobot Module              | MimiClaw Equivalent            | Notes                        |\n|-----------------------------|--------------------------------|------------------------------|\n| `agent/loop.py`             | `agent/agent_loop.c`           | ReAct loop with tool use     |\n| `agent/context.py`          | `agent/context_builder.c`      | Loads SOUL.md + USER.md + memory + tool guidance |\n| `agent/memory.py`           | `memory/memory_store.c`        | MEMORY.md + daily notes      |\n| `session/manager.py`        | `memory/session_mgr.c`         | JSONL per chat, ring buffer  |\n| `channels/telegram.py`      | `telegram/telegram_bot.c`      | Raw HTTP, no python-telegram-bot |\n| `bus/events.py` + `queue.py`| `bus/message_bus.c`            | FreeRTOS queues vs asyncio   |\n| `providers/litellm_provider.py` | `llm/llm_proxy.c`         | Direct Anthropic API only    |\n| `config/schema.py`          | `mimi_config.h` + `mimi_secrets.h` | Build-time secrets only  |\n| `cli/commands.py`           | `cli/serial_cli.c`             | esp_console REPL             |\n| `agent/tools/*`             | `tools/tool_registry.c` + `tool_web_search.c` | web_search via Brave API |\n| `agent/subagent.py`         | *(not yet implemented)*        | See TODO.md                  |\n| `agent/skills.py`           | *(not yet implemented)*        | See TODO.md                  |\n| `cron/service.py`           | *(not yet implemented)*        | See TODO.md                  |\n| `heartbeat/service.py`      | *(not yet implemented)*        | See TODO.md                  |\n","spiffs_data/skills/skill-creator.md":"# Skill Creator\n\nCreate new skills for MimiClaw.\n\n## When to use\nWhen the user asks to create a new skill, teach the bot something, or add a new capability.\n\n## How to create a skill\n1. Choose a short, descriptive name (lowercase, hyphens ok)\n2. Write a SKILL.md file with this structure:\n   - `# Title` — clear name\n   - Brief description paragraph\n   - `## When to use` — trigger conditions\n   - `## How to use` — step-by-step instructions\n   - `## Example` — concrete example (optional but helpful)\n3. Save to `/spiffs/skills/<name>.md` using write_file\n4. The skill will be automatically available after the next conversation\n\n## Best practices\n- Keep skills concise — the context window is limited\n- Focus on WHAT to do, not HOW (the agent is smart)\n- Include specific tool calls the agent should use\n- Test by asking the agent to use the new skill\n\n## Example\nTo create a \"translate\" skill:\nwrite_file path=\"/spiffs/skills/translate.md\" content=\"# Translate\\n\\nTranslate text between languages.\\n\\n## When to use\\nWhen the user asks to translate text.\\n\\n## How to use\\n1. Identify source and target languages\\n2. Translate directly using your language knowledge\\n3. For specialized terms, use web_search to verify\\n\"\n"},"files":{"spiffs_data/skills/weather.md":"# Weather\n\nGet current weather and forecasts using web_search.\n\n## When to use\nWhen the user asks about weather, temperature, or forecasts.\n\n## How to use\n1. Use get_current_time to know the current date\n2. Use web_search with a query like \"weather in [city] today\"\n3. Extract temperature, conditions, and forecast from results\n4. Present in a concise, friendly format\n\n## Example\nUser: \"What's the weather in Tokyo?\"\n→ get_current_time\n→ web_search \"weather Tokyo today February 2026\"\n→ \"Tokyo: 8°C, partly cloudy. High 12°C, low 4°C. Light wind from the north.\"\n","spiffs_data/skills/daily-briefing.md":"# Daily Briefing\n\nCompile a personalized daily briefing for the user.\n\n## When to use\nWhen the user asks for a daily briefing, morning update, or \"what's new today\".\nAlso useful as a heartbeat/cron task.\n\n## How to use\n1. Use get_current_time for today's date\n2. Read /spiffs/memory/MEMORY.md for user preferences and context\n3. Read today's daily note if it exists\n4. Use web_search for relevant news based on user interests\n5. Compile a concise briefing covering:\n   - Date and time\n   - Weather (if location known from USER.md)\n   - Relevant news/updates based on user interests\n   - Any pending tasks from memory\n   - Any scheduled cron jobs\n\n## Format\nKeep it brief — 5-10 bullet points max. Use the user's preferred language.\n","spiffs_data/skills/gpio-control.md":"# GPIO Control\n\nControl and monitor GPIO pins on the ESP32-S3 for digital I/O.\n\n## When to use\nWhen the user asks to:\n- Turn on/off LEDs, relays, or other outputs\n- Check switch states, button presses, or sensor readings\n- Confirm digital I/O status (switch confirmation)\n- Get an overview of all GPIO pin states\n\n## How to use\n1. To **read a switch/sensor**: use gpio_read with the pin number\n   - Returns HIGH (1) or LOW (0)\n   - HIGH typically means switch is ON / circuit closed\n   - LOW typically means switch is OFF / circuit open\n2. To **set an output**: use gpio_write with pin and state (1=HIGH, 0=LOW)\n3. To **scan all pins**: use gpio_read_all for a full status overview\n4. For **switch confirmation**: read the pin, report state, optionally toggle and re-read to verify\n\n## Pin safety\n- Only pins within the allowed range can be accessed\n- ESP32 flash pins (6-11) are always blocked\n- If a pin is rejected, suggest an alternative within the allowed range\n\n## Example\nUser: \"Check if the switch on pin 4 is on\"\n→ gpio_read {\"pin\": 4}\n→ \"Pin 4 = HIGH\"\n→ \"The switch on pin 4 is currently ON (HIGH).\"\n\nUser: \"Turn on the relay on pin 5\"\n→ gpio_write {\"pin\": 5, \"state\": 1}\n→ \"Pin 5 set to HIGH\"\n→ gpio_read {\"pin\": 5}\n→ \"Pin 5 = HIGH\"\n→ \"Relay on pin 5 is now ON. Confirmed HIGH.\"\n","skills/deploy/SKILL.md":"---\nname: deploy\ndescription: Deploy MimiClaw firmware to an ESP32-S3 board. Covers prerequisites, configuration, build, flash, verification, and troubleshooting.\n---\n\n# Deploy MimiClaw\n\nEnd-to-end guide for deploying MimiClaw to an ESP32-S3 dev board.\n\n## Prerequisites\n\n### Hardware\n- ESP32-S3 dev board with **16 MB flash + 8 MB PSRAM** (e.g. Xiaozhi AI board, ~$5-10)\n- USB Type-C data cable (not charge-only)\n\n### Software\n- **ESP-IDF v5.5+** installed and working\n  ```bash\n  # Install: https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32s3/get-started/\n  # Verify:\n  idf.py --version   # should show >= 5.5\n  ```\n\n### Credentials (get these first)\n- **WiFi SSID + password** — the network the ESP32 will connect to\n- **Telegram Bot Token** — create via [@BotFather](https://t.me/BotFather) on Telegram\n- **Anthropic API Key** — from [console.anthropic.com](https://console.anthropic.com)\n- *(Optional)* Brave Search API key — from [brave.com/search/api](https://brave.com/search/api/)\n- *(Optional)* HTTP proxy host:port — if in China or restricted network\n\n## Step 1: Clone and Set Target\n\n```bash\ngit clone https://github.com/memovai/mimiclaw.git\ncd mimiclaw\nidf.py set-target esp32s3\n```\n\n## Step 2: Configure Secrets\n\n```bash\ncp main/mimi_secrets.h.example main/mimi_secrets.h\n```\n\nEdit `main/mimi_secrets.h` — fill in ALL required fields:\n\n```c\n#define MIMI_SECRET_WIFI_SSID       \"YourWiFiName\"        // REQUIRED\n#define MIMI_SECRET_WIFI_PASS       \"YourWiFiPassword\"     // REQUIRED\n#define MIMI_SECRET_TG_TOKEN        \"123456:ABC-DEF...\"    // REQUIRED\n#define MIMI_SECRET_API_KEY         \"sk-ant-api03-...\"     // REQUIRED\n#define MIMI_SECRET_MODEL           \"\"                     // optional, defaults to claude-opus-4-5\n#define MIMI_SECRET_SEARCH_KEY      \"\"                     // optional: Brave Search API key\n#define MIMI_SECRET_PROXY_HOST      \"\"                     // optional: e.g. \"192.168.1.83\"\n#define MIMI_SECRET_PROXY_PORT      \"\"                     // optional: e.g. \"7897\"\n```\n\n**Proxy setup (China users):**\nIf you need a proxy to reach Telegram/Anthropic APIs, set both `PROXY_HOST` and `PROXY_PORT`. The proxy machine must:\n- Be on the same LAN as the ESP32\n- Support HTTP CONNECT method (Clash, V2Ray, etc.)\n- Have \"Allow LAN connections\" enabled\n\n## Step 3: Build\n\n```bash\nidf.py fullclean && idf.py build\n```\n\n**IMPORTANT:** Always `fullclean` after changing `mimi_secrets.h` — the secrets are compiled into the binary.\n\nExpected output: `Project build complete. To flash, run: idf.py flash`\n\n### Build Troubleshooting\n\n| Error | Fix |\n|-------|-----|\n| `mimi_secrets.h: No such file` | Run `cp main/mimi_secrets.h.example main/mimi_secrets.h` |\n| `esp_websocket_client not found` | Run `idf.py fullclean` then `idf.py build` (managed component auto-downloads) |\n| `Toolchain not found` | Re-run ESP-IDF `install.sh` and `source export.sh` |\n| Build runs out of memory | Close other apps, ESP-IDF build needs ~2GB RAM |\n\n## Step 4: Find Serial Port\n\n```bash\n# macOS\nls /dev/cu.usb*\n\n# Linux\nls /dev/ttyACM* /dev/ttyUSB*\n```\n\nCommon ports:\n- macOS USB-OTG: `/dev/cu.usbmodem1101` or `/dev/cu.usbmodem11401`\n- Linux: `/dev/ttyACM0`\n\n**If no port shows up:**\n- Try a different USB cable (must be data cable, not charge-only)\n- Try a different USB port\n- Check if board has a power LED lit\n\n## Step 5: Flash\n\n```bash\nidf.py -p PORT flash monitor\n```\n\nReplace `PORT` with your actual port. Example:\n```bash\nidf.py -p /dev/cu.usbmodem1101 flash monitor\n```\n\nThe monitor shows boot logs. Look for:\n```\nI (xxx) mimi: MimiClaw - ESP32-S3 AI Agent\nI (xxx) mimi: PSRAM free: ~8000000 bytes\nI (xxx) wifi: WiFi connected: 192.168.x.x\nI (xxx) telegram: Telegram bot token loaded\nI (xxx) mimi: All services started!\n```\n\n**Exit monitor:** `Ctrl+]`\n\n## Step 6: Verify\n\n1. Open Telegram, find your bot (the one you created with BotFather)\n2. Send: `Hello`\n3. You should see \"mimi is working...\" followed by a response\n4. Send: `What time is it?` — tests the get_current_time tool\n5. Send: `Search for latest news about ESP32` — tests web_search (if Brave key set)\n\n## Post-Deploy: Runtime Configuration\n\nConnect via serial (`idf.py -p PORT monitor`) and use CLI commands:\n\n```\nmimi> config_show                  # see current config\nmimi> wifi_set NewSSID NewPass     # change WiFi\nmimi> set_tg_token 123456:ABC...   # change Telegram token\nmimi> set_api_key sk-ant-...       # change API key\nmimi> set_model claude-sonnet-4-5  # change model\nmimi> set_proxy 192.168.1.83 7897  # set proxy\nmimi> clear_proxy                  # remove proxy\nmimi> heap_info                    # check memory\nmimi> restart                      # reboot\n```\n\nCLI settings are stored in NVS flash and take priority over build-time values.\n\n## OTA Update (over WiFi)\n\nAfter initial USB flash, future updates can be done over WiFi:\n\n1. Build new firmware: `idf.py build`\n2. Host the `.bin` file on a local HTTP server:\n   ```bash\n   cd build && python3 -m http.server 8080\n   ```\n3. Send to your bot on Telegram or use the OTA CLI command with the URL:\n   ```\n   http://YOUR_PC_IP:8080/mimiclaw.bin\n   ```\n\n## Flash Layout\n\n```\n16 MB Flash:\n├── 0x009000  NVS (24 KB) — runtime config\n├── 0x020000  OTA_0 (2 MB) — active firmware\n├── 0x220000  OTA_1 (2 MB) — update slot\n├── 0x420000  SPIFFS (12 MB) — memory, sessions, config\n└── 0xFF0000  Coredump (64 KB)\n```\n\n## Common Issues\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| No WiFi connection | Wrong SSID/password | Check `mimi_secrets.h`, `idf.py fullclean && build && flash` |\n| \"No bot token\" | Empty TG token | Set via `mimi_secrets.h` or CLI `set_tg_token` |\n| Bot doesn't respond | API key invalid | Check key at console.anthropic.com, set via CLI |\n| \"Markdown send failed\" | Normal with Markdown mode | Non-critical, falls back to plain text |\n| Proxy timeout | Proxy not reachable | Ensure same LAN, proxy allows LAN connections |\n| SPIFFS mount failed | First boot or corruption | Normal on first boot (auto-formats) |\n| Port busy/not found | Wrong port or cable | Try different USB port/cable, check `ls /dev/cu.usb*` |\n| Boot loop | Firmware crash | Flash via USB again, check serial logs for crash info |\n","docs/ARCHITECTURE.md":"# MimiClaw Architecture\n\n> ESP32-S3 AI Agent firmware — C/FreeRTOS implementation running on bare metal (no Linux).\n\n---\n\n## System Overview\n\n```\nTelegram App (User)\n    │\n    │  HTTPS Long Polling\n    │\n    ▼\n┌──────────────────────────────────────────────────┐\n│               ESP32-S3 (MimiClaw)                │\n│                                                  │\n│   ┌─────────────┐       ┌──────────────────┐     │\n│   │  Telegram    │──────▶│   Inbound Queue  │     │\n│   │  Poller      │       └────────┬─────────┘     │\n│   │  (Core 0)    │               │                │\n│   └─────────────┘               ▼                │\n│                     ┌────────────────────────┐    │\n│   ┌─────────────┐  │     Agent Loop          │    │\n│   │  WebSocket   │─▶│     (Core 1)           │    │\n│   │  Server      │  │                        │    │\n│   │  (:18789)    │  │  Context ──▶ LLM Proxy │    │\n│   └─────────────┘  │  Builder      (HTTPS)   │    │\n│                     │       ▲          │      │    │\n│   ┌─────────────┐  │       │     tool_use?   │    │\n│   │  Serial CLI  │  │       │          ▼      │    │\n│   │  (Core 0)    │  │  Tool Results ◀─ Tools  │    │\n│   └─────────────┘  │              (web_search)│    │\n│                     └──────────┬─────────────┘    │\n│                                │                  │\n│                         ┌──────▼───────┐          │\n│                         │ Outbound Queue│          │\n│                         └──────┬───────┘          │\n│                                │                  │\n│                         ┌──────▼───────┐          │\n│                         │  Outbound    │          │\n│                         │  Dispatch    │          │\n│                         │  (Core 0)    │          │\n│                         └──┬────────┬──┘          │\n│                            │        │             │\n│                     Telegram    WebSocket          │\n│                     sendMessage  send              │\n│                                                   │\n│   ┌──────────────────────────────────────────┐    │\n│   │  SPIFFS (12 MB)                          │    │\n│   │  /spiffs/config/  SOUL.md, USER.md       │    │\n│   │  /spiffs/memory/  MEMORY.md, YYYY-MM-DD  │    │\n│   │  /spiffs/sessions/ tg_<chat_id>.jsonl    │    │\n│   └──────────────────────────────────────────┘    │\n└───────────────────────────────────────────────────┘\n         │\n         │  Anthropic Messages API (HTTPS)\n         │  + Brave Search API (HTTPS)\n         ▼\n   ┌───────────┐   ┌──────────────┐\n   │ Claude API │   │ Brave Search │\n   └───────────┘   └──────────────┘\n```\n\n---\n\n## Data Flow\n\n```\n1. User sends message on Telegram (or WebSocket)\n2. Channel poller receives message, wraps in mimi_msg_t\n3. Message pushed to Inbound Queue (FreeRTOS xQueue)\n4. Agent Loop (Core 1) pops message:\n   a. Load session history from SPIFFS (JSONL)\n   b. Build system prompt (SOUL.md + USER.md + MEMORY.md + recent notes + tool guidance)\n   c. Build cJSON messages array (history + current message)\n   d. ReAct loop (max 10 iterations):\n      i.   Call Claude API via HTTPS (non-streaming, with tools array)\n      ii.  Parse JSON response → text blocks + tool_use blocks\n      iii. If stop_reason == \"tool_use\":\n           - Execute each tool (e.g. web_search → Brave Search API)\n           - Append assistant content + tool_result to messages\n           - Continue loop\n      iv.  If stop_reason == \"end_turn\": break with final text\n   e. Save user message + final assistant text to session file\n   f. Push response to Outbound Queue\n5. Outbound Dispatch (Core 0) pops response:\n   a. Route by channel field (\"telegram\" → sendMessage, \"websocket\" → WS frame)\n6. User receives reply\n```\n\n---\n\n## Module Map\n\n```\nmain/\n├── mimi.c                  Entry point — app_main() orchestrates init + startup\n├── mimi_config.h           All compile-time constants + build-time secrets include\n├── mimi_secrets.h          Build-time credentials (gitignored, highest priority)\n├── mimi_secrets.h.example  Template for mimi_secrets.h\n│\n├── bus/\n│   ├── message_bus.h       mimi_msg_t struct, queue API\n│   └── message_bus.c       Two FreeRTOS queues: inbound + outbound\n│\n├── wifi/\n│   ├── wifi_manager.h      WiFi STA lifecycle API\n│   └── wifi_manager.c      Event handler, exponential backoff\n│\n├── telegram/\n│   ├── telegram_bot.h      Bot init/start, send_message API\n│   └── telegram_bot.c      Long polling loop, JSON parsing, message splitting\n│\n├── llm/\n│   ├── llm_proxy.h         llm_chat() + llm_chat_tools() API, tool_use types\n│   └── llm_proxy.c         Anthropic Messages API (non-streaming), tool_use parsing\n│\n├── agent/\n│   ├── agent_loop.h        Agent task init/start\n│   ├── agent_loop.c        ReAct loop: LLM call → tool execution → repeat\n│   ├── context_builder.h   System prompt + messages builder API\n│   └── context_builder.c   Reads bootstrap files + memory + tool guidance\n│\n├── tools/\n│   ├── tool_registry.h     Tool definition struct, register/dispatch API\n│   ├── tool_registry.c     Tool registration, JSON schema builder, dispatch by name\n│   ├── tool_web_search.h   Web search tool API\n│   └── tool_web_search.c   Brave Search API via HTTPS (direct + proxy)\n│\n├── memory/\n│   ├── memory_store.h      Long-term + daily memory API\n│   ├── memory_store.c      MEMORY.md read/write, daily .md append/read\n│   ├── session_mgr.h       Per-chat session API\n│   └── session_mgr.c       JSONL session files, ring buffer history\n│\n├── gateway/\n│   ├── ws_server.h         WebSocket server API\n│   └── ws_server.c         ESP HTTP server with WS upgrade, client tracking\n│\n├── proxy/\n│   ├── http_proxy.h        Proxy connection API\n│   └── http_proxy.c        HTTP CONNECT tunnel + TLS via esp_tls\n│\n├── cli/\n│   ├── serial_cli.h        CLI init API\n│   └── serial_cli.c        esp_console REPL with debug/maintenance commands\n│\n└── ota/\n    ├── ota_manager.h       OTA update API\n    └── ota_manager.c       esp_https_ota wrapper\n```\n\n---\n\n## FreeRTOS Task Layout\n\n| Task               | Core | Priority | Stack  | Description                          |\n|--------------------|------|----------|--------|--------------------------------------|\n| `tg_poll`          | 0    | 5        | 12 KB  | Telegram long polling (30s timeout)  |\n| `agent_loop`       | 1    | 6        | 12 KB  | Message processing + Claude API call |\n| `outbound`         | 0    | 5        | 8 KB   | Route responses to Telegram / WS     |\n| `serial_cli`       | 0    | 3        | 4 KB   | USB serial console REPL              |\n| httpd (internal)   | 0    | 5        | —      | WebSocket server (esp_http_server)   |\n| wifi_event (IDF)   | 0    | 8        | —      | WiFi event handling (ESP-IDF)        |\n\n**Core allocation strategy**: Core 0 handles I/O (network, serial, WiFi). Core 1 is dedicated to the agent loop (CPU-bound JSON building + waiting on HTTPS).\n\n---\n\n## Memory Budget\n\n| Purpose                            | Location       | Size     |\n|------------------------------------|----------------|----------|\n| FreeRTOS task stacks               | Internal SRAM  | ~40 KB   |\n| WiFi buffers                       | Internal SRAM  | ~30 KB   |\n| TLS connections x2 (Telegram + Claude) | PSRAM      | ~120 KB  |\n| JSON parse buffers                 | PSRAM          | ~32 KB   |\n| Session history cache              | PSRAM          | ~32 KB   |\n| System prompt buffer               | PSRAM          | ~16 KB   |\n| LLM response stream buffer         | PSRAM          | ~32 KB   |\n| Remaining available                | PSRAM          | ~7.7 MB  |\n\nLarge buffers (32 KB+) are allocated from PSRAM via `heap_caps_calloc(1, size, MALLOC_CAP_SPIRAM)`.\n\n---\n\n## Flash Partition Layout\n\n```\nOffset      Size      Name        Purpose\n─────────────────────────────────────────────\n0x009000    24 KB     nvs         ESP-IDF internal use (WiFi calibration etc.)\n0x00F000     8 KB     otadata     OTA boot state\n0x011000     4 KB     phy_init    WiFi PHY calibration\n0x020000     2 MB     ota_0       Firmware slot A\n0x220000     2 MB     ota_1       Firmware slot B\n0x420000    12 MB     spiffs      Markdown memory, sessions, config\n0xFF0000    64 KB     coredump    Crash dump storage\n```\n\nTotal: 16 MB flash.\n\n---\n\n## Storage Layout (SPIFFS)\n\nSPIFFS is a flat filesystem — no real directories. Files use path-like names.\n\n```\n/spiffs/config/SOUL.md          AI personality definition\n/spiffs/config/USER.md          User profile\n/spiffs/memory/MEMORY.md        Long-term persistent memory\n/spiffs/memory/2026-02-05.md    Daily notes (one file per day)\n/spiffs/sessions/tg_12345.jsonl Session history (one file per Telegram chat)\n```\n\nSession files are JSONL (one JSON object per line):\n```json\n{\"role\":\"user\",\"content\":\"Hello\",\"ts\":1738764800}\n{\"role\":\"assistant\",\"content\":\"Hi there!\",\"ts\":1738764802}\n```\n\n---\n\n## Configuration\n\nAll configuration is done exclusively through `mimi_secrets.h` at build time. There is no runtime configuration — changing any setting requires `idf.py fullclean && idf.py build`.\n\n| Define                       | Description                             |\n|------------------------------|-----------------------------------------|\n| `MIMI_SECRET_WIFI_SSID`     | WiFi SSID                               |\n| `MIMI_SECRET_WIFI_PASS`     | WiFi password                           |\n| `MIMI_SECRET_TG_TOKEN`      | Telegram Bot API token                  |\n| `MIMI_SECRET_API_KEY`       | Anthropic API key                       |\n| `MIMI_SECRET_MODEL`         | Model ID (default: claude-opus-4-6)     |\n| `MIMI_SECRET_PROXY_HOST`    | HTTP proxy hostname/IP (optional)       |\n| `MIMI_SECRET_PROXY_PORT`    | HTTP proxy port (optional)              |\n| `MIMI_SECRET_SEARCH_KEY`    | Brave Search API key (optional)         |\n\nNVS is still initialized (required by ESP-IDF WiFi internals) but is not used for application configuration.\n\n---\n\n## Message Bus Protocol\n\nThe internal message bus uses two FreeRTOS queues carrying `mimi_msg_t`:\n\n```c\ntypedef struct {\n    char channel[16];   // \"telegram\", \"websocket\", \"cli\"\n    char chat_id[32];   // Telegram chat ID or WS client ID\n    char *content;      // Heap-allocated text (ownership transferred)\n} mimi_msg_t;\n```\n\n- **Inbound queue**: channels → agent loop (depth: 8)\n- **Outbound queue**: agent loop → dispatch → channels (depth: 8)\n- Content string ownership is transferred on push; receiver must `free()`.\n\n---\n\n## WebSocket Protocol\n\nPort: **18789**. Max clients: **4**.\n\n**Client → Server:**\n```json\n{\"type\": \"message\", \"content\": \"Hello\", \"chat_id\": \"ws_client1\"}\n```\n\n**Server → Client:**\n```json\n{\"type\": \"response\", \"content\": \"Hi there!\", \"chat_id\": \"ws_client1\"}\n```\n\nClient `chat_id` is auto-assigned on connection (`ws_<fd>`) but can be overridden in the first message.\n\n---\n\n## Claude API Integration\n\nEndpoint: `POST https://api.anthropic.com/v1/messages`\n\nRequest format (Anthropic-native, non-streaming, with tools):\n```json\n{\n  \"model\": \"claude-opus-4-6\",\n  \"max_tokens\": 4096,\n  \"system\": \"<system prompt>\",\n  \"tools\": [\n    {\n      \"name\": \"web_search\",\n      \"description\": \"Search the web for current information.\",\n      \"input_schema\": {\"type\": \"object\", \"properties\": {\"query\": {\"type\": \"string\"}}, \"required\": [\"query\"]}\n    }\n  ],\n  \"messages\": [\n    {\"role\": \"user\", \"content\": \"Hello\"},\n    {\"role\": \"assistant\", \"content\": \"Hi!\"},\n    {\"role\": \"user\", \"content\": \"What's the weather today?\"}\n  ]\n}\n```\n\nKey difference from OpenAI: `system` is a top-level field, not inside the `messages` array.\n\nNon-streaming JSON response:\n```json\n{\n  \"id\": \"msg_xxx\",\n  \"type\": \"message\",\n  \"role\": \"assistant\",\n  \"content\": [\n    {\"type\": \"text\", \"text\": \"Let me search for that.\"},\n    {\"type\": \"tool_use\", \"id\": \"toolu_xxx\", \"name\": \"web_search\", \"input\": {\"query\": \"weather today\"}}\n  ],\n  \"stop_reason\": \"tool_use\"\n}\n```\n\nWhen `stop_reason` is `\"tool_use\"`, the agent loop executes each tool and sends results back:\n```json\n{\"role\": \"assistant\", \"content\": [<text + tool_use blocks>]}\n{\"role\": \"user\", \"content\": [{\"type\": \"tool_result\", \"tool_use_id\": \"toolu_xxx\", \"content\": \"...\"}]}\n```\n\nThe loop repeats until `stop_reason` is `\"end_turn\"` (max 10 iterations).\n\n---\n\n## Startup Sequence\n\n```\napp_main()\n  ├── init_nvs()                    NVS flash init (erase if corrupted)\n  ├── esp_event_loop_create_default()\n  ├── init_spiffs()                 Mount SPIFFS at /spiffs\n  ├── message_bus_init()            Create inbound + outbound queues\n  ├── memory_store_init()           Verify SPIFFS paths\n  ├── session_mgr_init()\n  ├── wifi_manager_init()           Init WiFi STA mode + event handlers\n  ├── http_proxy_init()             Load proxy config from build-time secrets\n  ├── telegram_bot_init()           Load bot token from build-time secrets\n  ├── llm_proxy_init()              Load API key + model from build-time secrets\n  ├── tool_registry_init()          Register tools, build tools JSON\n  ├── agent_loop_init()\n  ├── serial_cli_init()             Start REPL (works without WiFi)\n  │\n  ├── wifi_manager_start()          Connect using build-time credentials\n  │   └── wifi_manager_wait_connected(30s)\n  │\n  └── [if WiFi connected]\n      ├── telegram_bot_start()      Launch tg_poll task (Core 0)\n      ├── agent_loop_start()        Launch agent_loop task (Core 1)\n      ├── ws_server_start()         Start httpd on port 18789\n      └── outbound_dispatch task    Launch outbound task (Core 0)\n```\n\nIf WiFi credentials are missing or connection times out, the CLI remains available for diagnostics.\n\n---\n\n## Serial CLI Commands\n\nThe CLI provides debug and maintenance commands only. All configuration is done via `mimi_secrets.h`.\n\n| Command                        | Description                          |\n|--------------------------------|--------------------------------------|\n| `wifi_status`                  | Show connection status and IP        |\n| `memory_read`                  | Print MEMORY.md contents             |\n| `memory_write <CONTENT>`       | Overwrite MEMORY.md                  |\n| `session_list`                 | List all session files               |\n| `session_clear <CHAT_ID>`      | Delete a session file                |\n| `heap_info`                    | Show internal + PSRAM free bytes     |\n| `restart`                      | Reboot the device                    |\n| `help`                         | List all available commands           |\n\n---\n\n## Nanobot Reference Mapping\n\n| Nanobot Module              | MimiClaw Equivalent            | Notes                        |\n|-----------------------------|--------------------------------|------------------------------|\n| `agent/loop.py`             | `agent/agent_loop.c`           | ReAct loop with tool use     |\n| `agent/context.py`          | `agent/context_builder.c`      | Loads SOUL.md + USER.md + memory + tool guidance |\n| `agent/memory.py`           | `memory/memory_store.c`        | MEMORY.md + daily notes      |\n| `session/manager.py`        | `memory/session_mgr.c`         | JSONL per chat, ring buffer  |\n| `channels/telegram.py`      | `telegram/telegram_bot.c`      | Raw HTTP, no python-telegram-bot |\n| `bus/events.py` + `queue.py`| `bus/message_bus.c`            | FreeRTOS queues vs asyncio   |\n| `providers/litellm_provider.py` | `llm/llm_proxy.c`         | Direct Anthropic API only    |\n| `config/schema.py`          | `mimi_config.h` + `mimi_secrets.h` | Build-time secrets only  |\n| `cli/commands.py`           | `cli/serial_cli.c`             | esp_console REPL             |\n| `agent/tools/*`             | `tools/tool_registry.c` + `tool_web_search.c` | web_search via Brave API |\n| `agent/subagent.py`         | *(not yet implemented)*        | See TODO.md                  |\n| `agent/skills.py`           | *(not yet implemented)*        | See TODO.md                  |\n| `cron/service.py`           | *(not yet implemented)*        | See TODO.md                  |\n| `heartbeat/service.py`      | *(not yet implemented)*        | See TODO.md                  |\n","spiffs_data/skills/skill-creator.md":"# Skill Creator\n\nCreate new skills for MimiClaw.\n\n## When to use\nWhen the user asks to create a new skill, teach the bot something, or add a new capability.\n\n## How to create a skill\n1. Choose a short, descriptive name (lowercase, hyphens ok)\n2. Write a SKILL.md file with this structure:\n   - `# Title` — clear name\n   - Brief description paragraph\n   - `## When to use` — trigger conditions\n   - `## How to use` — step-by-step instructions\n   - `## Example` — concrete example (optional but helpful)\n3. Save to `/spiffs/skills/<name>.md` using write_file\n4. The skill will be automatically available after the next conversation\n\n## Best practices\n- Keep skills concise — the context window is limited\n- Focus on WHAT to do, not HOW (the agent is smart)\n- Include specific tool calls the agent should use\n- Test by asking the agent to use the new skill\n\n## Example\nTo create a \"translate\" skill:\nwrite_file path=\"/spiffs/skills/translate.md\" content=\"# Translate\\n\\nTranslate text between languages.\\n\\n## When to use\\nWhen the user asks to translate text.\\n\\n## How to use\\n1. Identify source and target languages\\n2. Translate directly using your language knowledge\\n3. For specialized terms, use web_search to verify\\n\"\n"},"items":[{"name":"ARCHITECTURE.md","path":"docs/ARCHITECTURE.md","rawUrl":"https://raw.githubusercontent.com/memovai/mimiclaw/HEAD/docs/ARCHITECTURE.md","title":"docs - System Architecture & Module Boundaries","category":"project-spec","format":"markdown","content":"# MimiClaw Architecture\n\n> ESP32-S3 AI Agent firmware — C/FreeRTOS implementation running on bare metal (no Linux).\n\n---\n\n## System Overview\n\n```\nTelegram App (User)\n    │\n    │  HTTPS Long Polling\n    │\n    ▼\n┌──────────────────────────────────────────────────┐\n│               ESP32-S3 (MimiClaw)                │\n│                                                  │\n│   ┌─────────────┐       ┌──────────────────┐     │\n│   │  Telegram    │──────▶│   Inbound Queue  │     │\n│   │  Poller      │       └────────┬─────────┘     │\n│   │  (Core 0)    │               │                │\n│   └─────────────┘               ▼                │\n│                     ┌────────────────────────┐    │\n│   ┌─────────────┐  │     Agent Loop          │    │\n│   │  WebSocket   │─▶│     (Core 1)           │    │\n│   │  Server      │  │                        │    │\n│   │  (:18789)    │  │  Context ──▶ LLM Proxy │    │\n│   └─────────────┘  │  Builder      (HTTPS)   │    │\n│                     │       ▲          │      │    │\n│   ┌─────────────┐  │       │     tool_use?   │    │\n│   │  Serial CLI  │  │       │          ▼      │    │\n│   │  (Core 0)    │  │  Tool Results ◀─ Tools  │    │\n│   └─────────────┘  │              (web_search)│    │\n│                     └──────────┬─────────────┘    │\n│                                │                  │\n│                         ┌──────▼───────┐          │\n│                         │ Outbound Queue│          │\n│                         └──────┬───────┘          │\n│                                │                  │\n│                         ┌──────▼───────┐          │\n│                         │  Outbound    │          │\n│                         │  Dispatch    │          │\n│                         │  (Core 0)    │          │\n│                         └──┬────────┬──┘          │\n│                            │        │             │\n│                     Telegram    WebSocket          │\n│                     sendMessage  send              │\n│                                                   │\n│   ┌──────────────────────────────────────────┐    │\n│   │  SPIFFS (12 MB)                          │    │\n│   │  /spiffs/config/  SOUL.md, USER.md       │    │\n│   │  /spiffs/memory/  MEMORY.md, YYYY-MM-DD  │    │\n│   │  /spiffs/sessions/ tg_<chat_id>.jsonl    │    │\n│   └──────────────────────────────────────────┘    │\n└───────────────────────────────────────────────────┘\n         │\n         │  Anthropic Messages API (HTTPS)\n         │  + Brave Search API (HTTPS)\n         ▼\n   ┌───────────┐   ┌──────────────┐\n   │ Claude API │   │ Brave Search │\n   └───────────┘   └──────────────┘\n```\n\n---\n\n## Data Flow\n\n```\n1. User sends message on Telegram (or WebSocket)\n2. Channel poller receives message, wraps in mimi_msg_t\n3. Message pushed to Inbound Queue (FreeRTOS xQueue)\n4. Agent Loop (Core 1) pops message:\n   a. Load session history from SPIFFS (JSONL)\n   b. Build system prompt (SOUL.md + USER.md + MEMORY.md + recent notes + tool guidance)\n   c. Build cJSON messages array (history + current message)\n   d. ReAct loop (max 10 iterations):\n      i.   Call Claude API via HTTPS (non-streaming, with tools array)\n      ii.  Parse JSON response → text blocks + tool_use blocks\n      iii. If stop_reason == \"tool_use\":\n           - Execute each tool (e.g. web_search → Brave Search API)\n           - Append assistant content + tool_result to messages\n           - Continue loop\n      iv.  If stop_reason == \"end_turn\": break with final text\n   e. Save user message + final assistant text to session file\n   f. Push response to Outbound Queue\n5. Outbound Dispatch (Core 0) pops response:\n   a. Route by channel field (\"telegram\" → sendMessage, \"websocket\" → WS frame)\n6. User receives reply\n```\n\n---\n\n## Module Map\n\n```\nmain/\n├── mimi.c                  Entry point — app_main() orchestrates init + startup\n├── mimi_config.h           All compile-time constants + build-time secrets include\n├── mimi_secrets.h          Build-time credentials (gitignored, highest priority)\n├── mimi_secrets.h.example  Template for mimi_secrets.h\n│\n├── bus/\n│   ├── message_bus.h       mimi_msg_t struct, queue API\n│   └── message_bus.c       Two FreeRTOS queues: inbound + outbound\n│\n├── wifi/\n│   ├── wifi_manager.h      WiFi STA lifecycle API\n│   └── wifi_manager.c      Event handler, exponential backoff\n│\n├── telegram/\n│   ├── telegram_bot.h      Bot init/start, send_message API\n│   └── telegram_bot.c      Long polling loop, JSON parsing, message splitting\n│\n├── llm/\n│   ├── llm_proxy.h         llm_chat() + llm_chat_tools() API, tool_use types\n│   └── llm_proxy.c         Anthropic Messages API (non-streaming), tool_use parsing\n│\n├── agent/\n│   ├── agent_loop.h        Agent task init/start\n│   ├── agent_loop.c        ReAct loop: LLM call → tool execution → repeat\n│   ├── context_builder.h   System prompt + messages builder API\n│   └── context_builder.c   Reads bootstrap files + memory + tool guidance\n│\n├── tools/\n│   ├── tool_registry.h     Tool definition struct, register/dispatch API\n│   ├── tool_registry.c     Tool registration, JSON schema builder, dispatch by name\n│   ├── tool_web_search.h   Web search tool API\n│   └── tool_web_search.c   Brave Search API via HTTPS (direct + proxy)\n│\n├── memory/\n│   ├── memory_store.h      Long-term + daily memory API\n│   ├── memory_store.c      MEMORY.md read/write, daily .md append/read\n│   ├── session_mgr.h       Per-chat session API\n│   └── session_mgr.c       JSONL session files, ring buffer history\n│\n├── gateway/\n│   ├── ws_server.h         WebSocket server API\n│   └── ws_server.c         ESP HTTP server with WS upgrade, client tracking\n│\n├── proxy/\n│   ├── http_proxy.h        Proxy connection API\n│   └── http_proxy.c        HTTP CONNECT tunnel + TLS via esp_tls\n│\n├── cli/\n│   ├── serial_cli.h        CLI init API\n│   └── serial_cli.c        esp_console REPL with debug/maintenance commands\n│\n└── ota/\n    ├── ota_manager.h       OTA update API\n    └── ota_manager.c       esp_https_ota wrapper\n```\n\n---\n\n## FreeRTOS Task Layout\n\n| Task               | Core | Priority | Stack  | Description                          |\n|--------------------|------|----------|--------|--------------------------------------|\n| `tg_poll`          | 0    | 5        | 12 KB  | Telegram long polling (30s timeout)  |\n| `agent_loop`       | 1    | 6        | 12 KB  | Message processing + Claude API call |\n| `outbound`         | 0    | 5        | 8 KB   | Route responses to Telegram / WS     |\n| `serial_cli`       | 0    | 3        | 4 KB   | USB serial console REPL              |\n| httpd (internal)   | 0    | 5        | —      | WebSocket server (esp_http_server)   |\n| wifi_event (IDF)   | 0    | 8        | —      | WiFi event handling (ESP-IDF)        |\n\n**Core allocation strategy**: Core 0 handles I/O (network, serial, WiFi). Core 1 is dedicated to the agent loop (CPU-bound JSON building + waiting on HTTPS).\n\n---\n\n## Memory Budget\n\n| Purpose                            | Location       | Size     |\n|------------------------------------|----------------|----------|\n| FreeRTOS task stacks               | Internal SRAM  | ~40 KB   |\n| WiFi buffers                       | Internal SRAM  | ~30 KB   |\n| TLS connections x2 (Telegram + Claude) | PSRAM      | ~120 KB  |\n| JSON parse buffers                 | PSRAM          | ~32 KB   |\n| Session history cache              | PSRAM          | ~32 KB   |\n| System prompt buffer               | PSRAM          | ~16 KB   |\n| LLM response stream buffer         | PSRAM          | ~32 KB   |\n| Remaining available                | PSRAM          | ~7.7 MB  |\n\nLarge buffers (32 KB+) are allocated from PSRAM via `heap_caps_calloc(1, size, MALLOC_CAP_SPIRAM)`.\n\n---\n\n## Flash Partition Layout\n\n```\nOffset      Size      Name        Purpose\n─────────────────────────────────────────────\n0x009000    24 KB     nvs         ESP-IDF internal use (WiFi calibration etc.)\n0x00F000     8 KB     otadata     OTA boot state\n0x011000     4 KB     phy_init    WiFi PHY calibration\n0x020000     2 MB     ota_0       Firmware slot A\n0x220000     2 MB     ota_1       Firmware slot B\n0x420000    12 MB     spiffs      Markdown memory, sessions, config\n0xFF0000    64 KB     coredump    Crash dump storage\n```\n\nTotal: 16 MB flash.\n\n---\n\n## Storage Layout (SPIFFS)\n\nSPIFFS is a flat filesystem — no real directories. Files use path-like names.\n\n```\n/spiffs/config/SOUL.md          AI personality definition\n/spiffs/config/USER.md          User profile\n/spiffs/memory/MEMORY.md        Long-term persistent memory\n/spiffs/memory/2026-02-05.md    Daily notes (one file per day)\n/spiffs/sessions/tg_12345.jsonl Session history (one file per Telegram chat)\n```\n\nSession files are JSONL (one JSON object per line):\n```json\n{\"role\":\"user\",\"content\":\"Hello\",\"ts\":1738764800}\n{\"role\":\"assistant\",\"content\":\"Hi there!\",\"ts\":1738764802}\n```\n\n---\n\n## Configuration\n\nAll configuration is done exclusively through `mimi_secrets.h` at build time. There is no runtime configuration — changing any setting requires `idf.py fullclean && idf.py build`.\n\n| Define                       | Description                             |\n|------------------------------|-----------------------------------------|\n| `MIMI_SECRET_WIFI_SSID`     | WiFi SSID                               |\n| `MIMI_SECRET_WIFI_PASS`     | WiFi password                           |\n| `MIMI_SECRET_TG_TOKEN`      | Telegram Bot API token                  |\n| `MIMI_SECRET_API_KEY`       | Anthropic API key                       |\n| `MIMI_SECRET_MODEL`         | Model ID (default: claude-opus-4-6)     |\n| `MIMI_SECRET_PROXY_HOST`    | HTTP proxy hostname/IP (optional)       |\n| `MIMI_SECRET_PROXY_PORT`    | HTTP proxy port (optional)              |\n| `MIMI_SECRET_SEARCH_KEY`    | Brave Search API key (optional)         |\n\nNVS is still initialized (required by ESP-IDF WiFi internals) but is not used for application configuration.\n\n---\n\n## Message Bus Protocol\n\nThe internal message bus uses two FreeRTOS queues carrying `mimi_msg_t`:\n\n```c\ntypedef struct {\n    char channel[16];   // \"telegram\", \"websocket\", \"cli\"\n    char chat_id[32];   // Telegram chat ID or WS client ID\n    char *content;      // Heap-allocated text (ownership transferred)\n} mimi_msg_t;\n```\n\n- **Inbound queue**: channels → agent loop (depth: 8)\n- **Outbound queue**: agent loop → dispatch → channels (depth: 8)\n- Content string ownership is transferred on push; receiver must `free()`.\n\n---\n\n## WebSocket Protocol\n\nPort: **18789**. Max clients: **4**.\n\n**Client → Server:**\n```json\n{\"type\": \"message\", \"content\": \"Hello\", \"chat_id\": \"ws_client1\"}\n```\n\n**Server → Client:**\n```json\n{\"type\": \"response\", \"content\": \"Hi there!\", \"chat_id\": \"ws_client1\"}\n```\n\nClient `chat_id` is auto-assigned on connection (`ws_<fd>`) but can be overridden in the first message.\n\n---\n\n## Claude API Integration\n\nEndpoint: `POST https://api.anthropic.com/v1/messages`\n\nRequest format (Anthropic-native, non-streaming, with tools):\n```json\n{\n  \"model\": \"claude-opus-4-6\",\n  \"max_tokens\": 4096,\n  \"system\": \"<system prompt>\",\n  \"tools\": [\n    {\n      \"name\": \"web_search\",\n      \"description\": \"Search the web for current information.\",\n      \"input_schema\": {\"type\": \"object\", \"properties\": {\"query\": {\"type\": \"string\"}}, \"required\": [\"query\"]}\n    }\n  ],\n  \"messages\": [\n    {\"role\": \"user\", \"content\": \"Hello\"},\n    {\"role\": \"assistant\", \"content\": \"Hi!\"},\n    {\"role\": \"user\", \"content\": \"What's the weather today?\"}\n  ]\n}\n```\n\nKey difference from OpenAI: `system` is a top-level field, not inside the `messages` array.\n\nNon-streaming JSON response:\n```json\n{\n  \"id\": \"msg_xxx\",\n  \"type\": \"message\",\n  \"role\": \"assistant\",\n  \"content\": [\n    {\"type\": \"text\", \"text\": \"Let me search for that.\"},\n    {\"type\": \"tool_use\", \"id\": \"toolu_xxx\", \"name\": \"web_search\", \"input\": {\"query\": \"weather today\"}}\n  ],\n  \"stop_reason\": \"tool_use\"\n}\n```\n\nWhen `stop_reason` is `\"tool_use\"`, the agent loop executes each tool and sends results back:\n```json\n{\"role\": \"assistant\", \"content\": [<text + tool_use blocks>]}\n{\"role\": \"user\", \"content\": [{\"type\": \"tool_result\", \"tool_use_id\": \"toolu_xxx\", \"content\": \"...\"}]}\n```\n\nThe loop repeats until `stop_reason` is `\"end_turn\"` (max 10 iterations).\n\n---\n\n## Startup Sequence\n\n```\napp_main()\n  ├── init_nvs()                    NVS flash init (erase if corrupted)\n  ├── esp_event_loop_create_default()\n  ├── init_spiffs()                 Mount SPIFFS at /spiffs\n  ├── message_bus_init()            Create inbound + outbound queues\n  ├── memory_store_init()           Verify SPIFFS paths\n  ├── session_mgr_init()\n  ├── wifi_manager_init()           Init WiFi STA mode + event handlers\n  ├── http_proxy_init()             Load proxy config from build-time secrets\n  ├── telegram_bot_init()           Load bot token from build-time secrets\n  ├── llm_proxy_init()              Load API key + model from build-time secrets\n  ├── tool_registry_init()          Register tools, build tools JSON\n  ├── agent_loop_init()\n  ├── serial_cli_init()             Start REPL (works without WiFi)\n  │\n  ├── wifi_manager_start()          Connect using build-time credentials\n  │   └── wifi_manager_wait_connected(30s)\n  │\n  └── [if WiFi connected]\n      ├── telegram_bot_start()      Launch tg_poll task (Core 0)\n      ├── agent_loop_start()        Launch agent_loop task (Core 1)\n      ├── ws_server_start()         Start httpd on port 18789\n      └── outbound_dispatch task    Launch outbound task (Core 0)\n```\n\nIf WiFi credentials are missing or connection times out, the CLI remains available for diagnostics.\n\n---\n\n## Serial CLI Commands\n\nThe CLI provides debug and maintenance commands only. All configuration is done via `mimi_secrets.h`.\n\n| Command                        | Description                          |\n|--------------------------------|--------------------------------------|\n| `wifi_status`                  | Show connection status and IP        |\n| `memory_read`                  | Print MEMORY.md contents             |\n| `memory_write <CONTENT>`       | Overwrite MEMORY.md                  |\n| `session_list`                 | List all session files               |\n| `session_clear <CHAT_ID>`      | Delete a session file                |\n| `heap_info`                    | Show internal + PSRAM free bytes     |\n| `restart`                      | Reboot the device                    |\n| `help`                         | List all available commands           |\n\n---\n\n## Nanobot Reference Mapping\n\n| Nanobot Module              | MimiClaw Equivalent            | Notes                        |\n|-----------------------------|--------------------------------|------------------------------|\n| `agent/loop.py`             | `agent/agent_loop.c`           | ReAct loop with tool use     |\n| `agent/context.py`          | `agent/context_builder.c`      | Loads SOUL.md + USER.md + memory + tool guidance |\n| `agent/memory.py`           | `memory/memory_store.c`        | MEMORY.md + daily notes      |\n| `session/manager.py`        | `memory/session_mgr.c`         | JSONL per chat, ring buffer  |\n| `channels/telegram.py`      | `telegram/telegram_bot.c`      | Raw HTTP, no python-telegram-bot |\n| `bus/events.py` + `queue.py`| `bus/message_bus.c`            | FreeRTOS queues vs asyncio   |\n| `providers/litellm_provider.py` | `llm/llm_proxy.c`         | Direct Anthropic API only    |\n| `config/schema.py`          | `mimi_config.h` + `mimi_secrets.h` | Build-time secrets only  |\n| `cli/commands.py`           | `cli/serial_cli.c`             | esp_console REPL             |\n| `agent/tools/*`             | `tools/tool_registry.c` + `tool_web_search.c` | web_search via Brave API |\n| `agent/subagent.py`         | *(not yet implemented)*        | See TODO.md                  |\n| `agent/skills.py`           | *(not yet implemented)*        | See TODO.md                  |\n| `cron/service.py`           | *(not yet implemented)*        | See TODO.md                  |\n| `heartbeat/service.py`      | *(not yet implemented)*        | See TODO.md                  |\n","isInternal":false,"tokens":4108,"sizeBytes":17962},{"name":"SKILL.md","path":"skills/deploy/SKILL.md","rawUrl":"https://raw.githubusercontent.com/memovai/mimiclaw/HEAD/skills/deploy/SKILL.md","title":"deploy","category":"anthropic-skill","format":"markdown","content":"---\nname: deploy\ndescription: Deploy MimiClaw firmware to an ESP32-S3 board. Covers prerequisites, configuration, build, flash, verification, and troubleshooting.\n---\n\n# Deploy MimiClaw\n\nEnd-to-end guide for deploying MimiClaw to an ESP32-S3 dev board.\n\n## Prerequisites\n\n### Hardware\n- ESP32-S3 dev board with **16 MB flash + 8 MB PSRAM** (e.g. Xiaozhi AI board, ~$5-10)\n- USB Type-C data cable (not charge-only)\n\n### Software\n- **ESP-IDF v5.5+** installed and working\n  ```bash\n  # Install: https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32s3/get-started/\n  # Verify:\n  idf.py --version   # should show >= 5.5\n  ```\n\n### Credentials (get these first)\n- **WiFi SSID + password** — the network the ESP32 will connect to\n- **Telegram Bot Token** — create via [@BotFather](https://t.me/BotFather) on Telegram\n- **Anthropic API Key** — from [console.anthropic.com](https://console.anthropic.com)\n- *(Optional)* Brave Search API key — from [brave.com/search/api](https://brave.com/search/api/)\n- *(Optional)* HTTP proxy host:port — if in China or restricted network\n\n## Step 1: Clone and Set Target\n\n```bash\ngit clone https://github.com/memovai/mimiclaw.git\ncd mimiclaw\nidf.py set-target esp32s3\n```\n\n## Step 2: Configure Secrets\n\n```bash\ncp main/mimi_secrets.h.example main/mimi_secrets.h\n```\n\nEdit `main/mimi_secrets.h` — fill in ALL required fields:\n\n```c\n#define MIMI_SECRET_WIFI_SSID       \"YourWiFiName\"        // REQUIRED\n#define MIMI_SECRET_WIFI_PASS       \"YourWiFiPassword\"     // REQUIRED\n#define MIMI_SECRET_TG_TOKEN        \"123456:ABC-DEF...\"    // REQUIRED\n#define MIMI_SECRET_API_KEY         \"sk-ant-api03-...\"     // REQUIRED\n#define MIMI_SECRET_MODEL           \"\"                     // optional, defaults to claude-opus-4-5\n#define MIMI_SECRET_SEARCH_KEY      \"\"                     // optional: Brave Search API key\n#define MIMI_SECRET_PROXY_HOST      \"\"                     // optional: e.g. \"192.168.1.83\"\n#define MIMI_SECRET_PROXY_PORT      \"\"                     // optional: e.g. \"7897\"\n```\n\n**Proxy setup (China users):**\nIf you need a proxy to reach Telegram/Anthropic APIs, set both `PROXY_HOST` and `PROXY_PORT`. The proxy machine must:\n- Be on the same LAN as the ESP32\n- Support HTTP CONNECT method (Clash, V2Ray, etc.)\n- Have \"Allow LAN connections\" enabled\n\n## Step 3: Build\n\n```bash\nidf.py fullclean && idf.py build\n```\n\n**IMPORTANT:** Always `fullclean` after changing `mimi_secrets.h` — the secrets are compiled into the binary.\n\nExpected output: `Project build complete. To flash, run: idf.py flash`\n\n### Build Troubleshooting\n\n| Error | Fix |\n|-------|-----|\n| `mimi_secrets.h: No such file` | Run `cp main/mimi_secrets.h.example main/mimi_secrets.h` |\n| `esp_websocket_client not found` | Run `idf.py fullclean` then `idf.py build` (managed component auto-downloads) |\n| `Toolchain not found` | Re-run ESP-IDF `install.sh` and `source export.sh` |\n| Build runs out of memory | Close other apps, ESP-IDF build needs ~2GB RAM |\n\n## Step 4: Find Serial Port\n\n```bash\n# macOS\nls /dev/cu.usb*\n\n# Linux\nls /dev/ttyACM* /dev/ttyUSB*\n```\n\nCommon ports:\n- macOS USB-OTG: `/dev/cu.usbmodem1101` or `/dev/cu.usbmodem11401`\n- Linux: `/dev/ttyACM0`\n\n**If no port shows up:**\n- Try a different USB cable (must be data cable, not charge-only)\n- Try a different USB port\n- Check if board has a power LED lit\n\n## Step 5: Flash\n\n```bash\nidf.py -p PORT flash monitor\n```\n\nReplace `PORT` with your actual port. Example:\n```bash\nidf.py -p /dev/cu.usbmodem1101 flash monitor\n```\n\nThe monitor shows boot logs. Look for:\n```\nI (xxx) mimi: MimiClaw - ESP32-S3 AI Agent\nI (xxx) mimi: PSRAM free: ~8000000 bytes\nI (xxx) wifi: WiFi connected: 192.168.x.x\nI (xxx) telegram: Telegram bot token loaded\nI (xxx) mimi: All services started!\n```\n\n**Exit monitor:** `Ctrl+]`\n\n## Step 6: Verify\n\n1. Open Telegram, find your bot (the one you created with BotFather)\n2. Send: `Hello`\n3. You should see \"mimi is working...\" followed by a response\n4. Send: `What time is it?` — tests the get_current_time tool\n5. Send: `Search for latest news about ESP32` — tests web_search (if Brave key set)\n\n## Post-Deploy: Runtime Configuration\n\nConnect via serial (`idf.py -p PORT monitor`) and use CLI commands:\n\n```\nmimi> config_show                  # see current config\nmimi> wifi_set NewSSID NewPass     # change WiFi\nmimi> set_tg_token 123456:ABC...   # change Telegram token\nmimi> set_api_key sk-ant-...       # change API key\nmimi> set_model claude-sonnet-4-5  # change model\nmimi> set_proxy 192.168.1.83 7897  # set proxy\nmimi> clear_proxy                  # remove proxy\nmimi> heap_info                    # check memory\nmimi> restart                      # reboot\n```\n\nCLI settings are stored in NVS flash and take priority over build-time values.\n\n## OTA Update (over WiFi)\n\nAfter initial USB flash, future updates can be done over WiFi:\n\n1. Build new firmware: `idf.py build`\n2. Host the `.bin` file on a local HTTP server:\n   ```bash\n   cd build && python3 -m http.server 8080\n   ```\n3. Send to your bot on Telegram or use the OTA CLI command with the URL:\n   ```\n   http://YOUR_PC_IP:8080/mimiclaw.bin\n   ```\n\n## Flash Layout\n\n```\n16 MB Flash:\n├── 0x009000  NVS (24 KB) — runtime config\n├── 0x020000  OTA_0 (2 MB) — active firmware\n├── 0x220000  OTA_1 (2 MB) — update slot\n├── 0x420000  SPIFFS (12 MB) — memory, sessions, config\n└── 0xFF0000  Coredump (64 KB)\n```\n\n## Common Issues\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| No WiFi connection | Wrong SSID/password | Check `mimi_secrets.h`, `idf.py fullclean && build && flash` |\n| \"No bot token\" | Empty TG token | Set via `mimi_secrets.h` or CLI `set_tg_token` |\n| Bot doesn't respond | API key invalid | Check key at console.anthropic.com, set via CLI |\n| \"Markdown send failed\" | Normal with Markdown mode | Non-critical, falls back to plain text |\n| Proxy timeout | Proxy not reachable | Ensure same LAN, proxy allows LAN connections |\n| SPIFFS mount failed | First boot or corruption | Normal on first boot (auto-formats) |\n| Port busy/not found | Wrong port or cable | Try different USB port/cable, check `ls /dev/cu.usb*` |\n| Boot loop | Firmware crash | Flash via USB again, check serial logs for crash info |\n","frontmatter":{"name":"deploy","description":"Deploy MimiClaw firmware to an ESP32-S3 board. Covers prerequisites, configuration, build, flash, verification, and troubleshooting."},"isInternal":false,"tokens":1782,"sizeBytes":6245},{"name":"daily-briefing.md","path":"spiffs_data/skills/daily-briefing.md","rawUrl":"https://raw.githubusercontent.com/memovai/mimiclaw/HEAD/spiffs_data/skills/daily-briefing.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Daily Briefing\n\nCompile a personalized daily briefing for the user.\n\n## When to use\nWhen the user asks for a daily briefing, morning update, or \"what's new today\".\nAlso useful as a heartbeat/cron task.\n\n## How to use\n1. Use get_current_time for today's date\n2. Read /spiffs/memory/MEMORY.md for user preferences and context\n3. Read today's daily note if it exists\n4. Use web_search for relevant news based on user interests\n5. Compile a concise briefing covering:\n   - Date and time\n   - Weather (if location known from USER.md)\n   - Relevant news/updates based on user interests\n   - Any pending tasks from memory\n   - Any scheduled cron jobs\n\n## Format\nKeep it brief — 5-10 bullet points max. Use the user's preferred language.\n","isInternal":false,"tokens":177,"sizeBytes":734},{"name":"gpio-control.md","path":"spiffs_data/skills/gpio-control.md","rawUrl":"https://raw.githubusercontent.com/memovai/mimiclaw/HEAD/spiffs_data/skills/gpio-control.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# GPIO Control\n\nControl and monitor GPIO pins on the ESP32-S3 for digital I/O.\n\n## When to use\nWhen the user asks to:\n- Turn on/off LEDs, relays, or other outputs\n- Check switch states, button presses, or sensor readings\n- Confirm digital I/O status (switch confirmation)\n- Get an overview of all GPIO pin states\n\n## How to use\n1. To **read a switch/sensor**: use gpio_read with the pin number\n   - Returns HIGH (1) or LOW (0)\n   - HIGH typically means switch is ON / circuit closed\n   - LOW typically means switch is OFF / circuit open\n2. To **set an output**: use gpio_write with pin and state (1=HIGH, 0=LOW)\n3. To **scan all pins**: use gpio_read_all for a full status overview\n4. For **switch confirmation**: read the pin, report state, optionally toggle and re-read to verify\n\n## Pin safety\n- Only pins within the allowed range can be accessed\n- ESP32 flash pins (6-11) are always blocked\n- If a pin is rejected, suggest an alternative within the allowed range\n\n## Example\nUser: \"Check if the switch on pin 4 is on\"\n→ gpio_read {\"pin\": 4}\n→ \"Pin 4 = HIGH\"\n→ \"The switch on pin 4 is currently ON (HIGH).\"\n\nUser: \"Turn on the relay on pin 5\"\n→ gpio_write {\"pin\": 5, \"state\": 1}\n→ \"Pin 5 set to HIGH\"\n→ gpio_read {\"pin\": 5}\n→ \"Pin 5 = HIGH\"\n→ \"Relay on pin 5 is now ON. Confirmed HIGH.\"\n","isInternal":false,"tokens":362,"sizeBytes":1306},{"name":"skill-creator.md","path":"spiffs_data/skills/skill-creator.md","rawUrl":"https://raw.githubusercontent.com/memovai/mimiclaw/HEAD/spiffs_data/skills/skill-creator.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill Creator\n\nCreate new skills for MimiClaw.\n\n## When to use\nWhen the user asks to create a new skill, teach the bot something, or add a new capability.\n\n## How to create a skill\n1. Choose a short, descriptive name (lowercase, hyphens ok)\n2. Write a SKILL.md file with this structure:\n   - `# Title` — clear name\n   - Brief description paragraph\n   - `## When to use` — trigger conditions\n   - `## How to use` — step-by-step instructions\n   - `## Example` — concrete example (optional but helpful)\n3. Save to `/spiffs/skills/<name>.md` using write_file\n4. The skill will be automatically available after the next conversation\n\n## Best practices\n- Keep skills concise — the context window is limited\n- Focus on WHAT to do, not HOW (the agent is smart)\n- Include specific tool calls the agent should use\n- Test by asking the agent to use the new skill\n\n## Example\nTo create a \"translate\" skill:\nwrite_file path=\"/spiffs/skills/translate.md\" content=\"# Translate\\n\\nTranslate text between languages.\\n\\n## When to use\\nWhen the user asks to translate text.\\n\\n## How to use\\n1. Identify source and target languages\\n2. Translate directly using your language knowledge\\n3. For specialized terms, use web_search to verify\\n\"\n","isInternal":false,"tokens":299,"sizeBytes":1234},{"name":"weather.md","path":"spiffs_data/skills/weather.md","rawUrl":"https://raw.githubusercontent.com/memovai/mimiclaw/HEAD/spiffs_data/skills/weather.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Weather\n\nGet current weather and forecasts using web_search.\n\n## When to use\nWhen the user asks about weather, temperature, or forecasts.\n\n## How to use\n1. Use get_current_time to know the current date\n2. Use web_search with a query like \"weather in [city] today\"\n3. Extract temperature, conditions, and forecast from results\n4. Present in a concise, friendly format\n\n## Example\nUser: \"What's the weather in Tokyo?\"\n→ get_current_time\n→ web_search \"weather Tokyo today February 2026\"\n→ \"Tokyo: 8°C, partly cloudy. High 12°C, low 4°C. Light wind from the north.\"\n","isInternal":false,"tokens":143,"sizeBytes":573}],"systemPromptSnippet":"<yakaai_skills repo=\"memovai/mimiclaw\">\n<!-- File: docs/ARCHITECTURE.md (Tokens: ~4108 | Category: project-spec) -->\n# MimiClaw Architecture\n\n> ESP32-S3 AI Agent firmware — C/FreeRTOS implementation running on bare metal (no Linux).\n\n---\n\n## System Overview\n\n```\nTelegram App (User)\n    │\n    │  HTTPS Long Polling\n    │\n    ▼\n┌──────────────────────────────────────────────────┐\n│               ESP32-S3 (MimiClaw)                │\n│                                                  │\n│   ┌─────────────┐       ┌──────────────────┐     │\n│   │  Telegram    │──────▶│   Inbound Queue  │     │\n│   │  Poller      │       └────────┬─────────┘     │\n│   │  (Core 0)    │               │                │\n│   └─────────────┘               ▼                │\n│                     ┌────────────────────────┐    │\n│   ┌─────────────┐  │     Agent Loop          │    │\n│   │  WebSocket   │─▶│     (Core 1)           │    │\n│   │  Server      │  │                        │    │\n│   │  (:18789)    │  │  Context ──▶ LLM Proxy │    │\n│   └─────────────┘  │  Builder      (HTTPS)   │    │\n│                     │       ▲          │      │    │\n│   ┌─────────────┐  │       │     tool_use?   │    │\n│   │  Serial CLI  │  │       │          ▼      │    │\n│   │  (Core 0)    │  │  Tool Results ◀─ Tools  │    │\n│   └─────────────┘  │              (web_search)│    │\n│                     └──────────┬─────────────┘    │\n│                                │                  │\n│                         ┌──────▼───────┐          │\n│                         │ Outbound Queue│          │\n│                         └──────┬───────┘          │\n│                                │                  │\n│                         ┌──────▼───────┐          │\n│                         │  Outbound    │          │\n│                         │  Dispatch    │          │\n│                         │  (Core 0)    │          │\n│                         └──┬────────┬──┘          │\n│                            │        │             │\n│                     Telegram    WebSocket          │\n│                     sendMessage  send              │\n│                                                   │\n│   ┌──────────────────────────────────────────┐    │\n│   │  SPIFFS (12 MB)                          │    │\n│   │  /spiffs/config/  SOUL.md, USER.md       │    │\n│   │  /spiffs/memory/  MEMORY.md, YYYY-MM-DD  │    │\n│   │  /spiffs/sessions/ tg_<chat_id>.jsonl    │    │\n│   └──────────────────────────────────────────┘    │\n└───────────────────────────────────────────────────┘\n         │\n         │  Anthropic Messages API (HTTPS)\n         │  + Brave Search API (HTTPS)\n         ▼\n   ┌───────────┐   ┌──────────────┐\n   │ Claude API │   │ Brave Search │\n   └───────────┘   └──────────────┘\n```\n\n---\n\n## Data Flow\n\n```\n1. User sends message on Telegram (or WebSocket)\n2. Channel poller receives message, wraps in mimi_msg_t\n3. Message pushed to Inbound Queue (FreeRTOS xQueue)\n4. Agent Loop (Core 1) pops message:\n   a. Load session history from SPIFFS (JSONL)\n   b. Build system prompt (SOUL.md + USER.md + MEMORY.md + recent notes + tool guidance)\n   c. Build cJSON messages array (history + current message)\n   d. ReAct loop (max 10 iterations):\n      i.   Call Claude API via HTTPS (non-streaming, with tools array)\n      ii.  Parse JSON response → text blocks + tool_use blocks\n      iii. If stop_reason == \"tool_use\":\n           - Execute each tool (e.g. web_search → Brave Search API)\n           - Append assistant content + tool_result to messages\n           - Continue loop\n      iv.  If stop_reason == \"end_turn\": break with final text\n   e. Save user message + final assistant text to session file\n   f. Push response to Outbound Queue\n5. Outbound Dispatch (Core 0) pops response:\n   a. Route by channel field (\"telegram\" → sendMessage, \"websocket\" → WS frame)\n6. User receives reply\n```\n\n---\n\n## Module Map\n\n```\nmain/\n├── mimi.c                  Entry point — app_main() orchestrates init + startup\n├── mimi_config.h           All compile-time constants + build-time secrets include\n├── mimi_secrets.h          Build-time credentials (gitignored, highest priority)\n├── mimi_secrets.h.example  Template for mimi_secrets.h\n│\n├── bus/\n│   ├── message_bus.h       mimi_msg_t struct, queue API\n│   └── message_bus.c       Two FreeRTOS queues: inbound + outbound\n│\n├── wifi/\n│   ├── wifi_manager.h      WiFi STA lifecycle API\n│   └── wifi_manager.c      Event handler, exponential backoff\n│\n├── telegram/\n│   ├── telegram_bot.h      Bot init/start, send_message API\n│   └── telegram_bot.c      Long polling loop, JSON parsing, message splitting\n│\n├── llm/\n│   ├── llm_proxy.h         llm_chat() + llm_chat_tools() API, tool_use types\n│   └── llm_proxy.c         Anthropic Messages API (non-streaming), tool_use parsing\n│\n├── agent/\n│   ├── agent_loop.h        Agent task init/start\n│   ├── agent_loop.c        ReAct loop: LLM call → tool execution → repeat\n│   ├── context_builder.h   System prompt + messages builder API\n│   └── context_builder.c   Reads bootstrap files + memory + tool guidance\n│\n├── tools/\n│   ├── tool_registry.h     Tool definition struct, register/dispatch API\n│   ├── tool_registry.c     Tool registration, JSON schema builder, dispatch by name\n│   ├── tool_web_search.h   Web search tool API\n│   └── tool_web_search.c   Brave Search API via HTTPS (direct + proxy)\n│\n├── memory/\n│   ├── memory_store.h      Long-term + daily memory API\n│   ├── memory_store.c      MEMORY.md read/write, daily .md append/read\n│   ├── session_mgr.h       Per-chat session API\n│   └── session_mgr.c       JSONL session files, ring buffer history\n│\n├── gateway/\n│   ├── ws_server.h         WebSocket server API\n│   └── ws_server.c         ESP HTTP server with WS upgrade, client tracking\n│\n├── proxy/\n│   ├── http_proxy.h        Proxy connection API\n│   └── http_proxy.c        HTTP CONNECT tunnel + TLS via esp_tls\n│\n├── cli/\n│   ├── serial_cli.h        CLI init API\n│   └── serial_cli.c        esp_console REPL with debug/maintenance commands\n│\n└── ota/\n    ├── ota_manager.h       OTA update API\n    └── ota_manager.c       esp_https_ota wrapper\n```\n\n---\n\n## FreeRTOS Task Layout\n\n| Task               | Core | Priority | Stack  | Description                          |\n|--------------------|------|----------|--------|--------------------------------------|\n| `tg_poll`          | 0    | 5        | 12 KB  | Telegram long polling (30s timeout)  |\n| `agent_loop`       | 1    | 6        | 12 KB  | Message processing + Claude API call |\n| `outbound`         | 0    | 5        | 8 KB   | Route responses to Telegram / WS     |\n| `serial_cli`       | 0    | 3        | 4 KB   | USB serial console REPL              |\n| httpd (internal)   | 0    | 5        | —      | WebSocket server (esp_http_server)   |\n| wifi_event (IDF)   | 0    | 8        | —      | WiFi event handling (ESP-IDF)        |\n\n**Core allocation strategy**: Core 0 handles I/O (network, serial, WiFi). Core 1 is dedicated to the agent loop (CPU-bound JSON building + waiting on HTTPS).\n\n---\n\n## Memory Budget\n\n| Purpose                            | Location       | Size     |\n|------------------------------------|----------------|----------|\n| FreeRTOS task stacks               | Internal SRAM  | ~40 KB   |\n| WiFi buffers                       | Internal SRAM  | ~30 KB   |\n| TLS connections x2 (Telegram + Claude) | PSRAM      | ~120 KB  |\n| JSON parse buffers                 | PSRAM          | ~32 KB   |\n| Session history cache              | PSRAM          | ~32 KB   |\n| System prompt buffer               | PSRAM          | ~16 KB   |\n| LLM response stream buffer         | PSRAM          | ~32 KB   |\n| Remaining available                | PSRAM          | ~7.7 MB  |\n\nLarge buffers (32 KB+) are allocated from PSRAM via `heap_caps_calloc(1, size, MALLOC_CAP_SPIRAM)`.\n\n---\n\n## Flash Partition Layout\n\n```\nOffset      Size      Name        Purpose\n─────────────────────────────────────────────\n0x009000    24 KB     nvs         ESP-IDF internal use (WiFi calibration etc.)\n0x00F000     8 KB     otadata     OTA boot state\n0x011000     4 KB     phy_init    WiFi PHY calibration\n0x020000     2 MB     ota_0       Firmware slot A\n0x220000     2 MB     ota_1       Firmware slot B\n0x420000    12 MB     spiffs      Markdown memory, sessions, config\n0xFF0000    64 KB     coredump    Crash dump storage\n```\n\nTotal: 16 MB flash.\n\n---\n\n## Storage Layout (SPIFFS)\n\nSPIFFS is a flat filesystem — no real directories. Files use path-like names.\n\n```\n/spiffs/config/SOUL.md          AI personality definition\n/spiffs/config/USER.md          User profile\n/spiffs/memory/MEMORY.md        Long-term persistent memory\n/spiffs/memory/2026-02-05.md    Daily notes (one file per day)\n/spiffs/sessions/tg_12345.jsonl Session history (one file per Telegram chat)\n```\n\nSession files are JSONL (one JSON object per line):\n```json\n{\"role\":\"user\",\"content\":\"Hello\",\"ts\":1738764800}\n{\"role\":\"assistant\",\"content\":\"Hi there!\",\"ts\":1738764802}\n```\n\n---\n\n## Configuration\n\nAll configuration is done exclusively through `mimi_secrets.h` at build time. There is no runtime configuration — changing any setting requires `idf.py fullclean && idf.py build`.\n\n| Define                       | Description                             |\n|------------------------------|-----------------------------------------|\n| `MIMI_SECRET_WIFI_SSID`     | WiFi SSID                               |\n| `MIMI_SECRET_WIFI_PASS`     | WiFi password                           |\n| `MIMI_SECRET_TG_TOKEN`      | Telegram Bot API token                  |\n| `MIMI_SECRET_API_KEY`       | Anthropic API key                       |\n| `MIMI_SECRET_MODEL`         | Model ID (default: claude-opus-4-6)     |\n| `MIMI_SECRET_PROXY_HOST`    | HTTP proxy hostname/IP (optional)       |\n| `MIMI_SECRET_PROXY_PORT`    | HTTP proxy port (optional)              |\n| `MIMI_SECRET_SEARCH_KEY`    | Brave Search API key (optional)         |\n\nNVS is still initialized (required by ESP-IDF WiFi internals) but is not used for application configuration.\n\n---\n\n## Message Bus Protocol\n\nThe internal message bus uses two FreeRTOS queues carrying `mimi_msg_t`:\n\n```c\ntypedef struct {\n    char channel[16];   // \"telegram\", \"websocket\", \"cli\"\n    char chat_id[32];   // Telegram chat ID or WS client ID\n    char *content;      // Heap-allocated text (ownership transferred)\n} mimi_msg_t;\n```\n\n- **Inbound queue**: channels → agent loop (depth: 8)\n- **Outbound queue**: agent loop → dispatch → channels (depth: 8)\n- Content string ownership is transferred on push; receiver must `free()`.\n\n---\n\n## WebSocket Protocol\n\nPort: **18789**. Max clients: **4**.\n\n**Client → Server:**\n```json\n{\"type\": \"message\", \"content\": \"Hello\", \"chat_id\": \"ws_client1\"}\n```\n\n**Server → Client:**\n```json\n{\"type\": \"response\", \"content\": \"Hi there!\", \"chat_id\": \"ws_client1\"}\n```\n\nClient `chat_id` is auto-assigned on connection (`ws_<fd>`) but can be overridden in the first message.\n\n---\n\n## Claude API Integration\n\nEndpoint: `POST https://api.anthropic.com/v1/messages`\n\nRequest format (Anthropic-native, non-streaming, with tools):\n```json\n{\n  \"model\": \"claude-opus-4-6\",\n  \"max_tokens\": 4096,\n  \"system\": \"<system prompt>\",\n  \"tools\": [\n    {\n      \"name\": \"web_search\",\n      \"description\": \"Search the web for current information.\",\n      \"input_schema\": {\"type\": \"object\", \"properties\": {\"query\": {\"type\": \"string\"}}, \"required\": [\"query\"]}\n    }\n  ],\n  \"messages\": [\n    {\"role\": \"user\", \"content\": \"Hello\"},\n    {\"role\": \"assistant\", \"content\": \"Hi!\"},\n    {\"role\": \"user\", \"content\": \"What's the weather today?\"}\n  ]\n}\n```\n\nKey difference from OpenAI: `system` is a top-level field, not inside the `messages` array.\n\nNon-streaming JSON response:\n```json\n{\n  \"id\": \"msg_xxx\",\n  \"type\": \"message\",\n  \"role\": \"assistant\",\n  \"content\": [\n    {\"type\": \"text\", \"text\": \"Let me search for that.\"},\n    {\"type\": \"tool_use\", \"id\": \"toolu_xxx\", \"name\": \"web_search\", \"input\": {\"query\": \"weather today\"}}\n  ],\n  \"stop_reason\": \"tool_use\"\n}\n```\n\nWhen `stop_reason` is `\"tool_use\"`, the agent loop executes each tool and sends results back:\n```json\n{\"role\": \"assistant\", \"content\": [<text + tool_use blocks>]}\n{\"role\": \"user\", \"content\": [{\"type\": \"tool_result\", \"tool_use_id\": \"toolu_xxx\", \"content\": \"...\"}]}\n```\n\nThe loop repeats until `stop_reason` is `\"end_turn\"` (max 10 iterations).\n\n---\n\n## Startup Sequence\n\n```\napp_main()\n  ├── init_nvs()                    NVS flash init (erase if corrupted)\n  ├── esp_event_loop_create_default()\n  ├── init_spiffs()                 Mount SPIFFS at /spiffs\n  ├── message_bus_init()            Create inbound + outbound queues\n  ├── memory_store_init()           Verify SPIFFS paths\n  ├── session_mgr_init()\n  ├── wifi_manager_init()           Init WiFi STA mode + event handlers\n  ├── http_proxy_init()             Load proxy config from build-time secrets\n  ├── telegram_bot_init()           Load bot token from build-time secrets\n  ├── llm_proxy_init()              Load API key + model from build-time secrets\n  ├── tool_registry_init()          Register tools, build tools JSON\n  ├── agent_loop_init()\n  ├── serial_cli_init()             Start REPL (works without WiFi)\n  │\n  ├── wifi_manager_start()          Connect using build-time credentials\n  │   └── wifi_manager_wait_connected(30s)\n  │\n  └── [if WiFi connected]\n      ├── telegram_bot_start()      Launch tg_poll task (Core 0)\n      ├── agent_loop_start()        Launch agent_loop task (Core 1)\n      ├── ws_server_start()         Start httpd on port 18789\n      └── outbound_dispatch task    Launch outbound task (Core 0)\n```\n\nIf WiFi credentials are missing or connection times out, the CLI remains available for diagnostics.\n\n---\n\n## Serial CLI Commands\n\nThe CLI provides debug and maintenance commands only. All configuration is done via `mimi_secrets.h`.\n\n| Command                        | Description                          |\n|--------------------------------|--------------------------------------|\n| `wifi_status`                  | Show connection status and IP        |\n| `memory_read`                  | Print MEMORY.md contents             |\n| `memory_write <CONTENT>`       | Overwrite MEMORY.md                  |\n| `session_list`                 | List all session files               |\n| `session_clear <CHAT_ID>`      | Delete a session file                |\n| `heap_info`                    | Show internal + PSRAM free bytes     |\n| `restart`                      | Reboot the device                    |\n| `help`                         | List all available commands           |\n\n---\n\n## Nanobot Reference Mapping\n\n| Nanobot Module              | MimiClaw Equivalent            | Notes                        |\n|-----------------------------|--------------------------------|------------------------------|\n| `agent/loop.py`             | `agent/agent_loop.c`           | ReAct loop with tool use     |\n| `agent/context.py`          | `agent/context_builder.c`      | Loads SOUL.md + USER.md + memory + tool guidance |\n| `agent/memory.py`           | `memory/memory_store.c`        | MEMORY.md + daily notes      |\n| `session/manager.py`        | `memory/session_mgr.c`         | JSONL per chat, ring buffer  |\n| `channels/telegram.py`      | `telegram/telegram_bot.c`      | Raw HTTP, no python-telegram-bot |\n| `bus/events.py` + `queue.py`| `bus/message_bus.c`            | FreeRTOS queues vs asyncio   |\n| `providers/litellm_provider.py` | `llm/llm_proxy.c`         | Direct Anthropic API only    |\n| `config/schema.py`          | `mimi_config.h` + `mimi_secrets.h` | Build-time secrets only  |\n| `cli/commands.py`           | `cli/serial_cli.c`             | esp_console REPL             |\n| `agent/tools/*`             | `tools/tool_registry.c` + `tool_web_search.c` | web_search via Brave API |\n| `agent/subagent.py`         | *(not yet implemented)*        | See TODO.md                  |\n| `agent/skills.py`           | *(not yet implemented)*        | See TODO.md                  |\n| `cron/service.py`           | *(not yet implemented)*        | See TODO.md                  |\n| `heartbeat/service.py`      | *(not yet implemented)*        | See TODO.md                  |\n\n<!-- File: skills/deploy/SKILL.md (Tokens: ~1782 | Category: anthropic-skill) -->\n---\nname: deploy\ndescription: Deploy MimiClaw firmware to an ESP32-S3 board. Covers prerequisites, configuration, build, flash, verification, and troubleshooting.\n---\n\n# Deploy MimiClaw\n\nEnd-to-end guide for deploying MimiClaw to an ESP32-S3 dev board.\n\n## Prerequisites\n\n### Hardware\n- ESP32-S3 dev board with **16 MB flash + 8 MB PSRAM** (e.g. Xiaozhi AI board, ~$5-10)\n- USB Type-C data cable (not charge-only)\n\n### Software\n- **ESP-IDF v5.5+** installed and working\n  ```bash\n  # Install: https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32s3/get-started/\n  # Verify:\n  idf.py --version   # should show >= 5.5\n  ```\n\n### Credentials (get these first)\n- **WiFi SSID + password** — the network the ESP32 will connect to\n- **Telegram Bot Token** — create via [@BotFather](https://t.me/BotFather) on Telegram\n- **Anthropic API Key** — from [console.anthropic.com](https://console.anthropic.com)\n- *(Optional)* Brave Search API key — from [brave.com/search/api](https://brave.com/search/api/)\n- *(Optional)* HTTP proxy host:port — if in China or restricted network\n\n## Step 1: Clone and Set Target\n\n```bash\ngit clone https://github.com/memovai/mimiclaw.git\ncd mimiclaw\nidf.py set-target esp32s3\n```\n\n## Step 2: Configure Secrets\n\n```bash\ncp main/mimi_secrets.h.example main/mimi_secrets.h\n```\n\nEdit `main/mimi_secrets.h` — fill in ALL required fields:\n\n```c\n#define MIMI_SECRET_WIFI_SSID       \"YourWiFiName\"        // REQUIRED\n#define MIMI_SECRET_WIFI_PASS       \"YourWiFiPassword\"     // REQUIRED\n#define MIMI_SECRET_TG_TOKEN        \"123456:ABC-DEF...\"    // REQUIRED\n#define MIMI_SECRET_API_KEY         \"sk-ant-api03-...\"     // REQUIRED\n#define MIMI_SECRET_MODEL           \"\"                     // optional, defaults to claude-opus-4-5\n#define MIMI_SECRET_SEARCH_KEY      \"\"                     // optional: Brave Search API key\n#define MIMI_SECRET_PROXY_HOST      \"\"                     // optional: e.g. \"192.168.1.83\"\n#define MIMI_SECRET_PROXY_PORT      \"\"                     // optional: e.g. \"7897\"\n```\n\n**Proxy setup (China users):**\nIf you need a proxy to reach Telegram/Anthropic APIs, set both `PROXY_HOST` and `PROXY_PORT`. The proxy machine must:\n- Be on the same LAN as the ESP32\n- Support HTTP CONNECT method (Clash, V2Ray, etc.)\n- Have \"Allow LAN connections\" enabled\n\n## Step 3: Build\n\n```bash\nidf.py fullclean && idf.py build\n```\n\n**IMPORTANT:** Always `fullclean` after changing `mimi_secrets.h` — the secrets are compiled into the binary.\n\nExpected output: `Project build complete. To flash, run: idf.py flash`\n\n### Build Troubleshooting\n\n| Error | Fix |\n|-------|-----|\n| `mimi_secrets.h: No such file` | Run `cp main/mimi_secrets.h.example main/mimi_secrets.h` |\n| `esp_websocket_client not found` | Run `idf.py fullclean` then `idf.py build` (managed component auto-downloads) |\n| `Toolchain not found` | Re-run ESP-IDF `install.sh` and `source export.sh` |\n| Build runs out of memory | Close other apps, ESP-IDF build needs ~2GB RAM |\n\n## Step 4: Find Serial Port\n\n```bash\n# macOS\nls /dev/cu.usb*\n\n# Linux\nls /dev/ttyACM* /dev/ttyUSB*\n```\n\nCommon ports:\n- macOS USB-OTG: `/dev/cu.usbmodem1101` or `/dev/cu.usbmodem11401`\n- Linux: `/dev/ttyACM0`\n\n**If no port shows up:**\n- Try a different USB cable (must be data cable, not charge-only)\n- Try a different USB port\n- Check if board has a power LED lit\n\n## Step 5: Flash\n\n```bash\nidf.py -p PORT flash monitor\n```\n\nReplace `PORT` with your actual port. Example:\n```bash\nidf.py -p /dev/cu.usbmodem1101 flash monitor\n```\n\nThe monitor shows boot logs. Look for:\n```\nI (xxx) mimi: MimiClaw - ESP32-S3 AI Agent\nI (xxx) mimi: PSRAM free: ~8000000 bytes\nI (xxx) wifi: WiFi connected: 192.168.x.x\nI (xxx) telegram: Telegram bot token loaded\nI (xxx) mimi: All services started!\n```\n\n**Exit monitor:** `Ctrl+]`\n\n## Step 6: Verify\n\n1. Open Telegram, find your bot (the one you created with BotFather)\n2. Send: `Hello`\n3. You should see \"mimi is working...\" followed by a response\n4. Send: `What time is it?` — tests the get_current_time tool\n5. Send: `Search for latest news about ESP32` — tests web_search (if Brave key set)\n\n## Post-Deploy: Runtime Configuration\n\nConnect via serial (`idf.py -p PORT monitor`) and use CLI commands:\n\n```\nmimi> config_show                  # see current config\nmimi> wifi_set NewSSID NewPass     # change WiFi\nmimi> set_tg_token 123456:ABC...   # change Telegram token\nmimi> set_api_key sk-ant-...       # change API key\nmimi> set_model claude-sonnet-4-5  # change model\nmimi> set_proxy 192.168.1.83 7897  # set proxy\nmimi> clear_proxy                  # remove proxy\nmimi> heap_info                    # check memory\nmimi> restart                      # reboot\n```\n\nCLI settings are stored in NVS flash and take priority over build-time values.\n\n## OTA Update (over WiFi)\n\nAfter initial USB flash, future updates can be done over WiFi:\n\n1. Build new firmware: `idf.py build`\n2. Host the `.bin` file on a local HTTP server:\n   ```bash\n   cd build && python3 -m http.server 8080\n   ```\n3. Send to your bot on Telegram or use the OTA CLI command with the URL:\n   ```\n   http://YOUR_PC_IP:8080/mimiclaw.bin\n   ```\n\n## Flash Layout\n\n```\n16 MB Flash:\n├── 0x009000  NVS (24 KB) — runtime config\n├── 0x020000  OTA_0 (2 MB) — active firmware\n├── 0x220000  OTA_1 (2 MB) — update slot\n├── 0x420000  SPIFFS (12 MB) — memory, sessions, config\n└── 0xFF0000  Coredump (64 KB)\n```\n\n## Common Issues\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| No WiFi connection | Wrong SSID/password | Check `mimi_secrets.h`, `idf.py fullclean && build && flash` |\n| \"No bot token\" | Empty TG token | Set via `mimi_secrets.h` or CLI `set_tg_token` |\n| Bot doesn't respond | API key invalid | Check key at console.anthropic.com, set via CLI |\n| \"Markdown send failed\" | Normal with Markdown mode | Non-critical, falls back to plain text |\n| Proxy timeout | Proxy not reachable | Ensure same LAN, proxy allows LAN connections |\n| SPIFFS mount failed | First boot or corruption | Normal on first boot (auto-formats) |\n| Port busy/not found | Wrong port or cable | Try different USB port/cable, check `ls /dev/cu.usb*` |\n| Boot loop | Firmware crash | Flash via USB again, check serial logs for crash info |\n\n<!-- File: spiffs_data/skills/daily-briefing.md (Tokens: ~177 | Category: anthropic-skill) -->\n# Daily Briefing\n\nCompile a personalized daily briefing for the user.\n\n## When to use\nWhen the user asks for a daily briefing, morning update, or \"what's new today\".\nAlso useful as a heartbeat/cron task.\n\n## How to use\n1. Use get_current_time for today's date\n2. Read /spiffs/memory/MEMORY.md for user preferences and context\n3. Read today's daily note if it exists\n4. Use web_search for relevant news based on user interests\n5. Compile a concise briefing covering:\n   - Date and time\n   - Weather (if location known from USER.md)\n   - Relevant news/updates based on user interests\n   - Any pending tasks from memory\n   - Any scheduled cron jobs\n\n## Format\nKeep it brief — 5-10 bullet points max. Use the user's preferred language.\n\n<!-- File: spiffs_data/skills/gpio-control.md (Tokens: ~362 | Category: anthropic-skill) -->\n# GPIO Control\n\nControl and monitor GPIO pins on the ESP32-S3 for digital I/O.\n\n## When to use\nWhen the user asks to:\n- Turn on/off LEDs, relays, or other outputs\n- Check switch states, button presses, or sensor readings\n- Confirm digital I/O status (switch confirmation)\n- Get an overview of all GPIO pin states\n\n## How to use\n1. To **read a switch/sensor**: use gpio_read with the pin number\n   - Returns HIGH (1) or LOW (0)\n   - HIGH typically means switch is ON / circuit closed\n   - LOW typically means switch is OFF / circuit open\n2. To **set an output**: use gpio_write with pin and state (1=HIGH, 0=LOW)\n3. To **scan all pins**: use gpio_read_all for a full status overview\n4. For **switch confirmation**: read the pin, report state, optionally toggle and re-read to verify\n\n## Pin safety\n- Only pins within the allowed range can be accessed\n- ESP32 flash pins (6-11) are always blocked\n- If a pin is rejected, suggest an alternative within the allowed range\n\n## Example\nUser: \"Check if the switch on pin 4 is on\"\n→ gpio_read {\"pin\": 4}\n→ \"Pin 4 = HIGH\"\n→ \"The switch on pin 4 is currently ON (HIGH).\"\n\nUser: \"Turn on the relay on pin 5\"\n→ gpio_write {\"pin\": 5, \"state\": 1}\n→ \"Pin 5 set to HIGH\"\n→ gpio_read {\"pin\": 5}\n→ \"Pin 5 = HIGH\"\n→ \"Relay on pin 5 is now ON. Confirmed HIGH.\"\n\n<!-- File: spiffs_data/skills/skill-creator.md (Tokens: ~299 | Category: anthropic-skill) -->\n# Skill Creator\n\nCreate new skills for MimiClaw.\n\n## When to use\nWhen the user asks to create a new skill, teach the bot something, or add a new capability.\n\n## How to create a skill\n1. Choose a short, descriptive name (lowercase, hyphens ok)\n2. Write a SKILL.md file with this structure:\n   - `# Title` — clear name\n   - Brief description paragraph\n   - `## When to use` — trigger conditions\n   - `## How to use` — step-by-step instructions\n   - `## Example` — concrete example (optional but helpful)\n3. Save to `/spiffs/skills/<name>.md` using write_file\n4. The skill will be automatically available after the next conversation\n\n## Best practices\n- Keep skills concise — the context window is limited\n- Focus on WHAT to do, not HOW (the agent is smart)\n- Include specific tool calls the agent should use\n- Test by asking the agent to use the new skill\n\n## Example\nTo create a \"translate\" skill:\nwrite_file path=\"/spiffs/skills/translate.md\" content=\"# Translate\\n\\nTranslate text between languages.\\n\\n## When to use\\nWhen the user asks to translate text.\\n\\n## How to use\\n1. Identify source and target languages\\n2. Translate directly using your language knowledge\\n3. For specialized terms, use web_search to verify\\n\"\n\n<!-- File: spiffs_data/skills/weather.md (Tokens: ~143 | Category: anthropic-skill) -->\n# Weather\n\nGet current weather and forecasts using web_search.\n\n## When to use\nWhen the user asks about weather, temperature, or forecasts.\n\n## How to use\n1. Use get_current_time to know the current date\n2. Use web_search with a query like \"weather in [city] today\"\n3. Extract temperature, conditions, and forecast from results\n4. Present in a concise, friendly format\n\n## Example\nUser: \"What's the weather in Tokyo?\"\n→ get_current_time\n→ web_search \"weather Tokyo today February 2026\"\n→ \"Tokyo: 8°C, partly cloudy. High 12°C, low 4°C. Light wind from the north.\"\n</yakaai_skills>"}