diff --git a/.env.example b/.env.example index e615d56635..3b4506d6f5 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,9 @@ # LLM provider API keys: configure via the web dashboard setup wizard. # --- Gateway (required) --- +# Required for Docker/external binds. Run ./prepare-env.sh to generate. +# Local loopback-only development may opt into empty-token mode with: +# GOCLAW_ALLOW_INSECURE_NO_AUTH=1 GOCLAW_GATEWAY_TOKEN= GOCLAW_ENCRYPTION_KEY= POSTGRES_PASSWORD= diff --git a/.github/workflows/dev-beta-release.yaml b/.github/workflows/dev-beta-release.yaml new file mode 100644 index 0000000000..8c027273de --- /dev/null +++ b/.github/workflows/dev-beta-release.yaml @@ -0,0 +1,327 @@ +name: Dev CI and Beta Release + +on: + push: + branches: [dev] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: dev-beta-release-${{ github.ref }} + cancel-in-progress: false + +env: + GHCR_IMAGE: ghcr.io/${{ github.repository }} + DOCKERHUB_IMAGE: digitop/goclaw + INITIAL_VERSION: 3.11.3 + PRERELEASE_ID: beta + +jobs: + go: + runs-on: ubuntu-latest + services: + pg: + image: pgvector/pgvector:pg18 + env: + POSTGRES_PASSWORD: test + POSTGRES_DB: goclaw_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 5s + --health-timeout 3s + --health-retries 10 + env: + TEST_DATABASE_URL: postgres://postgres:test@localhost:5432/goclaw_test?sslmode=disable + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + - run: go build ./... + - run: go build -tags sqliteonly ./... + - run: go vet ./... + - name: Unit tests + run: go test -race -timeout=5m -coverpkg=./... -coverprofile=coverage.out ./... + - name: Invariant tests (P0) + run: go test -race -timeout=90s -tags integration ./tests/invariants/... + - name: Contract tests (P1) + run: go test -race -timeout=90s -tags integration ./tests/contracts/... || echo "::warning::Contract tests skipped (no server configured)" + continue-on-error: true + - name: Integration tests + run: go test -race -timeout=180s -tags integration ./tests/integration/ + - name: Coverage summary + run: go tool cover -func=coverage.out | tail -1 + + web: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ui/web + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: ui/web/pnpm-lock.yaml + - run: pnpm install --frozen-lockfile + - run: pnpm lint + - run: pnpm build + + beta_version: + needs: [go, web] + if: github.ref == 'refs/heads/dev' + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + released: ${{ steps.version.outputs.released }} + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} + notes_path: ${{ steps.version.outputs.notes_path }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Fetch upstream release tags + run: git fetch --force --tags https://github.com/nextlevelbuilder/goclaw.git "refs/tags/v*:refs/tags/v*" + - name: Compute semantic beta version + id: version + run: node scripts/ci/semantic-beta-version.mjs + - name: Create or verify beta tag + if: steps.version.outputs.released == 'true' + env: + TAG: ${{ steps.version.outputs.tag }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + if ! git rev-parse "$TAG" >/dev/null 2>&1; then + git tag -a "$TAG" -m "Release $TAG" + git push origin "$TAG" + fi + - name: Upload release notes + if: steps.version.outputs.released == 'true' + uses: actions/upload-artifact@v4 + with: + name: release-notes + path: ${{ steps.version.outputs.notes_path }} + + build_binaries: + needs: beta_version + if: needs.beta_version.outputs.released == 'true' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - goos: linux + goarch: amd64 + - goos: linux + goarch: arm64 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.beta_version.outputs.tag }} + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Build web UI + run: | + corepack enable && corepack prepare pnpm@10.28.2 --activate + cd ui/web && pnpm install --frozen-lockfile && pnpm build && cd ../.. + mkdir -p internal/webui/dist + cp -r ui/web/dist/* internal/webui/dist/ + + - name: Build binary + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + VERSION: ${{ needs.beta_version.outputs.tag }} + run: | + CGO_ENABLED=0 go build -tags embedui \ + -ldflags="-s -w -X github.com/nextlevelbuilder/goclaw/cmd.Version=${VERSION}" \ + -o goclaw . + tar -czf "goclaw-${VERSION}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz" goclaw migrations/ + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: binary-${{ matrix.goos }}-${{ matrix.goarch }} + path: goclaw-*.tar.gz + + publish_release: + needs: [beta_version, build_binaries, promote_beta_aliases] + if: needs.beta_version.outputs.released == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + pattern: binary-* + path: artifacts + merge-multiple: true + + - name: Download release notes + uses: actions/download-artifact@v4 + with: + name: release-notes + path: release-notes + + - name: Publish prerelease + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG: ${{ needs.beta_version.outputs.tag }} + run: | + if gh release view "$TAG" >/dev/null 2>&1; then + gh release edit "$TAG" \ + --title "GoClaw $TAG" \ + --notes-file release-notes/release-notes.md \ + --prerelease + else + gh release create "$TAG" \ + --title "GoClaw $TAG" \ + --notes-file release-notes/release-notes.md \ + --prerelease + fi + gh release upload "$TAG" artifacts/* --clobber + + docker_images: + needs: beta_version + if: needs.beta_version.outputs.released == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + include: + - variant: latest + suffix: "" + enable_otel: "false" + enable_embedui: "true" + enable_python: "true" + enable_full_skills: "false" + - variant: full + suffix: "-full" + enable_otel: "false" + enable_embedui: "true" + enable_python: "true" + enable_full_skills: "true" + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.beta_version.outputs.tag }} + + - uses: docker/setup-qemu-action@v3 + + - uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' + uses: docker/login-action@v3 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ env.DOCKERHUB_TOKEN }} + + - name: Resolve Docker tags + id: docker_tags + env: + TAG: ${{ needs.beta_version.outputs.tag }} + SUFFIX: ${{ matrix.suffix }} + run: | + { + echo "tags<> "$GITHUB_OUTPUT" + if [[ -z "$DOCKERHUB_USERNAME" || -z "$DOCKERHUB_TOKEN" ]]; then + echo "::notice::Docker Hub secrets not configured; publishing GHCR only." + fi + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.docker_tags.outputs.tags }} + build-args: | + ENABLE_OTEL=${{ matrix.enable_otel }} + ENABLE_EMBEDUI=${{ matrix.enable_embedui }} + ENABLE_PYTHON=${{ matrix.enable_python }} + ENABLE_FULL_SKILLS=${{ matrix.enable_full_skills }} + VERSION=${{ needs.beta_version.outputs.tag }} + cache-from: type=gha,scope=dev-beta-${{ matrix.variant }} + cache-to: type=gha,mode=max,scope=dev-beta-${{ matrix.variant }} + + promote_beta_aliases: + needs: [beta_version, docker_images] + if: needs.beta_version.outputs.released == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + steps: + - uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' + uses: docker/login-action@v3 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ env.DOCKERHUB_TOKEN }} + + - name: Promote beta aliases + env: + TAG: ${{ needs.beta_version.outputs.tag }} + run: | + docker buildx imagetools create -t "${GHCR_IMAGE}:beta" "${GHCR_IMAGE}:${TAG}" + docker buildx imagetools create -t "${GHCR_IMAGE}:beta-full" "${GHCR_IMAGE}:${TAG}-full" + if [[ -n "$DOCKERHUB_USERNAME" && -n "$DOCKERHUB_TOKEN" ]]; then + docker buildx imagetools create -t "${DOCKERHUB_IMAGE}:beta" "${DOCKERHUB_IMAGE}:${TAG}" + docker buildx imagetools create -t "${DOCKERHUB_IMAGE}:beta-full" "${DOCKERHUB_IMAGE}:${TAG}-full" + else + echo "::notice::Docker Hub secrets not configured; promoted GHCR beta aliases only." + fi diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fc111a93b7..5b4f9fa5f2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -168,14 +168,17 @@ jobs: enable_embedui: "false" enable_python: "false" enable_full_skills: "false" + enable_kubectl: "false" - variant: latest enable_embedui: "true" enable_python: "true" enable_full_skills: "false" + enable_kubectl: "false" - variant: full enable_embedui: "true" enable_python: "true" enable_full_skills: "true" + enable_kubectl: "true" - platform: linux/amd64 runner: ubuntu-latest arch: amd64 @@ -207,6 +210,7 @@ jobs: ENABLE_EMBEDUI=${{ matrix.enable_embedui }} ENABLE_PYTHON=${{ matrix.enable_python }} ENABLE_FULL_SKILLS=${{ matrix.enable_full_skills }} + ENABLE_KUBECTL=${{ matrix.enable_kubectl }} VERSION=v${{ needs.release.outputs.version }} cache-from: type=gha,scope=${{ matrix.variant }}-${{ matrix.arch }} cache-to: type=gha,mode=max,scope=${{ matrix.variant }}-${{ matrix.arch }} diff --git a/.gitignore b/.gitignore index 85435cf052..9a8708a87c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Test artifacts tests/integration/testdata/ +backup/ # Binary openclaw-go diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..e3f555993a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,268 @@ +# GoClaw Gateway + +PostgreSQL multi-tenant AI agent gateway with WebSocket RPC + HTTP API. + +## Language + +Always respond in the same language as the user's prompt. If the user writes in Vietnamese, respond in Vietnamese. If in English, respond in English. Match the user's language naturally. + +## Tech Stack + +**Backend:** Go 1.26, Cobra CLI, gorilla/websocket, pgx/v5 (database/sql, no ORM), golang-migrate, go-rod/rod, telego (Telegram) +**Web UI:** React 19, Vite 6, TypeScript, Tailwind CSS 4, Radix UI, Zustand, React Router 7. Located in `ui/web/`. **Use `pnpm` (not npm).** +**Desktop UI:** React 19, Vite 6, TypeScript, Tailwind CSS 4, Zustand, Framer Motion. Located in `ui/desktop/frontend/`. **Use `pnpm`.** +**Desktop App:** Wails v2 (`//go:build sqliteonly`). Located in `ui/desktop/`. Embeds gateway + React frontend in single binary. +**Database:** PostgreSQL 18 with pgvector (standard). SQLite via `modernc.org/sqlite` (desktop/lite). Raw SQL with `$1, $2` (PG) or `?` (SQLite) positional params. Nullable columns: `*string`, `*time.Time`, etc. + +## Project Structure + +``` +cmd/ CLI commands, gateway startup, onboard wizard, migrations +internal/ +├── agent/ Agent loop (think→act→observe), router, resolver, input guard +├── bootstrap/ System prompt files (SOUL.md, IDENTITY.md) + seeding + per-user seed +├── bus/ Event bus system +├── cache/ Caching layer +├── channels/ Channel manager: Telegram, Feishu/Lark, Zalo, Discord, WhatsApp +│ └── whatsapp/ Native WhatsApp via whatsmeow (v3) +├── config/ Config loading (JSON5) + env var overlay +├── consolidation/ Memory consolidation workers (episodic, semantic, dreaming) (v3) +├── crypto/ AES-256-GCM encryption for API keys +├── cron/ Cron scheduling (at/every/cron expr) +├── edition/ Edition system (Lite, Standard) with feature gating +├── eventbus/ Domain event bus with worker pool, dedup, retry (v3) +├── gateway/ WS + HTTP server, client, method router +│ └── methods/ RPC handlers (chat, agents, sessions, config, skills, cron, pairing) +├── hooks/ Hook system for extensibility +├── http/ HTTP API (/v1/chat/completions, /v1/agents, /v1/skills, etc.) +├── i18n/ Message catalog: T(locale, key, args...) + per-locale catalogs (en/vi/zh) +├── knowledgegraph/ Knowledge graph storage and traversal +├── mcp/ Model Context Protocol bridge/server +├── media/ Media handling utilities +├── memory/ Memory system (pgvector) +├── oauth/ OAuth authentication +├── orchestration/ Orchestration primitives: BatchQueue[T] generic, ChildResult, media conversion (v3) +├── permissions/ RBAC (admin/operator/viewer) +├── pipeline/ 8-stage agent pipeline (context→history→prompt→think→act→observe→memory→summarize) +├── providers/ LLM providers: Anthropic (native HTTP+SSE), OpenAI-compat (HTTP+SSE), DashScope (Alibaba Qwen), Codex CLI (stdio+MCP bridge), ACP (Anthropic Console Proxy), Codex (OpenAI), Vertex AI (GCP OAuth2 + OpenAI-compat) +├── providerresolve/ Provider adapter + model registry with forward-compat resolver +├── sandbox/ Docker-based code execution sandbox +├── scheduler/ Lane-based concurrency (main/subagent/cron) +├── sessions/ Session management +├── skills/ SKILL.md loader + BM25 search +├── store/ Store interfaces + implementations (PostgreSQL, SQLite) +│ ├── base/ Shared store abstractions: Dialect interface, helpers (NilStr, BuildMapUpdate, BuildScopeClause) +│ ├── pg/ PostgreSQL implementations (database/sql + pgx/v5) +│ └── sqlitestore/ SQLite implementations (modernc.org/sqlite) +├── tasks/ Task management +├── tokencount/ tiktoken BPE token counting +├── tools/ Tool registry, filesystem, exec, web, memory, subagent, MCP bridge, delegate +├── tracing/ LLM call tracing + optional OTel export (build-tag gated) +├── tts/ Text-to-Speech (OpenAI, ElevenLabs, Edge, MiniMax) +├── updater/ Desktop auto-update checker (Lite edition) +├── upgrade/ Database schema version tracking +├── vault/ Knowledge Vault with wikilinks, hybrid search, FS sync +├── workspace/ WorkspaceContext resolver for 6 scenarios +pkg/protocol/ Wire types (frames, methods, errors, events) +pkg/browser/ Browser automation (Rod + CDP) +migrations/ PostgreSQL migration files +ui/web/ React SPA (pnpm, Vite, Tailwind, Radix UI) +ui/desktop/ Wails v2 desktop app (React frontend + embedded gateway) +``` + +## Key Patterns + +- **Store layer:** Interface-based (`store.SessionStore`, `store.AgentStore`, etc.) with shared Dialect pattern in `store/base/`. PostgreSQL (`pg/`) and SQLite (`sqlitestore/`) implementations use `database/sql` + `pgx/v5/stdlib` + sqlx, raw SQL, `BuildMapUpdate()` and `BuildScopeClause()` helpers +- **Agent types:** `open` (per-user context, 7 files) vs `predefined` (shared context + USER.md per-user) +- **Agent identity:** Dual-identity pattern (agent_key vs UUID) applies to agents, teams, tenants. Rule: UUID for DB/FK/events, agent_key for logs/paths/UI. See `docs/agent-identity-conventions.md` +- **Context files:** `agent_context_files` (agent-level) + `user_context_files` (per-user), routed via `ContextFileInterceptor` +- **Providers:** Anthropic (native HTTP+SSE), OpenAI-compat (HTTP+SSE), DashScope (Alibaba Qwen), Codex CLI (stdio+MCP bridge), ACP (Anthropic Console Proxy), Codex (OpenAI), Vertex AI (GCP OAuth2 service account or ADC + OpenAI-compat endpoint, `internal/providers/vertex.go`). All use `RetryDo()` for retries. Loads from `llm_providers` table with encrypted API keys. ProviderAdapter enables pluggable implementations with ModelRegistry forward-compat resolver. Shared SSEScanner in `providers/sse_reader.go` for streaming providers +- **Pipeline:** 8-stage loop (context→history→prompt→think→act→observe→memory→summarize) with pluggable callbacks, always-on execution path +- **DomainEventBus:** Typed events with worker pool, dedup, retry. Used by consolidation pipeline and memory workers +- **3-tier memory:** Working (conversation) → Episodic (session summaries) → Semantic (KG). Progressive loading L0/L1/L2 with auto-inject for L0 +- **Knowledge Vault:** Document registry + [[wikilinks]] + hybrid search, query layer above existing stores, FS sync, unified search +- **Context propagation:** `store.WithAgentType(ctx)`, `store.WithUserID(ctx)`, `store.WithAgentID(ctx)`, `store.WithLocale(ctx)`, `store.WithTenantID(ctx)` +- **Request middleware:** Composable chain (cache, service tier, request guards), zero-alloc fast path for hot operations +- **Self-evolution:** Metrics → suggestions → auto-adapt. 3 progressive stages: metrics collection, suggestion analysis, guardrail-protected apply/rollback +- **Orchestration:** Delegate tool for inter-agent task delegation with agent_links, 3 delegation modes (auto/explicit/manual), token-aware work distribution. BatchQueue[T] generic for result aggregation +- **WebSocket protocol:** Frame types `req`/`res`/`event`. First request must be `connect` +- **Config:** JSON5 at `GOCLAW_CONFIG` env. Secrets in `.env.local` or env vars, never in config.json +- **Security:** Rate limiting, input guard (detection-only), CORS, shell deny patterns, SSRF protection, path traversal prevention, AES-256-GCM encryption. All security logs: `slog.Warn("security.*")` +- **Telegram formatting:** LLM output → `SanitizeAssistantContent()` → `markdownToTelegramHTML()` → `chunkHTML()` → `sendHTML()`. Tables rendered as ASCII in `
` tags
+- **i18n:** Web UI uses `i18next` with namespace-split locale files in `ui/web/src/i18n/locales/{lang}/`. Backend uses `internal/i18n` message catalog with `i18n.T(locale, key, args...)`. Locale propagated via `store.WithLocale(ctx)` — WS `connect` param `locale`, HTTP `Accept-Language` header. Supported: en (default), vi, zh. New user-facing strings: add key to `internal/i18n/keys.go`, add translations to all 3 catalog files. New UI strings: add key to all 3 locale dirs. Bootstrap templates (SOUL.md, etc.) stay English-only (LLM consumption).
+
+## Running
+
+```bash
+go build -o goclaw . && ./goclaw onboard && source .env.local && ./goclaw
+./goclaw migrate up                 # DB migrations
+# Integration tests (requires pgvector pg18 on port 5433)
+docker run -d --name pgtest -p 5433:5432 -e POSTGRES_PASSWORD=test -e POSTGRES_DB=goclaw_test pgvector/pgvector:pg18
+TEST_DATABASE_URL="postgres://postgres:test@localhost:5433/goclaw_test?sslmode=disable" \
+  go test -v -tags integration ./tests/integration/
+
+# Layered tests
+make test-invariants  # P0 - tenant isolation (blocking)
+make test-contracts   # P1 - API schemas (requires server)
+make test-scenarios   # P2 - user journeys (requires server)
+make test-critical    # P0 + P1 (pre-merge)
+
+cd ui/web && pnpm install && pnpm dev   # Web dashboard (dev)
+
+# Desktop (Wails + SQLite)
+cd ui/desktop && wails dev -tags sqliteonly  # Dev mode with hot reload (direct)
+make desktop-dev                             # Same as above via Makefile
+make desktop-build VERSION=0.1.0             # Build .app (macOS) or .exe (Windows)
+make desktop-dmg VERSION=0.1.0               # Create .dmg installer (macOS only)
+```
+
+## CI/CD & Releases
+
+### Workflows
+
+| Workflow | Trigger | Purpose |
+|----------|---------|---------|
+| `ci.yaml` | push main, PR→main/dev | Go build+test+vet, Web build |
+| `release.yaml` | tag `v[0-9]+.[0-9]+.[0-9]+` | Binaries + Docker (4 variants + web) + Discord |
+| `release-beta.yaml` | tag `v*-beta*` / `v*-rc*` | Beta binaries + Docker + GitHub prerelease |
+| `release-desktop.yaml` | tag `lite-v*` | Desktop app (macOS+Windows), auto prerelease for `-beta`/`-rc` tags |
+
+### Creating Releases
+
+**Standard release** — manual tag push after merging `dev` → `main`:
+```bash
+git tag v3.0.0 && git push origin v3.0.0
+```
+
+**Beta release** (from dev):
+```bash
+git tag v2.67.0-beta.1 && git push origin v2.67.0-beta.1   # standard beta
+git tag lite-v1.2.0-beta.1 && git push origin lite-v1.2.0-beta.1  # lite beta
+```
+
+**Desktop release:**
+```bash
+git tag lite-v1.1.0 && git push origin lite-v1.1.0   # stable
+git tag lite-v1.1.0-beta.1 && git push origin lite-v1.1.0-beta.1  # beta (prerelease)
+```
+
+### Docker Images
+
+Published to GHCR (`ghcr.io/nextlevelbuilder/goclaw`) and Docker Hub (`digitop/goclaw`).
+
+| Variant | Tag | Contents |
+|---------|-----|----------|
+| latest | `:latest`, `:vX.Y.Z` | Backend + web UI + Python |
+| base | `:base`, `:vX.Y.Z-base` | Backend only, no UI/runtimes |
+| full | `:full`, `:vX.Y.Z-full` | All runtimes + skills pre-installed |
+| web | `-web:latest` | Standalone web UI (Nginx) |
+| beta | `:beta`, `:vX.Y.Z-beta.N` | Beta builds from dev |
+
+OTel and Tailscale variants are not pre-built — build from source with the appropriate `--build-arg ENABLE_OTEL=true` or `-tags tsnet` flag if needed.
+
+### Tag Pattern Safety
+
+- `release.yaml`: tag-triggered (`v[0-9]+.[0-9]+.[0-9]+`) — clean semver only, no beta/rc
+- `release-beta.yaml`: tag-triggered (`v*-beta*`, `v*-rc*`) — never matches clean semver
+- `release-desktop.yaml`: tag-triggered (`lite-v*`) — `lite-` prefix prevents overlap
+- No workflow triggers overlap — each tag pattern is distinct. Merging to `main` only triggers CI, not release
+
+## Desktop Edition (Lite)
+
+- **Build tag:** `//go:build sqliteonly` — desktop binary includes only SQLite, no PostgreSQL
+- **Edition system:** `internal/edition/edition.go` — `Lite` preset auto-selected for SQLite backend. Check `edition.Current()` for feature limits
+- **Entry point:** `ui/desktop/main.go` + `ui/desktop/app.go` — Wails bindings, embedded gateway
+- **Secrets:** OS keyring (`go-keyring`) with file fallback at `~/.goclaw/secrets/`
+- **Data dir:** `~/.goclaw/data/` (SQLite DB, configs)
+- **Workspace:** `~/.goclaw/workspace/` (agent files, team workspace)
+- **Port:** 18790 (localhost only), configurable via `GOCLAW_PORT`
+- **WS params:** All WS method params use **camelCase** (`teamId`, `taskId`, `sessionKey`) — match Go struct `json:"..."` tags
+- **Version:** `cmd.Version` set via `-ldflags` at build time. Frontend calls `wails.getVersion()`
+- **Auto-update:** `internal/updater/updater.go` checks GitHub Releases for `lite-v*` tags. Frontend `UpdateBanner` shows notification
+- **Releases:** Tag `lite-v*` triggers `.github/workflows/release-desktop.yaml` → builds macOS (arm64+amd64) + Windows → GitHub Release
+- **Install scripts:** `scripts/install-lite.sh` (macOS), `scripts/install-lite.ps1` (Windows PowerShell)
+- **Lite limits:** 5 agents, 1 team, 5 members, 50 sessions. No channels, heartbeat, file storage UI, skill self-manage, KG, RBAC, multi-tenant
+- **Tool gating:** `TeamActionPolicy` in `internal/tools/team_action_policy.go` — lite blocks comment/review/approve/reject/attach/ask_user. `skill_manage`/`publish_skill` not registered in lite
+- **File serving:** 2-layer path isolation in `internal/http/files.go` — workspace boundary (all editions) + tenant scope (standard only with RBAC)
+
+## Plan Verification Rules
+
+Apply before finalizing any multi-phase plan. Trust-but-verify between scout → planner → final plan.
+
+### Verification discipline (what to verify)
+
+1. **Verify factual claims against code** — re-grep/re-count every number, path, endpoint. Don't copy from scout summaries.
+2. **Trace semantics, not just cite lines** — when plan references existing/upstream code, identify WHEN each field mutates and under WHAT conditions. Line-range citation without control-flow trace = how ports silently invert behavior. Check: every call, or specific branches only?
+3. **No fabricated identifiers / API families** — every symbol in plan must cite `file:line`. RED FLAGS: plausible-sounding wrappers (`Keyring`, `Validator`, `Manager`), centralized packages (`internal/security`, `internal/auth`) that may be scattered, OTel-style (`StartSpan/EndSpan`) when codebase is emit-based. When unsure, `go doc ` lists actual exported surface. Apply especially when plan says "reuse existing X".
+4. **Struct scope audit before adding state** — verify lifetime (per-request/session/agent/process) before adding a field to an existing struct. "Plausibly per-X" is a red flag — grep construction + ownership. Shared-instance state leaks across isolation boundaries.
+5. **Gate-premise test math** — before asserting "feature X triggers independently of Y", list all early-returns from function entry to X. Math-verify any fixture claiming "X without Y".
+6. **Port = config-shape match** — "faithful port" divergences in config field name/type are silent breaking changes for users copying upstream config. Match upstream shape, or explicitly flag each divergence with rationale in the phase file.
+7. **Verify external API endpoints via `docs-seeker`** — before writing endpoint into plan. Sibling APIs often use different roots.
+
+### Scope & coverage (where to look)
+
+8. **Grep delete scope deep** — `grep -rn '' .` whole repo. Stubs often have refs in catalogs/routing/switch cases. Enumerate ALL sites in todo.
+9. **Signature-change callers enumeration** — grep + list all callers explicitly. "Update all callers" insufficient.
+10. **Alias/shim coverage** — enumerate ALL exported symbols via `go doc `. Add compile-time signature guards.
+11. **Scout desktop and web separately** — `ui/desktop/frontend/` ≠ `ui/web/`. Different structure, i18n namespaces, test framework presence.
+
+### Phasing & ordering (when)
+
+12. **Re-scout on scope change** — if phase promotes from deferred → active, re-scout. Don't reuse brainstorm summary.
+13. **Cross-phase gates explicit** — "Phase N-1 merged + tests green" in phase Context. Execution order alone ≠ enforcement.
+14. **Zero-coverage characterization test = blocker step** — write byte/request-body fixture test BEFORE migration. Not "recommended".
+15. **i18n keys ordering** — add key + 3 catalogs as explicit todo step BEFORE handler code. Missing key = runtime crash.
+
+### Conventions & finalization
+
+16. **Context key style convention** — check existing `context.go` pattern before introducing new key types. Mixed = code smell.
+17. **Verify pass MANDATORY after rewrite** — spawn fresh Explore/grep to audit planner output. Don't trust self-validation.
+
+**Pattern to avoid:** user asks → planner writes → report "done".
+**Safer pattern:** user asks → scout → planner writes → audit-verify → report.
+
+**Red-team practice:** After planner completes, run `code-reviewer`/`brainstormer` in audit mode: "spot-check 15+ claims vs live codebase". Past catches: fabricated `crypto.Keyring`/`tracing.StartSpan` (agent-hooks plan); inverted TS-port semantics + wrong struct scope + misread early-return gate (context-pruning plan). See `plans/*/reports/audit-*.md` for concrete examples.
+
+## Post-Implementation Checklist
+
+After implementing or modifying Go code, run these checks:
+
+```bash
+go fix ./...                        # Apply Go version upgrades (run before commit)
+go build ./...                      # Compile check (PG build)
+go build -tags sqliteonly ./...     # Compile check (Desktop/SQLite build)
+go vet ./...                        # Static analysis
+go test -race ./tests/integration/  # Integration tests with race detector
+```
+
+Go conventions to follow:
+- Use `errors.Is(err, sentinel)` instead of `err == sentinel`
+- Use `switch/case` instead of `if/else if` chains on the same variable
+- Use `append(dst, src...)` instead of loop-based append
+- Always handle errors; don't ignore return values
+- **Migrations (dual-DB):** PostgreSQL and SQLite have **separate migration systems**. When adding schema changes: (1) PG: add SQL in `migrations/` + bump `RequiredSchemaVersion` in `internal/upgrade/version.go`. (2) SQLite: update `internal/store/sqlitestore/schema.sql` (full schema for fresh DBs) + add incremental patch in `schema.go` `migrations` map + bump `SchemaVersion` constant. **Always update both** — missing SQLite migrations cause desktop edition to crash on startup
+- **i18n strings:** When adding user-facing error messages, add key to `internal/i18n/keys.go` and translations to `catalog_en.go`, `catalog_vi.go`, `catalog_zh.go`. For UI strings, add to all locale JSON files in `ui/web/src/i18n/locales/{en,vi,zh}/`
+- **SQL safety:** When implementing or modifying SQL store code (`store/pg/*.go`), always verify: (1) All user inputs use parameterized queries (`$1, $2, ...`), never string concatenation — prevents SQL injection. (2) Queries are optimized — no N+1 queries, no unnecessary full table scans. (3) WHERE clauses, JOINs, and ORDER BY columns use existing indices — check migration files for available indexes
+- **DB query reuse:** Before adding a new DB query for key entities (teams, agents, sessions, users), check if the same data is already fetched earlier in the current flow/pipeline. Prefer passing resolved data through context, event payloads, or function params rather than re-querying. Duplicate queries waste DB resources and add latency
+- **Solution design:** When designing a fix or feature, identify the root cause first — don't just patch symptoms. Think through production scenarios (high concurrency, multi-tenant isolation, failure cascades, long-running sessions) to ensure the solution holds up. Prefer explicit configuration over runtime heuristics. Prefer the simplest solution that addresses the root cause directly
+- **Tenant-scope guards on admin writes:** `RoleAdmin` is not a tenant check. Writes to **global** tables (no `tenant_id` column — e.g. `builtin_tools`, disk config, package mgmt) must gate with `http.requireMasterScope` / WS `requireMasterScope(requireOwner(...))`. Writes to **tenant-scoped** tables must gate with `http.requireTenantAdmin` + SQL `WHERE tenant_id = $N`. Shared predicate: `store.IsMasterScope(ctx)`. See `CONTRIBUTING.md` → "Tenant-scope guards" for the full decision table and anti-patterns.
+- **Skip load / stress / benchmark tests.** Do NOT write throughput benchmarks, p95/p99 latency assertions, or `runtime.ReadMemStats`-based memory-leak tests for regular feature work. They flake on shared CI runners, waste runner time, and rarely catch real bugs. Only add load tests when explicitly requested for a specific investigation. For normal "prove it works" coverage, use unit + integration + chaos tests.
+
+## Mobile UI/UX Rules
+
+When implementing or modifying web UI components, follow these rules to ensure mobile compatibility:
+
+- **Viewport height:** Use `h-dvh` (dynamic viewport height), never `h-screen`. `h-screen` causes content to hide behind mobile browser chrome and virtual keyboards
+- **Input font-size:** All ``, `