ci: add release-bundle workflow for per-platform zip packages
- workflow_dispatch with tag, prerelease, draft inputs - Cross-compile picoclaw for 8 platforms - Build launcher on native runners via go tool wails - Launcher failure is non-blocking (continue-on-error) - Taskfile: task all, task build-launcher - Updated README with install instructions
This commit is contained in:
parent
946af6b53d
commit
78fdf70a7d
4 changed files with 708 additions and 0 deletions
407
.github/workflows/release-bundle.yml
vendored
Normal file
407
.github/workflows/release-bundle.yml
vendored
Normal file
|
|
@ -0,0 +1,407 @@
|
||||||
|
name: Release Bundle
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Release tag (e.g. v0.2.0)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
prerelease:
|
||||||
|
description: "Mark as pre-release"
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
draft:
|
||||||
|
description: "Create as draft"
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
# 1. Create Git tag
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
create-tag:
|
||||||
|
name: Create Git Tag
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Create and push tag
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git tag -a "$RELEASE_TAG" -m "Release $RELEASE_TAG"
|
||||||
|
git push origin "$RELEASE_TAG"
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
# 2. Build picoclaw (CGO_ENABLED=0, pure Go, cross-compile on ubuntu)
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
build-picoclaw:
|
||||||
|
name: Build picoclaw (${{ matrix.goos }}/${{ matrix.goarch }})
|
||||||
|
needs: create-tag
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
suffix: ""
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm64
|
||||||
|
suffix: ""
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: "7"
|
||||||
|
suffix: ""
|
||||||
|
- goos: linux
|
||||||
|
goarch: riscv64
|
||||||
|
suffix: ""
|
||||||
|
- goos: linux
|
||||||
|
goarch: loong64
|
||||||
|
suffix: ""
|
||||||
|
- goos: windows
|
||||||
|
goarch: amd64
|
||||||
|
suffix: ".exe"
|
||||||
|
- goos: darwin
|
||||||
|
goarch: amd64
|
||||||
|
suffix: ""
|
||||||
|
- goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
suffix: ""
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout tag
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Generate embedded assets
|
||||||
|
run: go generate ./...
|
||||||
|
|
||||||
|
- name: Build picoclaw
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
VERSION: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
OUTPUT="dist/picoclaw${{ matrix.suffix }}"
|
||||||
|
go build \
|
||||||
|
-tags stdjson \
|
||||||
|
-ldflags "-s -w \
|
||||||
|
-X github.com/sipeed/picoclaw/cmd/picoclaw/internal.version=${VERSION} \
|
||||||
|
-X github.com/sipeed/picoclaw/cmd/picoclaw/internal.gitCommit=$(git rev-parse --short HEAD) \
|
||||||
|
-X github.com/sipeed/picoclaw/cmd/picoclaw/internal.buildTime=$(date -u +%FT%TZ)" \
|
||||||
|
-o "${OUTPUT}" \
|
||||||
|
./cmd/picoclaw
|
||||||
|
|
||||||
|
- name: Upload picoclaw artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: picoclaw-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm && format('-armv{0}', matrix.goarm) || '' }}
|
||||||
|
path: dist/picoclaw${{ matrix.suffix }}
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
# 3a. Build Launcher — Windows (Wails requires WebView2)
|
||||||
|
# Note: continue-on-error=true so packaging still runs if wails build fails
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
build-launcher-windows:
|
||||||
|
name: Build launcher (windows/amd64)
|
||||||
|
needs: create-tag
|
||||||
|
runs-on: windows-latest
|
||||||
|
continue-on-error: true
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout tag
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Build launcher
|
||||||
|
working-directory: cmd/picoclaw-launcher
|
||||||
|
run: |
|
||||||
|
go tool wails build -tags stdjson -ldflags "-s -w" -o picoclaw-launcher.exe
|
||||||
|
|
||||||
|
- name: Upload launcher artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: launcher-windows-amd64
|
||||||
|
path: cmd/picoclaw-launcher/build/bin/picoclaw-launcher.exe
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
# 3b. Build Launcher — Linux/amd64 (Wails requires GTK/WebKit)
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
build-launcher-linux:
|
||||||
|
name: Build launcher (linux/amd64)
|
||||||
|
needs: create-tag
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout tag
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
|
||||||
|
- name: Install WebKit/GTK dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y --no-install-recommends \
|
||||||
|
libgtk-3-dev \
|
||||||
|
libwebkit2gtk-4.0-dev \
|
||||||
|
pkg-config \
|
||||||
|
build-essential
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Build launcher
|
||||||
|
working-directory: cmd/picoclaw-launcher
|
||||||
|
run: |
|
||||||
|
go tool wails build -tags stdjson -ldflags "-s -w" -o picoclaw-launcher
|
||||||
|
|
||||||
|
- name: Upload launcher artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: launcher-linux-amd64
|
||||||
|
path: cmd/picoclaw-launcher/build/bin/picoclaw-launcher
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
# 3c. Build Launcher — macOS (universal binary: amd64 + arm64)
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
build-launcher-macos:
|
||||||
|
name: Build launcher (darwin/universal)
|
||||||
|
needs: create-tag
|
||||||
|
runs-on: macos-latest
|
||||||
|
continue-on-error: true
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout tag
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Build launcher (universal)
|
||||||
|
working-directory: cmd/picoclaw-launcher
|
||||||
|
run: |
|
||||||
|
go tool wails build -tags stdjson -ldflags "-s -w" -platform darwin/universal -o picoclaw-launcher
|
||||||
|
|
||||||
|
- name: Upload launcher artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: launcher-darwin-universal
|
||||||
|
path: cmd/picoclaw-launcher/build/bin/picoclaw-launcher
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
# 4. Package: assemble zip bundles per platform and create GitHub Release
|
||||||
|
# Runs even if launcher builds fail (if: ${{ !cancelled() }})
|
||||||
|
# Launcher is included only if its artifact was successfully uploaded
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
package-and-release:
|
||||||
|
name: Package & Create Release
|
||||||
|
needs:
|
||||||
|
- build-picoclaw
|
||||||
|
- build-launcher-windows
|
||||||
|
- build-launcher-linux
|
||||||
|
- build-launcher-macos
|
||||||
|
# Run even if launcher jobs fail; only skip if workflow is cancelled
|
||||||
|
if: ${{ !cancelled() && needs.build-picoclaw.result == 'success' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout tag
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
|
||||||
|
# ── Download picoclaw artifacts (always present) ──
|
||||||
|
- name: Download picoclaw artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: picoclaw-*
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: false
|
||||||
|
|
||||||
|
# ── Download launcher artifacts (may be missing if build failed) ──
|
||||||
|
- name: Download launcher-windows artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
if: ${{ needs.build-launcher-windows.result == 'success' }}
|
||||||
|
with:
|
||||||
|
name: launcher-windows-amd64
|
||||||
|
path: artifacts/launcher-windows-amd64
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Download launcher-linux artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
if: ${{ needs.build-launcher-linux.result == 'success' }}
|
||||||
|
with:
|
||||||
|
name: launcher-linux-amd64
|
||||||
|
path: artifacts/launcher-linux-amd64
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Download launcher-macos artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
if: ${{ needs.build-launcher-macos.result == 'success' }}
|
||||||
|
with:
|
||||||
|
name: launcher-darwin-universal
|
||||||
|
path: artifacts/launcher-darwin-universal
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: List downloaded artifacts
|
||||||
|
run: find artifacts -type f | sort
|
||||||
|
|
||||||
|
# ── Assemble zip bundles ──
|
||||||
|
- name: Assemble zip bundles
|
||||||
|
env:
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p release staging
|
||||||
|
|
||||||
|
# ── helper: bundle picoclaw + optional launcher into a zip ──
|
||||||
|
make_zip() {
|
||||||
|
local NAME="$1" # bundle name, e.g. picoclaw_v0.1.0_windows_amd64
|
||||||
|
local PICOCLAW="$2" # source path of picoclaw binary
|
||||||
|
local LAUNCHER="${3:-}" # source path of launcher binary (empty = omit)
|
||||||
|
local EXT="${4:-}" # ".exe" or ""
|
||||||
|
|
||||||
|
local DIR="staging/${NAME}"
|
||||||
|
mkdir -p "${DIR}"
|
||||||
|
|
||||||
|
chmod +x "${PICOCLAW}" 2>/dev/null || true
|
||||||
|
cp "${PICOCLAW}" "${DIR}/picoclaw${EXT}"
|
||||||
|
|
||||||
|
if [ -n "${LAUNCHER}" ] && [ -f "${LAUNCHER}" ]; then
|
||||||
|
chmod +x "${LAUNCHER}" 2>/dev/null || true
|
||||||
|
cp "${LAUNCHER}" "${DIR}/picoclaw-launcher${EXT}"
|
||||||
|
echo "PicoClaw Bundle (picoclaw + picoclaw-launcher)" > "${DIR}/README.txt"
|
||||||
|
else
|
||||||
|
echo "PicoClaw CLI only (picoclaw-launcher not available for this platform)" > "${DIR}/README.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd staging && zip -r "../release/${NAME}.zip" "${NAME}/")
|
||||||
|
echo " created: release/${NAME}.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
WINLAUNCHER=""
|
||||||
|
LINLAUNCHER=""
|
||||||
|
MACLAUNCHER=""
|
||||||
|
|
||||||
|
[ -f "artifacts/launcher-windows-amd64/picoclaw-launcher.exe" ] \
|
||||||
|
&& WINLAUNCHER="artifacts/launcher-windows-amd64/picoclaw-launcher.exe"
|
||||||
|
[ -f "artifacts/launcher-linux-amd64/picoclaw-launcher" ] \
|
||||||
|
&& LINLAUNCHER="artifacts/launcher-linux-amd64/picoclaw-launcher"
|
||||||
|
[ -f "artifacts/launcher-darwin-universal/picoclaw-launcher" ] \
|
||||||
|
&& MACLAUNCHER="artifacts/launcher-darwin-universal/picoclaw-launcher"
|
||||||
|
|
||||||
|
# Windows amd64
|
||||||
|
make_zip \
|
||||||
|
"picoclaw_${TAG}_windows_amd64" \
|
||||||
|
"artifacts/picoclaw-windows-amd64/picoclaw.exe" \
|
||||||
|
"${WINLAUNCHER}" \
|
||||||
|
".exe"
|
||||||
|
|
||||||
|
# Linux amd64
|
||||||
|
make_zip \
|
||||||
|
"picoclaw_${TAG}_linux_amd64" \
|
||||||
|
"artifacts/picoclaw-linux-amd64/picoclaw" \
|
||||||
|
"${LINLAUNCHER}" \
|
||||||
|
""
|
||||||
|
|
||||||
|
# macOS amd64 (with universal launcher if available)
|
||||||
|
make_zip \
|
||||||
|
"picoclaw_${TAG}_darwin_amd64" \
|
||||||
|
"artifacts/picoclaw-darwin-amd64/picoclaw" \
|
||||||
|
"${MACLAUNCHER}" \
|
||||||
|
""
|
||||||
|
|
||||||
|
# macOS arm64 (with universal launcher if available)
|
||||||
|
make_zip \
|
||||||
|
"picoclaw_${TAG}_darwin_arm64" \
|
||||||
|
"artifacts/picoclaw-darwin-arm64/picoclaw" \
|
||||||
|
"${MACLAUNCHER}" \
|
||||||
|
""
|
||||||
|
|
||||||
|
# Linux arm64 (picoclaw only)
|
||||||
|
make_zip \
|
||||||
|
"picoclaw_${TAG}_linux_arm64" \
|
||||||
|
"artifacts/picoclaw-linux-arm64/picoclaw" \
|
||||||
|
"" ""
|
||||||
|
|
||||||
|
# Linux armv7 (picoclaw only)
|
||||||
|
make_zip \
|
||||||
|
"picoclaw_${TAG}_linux_armv7" \
|
||||||
|
"artifacts/picoclaw-linux-arm-armv7/picoclaw" \
|
||||||
|
"" ""
|
||||||
|
|
||||||
|
# Linux riscv64 (picoclaw only)
|
||||||
|
make_zip \
|
||||||
|
"picoclaw_${TAG}_linux_riscv64" \
|
||||||
|
"artifacts/picoclaw-linux-riscv64/picoclaw" \
|
||||||
|
"" ""
|
||||||
|
|
||||||
|
# Linux loong64 (picoclaw only)
|
||||||
|
make_zip \
|
||||||
|
"picoclaw_${TAG}_linux_loong64" \
|
||||||
|
"artifacts/picoclaw-linux-loong64/picoclaw" \
|
||||||
|
"" ""
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Final release assets ==="
|
||||||
|
ls -lh release/
|
||||||
|
|
||||||
|
# ── Create GitHub Release ──
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ inputs.tag }}
|
||||||
|
name: "PicoClaw ${{ inputs.tag }}"
|
||||||
|
draft: ${{ inputs.draft }}
|
||||||
|
prerelease: ${{ inputs.prerelease }}
|
||||||
|
generate_release_notes: true
|
||||||
|
files: release/*.zip
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
81
README.md
81
README.md
|
|
@ -82,6 +82,87 @@
|
||||||
|
|
||||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||||
|
|
||||||
|
## 🧠 Our Thinking
|
||||||
|
|
||||||
|
PicoClaw isn't just small — it **thinks differently**. While most AI agent frameworks treat the LLM as a black box that swallows everything (all tools, all history, all context), PicoClaw introduces a structured **Runtime Loop** that makes every token count.
|
||||||
|
|
||||||
|
### Three-Phase Runtime Loop
|
||||||
|
|
||||||
|
Every user interaction flows through three distinct phases:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ Phase 1 │ → │ Phase 2 │ → │ Phase 3 │
|
||||||
|
│ Analyse │ │ ExecuteLLM │ │ Reflect │
|
||||||
|
│ (Orchestrate) │ (Execute) │ │ (Learn) │
|
||||||
|
└──────────────┘ └──────────────┘ └──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Phase 1 — Analyse**: A lightweight LLM call (can use a cheaper/faster model) that understands intent, assigns semantic tags, and prepares the optimal context. No tools, no history bloat — just pure comprehension.
|
||||||
|
- **Phase 2 — ExecuteLLM**: The main LLM iterates with only the tools and context that Phase 1 deemed relevant. Fewer tools = less confusion = better decisions.
|
||||||
|
- **Phase 3 — Reflect**: Synchronous scoring + context update (< 2ms) before the user sees the reply, followed by async persistence. The agent learns from every interaction without adding latency.
|
||||||
|
|
||||||
|
### Turn Scoring & Instant Memory
|
||||||
|
|
||||||
|
Every Turn gets a **score** based on tool activity, intent weight, content density, and explicit user markers. This score drives a novel context selection algorithm:
|
||||||
|
|
||||||
|
```
|
||||||
|
Instant Memory = { high-score Turns } // always kept, unconditionally
|
||||||
|
∪ { tag-matched Turns, score > 0 } // relevant old Turns "resurrected"
|
||||||
|
∪ { recent M Turns } // continuity baseline
|
||||||
|
→ sorted by time, truncated to capacity
|
||||||
|
```
|
||||||
|
|
||||||
|
**No Turn is ever deleted.** Low-score Turns are simply excluded from context — but when a future Phase 1 produces matching tags, they can be recalled. It's like human memory: you don't forget things, you just can't always access them until something triggers the recall.
|
||||||
|
|
||||||
|
### Tag-Gated Tool Loading
|
||||||
|
|
||||||
|
Instead of feeding 20+ tool definitions to the LLM every request (wasting ~3,000 tokens per iteration), PicoClaw categorizes tools:
|
||||||
|
|
||||||
|
| Layer | Tools | Loading |
|
||||||
|
|-------|-------|---------|
|
||||||
|
| **always-on** | `read_file`, `write_file`, `shell`, etc. | Always present |
|
||||||
|
| **tag-gated** | MCP servers, Skills, `web_search`, `cron` | Only when Phase 1 tags match |
|
||||||
|
|
||||||
|
As MCP servers and Skills grow, the savings compound exponentially — and the LLM makes better decisions with a focused toolset.
|
||||||
|
|
||||||
|
### KV Cache-Friendly Message Ordering
|
||||||
|
|
||||||
|
We obsess over **prefix stability** to maximize KV cache hits across providers (Gemini implicit cache, Anthropic `cache_control`, OpenAI prompt caching):
|
||||||
|
|
||||||
|
```
|
||||||
|
[system_prompt] ← always cached ✅
|
||||||
|
[long_term_memory by tags] ← stable when same tags ✅
|
||||||
|
[high-score Turns, ASC by ID] ← fixed positions, append-only ✅
|
||||||
|
[tag-matched Turns] ← may vary
|
||||||
|
[recent Turns] ← rolling window
|
||||||
|
[current user message] ← always new
|
||||||
|
```
|
||||||
|
|
||||||
|
High-score Turns are **pinned** right after long-term memory in ascending order. New Turns only append — they never shift existing content. This maximizes the cache-hit prefix length, and the cost difference is dramatic at scale.
|
||||||
|
|
||||||
|
### Three-Layer Memory Hierarchy
|
||||||
|
|
||||||
|
| Layer | Lifecycle | Source | Consumer |
|
||||||
|
|-------|-----------|--------|----------|
|
||||||
|
| **Instant Memory** | Assembled per-Turn | Turn Store (score + tag filter) | Phase 2 (ExecuteLLM) |
|
||||||
|
| **Active Context** | Rolling update per `channel:chatID` | Phase 3 sync update | Phase 1 (Analyse) |
|
||||||
|
| **Long-Term Memory** | Persistent | MemoryDigest batch worker | Phase 1 (via tag retrieval) |
|
||||||
|
|
||||||
|
Each layer serves a different phase. No overlap, no waste. Active Context (current files, recent errors) helps Phase 1 understand terse messages like "fix it". Instant Memory gives Phase 2 the right historical context. Long-term memory accumulates wisdom across sessions.
|
||||||
|
|
||||||
|
### Why This Matters
|
||||||
|
|
||||||
|
Traditional agent loops dump everything into one LLM call and hope for the best. PicoClaw's approach means:
|
||||||
|
|
||||||
|
- 📉 **~60% fewer wasted tokens** from irrelevant tools and stale context
|
||||||
|
- 🎯 **Higher decision quality** — focused toolsets reduce LLM confusion
|
||||||
|
- ⚡ **Sub-2ms overhead** for scoring and context updates (zero perceived latency)
|
||||||
|
- 🧲 **Associative recall** — old conversations resurface when relevant, like human memory
|
||||||
|
- 💰 **Multi-model cost optimization** — use cheap models for analysis, strong models for execution
|
||||||
|
|
||||||
|
> *"The best token is the one you never send."*
|
||||||
|
|
||||||
## 🦾 Demonstration
|
## 🦾 Demonstration
|
||||||
|
|
||||||
### 🛠️ Standard Assistant Workflows
|
### 🛠️ Standard Assistant Workflows
|
||||||
|
|
|
||||||
81
README.zh.md
81
README.zh.md
|
|
@ -80,6 +80,87 @@
|
||||||
|
|
||||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||||
|
|
||||||
|
## 🧠 我们的思考
|
||||||
|
|
||||||
|
PicoClaw 不只是小——它的**思维方式**和其他 AI Agent 框架根本不同。大多数框架把 LLM 当黑箱,把所有工具、所有历史、所有上下文一股脑塞进去。PicoClaw 引入了结构化的 **Runtime Loop**,让每一个 token 都物尽其用。
|
||||||
|
|
||||||
|
### 三阶段 Runtime Loop
|
||||||
|
|
||||||
|
每次用户交互都经过三个清晰的阶段:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ Phase 1 │ → │ Phase 2 │ → │ Phase 3 │
|
||||||
|
│ 分析 Analyse│ │ 执行 ExecuteLLM│ │ 反思 Reflect │
|
||||||
|
│ (编排器) │ │ (执行器) │ │ (学习器) │
|
||||||
|
└──────────────┘ └──────────────┘ └──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Phase 1 — 分析**:轻量 LLM 调用(可用便宜/快速模型),理解用户意图、分配语义标签、准备最优上下文。无工具、无历史膨胀——纯粹的理解力。
|
||||||
|
- **Phase 2 — 执行**:主 LLM 只携带 Phase 1 认为相关的工具和上下文进行迭代。工具越少 = 干扰越少 = 决策越好。
|
||||||
|
- **Phase 3 — 反思**:同步打分 + 上下文更新(< 2ms),在用户看到回复之前完成,然后异步持久化。Agent 从每次交互中学习,且不增加延迟。
|
||||||
|
|
||||||
|
### Turn 打分 & 瞬时记忆
|
||||||
|
|
||||||
|
每个 Turn 都会根据工具活动、意图权重、内容密度和用户显式标记获得一个**分数**。这个分数驱动一个新颖的上下文选择算法:
|
||||||
|
|
||||||
|
```
|
||||||
|
瞬时记忆 = { 高分 Turn } // 无条件保留
|
||||||
|
∪ { tag 匹配的 Turn, score > 0 } // 旧 Turn 被"唤醒"
|
||||||
|
∪ { 最近 M 个 Turn } // 连贯性保底
|
||||||
|
→ 按时间排序,截断到容量上限
|
||||||
|
```
|
||||||
|
|
||||||
|
**Turn 永不删除。** 低分 Turn 只是被排除在上下文之外——但当未来 Phase 1 产生匹配的 tags 时,它们可以被重新召回。就像人类记忆:你不会忘记东西,只是在某个触发点之前无法访问它们。
|
||||||
|
|
||||||
|
### Tag 驱动的工具加载
|
||||||
|
|
||||||
|
传统框架每次请求都把 20+ 个工具定义喂给 LLM(每次迭代浪费 ~3,000 tokens)。PicoClaw 把工具分为两层:
|
||||||
|
|
||||||
|
| 层级 | 工具 | 加载方式 |
|
||||||
|
|------|------|---------|
|
||||||
|
| **always-on** | `read_file`、`write_file`、`shell` 等 | 始终加载 |
|
||||||
|
| **tag-gated** | MCP servers、Skills、`web_search`、`cron` | 仅当 Phase 1 的 tags 匹配时加载 |
|
||||||
|
|
||||||
|
随着 MCP Servers 和 Skills 的增多,节省量呈指数级增长——而且 LLM 在精简的工具集下做出更好的决策。
|
||||||
|
|
||||||
|
### KV Cache 友好的消息排列
|
||||||
|
|
||||||
|
我们执着于**前缀稳定性**,以最大化跨 Provider 的 KV Cache 命中率(Gemini 隐式缓存、Anthropic `cache_control`、OpenAI prompt caching):
|
||||||
|
|
||||||
|
```
|
||||||
|
[system_prompt] ← 始终缓存 ✅
|
||||||
|
[long_term_memory by tags] ← 相同 tags 时稳定 ✅
|
||||||
|
[高分 Turn, 按 ID 升序] ← 位置固定,只增不移 ✅
|
||||||
|
[tag 匹配的 Turn] ← 可能变化
|
||||||
|
[最近的 Turn] ← 滚动窗口
|
||||||
|
[当前用户消息] ← 总是新内容
|
||||||
|
```
|
||||||
|
|
||||||
|
高分 Turn 被**固定**在长期记忆之后,按升序排列。新 Turn 只追加到末尾,不改变已有内容的位置。这最大化了缓存命中的前缀长度,在规模化场景下成本差异显著。
|
||||||
|
|
||||||
|
### 三层记忆体系
|
||||||
|
|
||||||
|
| 层次 | 生命周期 | 数据来源 | 消费者 |
|
||||||
|
|------|---------|---------|--------|
|
||||||
|
| **瞬时记忆** | 每轮动态组装 | Turn 库(score + tag 筛选)| Phase 2(ExecuteLLM)|
|
||||||
|
| **活跃上下文** | 跨 Turn 滚动更新,per `channel:chatID` | Phase 3 同步维护 | Phase 1(Analyse)|
|
||||||
|
| **长期记忆** | 持久化 | MemoryDigest 定时批量提炼 | Phase 1(通过 tag 检索)|
|
||||||
|
|
||||||
|
每层服务不同的阶段。没有重叠,没有浪费。活跃上下文(当前文件、近期错误)帮助 Phase 1 理解简短消息如"修一下"。瞬时记忆为 Phase 2 提供正确的历史上下文。长期记忆跨会话积累智慧。
|
||||||
|
|
||||||
|
### 为什么这很重要
|
||||||
|
|
||||||
|
传统的 Agent 循环把所有东西塞进一个 LLM 调用,然后听天由命。PicoClaw 的方式意味着:
|
||||||
|
|
||||||
|
- 📉 **减少 ~60% 的浪费 token** — 排除无关工具和陈旧上下文
|
||||||
|
- 🎯 **更高的决策质量** — 精简工具集减少 LLM 的"选择困难症"
|
||||||
|
- ⚡ **< 2ms 的额外开销** — 打分和上下文更新零感知延迟
|
||||||
|
- 🧲 **联想式召回** — 旧对话在相关时自动浮现,就像人类记忆
|
||||||
|
- 💰 **多模型成本优化** — 分析用便宜模型,执行用强模型
|
||||||
|
|
||||||
|
> *"最好的 token 是你永远不需要发送的那个。"*
|
||||||
|
|
||||||
## 🦾 演示
|
## 🦾 演示
|
||||||
|
|
||||||
### 🛠️ 标准助手工作流
|
### 🛠️ 标准助手工作流
|
||||||
|
|
|
||||||
139
Taskfile.yml
Normal file
139
Taskfile.yml
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
BINARY_NAME: picoclaw
|
||||||
|
CMD_DIR: cmd/picoclaw
|
||||||
|
BUILD_DIR: build
|
||||||
|
LAUNCHER_DIR: cmd/picoclaw-launcher
|
||||||
|
LAUNCHER_BUILD_DIR: cmd/picoclaw-launcher/build/bin
|
||||||
|
INTERNAL: github.com/sipeed/picoclaw/cmd/picoclaw/internal
|
||||||
|
VERSION:
|
||||||
|
sh: git describe --tags --always --dirty 2>{{if eq OS "windows"}}nul{{else}}/dev/null{{end}} || echo dev
|
||||||
|
GIT_COMMIT:
|
||||||
|
sh: git rev-parse --short=8 HEAD 2>{{if eq OS "windows"}}nul{{else}}/dev/null{{end}} || echo dev
|
||||||
|
LDFLAGS: -ldflags "-X {{.INTERNAL}}.version={{.VERSION}} -X {{.INTERNAL}}.gitCommit={{.GIT_COMMIT}} -s -w"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
desc: Build the project
|
||||||
|
cmds:
|
||||||
|
- task: build
|
||||||
|
|
||||||
|
all:
|
||||||
|
desc: Build picoclaw + launcher (both binaries)
|
||||||
|
deps:
|
||||||
|
- build
|
||||||
|
- build-launcher
|
||||||
|
|
||||||
|
build:
|
||||||
|
desc: Build picoclaw binary (dev, skip generate)
|
||||||
|
cmds:
|
||||||
|
- go build -v {{.LDFLAGS}} -o {{.BUILD_DIR}}/{{.BINARY_NAME}}{{exeExt}} ./{{.CMD_DIR}}
|
||||||
|
sources:
|
||||||
|
- "**/*.go"
|
||||||
|
generates:
|
||||||
|
- "{{.BUILD_DIR}}/{{.BINARY_NAME}}{{exeExt}}"
|
||||||
|
|
||||||
|
build-full:
|
||||||
|
desc: Build with go generate (release)
|
||||||
|
cmds:
|
||||||
|
- task: generate
|
||||||
|
- task: build
|
||||||
|
|
||||||
|
build-launcher:
|
||||||
|
desc: Build picoclaw-launcher (Wails desktop GUI)
|
||||||
|
dir: '{{.LAUNCHER_DIR}}'
|
||||||
|
cmds:
|
||||||
|
- go tool wails build -tags stdjson -ldflags "-s -w" -o picoclaw-launcher{{exeExt}}
|
||||||
|
sources:
|
||||||
|
- "**/*.go"
|
||||||
|
- frontend/**
|
||||||
|
generates:
|
||||||
|
- '../../{{.LAUNCHER_BUILD_DIR}}/picoclaw-launcher{{exeExt}}'
|
||||||
|
|
||||||
|
generate:
|
||||||
|
desc: Run go generate
|
||||||
|
cmds:
|
||||||
|
- cmd: powershell -Command "Remove-Item -Recurse -Force '{{.CMD_DIR}}/internal/onboard/workspace' -ErrorAction SilentlyContinue"
|
||||||
|
platforms: [windows]
|
||||||
|
- cmd: rm -rf {{.CMD_DIR}}/internal/onboard/workspace
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- go generate ./...
|
||||||
|
|
||||||
|
test:
|
||||||
|
desc: Run all tests
|
||||||
|
cmds:
|
||||||
|
- go test -count=1 ./pkg/...
|
||||||
|
|
||||||
|
test-v:
|
||||||
|
desc: Run all tests (verbose)
|
||||||
|
cmds:
|
||||||
|
- go test -v -count=1 ./pkg/...
|
||||||
|
|
||||||
|
test-agent:
|
||||||
|
desc: Run agent tests
|
||||||
|
cmds:
|
||||||
|
- go test -v -count=1 ./pkg/agent/
|
||||||
|
|
||||||
|
test-config:
|
||||||
|
desc: Run config tests
|
||||||
|
cmds:
|
||||||
|
- go test -v -count=1 ./pkg/config/
|
||||||
|
|
||||||
|
test-init:
|
||||||
|
desc: Run init command tests
|
||||||
|
cmds:
|
||||||
|
- go test -v -count=1 ./cmd/picoclaw/internal/initcmd/
|
||||||
|
|
||||||
|
lint:
|
||||||
|
desc: Run linters
|
||||||
|
cmds:
|
||||||
|
- golangci-lint run
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
desc: Format code
|
||||||
|
cmds:
|
||||||
|
- gofmt -w .
|
||||||
|
|
||||||
|
vet:
|
||||||
|
desc: Run go vet
|
||||||
|
cmds:
|
||||||
|
- go vet ./...
|
||||||
|
|
||||||
|
clean:
|
||||||
|
desc: Remove build artifacts
|
||||||
|
cmds:
|
||||||
|
- cmd: powershell -Command "Remove-Item -Recurse -Force '{{.BUILD_DIR}}' -ErrorAction SilentlyContinue"
|
||||||
|
platforms: [windows]
|
||||||
|
- cmd: rm -rf {{.BUILD_DIR}}
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
|
||||||
|
deps:
|
||||||
|
desc: Download and verify dependencies
|
||||||
|
cmds:
|
||||||
|
- go mod download
|
||||||
|
- go mod verify
|
||||||
|
|
||||||
|
tidy:
|
||||||
|
desc: Tidy dependencies
|
||||||
|
cmds:
|
||||||
|
- go mod tidy
|
||||||
|
|
||||||
|
check:
|
||||||
|
desc: Full check (fmt + vet + test)
|
||||||
|
cmds:
|
||||||
|
- task: fmt
|
||||||
|
- task: vet
|
||||||
|
- task: test
|
||||||
|
|
||||||
|
install:
|
||||||
|
desc: Install picoclaw to system
|
||||||
|
cmds:
|
||||||
|
- task: build
|
||||||
|
- go install ./{{.CMD_DIR}}
|
||||||
|
|
||||||
|
run:
|
||||||
|
desc: Build and run
|
||||||
|
cmds:
|
||||||
|
- task: build
|
||||||
|
- "{{.BUILD_DIR}}/{{.BINARY_NAME}}{{exeExt}} {{.CLI_ARGS}}"
|
||||||
Loading…
Add table
Reference in a new issue