Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ tasks/
# Added by goreleaser init:
dist/
IMPROVEMENTS.md
CULL_CANDIDATES.md
picoclaw-latest/
35 changes: 27 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ firmware/overlay/ ← Files baked into Luckfox firmware image
|-----------|-------|--------|
| Total RAM | 64MB DDR2 | Only ~33MB usable after kernel |
| Usable RAM | ~33MB | Gateway uses 10-14MB, leaves 2-12MB free |
| GOMEMLIMIT | 8MiB | Baked into binary via `applyPerformanceDefaults()` |
| GOMEMLIMIT | 24MiB | Set in init script; binary default is 8MiB but overridden |
| GOGC | 20 | Aggressive GC to prevent RSS growth |
| Flash | 128MB SPI NAND | Limited storage, no swap |
| CPU | ARM Cortex-A7 (RV1103) | Single core, GOARM=7 |
Expand All @@ -39,12 +39,12 @@ firmware/overlay/ ← Files baked into Luckfox firmware image

### What We Changed
- **Onboarding**: Simplified to OpenRouter only (was 7 provider choices)
- **Performance**: Baked `GOGC=20` + `GOMEMLIMIT=8MiB` into binary
- **Performance**: Baked `GOGC=20` into binary; init script sets `GOMEMLIMIT=24MiB`
- **CLI**: Added `luckyclaw stop`, `restart`, `gateway -b` (background)
- **Init script**: Auto-starts gateway on boot with OOM protection
- **SSH banner**: Shows ASCII art, status, memory, all commands on login
- **Default model**: `google/gemini-2.0-flash-exp:free` (free tier)
- **Defaults**: `max_tokens=4096`, `max_tool_iterations=10` (was 8192/20)
- **Defaults**: `max_tokens=16384`, `max_tool_iterations=25` (tuned for web search headroom)

### What We Did NOT Change
All PicoClaw channels (Telegram, Discord, QQ, LINE, Slack, WhatsApp, etc.) and tools remain in the codebase. Users can configure any provider via `config.json` directly.
Expand All @@ -58,7 +58,7 @@ Go allocates ~500MB virtual memory (lazy reservations). The Linux OOM killer use
The `loadStore()` function in `pkg/cron/service.go` panicked on empty or corrupted `jobs.json`. **Fix**: Added graceful handling — treats empty/corrupt files as fresh state.

### 3. Init Script Must Bake Environment Variables
The init script at `/etc/init.d/S99luckyclaw` MUST export `GOGC=20`, `GOMEMLIMIT=8MiB`, and `TZ` before starting the daemon. Without these, the binary runs with Go defaults and immediately OOMs.
The init script at `/etc/init.d/S99luckyclaw` MUST export `GOGC=20`, `GOMEMLIMIT=24MiB`, and `TZ` before starting the daemon. Without these, the binary runs with Go defaults and immediately OOMs. WARNING: setting GOMEMLIMIT too low (e.g. 8MiB) causes the GC to spin at 100% CPU.

### 4. Busybox Limitations
Luckfox uses Busybox. `wget` doesn't support HTTPS. `sudo` doesn't exist—you're already root. `curl` isn't available. The Go binary handles all HTTPS via `net/http`.
Expand All @@ -67,7 +67,7 @@ Luckfox uses Busybox. `wget` doesn't support HTTPS. `sudo` doesn't exist—you'r
Telegram API DNS (`api.telegram.org`) sometimes fails to resolve. The init script adds a static entry to `/etc/hosts`.

### 6. Don't Add Unnecessary Dependencies
Every byte counts. The binary is already ~15MB stripped. Adding dependencies increases memory usage. Always test with `GOMEMLIMIT=8MiB`.
Every byte counts. The binary is already ~15MB stripped. Adding dependencies increases memory usage. Always test with `GOMEMLIMIT=24MiB`.

### 7. AI Agent Access to the Device
If you are an AI agent and need to test changes, examine logs, or execute commands directly on the Luckfox Pico hardware, **do not guess the IP or password**. Simply ask the user to provide the SSH IP address and password for the device, and use the `run_command` tool via `sshpass` (e.g., `sshpass -p <password> ssh root@<ip>`).
Expand All @@ -81,6 +81,14 @@ If you are an AI agent, you **MUST NEVER** execute code changes, environment mod
### 10. Multiple Daemon Instances & PID Tracking
If `luckyclaw gateway -b` is executed while a daemon started by `/etc/init.d/S99luckyclaw` is already running it will overwrite the `/var/run/luckyclaw.pid` file. Because the init script only tracks the latest PID, subsequent `stop` or `restart` commands will leave the original daemon alive as a zombie, causing duplicate Telegram processing and hallucinated timestamps in session memory. **Fix:** Going forward, making sure we strictly append `&& killall -9 luckyclaw` alongside the init script (which I've started doing in my deploy commands) completely eliminates the possibility of this happening again.

### 11. PicoClaw Upstream Reference
A shallow clone of the upstream PicoClaw repo is kept at `picoclaw-latest/` (gitignored). This is used for comparing upstream changes and evaluating code worth porting. To refresh it: `cd picoclaw-latest && git pull`. Do not commit this directory.

### 12. Log File Destinations & Workspace Paths
- **Gateway log**: `/var/log/luckyclaw.log` (stdout/stderr from the init script). The init script uses an `sh -c "exec ..."` wrapper because BusyBox's `start-stop-daemon -b` redirects fds to `/dev/null` before shell redirects take effect.
- **Heartbeat log**: `<workspace>/heartbeat.log` (written directly by the heartbeat service, not stdout).
- **Runtime workspace**: `/oem/.luckyclaw/workspace/` — this is where the bot actually reads/writes data at runtime. The firmware overlay installs template files to `/root/.luckyclaw/workspace/` but these are NOT used at runtime because `luckyclaw onboard` creates its config at `/oem/`. Any default template changes must also be reflected in `createDefaultHeartbeatTemplate()` in `pkg/heartbeat/service.go`.

## Build & Deploy

### Testing Before Commits
Expand All @@ -98,14 +106,25 @@ GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 \
```

### Deploy to Device

> **⚠️ IMPORTANT:** The binary MUST be deployed to `/usr/bin/luckyclaw` — this is where the init script
> (`/etc/init.d/S99luckyclaw`) and PATH (`which luckyclaw`) expect it. Do NOT deploy to `/usr/local/bin/`.
> The running process locks the file, so you must kill it before copying.

```bash
sshpass -p 'luckfox' scp build/luckyclaw-linux-arm root@192.168.1.175:/usr/local/bin/luckyclaw
sshpass -p 'luckfox' ssh root@192.168.1.175 "chmod +x /usr/local/bin/luckyclaw && luckyclaw version"
# 1. Kill running process (required — scp fails if binary is locked)
sshpass -p 'luckfox' ssh root@<IP> "killall -9 luckyclaw"

# 2. Copy new binary to /usr/bin/ (NOT /usr/local/bin/)
sshpass -p 'luckfox' scp build/luckyclaw-linux-arm root@<IP>:/usr/bin/luckyclaw

# 3. Restart via init script and verify
sshpass -p 'luckfox' ssh root@<IP> "chmod +x /usr/bin/luckyclaw && /etc/init.d/S99luckyclaw restart && sleep 2 && luckyclaw version"
```

### Test on Device
```bash
sshpass -p 'luckfox' ssh root@192.168.1.175
sshpass -p 'luckfox' ssh root@<IP>
luckyclaw status # Check everything
luckyclaw gateway -b # Start in background
luckyclaw stop # Stop cleanly
Expand Down
43 changes: 43 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Contributing to LuckyClaw

LuckyClaw is currently maintained by a single developer. Contributions via pull requests and issue reports are welcome on GitHub. Response times may vary.

## Before Submitting a PR

1. Run `make check` and ensure all tests pass locally.
2. Keep PRs focused. Avoid bundling unrelated changes together.
3. Include a clear description of what changed and why.

## PR Structure

Every pull request should include:

- **Description** -- What does this change do and why?
- **Type** -- Bug fix, feature, docs, or refactor.
- **Testing** -- How you tested the change (hardware, model/provider, channel).
- **Evidence** -- Logs or screenshots demonstrating the change works (optional but encouraged).

## AI-Assisted Contributions

LuckyClaw embraces AI-assisted development. If you use AI tools to generate code, please:

- **Disclose it** in the PR description. There is no stigma -- only transparency matters.
- **Read and understand** every line of generated code before submitting.
- **Test it** in a real environment, not just in an editor.
- **Review for security** -- AI-generated code can produce subtle bugs around path traversal, command injection, and credential handling.

AI-generated contributions are held to the same quality bar as human-written code.

## Code Standards

- Idiomatic Go, consistent with the existing codebase style.
- No unnecessary abstractions, dead code, or over-engineering.
- Include or update tests where appropriate.
- All CI checks (`make check`) must pass.

## Communication

- **GitHub Issues** -- Bug reports, feature requests, design discussions.
- **Pull Request comments** -- Code-specific feedback.

When in doubt, open an issue before writing code.
99 changes: 99 additions & 0 deletions IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Suggested Improvements (Backlog)

Items listed here are planned enhancements that are not yet scheduled for implementation.

## Cron Tool Enhancements

### Add `at_time` Parameter
**Priority**: Medium
**Description**: Add a new `at_time` parameter to the cron tool that accepts an ISO-8601 timestamp (e.g., `"2026-02-22T07:00:00+03:00"`). The tool would internally convert this to `atMS` using `time.Parse(time.RFC3339, at_time)`. This eliminates the need for the LLM to manually calculate `at_seconds` from `time.Now()` when the user specifies an absolute clock time for a one-time reminder.

**Benefit**: Reduces LLM arithmetic errors when converting "at 7:10 AM" → `at_seconds`. Currently the LLM must compute `target_time - current_time` in seconds, which is error-prone. With `at_time`, it just passes the ISO string directly.

**Blocked by**: Nothing. Can be implemented independently after Phase 12-H.

## PicoClaw Upstream Bugs

### Infinite Optimization Loop
**Priority**: High
**Description**: In `pkg/agent/loop.go` -> `summarizeSession()`, if the conversational history consists entirely of tool outputs (which causes `len(validMessages) == 0` during the extraction phase), the function returns early without invoking `al.sessions.TruncateHistory()`. This causes the token window boundary to be instantly breached again on the very next turn, locking the agent into an infinite "Memory threshold reached. Optimizing conversation history..." cycle that never actually optimizes.

**Benefit**: Prevents catastrophic session corruption when LLM APIs fail or large tool exchanges dominate a short time window.

**Blocked by**: Should be submitted as a PR to the [picoclaw](https://github.com/sipeed/picoclaw) upstream repository.

> **Status (checked 2026-03-09):** Bug confirmed still present in picoclaw-latest at `pkg/agent/loop.go` lines 1457-1459. The `len(validMessages) == 0` early return skips `TruncateHistory()`, causing the infinite loop. We fixed this in our fork but have not yet opened a PR.

## Installation / Deployment

### OTA Binary Updates (No Reflash)
**Priority**: Medium
**Description**: The LuckyClaw binary at `/usr/bin/luckyclaw` can be replaced via SCP without reflashing the entire firmware, since user data lives on `/oem/.luckyclaw/` (a separate partition). An `luckyclaw update` command could check the GitHub Releases API for the latest version, download the matching ARM binary, replace itself, and restart — all without touching config, sessions, cron jobs, or memory.

**Benefit**: Users can update without Windows, without SOCToolKit, and without losing any data. Dramatically lowers the friction of staying current.

**Blocked by**: Needs a stable releases workflow publishing individual ARM binaries (not just full `.img` files). Also needs a version comparison check (`luckyclaw version` already embeds the version tag).

### Open-Source Cross-Platform Flashing Tool
**Priority**: Low
**Description**: Currently, flashing the eMMC requires using the proprietary Rockchip `SOCToolKit`, which is Windows-only. We should develop or adopt an open-source, cross-platform CLI tool (e.g., in Python or Go) that can communicate with the Rockchip MaskROM protocol to flash `update.img` directly from Linux and macOS without needing Windows VMs or proprietary software.

**Benefit**: Dramatically simplifies the onboarding process for non-Windows users and allows for scripted/automated deployments.
**Blocked by**: Reverse engineering of Rockchip protocols or integrating existing open-source alternatives like `rkdeveloptool`.

## Performance Optimizations

### Cache System Prompt Between Messages
**Priority**: Medium
**Description**: `BuildSystemPrompt()` in `pkg/agent/context.go` re-reads `SOUL.md`, `USER.md`, `AGENTS.md`, skills summaries, and memory context from disk on every message. These files rarely change. Caching the result with a file-modification-time check would eliminate repeated disk I/O and string allocations.

**Benefit**: Eliminates ~5 file reads and ~10KB of string allocations per message. On the Luckfox's SPI NAND flash (slower than eMMC), this could save 5-10ms per message.

### Cache Tool Provider Definitions
**Priority**: Low
**Description**: `al.tools.ToProviderDefs()` in `runLLMIteration` rebuilds the full tool definition JSON on every LLM iteration (up to 15 per message). The tool registry doesn't change at runtime, so this can be computed once at startup and cached.

**Benefit**: Avoids rebuilding ~2KB of JSON schema per iteration. Minor memory saving but reduces GC pressure.

### Use `json.Marshal` Instead of `json.MarshalIndent` for Session Save
**Priority**: Low
**Description**: `SessionManager.Save()` uses `json.MarshalIndent` for pretty-printing. This is ~2x slower than `json.Marshal` and produces larger files on flash storage.

**Benefit**: Faster session saves, smaller session files on limited SPI NAND storage.

### Pre-allocate HTTP Response Buffer
**Priority**: Low
**Description**: `HTTPProvider.Chat()` uses `io.ReadAll(resp.Body)` which starts with a small buffer and grows dynamically. Pre-allocating based on `Content-Length` header (when available) would reduce reallocations.

**Benefit**: Fewer intermediate allocations during LLM response parsing.

## Benchmark Tests

### Add Performance Benchmarks to `make check`
**Priority**: Medium
**Description**: Introduce Go benchmark tests (`func BenchmarkXxx(b *testing.B)`) that measure the performance of critical hot-path functions. These should run as part of `make check` or as a separate `make bench` target. Proposed benchmarks:

1. **`BenchmarkBuildSystemPrompt`** — Measures time to build the full system prompt from disk files. Baseline: should be <5ms.
2. **`BenchmarkBuildMessages`** — Measures context assembly with varying history sizes (10, 50, 100 messages). Guards against regression as history grows.
3. **`BenchmarkSessionSave`** — Measures JSON serialization + atomic write for sessions of varying sizes. Ensures save stays <50ms.
4. **`BenchmarkToProviderDefs`** — Measures tool definition generation. Should be <1ms.
5. **`BenchmarkForceCompression`** — Measures conversation compression performance. Critical for memory-constrained devices.
6. **`BenchmarkGetHistory`** — Measures session history copy for varying message counts. Guards against O(n²) regressions.

**Benefit**: Catches performance regressions early, provides baseline numbers for the Luckfox board, and validates that optimization PRs actually improve performance.

**Blocked by**: Nothing. Can be implemented independently.

## Session Management

### Configurable Summarization Thresholds
**Priority**: Medium
**Description**: Port `SummarizeMessageThreshold` and `SummarizeTokenPercent` from picoclaw upstream into our config struct. Currently hardcoded at 20 messages / 75% of context window in `loop.go`. Making these configurable allows users to tune conversation memory behavior without rebuilding.

**Benefit**: Power users can trade token cost for longer conversation context, or reduce it on very small models.

### Improved Token Estimator
**Priority**: Low
**Description**: Port `utf8.RuneCountInString` with 2.5 chars/token ratio from picoclaw upstream (vs our current `len` with 3 chars/token). More accurate for mixed-language content and CJK text.

**Benefit**: Better context budget estimation, especially for non-English conversations.
Loading