diff --git a/.github/workflows/create_dmg.yml b/.github/workflows/create_dmg.yml
index 67fded40a..626318619 100644
--- a/.github/workflows/create_dmg.yml
+++ b/.github/workflows/create_dmg.yml
@@ -24,7 +24,7 @@ jobs:
go-version-file: go.mod
- name: Setup pnpm
- uses: pnpm/action-setup@v4
+ uses: pnpm/action-setup@v6
with:
version: 10.33.0
run_install: false
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 0e619dd27..39ad8810e 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -48,7 +48,7 @@ jobs:
go-version-file: go.mod
- name: Setup pnpm
- uses: pnpm/action-setup@v4
+ uses: pnpm/action-setup@v6
with:
version: 10.33.0
run_install: false
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c887bf493..1480d410d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -66,7 +66,7 @@ jobs:
go-version-file: go.mod
- name: Setup pnpm
- uses: pnpm/action-setup@v4
+ uses: pnpm/action-setup@v6
with:
version: 10.33.0
run_install: false
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cbb6a6347..a78c41c36 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,6 +35,8 @@ We are committed to maintaining a welcoming and respectful community. Be kind, c
For substantial new features, please open an issue first to discuss the design before writing code. This prevents wasted effort and ensures alignment with the project's direction.
+For documentation contributions, prefer the layout and naming conventions in [`docs/README.md`](docs/README.md). Run `make lint-docs` after adding or moving Markdown files to catch common consistency issues early.
+
---
## Getting Started
@@ -64,7 +66,7 @@ For substantial new features, please open an issue first to discuss the design b
```bash
make build # Build binary (runs go generate first)
make generate # Run go generate only
-make check # Full pre-commit check: deps + fmt + vet + test
+make check # Full pre-commit check: deps + fmt + vet + test + docs consistency checks
```
### Running Tests
@@ -81,9 +83,10 @@ go test -bench=. -benchmem -run='^$' ./... # Run benchmarks
make fmt # Format code
make vet # Static analysis
make lint # Full linter run
+make lint-docs # Check common documentation layout and naming conventions
```
-All CI checks must pass before a PR can be merged. Run `make check` locally before pushing to catch issues early.
+All CI checks must pass before a PR can be merged. Run `make check` locally before pushing to catch issues early, including the common docs consistency checks from `make lint-docs`.
---
diff --git a/Makefile b/Makefile
index afaa7c29a..c5d691c29 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: all build install uninstall clean help test build-all
+.PHONY: all build install uninstall clean help test build-all lint-docs
# Build variables
BINARY_NAME=picoclaw
@@ -308,9 +308,14 @@ test: generate
fmt:
@$(GOLANGCI_LINT) fmt
+## lint-docs: Check common documentation layout and naming conventions
+lint-docs:
+ @./scripts/lint-docs.sh
+
## lint: Run linters
lint:
@$(GOLANGCI_LINT) run --build-tags $(GO_BUILD_TAGS)
+ @./scripts/lint-docs.sh
## fix: Fix linting issues
fix:
@@ -326,8 +331,8 @@ update-deps:
@$(GO) get -u ./...
@$(GO) mod tidy
-## check: Run vet, fmt, and verify dependencies
-check: deps fmt vet test
+## check: Run deps, fmt, vet, tests, and docs consistency checks
+check: deps fmt vet test lint-docs
## run: Build and run picoclaw
run: build
diff --git a/README.md b/README.md
index 1ab514a29..5aac4bbc9 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-[中文](README.zh.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | **English**
+[中文](docs/project/README.zh.md) | [日本語](docs/project/README.ja.md) | [한국어](docs/project/README.ko.md) | [Português](docs/project/README.pt-br.md) | [Tiếng Việt](docs/project/README.vi.md) | [Français](docs/project/README.fr.md) | [Italiano](docs/project/README.it.md) | [Bahasa Indonesia](docs/project/README.id.md) | [Malay](docs/project/README.ms.md) | **English**
@@ -112,7 +112,7 @@ _*Recent builds may use 10-20MB due to rapid PR merges. Resource optimization is
-> **[Hardware Compatibility List](docs/hardware-compatibility.md)** — See all tested boards, from $5 RISC-V to Raspberry Pi to Android phones. Your board not listed? Submit a PR!
+> **[Hardware Compatibility List](docs/guides/hardware-compatibility.md)** — See all tested boards, from $5 RISC-V to Raspberry Pi to Android phones. Your board not listed? Submit a PR!
@@ -309,6 +309,7 @@ Use the TUI menus to: **1)** Configure a Provider -> **2)** Configure a Channel
For detailed TUI documentation, see [docs.picoclaw.io](https://docs.picoclaw.io).
+
### 📱 Android
Give your decade-old phone a second life! Turn it into a smart AI Assistant with PicoClaw.
@@ -379,7 +380,7 @@ This creates `~/.picoclaw/config.json` and the workspace directory.
> See `config/config.example.json` in the repo for a complete configuration template with all available options.
>
-> Please note: config.example.json format is version 0, with sensitive codes in it, and will be auto migrated to version 1+, then, the config.json will only store insensitive data, the sensitive codes will be stored in .security.yml, if you need manually modify the codes, please see `docs/security_configuration.md` for more details.
+> Please note: config.example.json format is version 0, with sensitive codes in it, and will be auto migrated to version 1+, then, the config.json will only store insensitive data, the sensitive codes will be stored in .security.yml, if you need manually modify the codes, please see `docs/security/security_configuration.md` for more details.
**3. Chat**
@@ -458,7 +459,7 @@ PicoClaw supports 30+ LLM providers through the `model_list` configuration. Use
}
```
-For full provider configuration details, see [Providers & Models](docs/providers.md).
+For full provider configuration details, see [Providers & Models](docs/guides/providers.md).
@@ -470,8 +471,8 @@ Talk to your PicoClaw through 18+ messaging platforms:
|---------|-------|----------|------|
| **Telegram** | Easy (bot token) | Long polling | [Guide](docs/channels/telegram/README.md) |
| **Discord** | Easy (bot token + intents) | WebSocket | [Guide](docs/channels/discord/README.md) |
-| **WhatsApp** | Easy (QR scan or bridge URL) | Native / Bridge | [Guide](docs/chat-apps.md#whatsapp) |
-| **Weixin** | Easy (Native QR scan) | iLink API | [Guide](docs/chat-apps.md#weixin) |
+| **WhatsApp** | Easy (QR scan or bridge URL) | Native / Bridge | [Guide](docs/guides/chat-apps.md#whatsapp) |
+| **Weixin** | Easy (Native QR scan) | iLink API | [Guide](docs/guides/chat-apps.md#weixin) |
| **QQ** | Easy (AppID + AppSecret) | WebSocket | [Guide](docs/channels/qq/README.md) |
| **Slack** | Easy (bot + app token) | Socket Mode | [Guide](docs/channels/slack/README.md) |
| **Matrix** | Medium (homeserver + token) | Sync API | [Guide](docs/channels/matrix/README.md) |
@@ -480,7 +481,7 @@ Talk to your PicoClaw through 18+ messaging platforms:
| **LINE** | Medium (credentials + webhook) | Webhook | [Guide](docs/channels/line/README.md) |
| **WeCom** | Easy (QR login or manual) | WebSocket | [Guide](docs/channels/wecom/README.md) |
| **VK** | Easy (group token) | Long Poll | [Guide](docs/channels/vk/README.md) |
-| **IRC** | Medium (server + nick) | IRC protocol | [Guide](docs/chat-apps.md#irc) |
+| **IRC** | Medium (server + nick) | IRC protocol | [Guide](docs/guides/chat-apps.md#irc) |
| **OneBot** | Medium (WebSocket URL) | OneBot v11 | [Guide](docs/channels/onebot/README.md) |
| **MaixCam** | Easy (enable) | TCP socket | [Guide](docs/channels/maixcam/README.md) |
| **Pico** | Easy (enable) | Native protocol | Built-in |
@@ -488,9 +489,9 @@ Talk to your PicoClaw through 18+ messaging platforms:
> All webhook-based channels share a single Gateway HTTP server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu uses WebSocket/SDK mode and does not use the shared HTTP server.
-> Log verbosity is controlled by `gateway.log_level` (default: `warn`). Supported values: `debug`, `info`, `warn`, `error`, `fatal`. Can also be set via `PICOCLAW_LOG_LEVEL`. See [Configuration](docs/configuration.md#gateway-log-level) for details.
+> Log verbosity is controlled by `gateway.log_level` (default: `warn`). Supported values: `debug`, `info`, `warn`, `error`, `fatal`. Can also be set via `PICOCLAW_LOG_LEVEL`. See [Configuration](docs/guides/configuration.md#gateway-log-level) for details.
-For detailed channel setup instructions, see [Chat Apps Configuration](docs/chat-apps.md).
+For detailed channel setup instructions, see [Chat Apps Configuration](docs/guides/chat-apps.md).
## 🔧 Tools
@@ -510,7 +511,7 @@ PicoClaw can search the web to provide up-to-date information. Configure in `too
### ⚙️ Other Tools
-PicoClaw includes built-in tools for file operations, code execution, scheduling, and more. See [Tools Configuration](docs/tools_configuration.md) for details.
+PicoClaw includes built-in tools for file operations, code execution, scheduling, and more. See [Tools Configuration](docs/reference/tools_configuration.md) for details.
## 🎯 Skills
@@ -547,7 +548,7 @@ Add to your `config.json`:
`tools.skills.github.*` is deprecated. Use `tools.skills.registries.github.*` instead.
-For more details, see [Tools Configuration - Skills](docs/tools_configuration.md#skills-tool).
+For more details, see [Tools Configuration - Skills](docs/reference/tools_configuration.md#skills-tool).
## 🔗 MCP (Model Context Protocol)
@@ -570,7 +571,7 @@ PicoClaw natively supports [MCP](https://modelcontextprotocol.io/) — connect a
}
```
-For full MCP configuration (stdio, SSE, HTTP transports, Tool Discovery), see [Tools Configuration - MCP](docs/tools_configuration.md#mcp-tool).
+For full MCP configuration (stdio, SSE, HTTP transports, Tool Discovery), see [Tools Configuration - MCP](docs/reference/tools_configuration.md#mcp-tool).
## Join the Agent Social Network
@@ -607,7 +608,7 @@ PicoClaw supports scheduled reminders and recurring tasks through the `cron` too
* **Recurring tasks**: "Remind me every 2 hours" -> triggers every 2 hours
* **Cron expressions**: "Remind me at 9am daily" -> uses cron expression
-See [docs/cron.md](docs/cron.md) for current schedule types, execution modes, command-job gates, and persistence details.
+See [docs/reference/cron.md](docs/reference/cron.md) for current schedule types, execution modes, command-job gates, and persistence details.
## 📚 Documentation
@@ -615,18 +616,18 @@ For detailed guides beyond this README:
| Topic | Description |
|-------|-------------|
-| [Docker & Quick Start](docs/docker.md) | Docker Compose setup, Launcher/Agent modes |
-| [Chat Apps](docs/chat-apps.md) | All 17+ channel setup guides |
-| [Configuration](docs/configuration.md) | Environment variables, workspace layout, security sandbox |
-| [Scheduled Tasks and Cron Jobs](docs/cron.md) | Cron schedule types, deliver modes, command gates, job storage |
-| [Providers & Models](docs/providers.md) | 30+ LLM providers, model routing, model_list configuration |
-| [Spawn & Async Tasks](docs/spawn-tasks.md) | Quick tasks, long tasks with spawn, async sub-agent orchestration |
-| [Hooks](docs/hooks/README.md) | Event-driven hook system: observers, interceptors, approval hooks |
-| [Steering](docs/steering.md) | Inject messages into a running agent loop between tool calls |
-| [SubTurn](docs/subturn.md) | Subagent coordination, concurrency control, lifecycle |
-| [Troubleshooting](docs/troubleshooting.md) | Common issues and solutions |
-| [Tools Configuration](docs/tools_configuration.md) | Per-tool enable/disable, exec policies, MCP, Skills |
-| [Hardware Compatibility](docs/hardware-compatibility.md) | Tested boards, minimum requirements |
+| [Docker & Quick Start](docs/guides/docker.md) | Docker Compose setup, Launcher/Agent modes |
+| [Chat Apps](docs/guides/chat-apps.md) | All 17+ channel setup guides |
+| [Configuration](docs/guides/configuration.md) | Environment variables, workspace layout, security sandbox |
+| [Scheduled Tasks and Cron Jobs](docs/reference/cron.md) | Cron schedule types, deliver modes, command gates, job storage |
+| [Providers & Models](docs/guides/providers.md) | 30+ LLM providers, model routing, model_list configuration |
+| [Spawn & Async Tasks](docs/guides/spawn-tasks.md) | Quick tasks, long tasks with spawn, async sub-agent orchestration |
+| [Hooks](docs/architecture/hooks/README.md) | Event-driven hook system: observers, interceptors, approval hooks |
+| [Steering](docs/architecture/steering.md) | Inject messages into a running agent loop between tool calls |
+| [SubTurn](docs/architecture/subturn.md) | Subagent coordination, concurrency control, lifecycle |
+| [Troubleshooting](docs/operations/troubleshooting.md) | Common issues and solutions |
+| [Tools Configuration](docs/reference/tools_configuration.md) | Per-tool enable/disable, exec policies, MCP, Skills |
+| [Hardware Compatibility](docs/guides/hardware-compatibility.md) | Tested boards, minimum requirements |
## 🤝 Contribute & Roadmap
diff --git a/cmd/membench/eval.go b/cmd/membench/eval.go
index bddee76fd..729c9f97f 100644
--- a/cmd/membench/eval.go
+++ b/cmd/membench/eval.go
@@ -36,6 +36,7 @@ type AggMetrics struct {
OverallHitRate float64 `json:"overallHitRate"`
ByCategory map[int]*CatMetrics `json:"byCategory"`
TotalQuestions int `json:"totalQuestions"`
+ ValidF1Count int `json:"validF1Count"`
}
// CatMetrics holds metrics for a single category.
@@ -43,6 +44,7 @@ type CatMetrics struct {
F1 float64 `json:"f1"`
HitRate float64 `json:"hitRate"`
QuestionCount int `json:"questionCount"`
+ ValidF1Count int `json:"validF1Count"`
}
// EvalLegacy evaluates using legacy session store (raw history + budget truncation).
@@ -201,38 +203,64 @@ func EvalSeahorse(
// aggregateMetrics computes overall and per-category metrics.
func aggregateMetrics(qaResults []QAResult) AggMetrics {
- byCat := map[int]*CatMetrics{}
+ type catAccum struct {
+ f1Sum float64
+ f1Count int
+ hitRateSum float64
+ hitRateCount int
+ }
+ byCatAcc := map[int]*catAccum{}
totalF1 := 0.0
totalHitRate := 0.0
+ validF1Count := 0
for _, qr := range qaResults {
- totalF1 += qr.TokenF1
- totalHitRate += qr.HitRate
- cat, ok := byCat[qr.Category]
- if !ok {
- cat = &CatMetrics{}
- byCat[qr.Category] = cat
+ // Skip sentinel -1.0 scores (LLM API/parse failures) from F1 averaging.
+ if qr.TokenF1 >= 0 {
+ totalF1 += qr.TokenF1
+ validF1Count++
}
- cat.F1 += qr.TokenF1
- cat.HitRate += qr.HitRate
- cat.QuestionCount++
+ totalHitRate += qr.HitRate
+ acc, ok := byCatAcc[qr.Category]
+ if !ok {
+ acc = &catAccum{}
+ byCatAcc[qr.Category] = acc
+ }
+ if qr.TokenF1 >= 0 {
+ acc.f1Sum += qr.TokenF1
+ acc.f1Count++
+ }
+ acc.hitRateSum += qr.HitRate
+ acc.hitRateCount++
}
- n := len(qaResults)
- if n == 0 {
- n = 1
+ nHit := len(qaResults)
+ if nHit == 0 {
+ nHit = 1
}
- agg := AggMetrics{
- OverallF1: totalF1 / float64(n),
- OverallHitRate: totalHitRate / float64(n),
+ byCat := map[int]*CatMetrics{}
+ for cat, acc := range byCatAcc {
+ cm := &CatMetrics{
+ QuestionCount: acc.hitRateCount,
+ ValidF1Count: acc.f1Count,
+ }
+ if acc.f1Count > 0 {
+ cm.F1 = acc.f1Sum / float64(acc.f1Count)
+ }
+ if acc.hitRateCount > 0 {
+ cm.HitRate = acc.hitRateSum / float64(acc.hitRateCount)
+ }
+ byCat[cat] = cm
+ }
+ var overallF1 float64
+ if validF1Count > 0 {
+ overallF1 = totalF1 / float64(validF1Count)
+ }
+ return AggMetrics{
+ OverallF1: overallF1,
+ OverallHitRate: totalHitRate / float64(nHit),
ByCategory: byCat,
TotalQuestions: len(qaResults),
+ ValidF1Count: validF1Count,
}
- for _, cat := range agg.ByCategory {
- if cat.QuestionCount > 0 {
- cat.F1 /= float64(cat.QuestionCount)
- cat.HitRate /= float64(cat.QuestionCount)
- }
- }
- return agg
}
// SaveResults writes per-sample eval results to JSON files.
@@ -277,27 +305,43 @@ func SaveAggregated(results []EvalResult, outDir string) error {
func computeModeAgg(results []EvalResult) AggMetrics {
agg := AggMetrics{ByCategory: map[int]*CatMetrics{}}
for _, r := range results {
- agg.OverallF1 += r.Agg.OverallF1 * float64(r.Agg.TotalQuestions)
+ // Backward compat: old eval JSON (token mode) without ValidF1Count → use TotalQuestions.
+ // LLM modes may legitimately have ValidF1Count==0 (all failures).
+ vf1 := r.Agg.ValidF1Count
+ if vf1 == 0 && r.Agg.TotalQuestions > 0 && !strings.HasSuffix(r.Mode, "-llm") {
+ vf1 = r.Agg.TotalQuestions
+ }
+ agg.OverallF1 += r.Agg.OverallF1 * float64(vf1)
agg.OverallHitRate += r.Agg.OverallHitRate * float64(r.Agg.TotalQuestions)
agg.TotalQuestions += r.Agg.TotalQuestions
+ agg.ValidF1Count += vf1
for cat, cm := range r.Agg.ByCategory {
existing, ok := agg.ByCategory[cat]
if !ok {
existing = &CatMetrics{}
agg.ByCategory[cat] = existing
}
- existing.F1 += cm.F1 * float64(cm.QuestionCount)
+ cvf1 := cm.ValidF1Count
+ if cvf1 == 0 && cm.QuestionCount > 0 && !strings.HasSuffix(r.Mode, "-llm") {
+ cvf1 = cm.QuestionCount
+ }
+ existing.F1 += cm.F1 * float64(cvf1)
existing.HitRate += cm.HitRate * float64(cm.QuestionCount)
existing.QuestionCount += cm.QuestionCount
+ existing.ValidF1Count += cvf1
}
}
+ if agg.ValidF1Count > 0 {
+ agg.OverallF1 /= float64(agg.ValidF1Count)
+ }
if agg.TotalQuestions > 0 {
- agg.OverallF1 /= float64(agg.TotalQuestions)
agg.OverallHitRate /= float64(agg.TotalQuestions)
}
for _, cat := range agg.ByCategory {
+ if cat.ValidF1Count > 0 {
+ cat.F1 /= float64(cat.ValidF1Count)
+ }
if cat.QuestionCount > 0 {
- cat.F1 /= float64(cat.QuestionCount)
cat.HitRate /= float64(cat.QuestionCount)
}
}
@@ -359,7 +403,9 @@ func printSection(title string, results []EvalResult) {
// PrintComparison outputs a human-readable comparison table to stdout.
func PrintComparison(results []EvalResult, llmResults []EvalResult) {
- printSection("No LLM generation", results)
+ if len(results) > 0 {
+ printSection("No LLM generation", results)
+ }
if len(llmResults) > 0 {
printSection("With LLM", llmResults)
}
diff --git a/cmd/membench/eval_llm.go b/cmd/membench/eval_llm.go
new file mode 100644
index 000000000..ee401d134
--- /dev/null
+++ b/cmd/membench/eval_llm.go
@@ -0,0 +1,346 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/sipeed/picoclaw/pkg/seahorse"
+)
+
+const answerSystemPrompt = `You are a helpful assistant. Given conversation context, answer the question concisely and accurately. If the answer is not in the context, say "I don't know". Answer in 1-3 sentences maximum.`
+
+const judgeSystemPrompt = `You are an impartial judge evaluating answer quality.
+Compare the candidate answer against the reference answer.
+Consider semantic equivalence — different wording expressing the same meaning should score high.
+
+Output ONLY a single integer score from 1 to 5:
+1 = completely wrong or irrelevant
+2 = partially related but mostly incorrect
+3 = partially correct, missing key details
+4 = mostly correct with minor omissions
+5 = fully correct, semantically equivalent
+
+Output ONLY the number, nothing else.`
+
+// generateAnswer asks the LLM to answer a question given retrieved context.
+func generateAnswer(ctx context.Context, client *LLMClient, contextText, question string) (string, error) {
+ // Truncate context to avoid exceeding model limits while preserving valid UTF-8.
+ contextRunes := []rune(contextText)
+ if len(contextRunes) > 6000 {
+ contextText = string(contextRunes[:6000]) + "\n... [truncated]"
+ }
+
+ userPrompt := fmt.Sprintf("## Conversation Context\n\n%s\n\n## Question\n\n%s", contextText, question)
+ return client.Complete(ctx, answerSystemPrompt, userPrompt)
+}
+
+// scoreRe matches the first standalone integer 1-5 in the judge response.
+var scoreRe = regexp.MustCompile(`\b([1-5])\b`)
+
+// judgeAnswer asks the LLM to score the candidate answer vs the gold answer.
+// Returns a score from 0.0 to 1.0, or -1.0 on parse failure.
+func judgeAnswer(
+ ctx context.Context,
+ judgeClient *LLMClient,
+ question, goldAnswer, candidateAnswer string,
+) (float64, error) {
+ userPrompt := fmt.Sprintf(
+ "Question: %s\n\nReference Answer: %s\n\nCandidate Answer: %s\n\nScore:",
+ question, goldAnswer, candidateAnswer,
+ )
+
+ response, err := judgeClient.Complete(ctx, judgeSystemPrompt, userPrompt)
+ if err != nil {
+ return -1.0, err
+ }
+
+ response = strings.TrimSpace(response)
+ if m := scoreRe.FindStringSubmatch(response); len(m) == 2 {
+ score, _ := strconv.Atoi(m[1])
+ return float64(score-1) / 4.0, nil // Normalize 1-5 to 0.0-1.0
+ }
+ log.Printf("WARNING: could not parse judge score from: %q, returning -1", response)
+ return -1.0, nil
+}
+
+// qaWork describes one QA evaluation unit.
+type qaWork struct {
+ sampleID string
+ qaIndex int
+ globalIndex int
+ totalQA int
+ qa *LocomoQA
+ contextText string
+ sample *LocomoSample
+}
+
+// qaResult collects one QA evaluation output.
+type qaResultOut struct {
+ index int // position in the flat QA list for ordering
+ result QAResult
+ answer string
+ score float64
+}
+
+// evalQAWorker processes a single QA item: generate answer + judge score.
+func evalQAWorker(
+ ctx context.Context,
+ w qaWork,
+ answerClient, judgeClient *LLMClient,
+ logPrefix string,
+) qaResultOut {
+ llmAnswer, err := generateAnswer(ctx, answerClient, w.contextText, w.qa.Question)
+ if err != nil {
+ log.Printf("WARN: LLM generation failed for sample %s Q%d: %v", w.sampleID, w.qaIndex, err)
+ llmAnswer = ""
+ }
+
+ score := -1.0
+ if llmAnswer != "" {
+ score, err = judgeAnswer(ctx, judgeClient, w.qa.Question, w.qa.AnswerString(), llmAnswer)
+ if err != nil {
+ log.Printf("WARN: LLM judge failed for sample %s Q%d: %v", w.sampleID, w.qaIndex, err)
+ }
+ }
+
+ hitRate := RecallHitRate(w.qa.Evidence, w.sample, w.contextText)
+
+ log.Printf("[%s] sample=%s q=%d/%d score=%.2f answer=%q",
+ logPrefix, w.sampleID, w.globalIndex, w.totalQA, score, truncateStr(llmAnswer, 80))
+
+ return qaResultOut{
+ index: w.globalIndex,
+ result: QAResult{
+ Question: w.qa.Question,
+ Category: w.qa.Category,
+ GoldAnswer: w.qa.AnswerString(),
+ TokenF1: score,
+ HitRate: hitRate,
+ },
+ answer: llmAnswer,
+ score: score,
+ }
+}
+
+// EvalLegacyLLM evaluates legacy store using LLM generation + LLM-as-Judge.
+func EvalLegacyLLM(
+ ctx context.Context,
+ samples []LocomoSample,
+ legacy *LegacyStore,
+ budgetTokens int,
+ answerClient, judgeClient *LLMClient,
+ concurrency int,
+) []EvalResult {
+ if concurrency < 1 {
+ concurrency = 1
+ }
+ totalQA := countTotalQA(samples)
+ results := make([]EvalResult, 0, len(samples))
+
+ for si := range samples {
+ sample := &samples[si]
+ history := legacy.GetHistory(sample.SampleID)
+
+ allContent := make([]string, 0, len(history))
+ for _, msg := range history {
+ allContent = append(allContent, msg.Content)
+ }
+
+ truncated, _ := BudgetTruncate(allContent, budgetTokens)
+ contextText := StringListToContent(truncated)
+
+ qaResults := make([]QAResult, len(sample.QA))
+
+ if concurrency <= 1 {
+ for qi := range sample.QA {
+ out := evalQAWorker(ctx, qaWork{
+ sampleID: sample.SampleID, qaIndex: qi,
+ globalIndex: si*len(sample.QA) + qi + 1, totalQA: totalQA,
+ qa: &sample.QA[qi], contextText: contextText, sample: sample,
+ }, answerClient, judgeClient, "legacy-llm")
+ qaResults[qi] = out.result
+ }
+ } else {
+ sem := make(chan struct{}, concurrency)
+ var wg sync.WaitGroup
+ for qi := range sample.QA {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ sem <- struct{}{}
+ defer func() { <-sem }()
+ out := evalQAWorker(ctx, qaWork{
+ sampleID: sample.SampleID, qaIndex: qi,
+ globalIndex: si*len(sample.QA) + qi + 1, totalQA: totalQA,
+ qa: &sample.QA[qi], contextText: contextText, sample: sample,
+ }, answerClient, judgeClient, "legacy-llm")
+ qaResults[qi] = out.result // safe: each goroutine writes distinct index
+ }()
+ }
+ wg.Wait()
+ }
+
+ results = append(results, EvalResult{
+ Mode: "legacy-llm",
+ SampleID: sample.SampleID,
+ QAResults: qaResults,
+ Agg: aggregateMetrics(qaResults),
+ })
+ }
+ return results
+}
+
+// buildSeahorseContext retrieves context for a seahorse QA item.
+func buildSeahorseContext(
+ ctx context.Context,
+ ir *SeahorseIngestResult,
+ sample *LocomoSample,
+ qa *LocomoQA,
+ budgetTokens int,
+) string {
+ store := ir.Engine.GetRetrieval().Store()
+ retrieval := ir.Engine.GetRetrieval()
+ convID := ir.ConvMap[sample.SampleID]
+
+ keywords := ExtractKeywords(qa.Question)
+ bestRank := map[int64]float64{}
+ for _, kw := range keywords {
+ searchResults, err := store.SearchMessages(ctx, seahorse.SearchInput{
+ Pattern: kw,
+ ConversationID: convID,
+ Limit: 20,
+ })
+ if err != nil {
+ continue
+ }
+ for _, sr := range searchResults {
+ if sr.MessageID > 0 {
+ if prev, ok := bestRank[sr.MessageID]; !ok || sr.Rank < prev {
+ bestRank[sr.MessageID] = sr.Rank
+ }
+ }
+ }
+ }
+
+ messageIDs := make([]int64, 0, len(bestRank))
+ for id := range bestRank {
+ messageIDs = append(messageIDs, id)
+ }
+ sort.Slice(messageIDs, func(i, j int) bool {
+ return bestRank[messageIDs[i]] < bestRank[messageIDs[j]]
+ })
+
+ var contentParts []string
+ if len(messageIDs) > 0 {
+ expandResult, err := retrieval.ExpandMessages(ctx, messageIDs)
+ if err == nil {
+ for _, msg := range expandResult.Messages {
+ contentParts = append(contentParts, msg.Content)
+ }
+ }
+ }
+ if len(contentParts) == 0 {
+ return ""
+ }
+ truncated, _ := BudgetTruncate(contentParts, budgetTokens)
+ return StringListToContent(truncated)
+}
+
+// EvalSeahorseLLM evaluates seahorse retrieval using LLM generation + LLM-as-Judge.
+func EvalSeahorseLLM(
+ ctx context.Context,
+ samples []LocomoSample,
+ ir *SeahorseIngestResult,
+ budgetTokens int,
+ answerClient, judgeClient *LLMClient,
+ concurrency int,
+) []EvalResult {
+ if concurrency < 1 {
+ concurrency = 1
+ }
+ totalQA := countTotalQA(samples)
+ results := make([]EvalResult, 0, len(samples))
+
+ for si := range samples {
+ sample := &samples[si]
+ if _, ok := ir.ConvMap[sample.SampleID]; !ok {
+ log.Printf("WARN: no conversation ID for sample %s", sample.SampleID)
+ continue
+ }
+
+ qaResults := make([]QAResult, len(sample.QA))
+
+ evalOne := func(qi int) {
+ qa := &sample.QA[qi]
+ contextText := buildSeahorseContext(ctx, ir, sample, qa, budgetTokens)
+ if contextText == "" {
+ qaResults[qi] = QAResult{
+ Question: qa.Question,
+ Category: qa.Category,
+ GoldAnswer: qa.AnswerString(),
+ TokenF1: 0.0,
+ HitRate: 0.0,
+ }
+ log.Printf("[seahorse-llm] sample=%s q=%d/%d score=0.00 answer=(no context)",
+ sample.SampleID, si*len(sample.QA)+qi+1, totalQA)
+ return
+ }
+ out := evalQAWorker(ctx, qaWork{
+ sampleID: sample.SampleID, qaIndex: qi,
+ globalIndex: si*len(sample.QA) + qi + 1, totalQA: totalQA,
+ qa: qa, contextText: contextText, sample: sample,
+ }, answerClient, judgeClient, "seahorse-llm")
+ qaResults[qi] = out.result
+ }
+
+ if concurrency <= 1 {
+ for qi := range sample.QA {
+ evalOne(qi)
+ }
+ } else {
+ sem := make(chan struct{}, concurrency)
+ var wg sync.WaitGroup
+ for qi := range sample.QA {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ sem <- struct{}{}
+ defer func() { <-sem }()
+ evalOne(qi)
+ }()
+ }
+ wg.Wait()
+ }
+
+ results = append(results, EvalResult{
+ Mode: "seahorse-llm",
+ SampleID: sample.SampleID,
+ QAResults: qaResults,
+ Agg: aggregateMetrics(qaResults),
+ })
+ }
+ return results
+}
+
+func countTotalQA(samples []LocomoSample) int {
+ n := 0
+ for i := range samples {
+ n += len(samples[i].QA)
+ }
+ return n
+}
+
+func truncateStr(s string, maxLen int) string {
+ s = strings.ReplaceAll(s, "\n", " ")
+ runes := []rune(s)
+ if len(runes) > maxLen {
+ return string(runes[:maxLen]) + "..."
+ }
+ return s
+}
diff --git a/cmd/membench/eval_test.go b/cmd/membench/eval_test.go
index d500a38ca..32dea07c9 100644
--- a/cmd/membench/eval_test.go
+++ b/cmd/membench/eval_test.go
@@ -102,3 +102,81 @@ func TestComputeModeAgg(t *testing.T) {
t.Errorf("TotalQuestions = %d, want 10", got.TotalQuestions)
}
}
+
+func TestAggregateMetricsSentinel(t *testing.T) {
+ qa := []QAResult{
+ {Category: 1, TokenF1: 0.8, HitRate: 0.5},
+ {Category: 1, TokenF1: -1.0, HitRate: 0.3},
+ {Category: 1, TokenF1: 0.4, HitRate: 0.7},
+ }
+ agg := aggregateMetrics(qa)
+
+ if agg.ValidF1Count != 2 {
+ t.Errorf("ValidF1Count = %d, want 2", agg.ValidF1Count)
+ }
+ if agg.TotalQuestions != 3 {
+ t.Errorf("TotalQuestions = %d, want 3", agg.TotalQuestions)
+ }
+ wantF1 := (0.8 + 0.4) / 2.0
+ if math.Abs(agg.OverallF1-wantF1) > 1e-9 {
+ t.Errorf("OverallF1 = %.6f, want %.6f", agg.OverallF1, wantF1)
+ }
+ wantHR := (0.5 + 0.3 + 0.7) / 3.0
+ if math.Abs(agg.OverallHitRate-wantHR) > 1e-9 {
+ t.Errorf("OverallHitRate = %.6f, want %.6f", agg.OverallHitRate, wantHR)
+ }
+}
+
+func TestAggregateMetricsAllSentinel(t *testing.T) {
+ qa := []QAResult{
+ {Category: 1, TokenF1: -1.0, HitRate: 0.5},
+ {Category: 1, TokenF1: -1.0, HitRate: 0.3},
+ }
+ agg := aggregateMetrics(qa)
+
+ if agg.ValidF1Count != 0 {
+ t.Errorf("ValidF1Count = %d, want 0", agg.ValidF1Count)
+ }
+ if agg.OverallF1 != 0 {
+ t.Errorf("OverallF1 = %.6f, want 0", agg.OverallF1)
+ }
+}
+
+func TestComputeModeAggSentinelWeighting(t *testing.T) {
+ results := []EvalResult{
+ {
+ Mode: "test",
+ SampleID: "s1",
+ QAResults: []QAResult{
+ {Category: 1, TokenF1: 0.8, HitRate: 0.5},
+ {Category: 1, TokenF1: -1.0, HitRate: 0.3},
+ },
+ },
+ {
+ Mode: "test",
+ SampleID: "s2",
+ QAResults: []QAResult{
+ {Category: 1, TokenF1: 0.4, HitRate: 0.6},
+ {Category: 1, TokenF1: 0.6, HitRate: 0.8},
+ },
+ },
+ }
+ for i := range results {
+ results[i].Agg = aggregateMetrics(results[i].QAResults)
+ }
+
+ got := computeModeAgg(results)
+
+ // s1: ValidF1Count=1, F1=0.8; s2: ValidF1Count=2, F1=0.5
+ // Weighted: (0.8*1 + 0.5*2) / 3 = 1.8/3 = 0.6
+ wantF1 := 0.6
+ if math.Abs(got.OverallF1-wantF1) > 1e-9 {
+ t.Errorf("OverallF1 = %.6f, want %.6f", got.OverallF1, wantF1)
+ }
+ if got.ValidF1Count != 3 {
+ t.Errorf("ValidF1Count = %d, want 3", got.ValidF1Count)
+ }
+ if got.TotalQuestions != 4 {
+ t.Errorf("TotalQuestions = %d, want 4", got.TotalQuestions)
+ }
+}
diff --git a/cmd/membench/llm_client.go b/cmd/membench/llm_client.go
new file mode 100644
index 000000000..6c62424da
--- /dev/null
+++ b/cmd/membench/llm_client.go
@@ -0,0 +1,198 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+ "time"
+)
+
+// LLMClient wraps an OpenAI-compatible chat completion endpoint.
+type LLMClient struct {
+ BaseURL string
+ Model string
+ APIKey string
+ NoThinking bool // send chat_template_kwargs to disable thinking (llama.cpp specific)
+ MaxRetries int // max retry attempts for transient errors (0 = no retry)
+ Client *http.Client
+}
+
+// LLMClientOptions configures the LLM client.
+type LLMClientOptions struct {
+ BaseURL string
+ Model string
+ APIKey string
+ Timeout time.Duration
+ NoThinking bool
+ MaxRetries int // max retry attempts (default 3)
+}
+
+// NewLLMClient creates a client for an OpenAI-compatible chat completion API.
+func NewLLMClient(opts LLMClientOptions) *LLMClient {
+ if opts.Timeout == 0 {
+ opts.Timeout = 120 * time.Second
+ }
+ maxRetries := opts.MaxRetries
+ if maxRetries < 0 {
+ maxRetries = 3
+ }
+ return &LLMClient{
+ BaseURL: strings.TrimRight(opts.BaseURL, "/"),
+ Model: opts.Model,
+ APIKey: opts.APIKey,
+ NoThinking: opts.NoThinking,
+ MaxRetries: maxRetries,
+ Client: &http.Client{
+ Timeout: opts.Timeout,
+ },
+ }
+}
+
+type chatRequest struct {
+ Model string `json:"model"`
+ Messages []chatMessage `json:"messages"`
+ Temperature float64 `json:"temperature"`
+ MaxTokens int `json:"max_tokens"`
+ ChatTemplateKwargs map[string]any `json:"chat_template_kwargs,omitempty"` // llama.cpp
+ Think *bool `json:"think,omitempty"` // Ollama
+ Thinking map[string]any `json:"thinking,omitempty"` // GLM (智谱)
+}
+
+type chatMessage struct {
+ Role string `json:"role"`
+ Content string `json:"content"`
+}
+
+type chatResponse struct {
+ Choices []struct {
+ Message struct {
+ Content string `json:"content"`
+ ReasoningContent string `json:"reasoning_content,omitempty"`
+ } `json:"message"`
+ } `json:"choices"`
+}
+
+// Complete sends a chat completion request and returns the assistant's reply.
+func (c *LLMClient) Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error) {
+ sysContent := systemPrompt
+ if c.NoThinking && sysContent != "" {
+ // Prepend /no_think tag — works with Ollama /v1 endpoint and
+ // Qwen chat templates where the JSON think field is ignored.
+ sysContent = "/no_think\n" + sysContent
+ }
+ messages := []chatMessage{}
+ if sysContent != "" {
+ messages = append(messages, chatMessage{Role: "system", Content: sysContent})
+ }
+ messages = append(messages, chatMessage{Role: "user", Content: userPrompt})
+
+ body := chatRequest{
+ Model: c.Model,
+ Messages: messages,
+ Temperature: 0.1,
+ MaxTokens: 512,
+ }
+ if c.NoThinking {
+ // llama.cpp: chat_template_kwargs
+ body.ChatTemplateKwargs = map[string]any{
+ "enable_thinking": false,
+ }
+ // Ollama (0.9+): think field
+ thinkFalse := false
+ body.Think = &thinkFalse
+ // GLM (智谱): thinking field
+ body.Thinking = map[string]any{
+ "type": "disabled",
+ }
+ }
+
+ jsonBody, err := json.Marshal(body)
+ if err != nil {
+ return "", fmt.Errorf("marshal request: %w", err)
+ }
+
+ endpoint := strings.TrimRight(c.BaseURL, "/") + "/chat/completions"
+ req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(jsonBody))
+ if err != nil {
+ return "", fmt.Errorf("create request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+ if c.APIKey != "" {
+ req.Header.Set("Authorization", "Bearer "+c.APIKey)
+ }
+
+ var respBody []byte
+ var lastErr error
+ for attempt := 0; attempt <= c.MaxRetries; attempt++ {
+ if attempt > 0 {
+ backoff := time.Duration(1<<(attempt-1)) * time.Second // 1s, 2s, 4s, ...
+ log.Printf("LLM retry %d/%d after %v: %v", attempt, c.MaxRetries, backoff, lastErr)
+ select {
+ case <-ctx.Done():
+ return "", ctx.Err()
+ case <-time.After(backoff):
+ }
+ // Rebuild request (body reader is consumed)
+ req, err = http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(jsonBody))
+ if err != nil {
+ return "", fmt.Errorf("create request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+ if c.APIKey != "" {
+ req.Header.Set("Authorization", "Bearer "+c.APIKey)
+ }
+ }
+
+ var resp *http.Response
+ resp, lastErr = c.Client.Do(req)
+ if lastErr != nil {
+ continue // network/timeout error → retry
+ }
+
+ respBody, lastErr = io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if lastErr != nil {
+ continue
+ }
+
+ if resp.StatusCode == 429 || resp.StatusCode >= 500 {
+ lastErr = fmt.Errorf("API error %d: %s", resp.StatusCode, string(respBody))
+ continue // rate limit or server error → retry
+ }
+ if resp.StatusCode != 200 {
+ return "", fmt.Errorf("API error %d: %s", resp.StatusCode, string(respBody))
+ }
+
+ lastErr = nil
+ break
+ }
+ if lastErr != nil {
+ return "", fmt.Errorf("after %d retries: %w", c.MaxRetries, lastErr)
+ }
+
+ var chatResp chatResponse
+ if err := json.Unmarshal(respBody, &chatResp); err != nil {
+ return "", fmt.Errorf("parse response: %w", err)
+ }
+ if len(chatResp.Choices) == 0 {
+ return "", fmt.Errorf("no choices in response")
+ }
+ content := strings.TrimSpace(chatResp.Choices[0].Message.Content)
+ // Strip any residual ... blocks
+ if idx := strings.Index(content, ""); idx >= 0 {
+ content = strings.TrimSpace(content[idx+len(""):])
+ }
+ // Fallback: GLM/DeepSeek put thinking output in reasoning_content when thinking is enabled
+ if content == "" && chatResp.Choices[0].Message.ReasoningContent != "" {
+ content = strings.TrimSpace(chatResp.Choices[0].Message.ReasoningContent)
+ }
+ if content == "" {
+ return "", fmt.Errorf("empty LLM response")
+ }
+ return content, nil
+}
diff --git a/cmd/membench/main.go b/cmd/membench/main.go
index 0c5a9387a..c07bb3471 100644
--- a/cmd/membench/main.go
+++ b/cmd/membench/main.go
@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"strings"
+ "time"
"github.com/spf13/cobra"
@@ -15,10 +16,22 @@ import (
)
var (
- flagData string
- flagOut string
- flagMode string
- flagBudget int
+ flagData string
+ flagOut string
+ flagMode string
+ flagBudget int
+ flagEvalMode string
+ flagAPIBase string
+ flagAPIKey string
+ flagModel string
+ flagNoThinking bool
+ flagLimit int
+ flagTimeout int
+ flagRetries int
+ flagJudgeModel string
+ flagJudgeAPIBase string
+ flagJudgeAPIKey string
+ flagConcurrency int
)
func main() {
@@ -48,6 +61,22 @@ func main() {
evalCmd.Flags().StringVar(&flagOut, "out", "./bench-out", "output working directory")
evalCmd.Flags().StringVar(&flagMode, "mode", "all", "modes to evaluate: legacy, seahorse, or all")
evalCmd.Flags().IntVar(&flagBudget, "budget", 4000, "token budget for retrieval")
+ evalCmd.Flags().
+ StringVar(&flagEvalMode, "eval-mode", "token", "evaluation mode: token (direct match) or llm (LLM-as-Judge)")
+ evalCmd.Flags().
+ StringVar(&flagAPIBase, "api-base", "", "API base URL with version path, e.g. http://host/v1 (default: http://127.0.0.1:8080/v1, env: MEMBENCH_API_BASE)")
+ evalCmd.Flags().StringVar(&flagAPIKey, "api-key", "", "API key for the LLM endpoint (env: MEMBENCH_API_KEY)")
+ evalCmd.Flags().StringVar(&flagModel, "model", "", "model name for LLM eval (env: MEMBENCH_MODEL)")
+ evalCmd.Flags().
+ BoolVar(&flagNoThinking, "no-thinking", false, "disable thinking mode via chat_template_kwargs (llama.cpp + Qwen)")
+ evalCmd.Flags().IntVar(&flagLimit, "limit", 0, "max QA questions per sample (0 = all)")
+ evalCmd.Flags().IntVar(&flagTimeout, "timeout", 120, "HTTP timeout in seconds for LLM requests")
+ evalCmd.Flags().IntVar(&flagRetries, "retries", 3, "max retry attempts for transient LLM errors (timeout/5xx/429)")
+ evalCmd.Flags().StringVar(&flagJudgeModel, "judge-model", "", "model for judge scoring (defaults to --model)")
+ evalCmd.Flags().
+ StringVar(&flagJudgeAPIBase, "judge-api-base", "", "API base URL for judge model (defaults to --api-base)")
+ evalCmd.Flags().StringVar(&flagJudgeAPIKey, "judge-api-key", "", "API key for judge model (defaults to --api-key)")
+ evalCmd.Flags().IntVar(&flagConcurrency, "concurrency", 1, "number of concurrent QA evaluations")
reportCmd := &cobra.Command{
Use: "report",
@@ -65,6 +94,22 @@ func main() {
runCmd.Flags().StringVar(&flagOut, "out", "./bench-out", "output working directory")
runCmd.Flags().StringVar(&flagMode, "mode", "all", "modes to run: legacy, seahorse, or all")
runCmd.Flags().IntVar(&flagBudget, "budget", 4000, "token budget for retrieval")
+ runCmd.Flags().
+ StringVar(&flagEvalMode, "eval-mode", "token", "evaluation mode: token (direct match) or llm (LLM-as-Judge)")
+ runCmd.Flags().
+ StringVar(&flagAPIBase, "api-base", "", "API base URL with version path, e.g. http://host/v1 (default: http://127.0.0.1:8080/v1, env: MEMBENCH_API_BASE)")
+ runCmd.Flags().StringVar(&flagAPIKey, "api-key", "", "API key for the LLM endpoint (env: MEMBENCH_API_KEY)")
+ runCmd.Flags().StringVar(&flagModel, "model", "", "model name for LLM eval (env: MEMBENCH_MODEL)")
+ runCmd.Flags().
+ BoolVar(&flagNoThinking, "no-thinking", false, "disable thinking mode via chat_template_kwargs (llama.cpp + Qwen)")
+ runCmd.Flags().IntVar(&flagLimit, "limit", 0, "max QA questions per sample (0 = all)")
+ runCmd.Flags().IntVar(&flagTimeout, "timeout", 120, "HTTP timeout in seconds for LLM requests")
+ runCmd.Flags().IntVar(&flagRetries, "retries", 3, "max retry attempts for transient LLM errors (timeout/5xx/429)")
+ runCmd.Flags().StringVar(&flagJudgeModel, "judge-model", "", "model for judge scoring (defaults to --model)")
+ runCmd.Flags().
+ StringVar(&flagJudgeAPIBase, "judge-api-base", "", "API base URL for judge model (defaults to --api-base)")
+ runCmd.Flags().StringVar(&flagJudgeAPIKey, "judge-api-key", "", "API key for judge model (defaults to --api-key)")
+ runCmd.Flags().IntVar(&flagConcurrency, "concurrency", 1, "number of concurrent QA evaluations")
rootCmd.AddCommand(ingestCmd, evalCmd, reportCmd, runCmd)
@@ -136,7 +181,50 @@ func runEval(cmd *cobra.Command, args []string) error {
}
log.Printf("Loaded %d samples", len(samples))
- var allResults []EvalResult
+ if flagLimit > 0 {
+ for i := range samples {
+ if len(samples[i].QA) > flagLimit {
+ samples[i].QA = samples[i].QA[:flagLimit]
+ }
+ }
+ log.Printf("Limited to %d QA per sample", flagLimit)
+ }
+
+ evalMode := strings.ToLower(strings.TrimSpace(flagEvalMode))
+ var useLLM bool
+ switch evalMode {
+ case "token":
+ useLLM = false
+ case "llm":
+ useLLM = true
+ default:
+ return fmt.Errorf("invalid --eval-mode %q: must be token or llm", flagEvalMode)
+ }
+ var answerClient, judgeClient *LLMClient
+ if useLLM {
+ opts, err := buildLLMOptions()
+ if err != nil {
+ return err
+ }
+ answerClient = NewLLMClient(opts)
+ judgeClient = answerClient // default: same client
+ if flagJudgeModel != "" {
+ jOpts := opts // copy base settings
+ jOpts.Model = flagJudgeModel
+ if flagJudgeAPIBase != "" {
+ jOpts.BaseURL = flagJudgeAPIBase
+ }
+ if flagJudgeAPIKey != "" {
+ jOpts.APIKey = flagJudgeAPIKey
+ }
+ judgeClient = NewLLMClient(jOpts)
+ log.Printf("Judge model: model=%s base=%s no-thinking=%v", jOpts.Model, jOpts.BaseURL, jOpts.NoThinking)
+ }
+ log.Printf("LLM eval mode: model=%s base=%s no-thinking=%v concurrency=%d",
+ opts.Model, opts.BaseURL, opts.NoThinking, flagConcurrency)
+ }
+
+ var tokenResults, llmResults []EvalResult
for _, mode := range modes {
switch mode {
@@ -145,21 +233,34 @@ func runEval(cmd *cobra.Command, args []string) error {
for i := range samples {
legacy.IngestSample(&samples[i])
}
- results := EvalLegacy(ctx, samples, legacy, flagBudget)
- allResults = append(allResults, results...)
- log.Printf("legacy: evaluated %d samples", len(results))
+ if useLLM {
+ results := EvalLegacyLLM(ctx, samples, legacy, flagBudget, answerClient, judgeClient, flagConcurrency)
+ llmResults = append(llmResults, results...)
+ log.Printf("legacy-llm: evaluated %d samples", len(results))
+ } else {
+ results := EvalLegacy(ctx, samples, legacy, flagBudget)
+ tokenResults = append(tokenResults, results...)
+ log.Printf("legacy: evaluated %d samples", len(results))
+ }
case "seahorse":
dbPath := filepath.Join(flagOut, "seahorse.db")
ir, err := IngestSeahorse(ctx, samples, dbPath)
if err != nil {
return fmt.Errorf("ingest seahorse: %w", err)
}
- results := EvalSeahorse(ctx, samples, ir, flagBudget)
- allResults = append(allResults, results...)
- log.Printf("seahorse: evaluated %d samples", len(results))
+ if useLLM {
+ results := EvalSeahorseLLM(ctx, samples, ir, flagBudget, answerClient, judgeClient, flagConcurrency)
+ llmResults = append(llmResults, results...)
+ log.Printf("seahorse-llm: evaluated %d samples", len(results))
+ } else {
+ results := EvalSeahorse(ctx, samples, ir, flagBudget)
+ tokenResults = append(tokenResults, results...)
+ log.Printf("seahorse: evaluated %d samples", len(results))
+ }
}
}
+ allResults := append(tokenResults, llmResults...)
if err := SaveResults(allResults, flagOut); err != nil {
return fmt.Errorf("save results: %w", err)
}
@@ -167,7 +268,7 @@ func runEval(cmd *cobra.Command, args []string) error {
return fmt.Errorf("save aggregated: %w", err)
}
- PrintComparison(allResults, nil)
+ PrintComparison(tokenResults, llmResults)
return nil
}
@@ -199,10 +300,62 @@ func runReport(cmd *cobra.Command, args []string) error {
return fmt.Errorf("no eval results found in %s", flagOut)
}
- PrintComparison(allResults, nil)
+ var tokenResults, llmResults []EvalResult
+ for _, r := range allResults {
+ if strings.HasSuffix(r.Mode, "-llm") {
+ llmResults = append(llmResults, r)
+ } else {
+ tokenResults = append(tokenResults, r)
+ }
+ }
+ PrintComparison(tokenResults, llmResults)
return nil
}
func runAll(cmd *cobra.Command, args []string) error {
return runEval(cmd, args)
}
+
+// envOrFlag returns the flag value if non-empty, otherwise falls back to the
+// environment variable.
+func envOrFlag(flag, envKey string) string {
+ if flag != "" {
+ return flag
+ }
+ return os.Getenv(envKey)
+}
+
+// buildLLMOptions resolves LLM client configuration from flags and environment
+// variables. Flag values take precedence over environment variables.
+//
+// Environment variables:
+//
+// MEMBENCH_API_BASE – OpenAI-compatible base URL (default http://127.0.0.1:8080/v1)
+// MEMBENCH_API_KEY – Bearer token for the endpoint
+// MEMBENCH_MODEL – Model name to send in the request
+func buildLLMOptions() (LLMClientOptions, error) {
+ base := envOrFlag(flagAPIBase, "MEMBENCH_API_BASE")
+ if base == "" {
+ base = "http://127.0.0.1:8080/v1"
+ }
+ model := envOrFlag(flagModel, "MEMBENCH_MODEL")
+ if model == "" {
+ return LLMClientOptions{}, fmt.Errorf(
+ "--model or MEMBENCH_MODEL is required for LLM eval mode",
+ )
+ }
+ apiKey := envOrFlag(flagAPIKey, "MEMBENCH_API_KEY")
+
+ if flagTimeout <= 0 {
+ return LLMClientOptions{}, fmt.Errorf("--timeout must be > 0, got %d", flagTimeout)
+ }
+
+ return LLMClientOptions{
+ BaseURL: base,
+ Model: model,
+ APIKey: apiKey,
+ NoThinking: flagNoThinking,
+ Timeout: time.Duration(flagTimeout) * time.Second,
+ MaxRetries: flagRetries,
+ }, nil
+}
diff --git a/cmd/picoclaw/internal/auth/helpers.go b/cmd/picoclaw/internal/auth/helpers.go
index 531cb76aa..523f6a16a 100644
--- a/cmd/picoclaw/internal/auth/helpers.go
+++ b/cmd/picoclaw/internal/auth/helpers.go
@@ -17,24 +17,24 @@ import (
)
const (
- supportedProvidersMsg = "supported providers: openai, anthropic, google-antigravity"
+ supportedProvidersMsg = "supported providers: openai, anthropic, google-antigravity, antigravity"
defaultAnthropicModel = "claude-sonnet-4.6"
)
-func authLoginCmd(provider string, useDeviceCode bool, useOauth bool) error {
+func authLoginCmd(provider string, useDeviceCode bool, useOauth bool, noBrowser bool) error {
switch provider {
case "openai":
- return authLoginOpenAI(useDeviceCode)
+ return authLoginOpenAI(useDeviceCode, noBrowser)
case "anthropic":
return authLoginAnthropic(useOauth)
case "google-antigravity", "antigravity":
- return authLoginGoogleAntigravity()
+ return authLoginGoogleAntigravity(noBrowser)
default:
return fmt.Errorf("unsupported provider: %s (%s)", provider, supportedProvidersMsg)
}
}
-func authLoginOpenAI(useDeviceCode bool) error {
+func authLoginOpenAI(useDeviceCode bool, noBrowser bool) error {
cfg := auth.OpenAIOAuthConfig()
var cred *auth.AuthCredential
@@ -43,7 +43,7 @@ func authLoginOpenAI(useDeviceCode bool) error {
if useDeviceCode {
cred, err = auth.LoginDeviceCode(cfg)
} else {
- cred, err = auth.LoginBrowser(cfg)
+ cred, err = auth.LoginBrowserWithOptions(cfg, auth.LoginBrowserOptions{NoBrowser: noBrowser})
}
if err != nil {
@@ -92,10 +92,10 @@ func authLoginOpenAI(useDeviceCode bool) error {
return nil
}
-func authLoginGoogleAntigravity() error {
+func authLoginGoogleAntigravity(noBrowser bool) error {
cfg := auth.GoogleAntigravityOAuthConfig()
- cred, err := auth.LoginBrowser(cfg)
+ cred, err := auth.LoginBrowserWithOptions(cfg, auth.LoginBrowserOptions{NoBrowser: noBrowser})
if err != nil {
return fmt.Errorf("login failed: %w", err)
}
diff --git a/cmd/picoclaw/internal/auth/login.go b/cmd/picoclaw/internal/auth/login.go
index afbe098aa..b9b44db34 100644
--- a/cmd/picoclaw/internal/auth/login.go
+++ b/cmd/picoclaw/internal/auth/login.go
@@ -7,6 +7,7 @@ func newLoginCommand() *cobra.Command {
provider string
useDeviceCode bool
useOauth bool
+ noBrowser bool
)
cmd := &cobra.Command{
@@ -14,12 +15,15 @@ func newLoginCommand() *cobra.Command {
Short: "Login via OAuth or paste token",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
- return authLoginCmd(provider, useDeviceCode, useOauth)
+ return authLoginCmd(provider, useDeviceCode, useOauth, noBrowser)
},
}
- cmd.Flags().StringVarP(&provider, "provider", "p", "", "Provider to login with (openai, anthropic)")
+ cmd.Flags().StringVarP(
+ &provider, "provider", "p", "", "Provider to login with (openai, anthropic, google-antigravity, antigravity)",
+ )
cmd.Flags().BoolVar(&useDeviceCode, "device-code", false, "Use device code flow (for headless environments)")
+ cmd.Flags().BoolVar(&noBrowser, "no-browser", false, "Do not auto-open a browser during OAuth login")
cmd.Flags().BoolVar(
&useOauth, "setup-token", false,
"Use setup-token flow for Anthropic (from `claude setup-token`)",
diff --git a/cmd/picoclaw/internal/auth/login_test.go b/cmd/picoclaw/internal/auth/login_test.go
index d6a03c25b..5129d9aaf 100644
--- a/cmd/picoclaw/internal/auth/login_test.go
+++ b/cmd/picoclaw/internal/auth/login_test.go
@@ -18,6 +18,7 @@ func TestNewLoginSubCommand(t *testing.T) {
assert.True(t, cmd.HasFlags())
assert.NotNil(t, cmd.Flags().Lookup("device-code"))
+ assert.NotNil(t, cmd.Flags().Lookup("no-browser"))
providerFlag := cmd.Flags().Lookup("provider")
require.NotNil(t, providerFlag)
diff --git a/cmd/picoclaw/internal/gateway/command.go b/cmd/picoclaw/internal/gateway/command.go
index 7fa588c5c..7dd03b495 100644
--- a/cmd/picoclaw/internal/gateway/command.go
+++ b/cmd/picoclaw/internal/gateway/command.go
@@ -2,19 +2,34 @@ package gateway
import (
"fmt"
+ "os"
"github.com/spf13/cobra"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
+ "github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/gateway"
"github.com/sipeed/picoclaw/pkg/logger"
+ "github.com/sipeed/picoclaw/pkg/netbind"
"github.com/sipeed/picoclaw/pkg/utils"
)
+func resolveGatewayHostOverride(explicit bool, host string) (string, error) {
+ if !explicit {
+ return "", nil
+ }
+ normalized, err := netbind.NormalizeHostInput(host)
+ if err != nil {
+ return "", fmt.Errorf("invalid --host value: %w", err)
+ }
+ return normalized, nil
+}
+
func NewGatewayCommand() *cobra.Command {
var debug bool
var noTruncate bool
var allowEmpty bool
+ var host string
cmd := &cobra.Command{
Use: "gateway",
@@ -33,7 +48,25 @@ func NewGatewayCommand() *cobra.Command {
return nil
},
- RunE: func(_ *cobra.Command, _ []string) error {
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ resolvedHost, err := resolveGatewayHostOverride(cmd.Flags().Changed("host"), host)
+ if err != nil {
+ return err
+ }
+ if resolvedHost != "" {
+ prevHost, hadPrev := os.LookupEnv(config.EnvGatewayHost)
+ if err := os.Setenv(config.EnvGatewayHost, resolvedHost); err != nil {
+ return fmt.Errorf("failed to set %s: %w", config.EnvGatewayHost, err)
+ }
+ defer func() {
+ if hadPrev {
+ _ = os.Setenv(config.EnvGatewayHost, prevHost)
+ return
+ }
+ _ = os.Unsetenv(config.EnvGatewayHost)
+ }()
+ }
+
return gateway.Run(debug, internal.GetPicoclawHome(), internal.GetConfigPath(), allowEmpty)
},
}
@@ -47,6 +80,12 @@ func NewGatewayCommand() *cobra.Command {
false,
"Continue starting even when no default model is configured",
)
+ cmd.Flags().StringVar(
+ &host,
+ "host",
+ "",
+ "Host address for gateway binding (overrides gateway.host for this run)",
+ )
return cmd
}
diff --git a/cmd/picoclaw/internal/gateway/command_test.go b/cmd/picoclaw/internal/gateway/command_test.go
index 839a7315a..825369abb 100644
--- a/cmd/picoclaw/internal/gateway/command_test.go
+++ b/cmd/picoclaw/internal/gateway/command_test.go
@@ -29,4 +29,38 @@ func TestNewGatewayCommand(t *testing.T) {
assert.True(t, cmd.HasFlags())
assert.NotNil(t, cmd.Flags().Lookup("debug"))
assert.NotNil(t, cmd.Flags().Lookup("allow-empty"))
+ assert.NotNil(t, cmd.Flags().Lookup("host"))
+}
+
+func TestResolveGatewayHostOverride(t *testing.T) {
+ tests := []struct {
+ name string
+ explicit bool
+ host string
+ wantHost string
+ wantErr bool
+ }{
+ {name: "implicit empty host is allowed", explicit: false, host: "", wantHost: "", wantErr: false},
+ {name: "explicit empty host rejected", explicit: true, host: " ", wantHost: "", wantErr: true},
+ {name: "explicit localhost kept", explicit: true, host: " localhost ", wantHost: "localhost", wantErr: false},
+ {
+ name: "explicit multi host normalized",
+ explicit: true,
+ host: " [::1] , 127.0.0.1 ",
+ wantHost: "::1,127.0.0.1",
+ wantErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := resolveGatewayHostOverride(tt.explicit, tt.host)
+ if (err != nil) != tt.wantErr {
+ t.Fatalf("resolveGatewayHostOverride() err = %v, wantErr %t", err, tt.wantErr)
+ }
+ if got != tt.wantHost {
+ t.Fatalf("resolveGatewayHostOverride() host = %q, want %q", got, tt.wantHost)
+ }
+ })
+ }
}
diff --git a/config/config.example.json b/config/config.example.json
index 2d2d38496..858472488 100644
--- a/config/config.example.json
+++ b/config/config.example.json
@@ -269,10 +269,15 @@
"base_url": "",
"max_results": 0
},
- "duckduckgo": {
+ "provider": "auto",
+ "sogou": {
"enabled": true,
"max_results": 5
},
+ "duckduckgo": {
+ "enabled": false,
+ "max_results": 5
+ },
"perplexity": {
"enabled": false,
"api_key": "pplx-xxx",
@@ -472,7 +477,7 @@
},
"gateway": {
"_comment": "Default log level is set to 'fatal'. Other available options are 'debug', 'info', 'warn' and 'error'.",
- "host": "127.0.0.1",
+ "host": "localhost",
"port": 18790,
"hot_reload": false,
"log_level": "fatal"
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 000000000..529eb49ec
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,132 @@
+# PicoClaw Documentation
+
+PicoClaw documentation is organized by document type first and language second.
+
+This file describes the recommended documentation layout, how translated files should be named, and what `make lint-docs` currently checks locally.
+
+These conventions are intended as contributor guidance for new or moved docs. Existing docs may still have historical exceptions, and `make lint-docs` only checks a common subset of the patterns described here.
+
+## Reader Navigation
+
+If you are browsing docs rather than reorganizing them, start with these directory indexes:
+
+- [Guides](guides/README.md): setup, configuration, provider, and workflow guides.
+- [Reference](reference/README.md): precise configuration and behavior reference.
+- [Operations](operations/README.md): debugging and troubleshooting material.
+- [Security](security/README.md): security-focused guides and controls.
+- [Architecture](architecture/README.md): implementation notes and internal design docs.
+- [Migration](migration/README.md): upgrade and migration notes.
+
+For channel-specific setup, start with [Chat Apps Configuration](guides/chat-apps.md) and then drill into `docs/channels//README.md` as needed.
+
+## Principles
+
+- Choose the document type directory first. Do not create language buckets such as `docs/zh/` or `docs/fr/`.
+- Keep each translated document next to its English source document.
+- Use English as the base filename with no locale suffix.
+- Use lowercase locale suffixes for translations, for example `configuration.zh.md` or `README.pt-br.md`.
+- Keep module-specific docs next to the code they describe instead of moving them into `docs/`.
+
+## Recommended Directories
+
+- `README.md`: English project entry document at the repository root.
+- `docs/project/`: translated project entry documents such as `README.zh.md` and `CONTRIBUTING.zh.md`.
+- `docs/guides/`: setup and usage guides.
+- `docs/reference/`: reference material and detailed configuration docs.
+- `docs/operations/`: debugging and troubleshooting docs.
+- `docs/security/`: security-related documentation.
+- `docs/architecture/`: architecture and internal design notes.
+- `docs/channels/`: channel-specific integration guides.
+- `docs/design/`: design proposals and investigations.
+- `docs/migration/`: migration notes.
+
+## Recommended Naming
+
+- English documents use the base filename:
+ - `README.md`
+ - `configuration.md`
+- Translations use `..md`:
+ - `README.zh.md`
+ - `configuration.fr.md`
+ - `README.pt-br.md`
+- Code-adjacent translated READMEs follow the same rule:
+ - `pkg/audio/asr/README.zh.md`
+ - `pkg/isolation/README.zh.md`
+
+## Common Patterns To Avoid
+
+- Root-level translated entry docs such as `README.zh.md` or `CONTRIBUTING.fr.md`
+ - Use `docs/project/README.zh.md` or `docs/project/CONTRIBUTING.fr.md` instead.
+- Language directories under `docs/` such as `docs/zh/`, `docs/ZH/`, `docs/ja/`, or `docs/fr/`
+ - Use `docs//..md` instead.
+- Nested locale buckets such as `docs/guides/zh/configuration.md` or `docs/channels/telegram/zh/README.md`
+ - Keep translations beside the English source file instead.
+- Legacy translation filenames such as `README_zh.md` or `README_CN.md`
+ - Use `README.zh.md`.
+- Non-canonical locale suffixes such as `configuration_zh.md` or `configuration.ZH.md`
+ - Use lowercase `..md`, for example `configuration.zh.md`.
+
+## Translation Placement
+
+- For docs under `docs/guides`, `docs/reference`, `docs/operations`, `docs/security`, `docs/architecture`, `docs/channels`, and `docs/migration`, keep translations beside the English source file.
+- For project entry translations, keep translated files in `docs/project/` and keep the English source in the repository root.
+- In most cases, each translated file should have an English source document:
+ - `docs/guides/configuration.zh.md` usually sits beside `docs/guides/configuration.md`
+ - `docs/project/README.zh.md` usually corresponds to `README.md`
+- Exception: `docs/design/` may contain locale-specific working notes without an English source document. The naming rules still apply there.
+
+## Code-Adjacent Docs
+
+Keep documentation next to the implementation when it primarily describes a package, command, example, or subproject.
+
+Examples:
+
+- `pkg/**/README.md`
+- `cmd/**/README.md`
+- `web/README.md`
+- `examples/**/README.md`
+
+These files still follow the same translation naming rules.
+
+## Adding a New Document
+
+1. Pick the correct document type directory.
+2. Create the English source file first.
+3. Add translated siblings after the English source exists when that source is part of the same docs set.
+4. Update links from existing docs when the new doc becomes a navigation target.
+5. Run `make lint-docs` locally when adding or moving docs.
+
+## Examples
+
+- New setup guide:
+ - `docs/guides/launcher-setup.md`
+ - `docs/guides/launcher-setup.zh.md`
+- New security guide:
+ - `docs/security/token-rotation.md`
+- New translated package README:
+ - `pkg/channels/README.zh.md`
+
+## Validation
+
+Run:
+
+```bash
+make lint-docs
+```
+
+The local docs linter currently checks these common cases:
+
+- no root-level translated `README` or `CONTRIBUTING` files
+- no `docs//` language buckets, regardless of case
+- no nested locale buckets under typed docs directories
+- no legacy `README_*.md` filenames
+- no non-canonical translation-like filenames such as `_zh.md` or `.ZH.md`
+- no extra Markdown files directly under `docs/` except `docs/README.md`
+- every translated Markdown file has a matching English source file
+ - except for locale-specific working notes under `docs/design/`
+
+`make lint-docs` is a local consistency check for common naming and placement mistakes. It helps contributors stay close to the recommended layout, but it is not intended to describe every acceptable documentation pattern in the repository.
+
+When a check fails, `make lint-docs` prints the failing path, the reason, and a suggested fix.
+
+If you change these recommendations or want the local linter to reflect them more closely, update this file and `scripts/lint-docs.sh` together.
diff --git a/docs/architecture/README.md b/docs/architecture/README.md
new file mode 100644
index 000000000..6df7447a7
--- /dev/null
+++ b/docs/architecture/README.md
@@ -0,0 +1,12 @@
+# Architecture
+
+Internal architecture notes for major runtime mechanisms and subsystem design.
+
+- [Steering](steering.md): injecting messages into a running agent loop between tool calls.
+- [SubTurn Mechanism](subturn.md): sub-agent coordination, concurrency control, and lifecycle handling.
+- [Session System](session-system.md): session scope allocation, JSONL persistence, alias compatibility, and migration. ([ZH](session-system.zh.md))
+- [Routing System](routing-system.md): agent dispatch, session policy selection, and light/heavy model routing. ([ZH](routing-system.zh.md))
+- [Hook System Guide](hooks/README.md): current hook architecture and protocol details.
+- [Agent Refactor](agent-refactor/README.md): notes and checkpoints for the agent refactor work.
+
+For proposal-style or exploratory docs, also see [`../design/`](../design/).
diff --git a/docs/agent-refactor/README.md b/docs/architecture/agent-refactor/README.md
similarity index 100%
rename from docs/agent-refactor/README.md
rename to docs/architecture/agent-refactor/README.md
diff --git a/docs/agent-refactor/context.md b/docs/architecture/agent-refactor/context.md
similarity index 100%
rename from docs/agent-refactor/context.md
rename to docs/architecture/agent-refactor/context.md
diff --git a/docs/architecture/agent-refactor/loop-split.md b/docs/architecture/agent-refactor/loop-split.md
new file mode 100644
index 000000000..0c759e63d
--- /dev/null
+++ b/docs/architecture/agent-refactor/loop-split.md
@@ -0,0 +1,86 @@
+# AgentLoop File Split
+
+## Overview
+
+The `pkg/agent/loop.go` file (originally 4384 lines) has been split into 12 focused source files. This is a pure refactoring with no behavioral changes.
+
+## Goals
+
+- Reduce cognitive load when navigating agent loop code
+- Enable parallel work by decoupling concerns
+- Maintain all existing functionality and tests
+- Keep imports minimal per file
+
+## File Map
+
+| File | Lines | Responsibility |
+|------|-------|----------------|
+| `loop.go` | ~650 | Core `AgentLoop` struct, `Run`, `Stop`, `Close`, `ReloadProviderAndConfig`, `runAgentLoop` |
+| `loop_turn.go` | ~1880 | Turn execution: `runTurn`, `abortTurn`, `selectCandidates`, `askSideQuestion`, `isolatedSideQuestionProvider`, side question model config |
+| `loop_utils.go` | ~480 | Standalone utility functions: formatters, cloners, helpers (no receiver) |
+| `loop_init.go` | ~355 | `NewAgentLoop` constructor and `registerSharedTools` |
+| `loop_message.go` | ~300 | Message handling: `processMessage`, `processSystemMessage`, routing helpers, `ProcessDirect`, `ProcessHeartbeat` |
+| `loop_command.go` | ~265 | Command processing: `handleCommand`, `applyExplicitSkillCommand`, pending skills management |
+| `loop_mcp.go` | ~235 | MCP runtime: `ensureMCPInitialized`, server discovery, deferred server handling |
+| `loop_event.go` | ~205 | Event system helpers: `emitEvent`, `logEvent`, `hookAbortError`, `newTurnEventScope`, `MountHook`, `SubscribeEvents` |
+| `loop_media.go` | ~198 | Media resolution: `resolveMediaRefs`, artifact building, MIME detection |
+| `loop_outbound.go` | ~165 | Response publishing: `PublishResponseIfNeeded`, `publishPicoReasoning`, `handleReasoning` |
+| `loop_transcribe.go` | ~110 | Audio transcription: `transcribeAudioInMessage`, `sendTranscriptionFeedback` |
+| `loop_steering.go` | ~97 | Steering queue: `runTurnWithSteering`, `processMessageSync`, `resolveSteeringTarget` |
+| `loop_inject.go` | ~104 | Setter injection: `SetChannelManager`, `SetMediaStore`, `SetTranscriber`, `GetRegistry`, `GetConfig`, `RecordLastChannel` |
+
+## Core Principles Applied
+
+### 1. Same Package, Independent Files
+All files belong to the `agent` package and compile together. This preserves the original visibility rules — no interface abstraction was introduced in this phase.
+
+### 2. No Logic Changes
+All functions were moved verbatim (except updating import statements). The extraction script used the original `loop.go.backup` as source of truth to ensure no drift.
+
+### 3. Shared Types Remain in loop.go
+The `AgentLoop` struct, `processOptions`, `continuationTarget`, and all hook/event types stay in `loop.go` since they are referenced across files.
+
+### 4. Turn State Is Central
+`loop_turn.go` is the largest file because the turn lifecycle (`runTurn`) is inherently large. It contains the core LLM interaction loop, tool execution, subturn spawning, and steering injection.
+
+## What's Left in loop.go
+
+```go
+// Core struct
+type AgentLoop struct { ... }
+
+// Main lifecycle
+func (al *AgentLoop) Run(ctx context.Context) error
+func (al *AgentLoop) Stop()
+func (al *AgentLoop) Close()
+func (al *AgentLoop) ReloadProviderAndConfig(ctx, provider, cfg)
+
+// Turn orchestration (calls into loop_turn.go)
+func (al *AgentLoop) runAgentLoop(ctx, agent, opts) (string, error)
+```
+
+## Extraction Method
+
+The split was done programmatically using Node.js to:
+1. Identify function boundaries using brace counting
+2. Extract each function to its target file
+3. Add necessary imports to each file
+4. Remove the extracted function from loop.go
+5. Run `go fmt` and `go vet` to verify
+
+## Testing
+
+All existing tests pass. The 5 failing tests (`TestGlobalSkillFileContentChange` and 4 Seahorse tests) are pre-existing failures unrelated to this refactor (database file locking issues on Windows).
+
+Build status: `go build ./pkg/agent/...` passes with no errors.
+
+## Phase 2: Dependency Inversion (Planned)
+
+A future phase will introduce interface types to decouple `AgentLoop` from its dependencies, enabling:
+- Easier testing with mock dependencies
+- Alternative runtime configurations
+- Cleaner boundaries for MCP and other extensions
+
+## See Also
+
+- [context.md](context.md) — context management and session handling
diff --git a/docs/hooks/README.md b/docs/architecture/hooks/README.md
similarity index 100%
rename from docs/hooks/README.md
rename to docs/architecture/hooks/README.md
diff --git a/docs/hooks/README.zh.md b/docs/architecture/hooks/README.zh.md
similarity index 100%
rename from docs/hooks/README.zh.md
rename to docs/architecture/hooks/README.zh.md
diff --git a/docs/hooks/hook-json-protocol.md b/docs/architecture/hooks/hook-json-protocol.md
similarity index 100%
rename from docs/hooks/hook-json-protocol.md
rename to docs/architecture/hooks/hook-json-protocol.md
diff --git a/docs/hooks/hook-json-protocol.zh.md b/docs/architecture/hooks/hook-json-protocol.zh.md
similarity index 100%
rename from docs/hooks/hook-json-protocol.zh.md
rename to docs/architecture/hooks/hook-json-protocol.zh.md
diff --git a/docs/hooks/plugin-tool-injection.md b/docs/architecture/hooks/plugin-tool-injection.md
similarity index 100%
rename from docs/hooks/plugin-tool-injection.md
rename to docs/architecture/hooks/plugin-tool-injection.md
diff --git a/docs/hooks/plugin-tool-injection.zh.md b/docs/architecture/hooks/plugin-tool-injection.zh.md
similarity index 100%
rename from docs/hooks/plugin-tool-injection.zh.md
rename to docs/architecture/hooks/plugin-tool-injection.zh.md
diff --git a/docs/architecture/routing-system.md b/docs/architecture/routing-system.md
new file mode 100644
index 000000000..3b4663ee8
--- /dev/null
+++ b/docs/architecture/routing-system.md
@@ -0,0 +1,282 @@
+# Routing System
+
+> Back to [README](../README.md)
+
+In PicoClaw, the runtime "routing system" is not just one decision.
+It is the combined pipeline that decides:
+
+1. which agent handles an inbound message
+2. which session dimensions should isolate that conversation
+3. whether the turn should use the agent's primary model or a configured light model
+
+This document covers the runtime path in `pkg/routing` and its integration in `pkg/agent`.
+It does not describe the launcher's HTTP `ServeMux` routes or the frontend's TanStack Router files under `web/`.
+
+## Routing Layers
+
+| Layer | Files | Responsibility |
+| --- | --- | --- |
+| Agent dispatch | `pkg/routing/route.go`, `pkg/routing/agent_id.go` | Choose the target agent for the inbound message. |
+| Session policy selection | `pkg/routing/route.go` | Decide which dimensions should define session isolation for that routed turn. |
+| Model routing | `pkg/routing/router.go`, `pkg/routing/features.go`, `pkg/routing/classifier.go` | Choose between the primary model and a configured light model based on message complexity. |
+| Runtime integration | `pkg/agent/registry.go`, `pkg/agent/loop_message.go`, `pkg/agent/loop_turn.go` | Apply the route result, allocate session scope, and select model candidates before provider execution. |
+
+## End-To-End Flow
+
+The normal path for a user message is:
+
+```text
+InboundMessage
+ -> NormalizeInboundContext
+ -> RouteResolver.ResolveRoute(...)
+ -> session.AllocateRouteSession(...)
+ -> ensureSessionMetadata(...)
+ -> Router.SelectModel(...)
+ -> provider execution
+```
+
+The first half answers "who should handle this message and what session does it belong to".
+The second half answers "which model tier should that agent use for this turn".
+
+## Agent Dispatch
+
+`routing.RouteResolver` turns a normalized `bus.InboundContext` into a `ResolvedRoute`:
+
+```go
+type ResolvedRoute struct {
+ AgentID string
+ Channel string
+ AccountID string
+ SessionPolicy SessionPolicy
+ MatchedBy string
+}
+```
+
+`MatchedBy` is a debugging aid.
+Typical values are:
+
+- `default`
+- `dispatch.rule`
+- `dispatch.rule:`
+
+## Dispatch Input View
+
+Before matching rules, the resolver builds a normalized `dispatchView`.
+Each field is normalized to the exact shape expected by rule matching.
+
+| Selector field | Runtime shape |
+| --- | --- |
+| `channel` | lowercased channel name |
+| `account` | normalized account ID |
+| `space` | `:` |
+| `chat` | `:` |
+| `topic` | `topic:` |
+| `sender` | lowercased canonical sender ID |
+| `mentioned` | boolean copied from inbound context |
+
+This means dispatch rules must match the normalized shape, for example:
+
+```json
+{
+ "agents": {
+ "dispatch": {
+ "rules": [
+ {
+ "name": "support-group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-100123"
+ }
+ },
+ {
+ "name": "slack-mentions",
+ "agent": "support",
+ "when": {
+ "channel": "slack",
+ "space": "workspace:t001",
+ "mentioned": true
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+## Dispatch Algorithm
+
+`ResolveRoute(...)` follows this sequence:
+
+1. Normalize `channel` and `account`.
+2. Clone `session.identity_links` from config.
+3. Build the normalized dispatch view.
+4. Scan `agents.dispatch.rules` in order.
+5. Skip rules with no constraints at all.
+6. Return the first rule whose selector fields all match exactly.
+7. If no rule matches, fall back to the default agent.
+
+Important consequences:
+
+- first match wins
+- there is no score or priority field beyond list order
+- invalid target agent IDs fall back to the default agent
+- sender matching can see canonical identities produced by `identity_links`
+
+## Default Agent Resolution
+
+If no dispatch rule wins, or if a rule points at an unknown agent, the resolver picks a default agent using this order:
+
+1. the agent marked `default: true`
+2. otherwise the first entry in `agents.list`
+3. otherwise implicit `main`
+
+Both agent IDs and account IDs are normalized through the helpers in `pkg/routing/agent_id.go`.
+
+## Session Policy Handoff
+
+Agent dispatch does not directly build a session key.
+Instead it emits a `SessionPolicy`:
+
+```go
+type SessionPolicy struct {
+ Dimensions []string
+ IdentityLinks map[string][]string
+}
+```
+
+The dimensions come from:
+
+- global `session.dimensions`
+- or `dispatch_rule.session_dimensions` when the matching rule overrides them
+
+Only these dimension names survive normalization:
+
+- `space`
+- `chat`
+- `topic`
+- `sender`
+
+Invalid or duplicated entries are silently dropped.
+
+`pkg/session/AllocateRouteSession(...)` then turns that policy into:
+
+- a structured `SessionScope`
+- a canonical routed session key
+- legacy compatibility aliases
+
+So the routing package owns "what should isolate this conversation", while the session package owns "how that isolation becomes keys and durable storage".
+
+## Identity Links
+
+`session.identity_links` is shared between dispatch and session allocation.
+That is intentional: a sender canonicalized for routing should also map to the same session identity.
+
+Without that symmetry, the system could route two messages to the same agent but still fragment their history into different sessions.
+
+## Model Routing
+
+The second routing stage decides whether a turn can use a cheaper or faster light model.
+
+Config shape:
+
+```json
+{
+ "routing": {
+ "enabled": true,
+ "light_model": "gemini-2.0-flash",
+ "threshold": 0.35
+ }
+}
+```
+
+`pkg/routing.Router` compares the current turn against structural features and returns:
+
+- chosen model name
+- whether the light model was used
+- computed complexity score
+
+If the score is below the threshold, the light model wins.
+Otherwise the agent's primary model is used.
+At runtime this only matters when the agent actually has light-model candidates configured; otherwise execution stays on the primary candidate set.
+
+## Complexity Features
+
+`ExtractFeatures(...)` computes a language-agnostic feature vector:
+
+| Feature | Meaning |
+| --- | --- |
+| `TokenEstimate` | Approximate token count; CJK runes count more accurately than a flat rune split. |
+| `CodeBlockCount` | Number of fenced code blocks in the current message. |
+| `RecentToolCalls` | Tool-call count across the last six history entries. |
+| `ConversationDepth` | Total history length. |
+| `HasAttachments` | Detects embedded media or common media URL/file extensions. |
+
+This is intentionally structural rather than keyword-based, so the router behaves the same across languages.
+
+## RuleClassifier Scoring
+
+The current classifier is `RuleClassifier`.
+It uses a weighted sum capped to `[0, 1]`.
+
+| Signal | Score |
+| --- | --- |
+| attachments present | `1.00` |
+| token estimate `> 200` | `0.35` |
+| token estimate `> 50` | `0.15` |
+| code block present | `0.40` |
+| recent tool calls `> 3` | `0.25` |
+| recent tool calls `1..3` | `0.10` |
+| conversation depth `> 10` | `0.10` |
+
+The default threshold is `0.35`.
+That makes the following behavior intentional:
+
+- trivial chat stays on the light model
+- code tasks usually jump to the heavy model immediately
+- attachments always force the heavy model
+- long, plain-text prompts cross the heavy-model boundary at the default threshold
+
+## Runtime Integration
+
+Agent dispatch and model routing happen in different places:
+
+- `pkg/agent/registry.go` owns `RouteResolver`
+- `pkg/agent/loop_message.go` resolves the route and allocates session scope
+- `pkg/agent/loop_turn.go:selectCandidates` calls `agent.Router.SelectModel(...)`
+
+When the light model is selected, the agent loop swaps to `agent.LightCandidates`.
+When it is not selected, execution stays on the agent's primary provider candidate set.
+
+## Explicit Session Keys
+
+One nuance sits just outside `pkg/routing` but matters for the full routing story.
+
+After a route is allocated, `pkg/agent/loop_utils.go:resolveScopeKey` preserves an explicit incoming session key when the caller already supplied:
+
+- an opaque canonical key
+- a legacy `agent:...` key
+
+That makes manual system flows, tests, and compatibility paths deterministic even when the normal routed scope would have produced a different key.
+
+## What This Document Does Not Cover
+
+The repository also contains two unrelated route systems:
+
+- backend HTTP routes registered in `web/backend/api/router.go`
+- frontend file routes under `web/frontend/src/routes/`
+
+Those are launcher implementation details.
+They are separate from the runtime routing system described here.
+
+## Related Files
+
+- `pkg/routing/route.go`
+- `pkg/routing/router.go`
+- `pkg/routing/classifier.go`
+- `pkg/routing/features.go`
+- `pkg/routing/agent_id.go`
+- `pkg/session/allocator.go`
+- `pkg/agent/registry.go`
+- `pkg/agent/loop_message.go`
+- `pkg/agent/loop_turn.go`
diff --git a/docs/architecture/routing-system.zh.md b/docs/architecture/routing-system.zh.md
new file mode 100644
index 000000000..018b9e7b2
--- /dev/null
+++ b/docs/architecture/routing-system.zh.md
@@ -0,0 +1,281 @@
+# 路由系统
+
+> 返回 [README](../README.md)
+
+在 PicoClaw 里,“路由系统”不是单一判断。
+它实际上是组合起来的一条运行时决策链,负责决定:
+
+1. 哪个 agent 来处理一条入站消息
+2. 这条消息应该落在哪种 session 隔离维度下
+3. 这一轮该使用 agent 的主模型,还是配置中的轻量模型
+
+本文覆盖 `pkg/routing` 及其在 `pkg/agent` 中的集成方式。
+它不讨论 `web/` 目录下 launcher 的 HTTP `ServeMux` 路由,也不讨论前端 TanStack Router 文件路由。
+
+## 路由分层
+
+| 层次 | 文件 | 作用 |
+| --- | --- | --- |
+| Agent 分发 | `pkg/routing/route.go`、`pkg/routing/agent_id.go` | 为入站消息选择目标 agent。 |
+| Session 策略选择 | `pkg/routing/route.go` | 决定该 turn 的会话隔离维度。 |
+| 模型路由 | `pkg/routing/router.go`、`pkg/routing/features.go`、`pkg/routing/classifier.go` | 根据消息复杂度在主模型和轻量模型之间做选择。 |
+| 运行时集成 | `pkg/agent/registry.go`、`pkg/agent/loop_message.go`、`pkg/agent/loop_turn.go` | 应用 route 结果、分配 session scope,并在真正调用 provider 前选出模型候选集。 |
+
+## 端到端流程
+
+普通用户消息的路径如下:
+
+```text
+InboundMessage
+ -> NormalizeInboundContext
+ -> RouteResolver.ResolveRoute(...)
+ -> session.AllocateRouteSession(...)
+ -> ensureSessionMetadata(...)
+ -> Router.SelectModel(...)
+ -> provider execution
+```
+
+前半段回答的是“谁来处理,以及属于哪段会话”。
+后半段回答的是“这个 agent 这一轮该走哪一档模型”。
+
+## Agent 分发
+
+`routing.RouteResolver` 会把归一化后的 `bus.InboundContext` 转成 `ResolvedRoute`:
+
+```go
+type ResolvedRoute struct {
+ AgentID string
+ Channel string
+ AccountID string
+ SessionPolicy SessionPolicy
+ MatchedBy string
+}
+```
+
+`MatchedBy` 主要用于日志和调试,常见值包括:
+
+- `default`
+- `dispatch.rule`
+- `dispatch.rule:`
+
+## Dispatch 输入视图
+
+真正做规则匹配前,resolver 会先构造一个归一化后的 `dispatchView`。
+每个字段都会变成规则匹配所期待的固定形状。
+
+| Selector 字段 | 运行时形状 |
+| --- | --- |
+| `channel` | 小写 channel 名称 |
+| `account` | 归一化后的 account ID |
+| `space` | `:` |
+| `chat` | `:` |
+| `topic` | `topic:` |
+| `sender` | 小写 canonical sender ID |
+| `mentioned` | 直接来自 inbound context 的布尔值 |
+
+这意味着 dispatch rule 必须写成归一化后的形状,例如:
+
+```json
+{
+ "agents": {
+ "dispatch": {
+ "rules": [
+ {
+ "name": "support-group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-100123"
+ }
+ },
+ {
+ "name": "slack-mentions",
+ "agent": "support",
+ "when": {
+ "channel": "slack",
+ "space": "workspace:t001",
+ "mentioned": true
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+## Dispatch 算法
+
+`ResolveRoute(...)` 的流程是:
+
+1. 归一化 `channel` 和 `account`。
+2. 从配置复制 `session.identity_links`。
+3. 构建归一化后的 dispatch view。
+4. 按顺序扫描 `agents.dispatch.rules`。
+5. 没有任何约束条件的 rule 会被跳过。
+6. 第一个所有 selector 字段都精确匹配的 rule 胜出。
+7. 如果没有 rule 匹配,则回退到默认 agent。
+
+这带来几个重要结论:
+
+- 第一条命中的规则优先,没有额外 priority 字段
+- rule 顺序本身就是优先级
+- 指向无效 agent 的 rule 最终会回退到默认 agent
+- sender 匹配看到的是经过 `identity_links` 归一化后的身份
+
+## 默认 Agent 解析
+
+如果没有 dispatch rule 命中,或者 rule 指向了不存在的 agent,resolver 会按以下顺序选择默认 agent:
+
+1. `default: true` 的 agent
+2. 否则取 `agents.list` 的第一项
+3. 如果配置里没有 agent,则使用隐式 `main`
+
+Agent ID 和 Account ID 都会经过 `pkg/routing/agent_id.go` 中的归一化逻辑。
+
+## Session 策略交接
+
+Agent 分发本身不会直接生成 session key。
+它只会产出一个 `SessionPolicy`:
+
+```go
+type SessionPolicy struct {
+ Dimensions []string
+ IdentityLinks map[string][]string
+}
+```
+
+维度来源有两种:
+
+- 全局 `session.dimensions`
+- 如果命中的 dispatch rule 指定了 `session_dimensions`,则用 rule 覆盖
+
+最终只有这些维度名会被保留下来:
+
+- `space`
+- `chat`
+- `topic`
+- `sender`
+
+非法项或重复项会被静默丢弃。
+
+随后 `pkg/session/AllocateRouteSession(...)` 再把这份策略转成:
+
+- 结构化 `SessionScope`
+- canonical routed session key
+- legacy 兼容 alias
+
+所以可以把职责边界理解为:
+
+- `pkg/routing` 决定“这段对话应该按什么维度隔离”
+- `pkg/session` 决定“这些维度如何变成 key 和持久化状态”
+
+## Identity Links
+
+`session.identity_links` 会同时被 dispatch 和 session allocation 使用。
+这是刻意保持一致的设计:如果某个 sender 在路由阶段已经被规范化,那么 session 阶段也应该落到同一个身份上。
+
+否则就会出现“消息路由到了同一个 agent,但上下文仍被拆成多个 session”的问题。
+
+## 模型路由
+
+第二阶段路由决定这一轮能否使用更便宜或更快的轻量模型。
+
+配置形状如下:
+
+```json
+{
+ "routing": {
+ "enabled": true,
+ "light_model": "gemini-2.0-flash",
+ "threshold": 0.35
+ }
+}
+```
+
+`pkg/routing.Router` 会根据当前 turn 的结构特征,返回:
+
+- 选中的模型名
+- 是否使用了 light model
+- 复杂度分数
+
+当分数低于阈值时,走轻量模型;否则仍使用 agent 的主模型。
+但在运行时,只有当 agent 实际配置了 light-model candidates 时,这个判断才会产生效果;否则仍会停留在主模型候选集上。
+
+## 复杂度特征
+
+`ExtractFeatures(...)` 会计算一个与自然语言内容无关、偏结构化的特征向量:
+
+| 特征 | 含义 |
+| --- | --- |
+| `TokenEstimate` | 估算 token 数;对 CJK 文本比简单 rune 平分更准确。 |
+| `CodeBlockCount` | 当前消息中 fenced code block 的数量。 |
+| `RecentToolCalls` | 最近 6 条历史消息中的 tool call 总数。 |
+| `ConversationDepth` | 整体历史长度。 |
+| `HasAttachments` | 是否检测到嵌入媒体或常见媒体 URL / 文件扩展名。 |
+
+这样做的目的,是让模型路由不依赖关键词,从而在不同语言下都保持一致行为。
+
+## RuleClassifier 评分
+
+当前分类器是 `RuleClassifier`,使用加权求和并把结果截断到 `[0, 1]`。
+
+| 信号 | 分值 |
+| --- | --- |
+| 存在附件 | `1.00` |
+| token 估计 `> 200` | `0.35` |
+| token 估计 `> 50` | `0.15` |
+| 存在代码块 | `0.40` |
+| 最近 tool calls `> 3` | `0.25` |
+| 最近 tool calls `1..3` | `0.10` |
+| 会话深度 `> 10` | `0.10` |
+
+默认阈值是 `0.35`。
+这意味着以下行为是刻意设计出来的:
+
+- 很轻的闲聊仍走轻量模型
+- 编码类请求通常会立刻切到重模型
+- 带附件的请求一定走重模型
+- 很长的纯文本请求在默认阈值下也会跨过重模型边界
+
+## 运行时集成
+
+Agent 分发和模型路由发生在不同位置:
+
+- `pkg/agent/registry.go` 持有 `RouteResolver`
+- `pkg/agent/loop_message.go` 负责 resolve route 并分配 session scope
+- `pkg/agent/loop_turn.go:selectCandidates` 调用 `agent.Router.SelectModel(...)`
+
+当 light model 被选中时,agent loop 会切换到 `agent.LightCandidates`。
+如果没有被选中,则继续使用 agent 的主 provider 候选集。
+
+## 显式 Session Key
+
+还有一个不在 `pkg/routing` 内部、但对整体“路由语义”很重要的细节。
+
+在 route 分配完成后,`pkg/agent/loop_utils.go:resolveScopeKey` 会优先保留调用方显式传入的 session key,只要它属于以下格式之一:
+
+- 不透明 canonical key
+- legacy `agent:...` key
+
+这样一来,手工系统流、测试和兼容路径即使在正常路由 scope 会生成不同 key 的情况下,仍然能保持确定性。
+
+## 本文不覆盖的内容
+
+仓库里还存在两套和这里无关的“route”系统:
+
+- `web/backend/api/router.go` 注册的后端 HTTP 路由
+- `web/frontend/src/routes/` 下的前端文件路由
+
+它们属于 launcher 的实现细节,和本文描述的运行时路由系统是两回事。
+
+## 相关文件
+
+- `pkg/routing/route.go`
+- `pkg/routing/router.go`
+- `pkg/routing/classifier.go`
+- `pkg/routing/features.go`
+- `pkg/routing/agent_id.go`
+- `pkg/session/allocator.go`
+- `pkg/agent/registry.go`
+- `pkg/agent/loop_message.go`
+- `pkg/agent/loop_turn.go`
diff --git a/docs/architecture/session-system.md b/docs/architecture/session-system.md
new file mode 100644
index 000000000..7f896d367
--- /dev/null
+++ b/docs/architecture/session-system.md
@@ -0,0 +1,255 @@
+# Session System
+
+> Back to [README](../README.md)
+
+This document describes the runtime session system used by PicoClaw to:
+
+- map inbound messages onto stable conversation scopes
+- persist message history and summaries
+- preserve compatibility with legacy `agent:...` session keys while the runtime uses opaque canonical keys
+
+This document covers the core runtime path in `pkg/session`, `pkg/memory`, and `pkg/agent`.
+It does not describe launcher login cookies or dashboard authentication sessions in `web/backend/middleware`.
+
+## Responsibilities
+
+The session system has four jobs:
+
+1. Decide which messages should share the same conversation context.
+2. Persist that context durably across turns and restarts.
+3. Expose a small `SessionStore` interface to the agent loop.
+4. Keep older session-key formats working during storage and routing migrations.
+
+## Main Components
+
+| Layer | Files | Responsibility |
+| --- | --- | --- |
+| Session contract | `pkg/session/session_store.go` | Defines the `SessionStore` interface used by the agent loop. |
+| Legacy backend | `pkg/session/manager.go` | Stores one JSON file per session. Still used as a fallback. |
+| Session adapter | `pkg/session/jsonl_backend.go` | Adapts `pkg/memory.Store` to `SessionStore`, including alias and scope metadata support. |
+| Durable storage | `pkg/memory/jsonl.go` | Append-only JSONL storage plus `.meta.json` sidecar metadata. |
+| Scope and key building | `pkg/session/scope.go`, `pkg/session/key.go`, `pkg/session/allocator.go` | Builds structured scopes, opaque canonical keys, and legacy aliases from routing results. |
+| Runtime integration | `pkg/agent/instance.go`, `pkg/agent/loop.go`, `pkg/agent/loop_message.go` | Initializes the store, allocates session scope, and persists metadata before turns run. |
+
+## Session Data Model
+
+The structured session identity is represented by `session.SessionScope`:
+
+| Field | Meaning |
+| --- | --- |
+| `Version` | Schema version. Current value is `ScopeVersionV1`. |
+| `AgentID` | Routed agent handling the turn. |
+| `Channel` | Normalized inbound channel name. |
+| `Account` | Normalized account or bot identifier. |
+| `Dimensions` | Ordered list of active partition dimensions such as `chat` or `sender`. |
+| `Values` | Concrete normalized values for each selected dimension. |
+
+Only four dimensions are currently recognized by the allocator:
+
+- `space`
+- `chat`
+- `topic`
+- `sender`
+
+The default config uses:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+That means one shared conversation per chat unless a dispatch rule overrides it.
+
+## Canonical Keys And Legacy Aliases
+
+The runtime now prefers opaque canonical keys:
+
+```text
+sk_v1_
+```
+
+These keys are built from a canonical scope signature in `pkg/session/key.go`.
+The goal is to make storage keys stable while decoupling them from any specific legacy text format.
+
+For compatibility, the allocator also emits legacy aliases such as:
+
+```text
+agent:main:direct:user123
+agent:main:slack:channel:c001
+agent:main:pico:direct:pico:session-123
+```
+
+These aliases matter because older sessions, tests, and some tools still refer to the legacy shape.
+The JSONL backend resolves aliases back to the canonical key before reads and writes.
+
+The agent loop also preserves explicit incoming session keys when the caller already supplied one of the recognized explicit formats:
+
+- opaque canonical key
+- legacy `agent:...` key
+
+That behavior lives in `pkg/agent/loop_utils.go:resolveScopeKey`.
+
+## Allocation Flow
+
+The end-to-end flow for a normal inbound message is:
+
+```text
+InboundMessage
+ -> RouteResolver.ResolveRoute(...)
+ -> session.AllocateRouteSession(...)
+ -> resolveScopeKey(...)
+ -> ensureSessionMetadata(...)
+ -> AgentLoop turn execution
+ -> SessionStore read/write operations
+```
+
+More concretely:
+
+1. `pkg/agent/loop_message.go` resolves the agent route from normalized inbound context.
+2. `session.AllocateRouteSession` converts the route's `SessionPolicy` plus inbound context into a structured `SessionScope`.
+3. The allocator builds:
+ - `SessionKey`: canonical routed session key
+ - `SessionAliases`: compatibility aliases for that routed scope
+ - `MainSessionKey`: agent-level main session key
+ - `MainAliases`: legacy alias for the main session
+4. `runAgentLoop` persists scope metadata and aliases through `ensureSessionMetadata`.
+5. During later reads or writes, `JSONLBackend.ResolveSessionKey` maps aliases back onto the canonical key.
+
+The main session key is separate from routed chat sessions.
+It is mainly used for agent-level or system-style flows that need one stable per-agent conversation, for example `processSystemMessage`.
+
+## Scope Construction Rules
+
+`pkg/session/allocator.go` builds scope values from normalized inbound context.
+Important rules:
+
+- `space` becomes `:`
+- `chat` becomes `:`
+- `topic` becomes `topic:`
+- `sender` is canonicalized through `session.identity_links` before being stored
+
+There are two special cases worth calling out.
+
+### Telegram forum isolation
+
+Telegram forum topics must stay isolated even when the configured dimensions only mention `chat`.
+To preserve that behavior, the allocator appends `/` to the `chat` value for Telegram forum messages unless `topic` is already an explicit dimension.
+
+Example:
+
+```text
+group:-1001234567890/42
+group:-1001234567890/99
+```
+
+Those produce different session keys.
+
+### Identity links
+
+`session.identity_links` lets multiple sender identifiers collapse into one canonical identity.
+Both dispatch matching and session allocation use that mapping so that the same person can keep one conversation even if their raw sender IDs differ across channels or accounts.
+
+## Storage Format
+
+The default runtime backend is `pkg/memory.JSONLStore`, wrapped by `session.JSONLBackend`.
+
+Each session uses two files:
+
+```text
+{sanitized_key}.jsonl
+{sanitized_key}.meta.json
+```
+
+The files store:
+
+- `.jsonl`: one `providers.Message` per line, append-only
+- `.meta.json`: summary, timestamps, line counts, logical truncation offset, scope, aliases
+
+`SessionMeta` currently includes:
+
+- `Key`
+- `Summary`
+- `Skip`
+- `Count`
+- `CreatedAt`
+- `UpdatedAt`
+- `Scope`
+- `Aliases`
+
+## Write And Crash Semantics
+
+The JSONL store is designed around append-first durability and stale-over-loss recovery:
+
+- `AddMessage` and `AddFullMessage` append one JSON line, `fsync`, then update metadata.
+- `TruncateHistory` is logical first: it only advances `meta.Skip`.
+- `Compact` physically rewrites the JSONL file to remove skipped lines.
+- `SetHistory` and `Compact` write metadata before rewriting JSONL so a crash may temporarily expose old data, but should not lose data.
+- Corrupt JSONL lines are skipped during reads instead of failing the entire session.
+
+`JSONLBackend.Save` maps onto `store.Compact(...)`.
+In other words, `Save` is no longer "flush dirty memory to disk"; it is now "reclaim dead lines after logical truncation".
+
+## Concurrency Model
+
+`pkg/memory.JSONLStore` uses a fixed 64-shard mutex array keyed by session hash.
+That gives per-session serialization without keeping an unbounded mutex map in memory.
+
+The legacy `SessionManager` uses a single in-memory map guarded by an RW mutex.
+
+Both backends satisfy the same `SessionStore` interface, which is why the agent loop does not need storage-specific code.
+
+## Compatibility And Migration
+
+`pkg/agent/instance.go:initSessionStore` prefers the JSONL backend.
+
+Startup sequence:
+
+1. Create `memory.NewJSONLStore(dir)`.
+2. Run `memory.MigrateFromJSON(...)` to import legacy `.json` sessions.
+3. Wrap the store with `session.NewJSONLBackend(store)`.
+4. If JSONL initialization or migration fails, fall back to `session.NewSessionManager(dir)`.
+
+This fallback is intentional: a partial migration would be worse than staying on the legacy store for one run.
+
+### Alias promotion
+
+When canonical metadata is first created, `EnsureSessionMetadata` may promote history from a non-empty legacy alias into the canonical session.
+That promotion only happens when the canonical session is still empty, so active canonical history is not overwritten.
+
+This is how the system preserves old histories such as:
+
+- legacy direct-message keys
+- older Pico direct-session keys
+
+while moving the runtime onto opaque canonical keys.
+
+## Other SessionStore Implementations
+
+`pkg/agent/subturn.go` defines an `ephemeralSessionStore`.
+It satisfies the same `SessionStore` interface, but keeps data in memory only and is destroyed when the sub-turn ends.
+
+That lets SubTurn reuse the same session-facing APIs without writing child-session history into the parent's durable storage.
+
+## Operational Consumers
+
+The session system is consumed by more than the agent loop:
+
+- `web/backend/api/session.go` reads JSONL metadata and legacy JSON sessions to expose session history in the launcher UI.
+- `pkg/agent/steering.go` can recover scope metadata for active steering flows.
+- tooling and tests can still refer to legacy aliases because alias resolution is handled below the agent loop.
+
+## Related Files
+
+- `pkg/session/session_store.go`
+- `pkg/session/manager.go`
+- `pkg/session/jsonl_backend.go`
+- `pkg/session/scope.go`
+- `pkg/session/key.go`
+- `pkg/session/allocator.go`
+- `pkg/memory/jsonl.go`
+- `pkg/agent/instance.go`
+- `pkg/agent/loop.go`
+- `pkg/agent/loop_message.go`
diff --git a/docs/architecture/session-system.zh.md b/docs/architecture/session-system.zh.md
new file mode 100644
index 000000000..8de4e515c
--- /dev/null
+++ b/docs/architecture/session-system.zh.md
@@ -0,0 +1,254 @@
+# Session 系统
+
+> 返回 [README](../README.md)
+
+本文说明 PicoClaw 运行时的 Session 系统如何完成以下事情:
+
+- 把入站消息映射到稳定的会话作用域
+- 持久化消息历史与摘要
+- 在运行时使用不透明 canonical key 的同时,继续兼容旧的 `agent:...` session key
+
+本文覆盖 `pkg/session`、`pkg/memory` 和 `pkg/agent` 中的核心运行时链路。
+它不讨论 `web/backend/middleware` 中 launcher 登录 Cookie 或 dashboard 鉴权 session。
+
+## 职责
+
+Session 系统承担四件事:
+
+1. 决定哪些消息应该共享同一段上下文。
+2. 让这段上下文能跨 turn、跨进程重启持久存在。
+3. 向 agent loop 暴露一个足够小的 `SessionStore` 抽象。
+4. 在存储层和路由层迁移期间继续兼容旧 session key。
+
+## 主要组件
+
+| 层次 | 文件 | 作用 |
+| --- | --- | --- |
+| Session 抽象 | `pkg/session/session_store.go` | 定义 agent loop 依赖的 `SessionStore` 接口。 |
+| 旧后端 | `pkg/session/manager.go` | 每个 session 一个 JSON 文件的旧实现,仍作为回退方案保留。 |
+| Session 适配层 | `pkg/session/jsonl_backend.go` | 把 `pkg/memory.Store` 适配成 `SessionStore`,并支持 alias 与 scope metadata。 |
+| 持久化存储 | `pkg/memory/jsonl.go` | Append-only JSONL 存储与 `.meta.json` 元数据侧文件。 |
+| Scope / Key 构建 | `pkg/session/scope.go`、`pkg/session/key.go`、`pkg/session/allocator.go` | 从路由结果生成结构化 scope、不透明 canonical key 和 legacy alias。 |
+| 运行时集成 | `pkg/agent/instance.go`、`pkg/agent/loop.go`、`pkg/agent/loop_message.go` | 初始化存储、分配 session scope,并在 turn 执行前落 metadata。 |
+
+## Session 数据模型
+
+结构化的会话身份由 `session.SessionScope` 表示:
+
+| 字段 | 含义 |
+| --- | --- |
+| `Version` | Scope 模式版本,当前为 `ScopeVersionV1`。 |
+| `AgentID` | 处理该 turn 的路由 agent。 |
+| `Channel` | 归一化后的入站 channel 名称。 |
+| `Account` | 归一化后的 bot / account 标识。 |
+| `Dimensions` | 当前启用的隔离维度顺序,例如 `chat` 或 `sender`。 |
+| `Values` | 每个维度对应的具体归一化值。 |
+
+Allocator 当前只识别四个维度:
+
+- `space`
+- `chat`
+- `topic`
+- `sender`
+
+默认配置是:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+也就是默认按 chat 共享上下文;如果 dispatch rule 覆盖了维度,则以 rule 为准。
+
+## Canonical Key 与 Legacy Alias
+
+运行时现在优先使用不透明 canonical key:
+
+```text
+sk_v1_
+```
+
+它由 `pkg/session/key.go` 中的 scope signature 计算得到。
+这样可以让存储 key 稳定,同时不再把持久化格式和某一种旧文本 key 绑定死。
+
+为了兼容旧数据,allocator 还会生成 legacy alias,例如:
+
+```text
+agent:main:direct:user123
+agent:main:slack:channel:c001
+agent:main:pico:direct:pico:session-123
+```
+
+这些 alias 很重要,因为旧 session、部分测试以及某些工具仍然会引用这种格式。
+JSONL backend 会在读写前先把 alias 解析回 canonical key。
+
+此外,如果调用方已经显式传入了受支持的 session key,agent loop 会保留它,不强行改成新分配的 routed key。
+这条逻辑在 `pkg/agent/loop_utils.go:resolveScopeKey` 中:
+
+- 不透明 canonical key
+- legacy `agent:...` key
+
+都属于“显式 key”。
+
+## 分配流程
+
+普通入站消息的完整链路如下:
+
+```text
+InboundMessage
+ -> RouteResolver.ResolveRoute(...)
+ -> session.AllocateRouteSession(...)
+ -> resolveScopeKey(...)
+ -> ensureSessionMetadata(...)
+ -> AgentLoop turn 执行
+ -> SessionStore 读写
+```
+
+具体来说:
+
+1. `pkg/agent/loop_message.go` 先用归一化后的 inbound context 解析 agent route。
+2. `session.AllocateRouteSession` 把 route 的 `SessionPolicy` 和 inbound context 组合成结构化 `SessionScope`。
+3. Allocator 会生成:
+ - `SessionKey`:当前路由会话的 canonical key
+ - `SessionAliases`:该路由会话的兼容 alias
+ - `MainSessionKey`:agent 级主会话 key
+ - `MainAliases`:主会话对应的 legacy alias
+4. `runAgentLoop` 通过 `ensureSessionMetadata` 持久化 scope metadata 和 alias。
+5. 后续读写时,`JSONLBackend.ResolveSessionKey` 会先把 alias 映射回 canonical key。
+
+`MainSessionKey` 和普通聊天会话是分开的。
+它主要服务于 agent 级、系统级的上下文场景,比如 `processSystemMessage`。
+
+## Scope 构建规则
+
+`pkg/session/allocator.go` 会从归一化后的 inbound context 生成 scope 值。
+关键规则如下:
+
+- `space` 变成 `:`
+- `chat` 变成 `:`
+- `topic` 变成 `topic:`
+- `sender` 会先经过 `session.identity_links` 归一化再写入
+
+其中有两个需要单独记住的特殊规则。
+
+### Telegram forum 隔离
+
+Telegram forum topic 必须默认保持隔离,即使配置只写了 `chat` 维度。
+为此,如果消息来自 Telegram forum 且策略里没有显式包含 `topic`,allocator 会把 `/` 拼到 `chat` 值后面。
+
+例如:
+
+```text
+group:-1001234567890/42
+group:-1001234567890/99
+```
+
+这两者会得到不同的 session key。
+
+### Identity links
+
+`session.identity_links` 可以把多个 sender 标识折叠为一个 canonical identity。
+dispatch 匹配和 session 分配都会使用这套映射,因此同一个人即使跨 channel 或 account 使用不同原始 sender ID,也可以继续落到同一段上下文里。
+
+## 存储格式
+
+默认运行时后端是 `pkg/memory.JSONLStore`,外面包了一层 `session.JSONLBackend`。
+
+每个 session 使用两类文件:
+
+```text
+{sanitized_key}.jsonl
+{sanitized_key}.meta.json
+```
+
+各自保存:
+
+- `.jsonl`:一行一个 `providers.Message`,append-only
+- `.meta.json`:摘要、时间戳、行数、逻辑截断偏移、scope、aliases
+
+`SessionMeta` 当前包含:
+
+- `Key`
+- `Summary`
+- `Skip`
+- `Count`
+- `CreatedAt`
+- `UpdatedAt`
+- `Scope`
+- `Aliases`
+
+## 写入与崩溃语义
+
+JSONL store 的设计核心是“追加优先、宁可暂时读到旧数据也不要丢数据”:
+
+- `AddMessage` / `AddFullMessage` 先追加一行 JSON,再 `fsync`,最后更新 metadata。
+- `TruncateHistory` 先做逻辑截断,本质上只是推进 `meta.Skip`。
+- `Compact` 才会真正重写 JSONL 文件,把被跳过的旧行物理移除。
+- `SetHistory` 和 `Compact` 都会先写 metadata 再改写 JSONL;如果中途崩溃,最多短时间暴露旧数据,不应丢数据。
+- 读取 JSONL 时如果碰到损坏行,会跳过该行,而不是让整个 session 读取失败。
+
+`JSONLBackend.Save` 对应到底层的 `store.Compact(...)`。
+也就是说,`Save` 在新实现里不再是“把内存脏数据刷盘”,而是“在逻辑截断后回收无效行占用的磁盘空间”。
+
+## 并发模型
+
+`pkg/memory.JSONLStore` 使用固定 64 分片 mutex,按 session key 的 hash 做串行化。
+这样既能做到“按 session 串行”,又不会因为 session 数量增长而把 mutex map 做成无界结构。
+
+旧的 `SessionManager` 则是一个内存 map 加 RW mutex。
+
+这两个实现都满足同一个 `SessionStore` 接口,所以 agent loop 不需要写任何存储后端特化逻辑。
+
+## 兼容与迁移
+
+`pkg/agent/instance.go:initSessionStore` 会优先初始化 JSONL 后端。
+
+启动过程如下:
+
+1. 创建 `memory.NewJSONLStore(dir)`。
+2. 执行 `memory.MigrateFromJSON(...)`,把旧 `.json` session 迁入新格式。
+3. 用 `session.NewJSONLBackend(store)` 包装。
+4. 如果 JSONL 初始化或迁移失败,则回退到 `session.NewSessionManager(dir)`。
+
+这个回退是刻意设计的:做一半的迁移,比整轮继续使用旧后端更危险。
+
+### Alias 提升
+
+第一次为 canonical key 建 metadata 时,`EnsureSessionMetadata` 会尝试把某个非空 legacy alias 的历史提升到 canonical session。
+但这件事只会在 canonical session 仍然为空时发生,因此不会覆盖已经存在的 canonical 历史。
+
+这保证了系统在迁移到 opaque key 的同时,仍能保留旧历史,例如:
+
+- 旧的 direct-message key
+- 旧的 Pico direct-session key
+
+## 其他 SessionStore 实现
+
+`pkg/agent/subturn.go` 里定义了 `ephemeralSessionStore`。
+它同样实现 `SessionStore`,但只存在于内存里,在 sub-turn 结束时销毁。
+
+这样 SubTurn 就能复用相同的 session 接口,而不会把子任务历史写进父会话的持久存储。
+
+## 运行时消费者
+
+Session 系统不只被 agent loop 使用:
+
+- `web/backend/api/session.go` 会读取 JSONL metadata 和旧 JSON session,并把历史暴露给 launcher UI。
+- `pkg/agent/steering.go` 可以在 steering 场景下恢复 scope metadata。
+- 因为 alias 解析发生在 agent loop 之下,测试和工具仍然可以继续使用 legacy alias。
+
+## 相关文件
+
+- `pkg/session/session_store.go`
+- `pkg/session/manager.go`
+- `pkg/session/jsonl_backend.go`
+- `pkg/session/scope.go`
+- `pkg/session/key.go`
+- `pkg/session/allocator.go`
+- `pkg/memory/jsonl.go`
+- `pkg/agent/instance.go`
+- `pkg/agent/loop.go`
+- `pkg/agent/loop_message.go`
diff --git a/docs/steering.md b/docs/architecture/steering.md
similarity index 86%
rename from docs/steering.md
rename to docs/architecture/steering.md
index 63294ac5f..1a993fdb3 100644
--- a/docs/steering.md
+++ b/docs/architecture/steering.md
@@ -170,13 +170,19 @@ This is saved to the session via `AddFullMessage` and sent to the model, so it i
## Automatic bus drain
-When the agent loop (`Run()`) starts processing a message, it spawns a background goroutine that keeps consuming new inbound messages from the bus. These messages are automatically redirected into the steering queue via `Steer()`. This means:
+When the agent loop (`Run()`) starts, it reads inbound messages from a shared message bus. The routing logic determines how each message is handled:
-- Users on any channel (Telegram, Discord, etc.) don't need to do anything special — their messages are automatically captured as steering when the agent is busy
-- Audio messages are transcribed before being steered, so the agent receives text. If transcription fails, the original (non-transcribed) message is steered as-is
-- Only messages that resolve to the **same steering scope** as the active turn are redirected. Messages for other chats/sessions are requeued onto the inbound bus so they can be processed normally
-- `system` inbound messages are not treated as steering input
-- When `processMessage` finishes, the drain goroutine is canceled and normal message consumption resumes
+1. **No active turn for the message's session** — the message is dispatched to a **worker goroutine** that processes the full turn (LLM calls, tool execution, steering drain)
+2. **An active turn already exists for the same session** — the message is enqueued directly into that session's **steering queue** via `enqueueSteeringMessage`. No background drain goroutine is needed
+3. **Non-routable message** (e.g. `system`) — processed synchronously in the main loop
+
+This design enables **parallel processing of messages from different sessions** while keeping same-session messages strictly sequential. Key implications:
+
+- Messages from different users/channels are processed **concurrently** (up to `max_parallel_turns`)
+- Messages from the same session are **serialized** — subsequent messages go to the steering queue
+- Users don't need to do anything special — their messages are automatically captured as steering when the agent is busy for their session
+- Audio messages are transcribed within the worker that processes the turn, so the agent receives text
+- `system` inbound messages are processed immediately and do not trigger steering
## Steering with media
diff --git a/docs/subturn.md b/docs/architecture/subturn.md
similarity index 85%
rename from docs/subturn.md
rename to docs/architecture/subturn.md
index b84c06627..0a927b56d 100644
--- a/docs/subturn.md
+++ b/docs/architecture/subturn.md
@@ -112,13 +112,17 @@ When the parent task is forcefully aborted (e.g., user interrupts with `/stop`):
## Agent Loop Integration
-### Bus Draining During Processing
+### Message Routing and Steering
-When a message enters the `Run()` loop, the agent starts a `drainBusToSteering` goroutine before calling `processMessage`. This goroutine runs concurrently with the entire processing lifecycle and continuously consumes any new inbound messages from the bus, redirecting them into the **steering queue** instead of dropping them.
+When a message enters the `Run()` loop, the agent determines whether to start a new worker or enqueue to steering:
-This ensures that if a user sends a follow-up message while the agent is processing (including during SubTurn execution), the message is not lost — it will be picked up between tool call iterations via `dequeueSteeringMessages`.
+- If **no active turn** exists for the message's session key, the session is atomically reserved and a **worker goroutine** is spawned. The worker processes the full turn lifecycle: `processMessage` → tool execution → steering drain → `Continue` for queued messages.
+- If an **active turn already exists** for the same session, the message is enqueued directly into that session's steering queue. It will be picked up by the existing worker's steering drain loop.
-The drain goroutine stops automatically when `processMessage` returns (via a cancellable context).
+This ensures that:
+- Messages from **different sessions** are processed **in parallel** (up to `max_parallel_turns` concurrent workers)
+- Messages from the **same session** are strictly **serialized** — they go to the steering queue and are processed sequentially within the active turn
+- No background drain goroutine is needed; steering is handled by the worker itself after processing
### Pending Result Polling
@@ -129,7 +133,7 @@ The agent loop polls for async SubTurn results at two points per iteration:
### Turn State Tracking
-All active root turns are registered in `AgentLoop.activeTurnStates` (`sync.Map`, keyed by session key). This allows `HardAbort` and `/subagents` observability commands to find and operate on active turns.
+All active turns are registered in `AgentLoop.activeTurnStates` (`sync.Map`, keyed by session key). A reservation sentinel is stored atomically via `LoadOrStore` before the worker starts, then replaced with the real `*turnState` when `runTurn` registers. This prevents a TOCTOU race where multiple messages for the same session could spawn concurrent workers. The sentinel is cleaned up by the worker's deferred cleanup. This allows `HardAbort` and `/subagents` observability commands to find and operate on active turns.
## Event Bus Integration
@@ -181,10 +185,10 @@ Creates a new spawner instance for the given AgentLoop. Pass the returned value
### Continue
```go
-func (al *AgentLoop) Continue(ctx context.Context, sessionKey string) error
+func (al *AgentLoop) Continue(ctx context.Context, sessionKey, channel, chatID string) (string, error)
```
-Resumes an idle agent turn by injecting any queued steering messages as a new LLM iteration. Used when the agent is waiting and a deferred steering message needs to be processed without a new inbound message arriving.
+Resumes an idle agent turn by dequeuing steering messages for the given session and running them through the agent loop. Returns the response string if processing occurred, or empty string if no steering messages were pending. Uses session-aware active turn checking — it only blocks if a turn is active for the *same* session, not for unrelated sessions.
## Context Propagation
diff --git a/docs/channels/dingtalk/README.fr.md b/docs/channels/dingtalk/README.fr.md
index eec59f6f2..ea0d45194 100644
--- a/docs/channels/dingtalk/README.fr.md
+++ b/docs/channels/dingtalk/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# DingTalk
diff --git a/docs/channels/dingtalk/README.ja.md b/docs/channels/dingtalk/README.ja.md
index c465b6e2f..4796038f9 100644
--- a/docs/channels/dingtalk/README.ja.md
+++ b/docs/channels/dingtalk/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# DingTalk
diff --git a/docs/channels/dingtalk/README.pt-br.md b/docs/channels/dingtalk/README.pt-br.md
index a96480342..c4a3da804 100644
--- a/docs/channels/dingtalk/README.pt-br.md
+++ b/docs/channels/dingtalk/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# DingTalk
diff --git a/docs/channels/dingtalk/README.vi.md b/docs/channels/dingtalk/README.vi.md
index b760e28f7..83550a14e 100644
--- a/docs/channels/dingtalk/README.vi.md
+++ b/docs/channels/dingtalk/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# DingTalk
diff --git a/docs/channels/dingtalk/README.zh.md b/docs/channels/dingtalk/README.zh.md
index 13c7080b3..7c672c383 100644
--- a/docs/channels/dingtalk/README.zh.md
+++ b/docs/channels/dingtalk/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# 钉钉
diff --git a/docs/channels/discord/README.fr.md b/docs/channels/discord/README.fr.md
index e8ac64668..951eb59be 100644
--- a/docs/channels/discord/README.fr.md
+++ b/docs/channels/discord/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# Discord
diff --git a/docs/channels/discord/README.ja.md b/docs/channels/discord/README.ja.md
index e4d71f41b..212abc1a3 100644
--- a/docs/channels/discord/README.ja.md
+++ b/docs/channels/discord/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# Discord
diff --git a/docs/channels/discord/README.pt-br.md b/docs/channels/discord/README.pt-br.md
index b782a944b..32d828b76 100644
--- a/docs/channels/discord/README.pt-br.md
+++ b/docs/channels/discord/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# Discord
diff --git a/docs/channels/discord/README.vi.md b/docs/channels/discord/README.vi.md
index ea25dc003..e9ad6f5cc 100644
--- a/docs/channels/discord/README.vi.md
+++ b/docs/channels/discord/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# Discord
diff --git a/docs/channels/discord/README.zh.md b/docs/channels/discord/README.zh.md
index 30fe3d28b..d6785ac3b 100644
--- a/docs/channels/discord/README.zh.md
+++ b/docs/channels/discord/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# Discord
diff --git a/docs/channels/feishu/README.fr.md b/docs/channels/feishu/README.fr.md
index 8f9fdafcc..0d82c9655 100644
--- a/docs/channels/feishu/README.fr.md
+++ b/docs/channels/feishu/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# Feishu
diff --git a/docs/channels/feishu/README.ja.md b/docs/channels/feishu/README.ja.md
index 955ecc233..c19e9fbec 100644
--- a/docs/channels/feishu/README.ja.md
+++ b/docs/channels/feishu/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# 飛書(Feishu)
diff --git a/docs/channels/feishu/README.pt-br.md b/docs/channels/feishu/README.pt-br.md
index 11089cf2c..73ab981e0 100644
--- a/docs/channels/feishu/README.pt-br.md
+++ b/docs/channels/feishu/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# Feishu
diff --git a/docs/channels/feishu/README.vi.md b/docs/channels/feishu/README.vi.md
index abe51db97..1db4c1146 100644
--- a/docs/channels/feishu/README.vi.md
+++ b/docs/channels/feishu/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# Feishu
diff --git a/docs/channels/feishu/README.zh.md b/docs/channels/feishu/README.zh.md
index 882ee3d3f..afe117286 100644
--- a/docs/channels/feishu/README.zh.md
+++ b/docs/channels/feishu/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# 飞书
diff --git a/docs/channels/line/README.fr.md b/docs/channels/line/README.fr.md
index 522ff1d2f..c37e1c3a0 100644
--- a/docs/channels/line/README.fr.md
+++ b/docs/channels/line/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# Line
diff --git a/docs/channels/line/README.ja.md b/docs/channels/line/README.ja.md
index a751d61e9..ed374c5e3 100644
--- a/docs/channels/line/README.ja.md
+++ b/docs/channels/line/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# Line
diff --git a/docs/channels/line/README.pt-br.md b/docs/channels/line/README.pt-br.md
index 73a1ab837..5feea3153 100644
--- a/docs/channels/line/README.pt-br.md
+++ b/docs/channels/line/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# Line
diff --git a/docs/channels/line/README.vi.md b/docs/channels/line/README.vi.md
index d799a934d..e834610e8 100644
--- a/docs/channels/line/README.vi.md
+++ b/docs/channels/line/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# Line
diff --git a/docs/channels/line/README.zh.md b/docs/channels/line/README.zh.md
index cdc4380c3..5b353de1b 100644
--- a/docs/channels/line/README.zh.md
+++ b/docs/channels/line/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# Line
diff --git a/docs/channels/maixcam/README.fr.md b/docs/channels/maixcam/README.fr.md
index c4871f10a..23f8c11cc 100644
--- a/docs/channels/maixcam/README.fr.md
+++ b/docs/channels/maixcam/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# MaixCam
diff --git a/docs/channels/maixcam/README.ja.md b/docs/channels/maixcam/README.ja.md
index 6d06370d7..adec19445 100644
--- a/docs/channels/maixcam/README.ja.md
+++ b/docs/channels/maixcam/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# MaixCam
diff --git a/docs/channels/maixcam/README.pt-br.md b/docs/channels/maixcam/README.pt-br.md
index 6243bb67b..dd606ff53 100644
--- a/docs/channels/maixcam/README.pt-br.md
+++ b/docs/channels/maixcam/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# MaixCam
diff --git a/docs/channels/maixcam/README.vi.md b/docs/channels/maixcam/README.vi.md
index 7f0dc5812..09aba3540 100644
--- a/docs/channels/maixcam/README.vi.md
+++ b/docs/channels/maixcam/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# MaixCam
diff --git a/docs/channels/maixcam/README.zh.md b/docs/channels/maixcam/README.zh.md
index f9e434976..2b4fdb87a 100644
--- a/docs/channels/maixcam/README.zh.md
+++ b/docs/channels/maixcam/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# MaixCam
diff --git a/docs/channels/matrix/README.fr.md b/docs/channels/matrix/README.fr.md
index e4e1341c1..5ff329a28 100644
--- a/docs/channels/matrix/README.fr.md
+++ b/docs/channels/matrix/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# Guide de configuration du canal Matrix
diff --git a/docs/channels/matrix/README.ja.md b/docs/channels/matrix/README.ja.md
index fb80cd484..adb14a1f9 100644
--- a/docs/channels/matrix/README.ja.md
+++ b/docs/channels/matrix/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# Matrix チャンネル設定ガイド
diff --git a/docs/channels/matrix/README.pt-br.md b/docs/channels/matrix/README.pt-br.md
index 22deaf861..4f606f3ed 100644
--- a/docs/channels/matrix/README.pt-br.md
+++ b/docs/channels/matrix/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# Guia de Configuração do Canal Matrix
diff --git a/docs/channels/matrix/README.vi.md b/docs/channels/matrix/README.vi.md
index d01b5ae3d..27f2ce746 100644
--- a/docs/channels/matrix/README.vi.md
+++ b/docs/channels/matrix/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# Hướng dẫn Cấu hình Kênh Matrix
diff --git a/docs/channels/matrix/README.zh.md b/docs/channels/matrix/README.zh.md
index 08a746d7f..97634e2e6 100644
--- a/docs/channels/matrix/README.zh.md
+++ b/docs/channels/matrix/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# Matrix 通道配置指南
diff --git a/docs/channels/onebot/README.fr.md b/docs/channels/onebot/README.fr.md
index 209dd529d..8a2aec8d2 100644
--- a/docs/channels/onebot/README.fr.md
+++ b/docs/channels/onebot/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# OneBot
diff --git a/docs/channels/onebot/README.ja.md b/docs/channels/onebot/README.ja.md
index d08908d69..d2616e582 100644
--- a/docs/channels/onebot/README.ja.md
+++ b/docs/channels/onebot/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# OneBot
diff --git a/docs/channels/onebot/README.pt-br.md b/docs/channels/onebot/README.pt-br.md
index 7043cc867..2e037361f 100644
--- a/docs/channels/onebot/README.pt-br.md
+++ b/docs/channels/onebot/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# OneBot
diff --git a/docs/channels/onebot/README.vi.md b/docs/channels/onebot/README.vi.md
index 5ee1f37fd..3dfcf8161 100644
--- a/docs/channels/onebot/README.vi.md
+++ b/docs/channels/onebot/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# OneBot
diff --git a/docs/channels/onebot/README.zh.md b/docs/channels/onebot/README.zh.md
index 6f9f07c0d..4e5210b82 100644
--- a/docs/channels/onebot/README.zh.md
+++ b/docs/channels/onebot/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# OneBot
diff --git a/docs/channels/qq/README.fr.md b/docs/channels/qq/README.fr.md
index e46bd7ebd..2202fa09d 100644
--- a/docs/channels/qq/README.fr.md
+++ b/docs/channels/qq/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# QQ
diff --git a/docs/channels/qq/README.ja.md b/docs/channels/qq/README.ja.md
index 791428cc2..d9e86a061 100644
--- a/docs/channels/qq/README.ja.md
+++ b/docs/channels/qq/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# QQ
diff --git a/docs/channels/qq/README.pt-br.md b/docs/channels/qq/README.pt-br.md
index d5eb0080b..b0a7e5568 100644
--- a/docs/channels/qq/README.pt-br.md
+++ b/docs/channels/qq/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# QQ
diff --git a/docs/channels/qq/README.vi.md b/docs/channels/qq/README.vi.md
index d3973df41..cf940d05d 100644
--- a/docs/channels/qq/README.vi.md
+++ b/docs/channels/qq/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# QQ
diff --git a/docs/channels/qq/README.zh.md b/docs/channels/qq/README.zh.md
index fa3b129e0..dc40f6225 100644
--- a/docs/channels/qq/README.zh.md
+++ b/docs/channels/qq/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# QQ
diff --git a/docs/channels/slack/README.fr.md b/docs/channels/slack/README.fr.md
index 7d0d09f5d..be533052a 100644
--- a/docs/channels/slack/README.fr.md
+++ b/docs/channels/slack/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# Slack
diff --git a/docs/channels/slack/README.ja.md b/docs/channels/slack/README.ja.md
index b2184310e..38cfc0134 100644
--- a/docs/channels/slack/README.ja.md
+++ b/docs/channels/slack/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# Slack
diff --git a/docs/channels/slack/README.pt-br.md b/docs/channels/slack/README.pt-br.md
index 6d1b7c520..d2676d44a 100644
--- a/docs/channels/slack/README.pt-br.md
+++ b/docs/channels/slack/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# Slack
diff --git a/docs/channels/slack/README.vi.md b/docs/channels/slack/README.vi.md
index dff55b9ad..3bbbe3132 100644
--- a/docs/channels/slack/README.vi.md
+++ b/docs/channels/slack/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# Slack
diff --git a/docs/channels/slack/README.zh.md b/docs/channels/slack/README.zh.md
index e8dba16b8..8ecfe88bf 100644
--- a/docs/channels/slack/README.zh.md
+++ b/docs/channels/slack/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# Slack
diff --git a/docs/channels/telegram/README.fr.md b/docs/channels/telegram/README.fr.md
index 944b0091f..51db2082f 100644
--- a/docs/channels/telegram/README.fr.md
+++ b/docs/channels/telegram/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# Telegram
diff --git a/docs/channels/telegram/README.ja.md b/docs/channels/telegram/README.ja.md
index 58e4cbdfa..03303f255 100644
--- a/docs/channels/telegram/README.ja.md
+++ b/docs/channels/telegram/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# Telegram
diff --git a/docs/channels/telegram/README.md b/docs/channels/telegram/README.md
index e4b298176..3b114ebef 100644
--- a/docs/channels/telegram/README.md
+++ b/docs/channels/telegram/README.md
@@ -2,7 +2,7 @@
# Telegram
-The Telegram channel uses long polling via the Telegram Bot API for bot-based communication. It supports text messages, media attachments (photos, voice, audio, documents), voice transcription ([setup](../../providers.md#voice-transcription)), and built-in command handling.
+The Telegram channel uses long polling via the Telegram Bot API for bot-based communication. It supports text messages, media attachments (photos, voice, audio, documents), voice transcription ([setup](../../guides/providers.md#voice-transcription)), and built-in command handling.
## Configuration
diff --git a/docs/channels/telegram/README.pt-br.md b/docs/channels/telegram/README.pt-br.md
index 2cd4c99c7..4af8d7a25 100644
--- a/docs/channels/telegram/README.pt-br.md
+++ b/docs/channels/telegram/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# Telegram
diff --git a/docs/channels/telegram/README.vi.md b/docs/channels/telegram/README.vi.md
index efe6cf821..c6a276754 100644
--- a/docs/channels/telegram/README.vi.md
+++ b/docs/channels/telegram/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# Telegram
diff --git a/docs/channels/telegram/README.zh.md b/docs/channels/telegram/README.zh.md
index fa5dc42d6..543e16e47 100644
--- a/docs/channels/telegram/README.zh.md
+++ b/docs/channels/telegram/README.zh.md
@@ -1,8 +1,8 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# Telegram
-Telegram Channel 通过 Telegram 机器人 API 使用长轮询实现基于机器人的通信。它支持文本消息、媒体附件(照片、语音、音频、文档)、语音转录(配置见[提供商与模型配置](../../zh/providers.md#语音转录)),以及内置命令处理器。
+Telegram Channel 通过 Telegram 机器人 API 使用长轮询实现基于机器人的通信。它支持文本消息、媒体附件(照片、语音、音频、文档)、语音转录(配置见[提供商与模型配置](../../guides/providers.zh.md#语音转录)),以及内置命令处理器。
## 配置
diff --git a/docs/channels/vk/README.md b/docs/channels/vk/README.md
index c3f4b80e4..5e0c72bce 100644
--- a/docs/channels/vk/README.md
+++ b/docs/channels/vk/README.md
@@ -101,7 +101,7 @@ The VK channel supports both voice message reception and text-to-speech capabili
- **ASR (Automatic Speech Recognition)**: Voice messages can be transcribed to text using configured voice models
- **TTS (Text-to-Speech)**: Text responses can be converted to voice messages
-To enable voice transcription, configure a voice model in your providers setup. See [Voice Transcription](../../providers.md#voice-transcription) for details.
+To enable voice transcription, configure a voice model in your providers setup. See [Voice Transcription](../../guides/providers.md#voice-transcription) for details.
### Group Chat Support
diff --git a/docs/channels/wecom/README.fr.md b/docs/channels/wecom/README.fr.md
index b2cad168e..843943bdf 100644
--- a/docs/channels/wecom/README.fr.md
+++ b/docs/channels/wecom/README.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../../README.fr.md)
+> Retour au [README](../../project/README.fr.md)
# WeCom
diff --git a/docs/channels/wecom/README.ja.md b/docs/channels/wecom/README.ja.md
index 02224b6a9..459a922a6 100644
--- a/docs/channels/wecom/README.ja.md
+++ b/docs/channels/wecom/README.ja.md
@@ -1,4 +1,4 @@
-> [README](../../../README.ja.md) に戻る
+> [README](../../project/README.ja.md) に戻る
# WeCom
diff --git a/docs/channels/wecom/README.pt-br.md b/docs/channels/wecom/README.pt-br.md
index d20631910..07a5e23b9 100644
--- a/docs/channels/wecom/README.pt-br.md
+++ b/docs/channels/wecom/README.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../../README.pt-br.md)
+> Voltar ao [README](../../project/README.pt-br.md)
# WeCom
diff --git a/docs/channels/wecom/README.vi.md b/docs/channels/wecom/README.vi.md
index 08d571e24..4769fd6d6 100644
--- a/docs/channels/wecom/README.vi.md
+++ b/docs/channels/wecom/README.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../../README.vi.md)
+> Quay lại [README](../../project/README.vi.md)
# WeCom
diff --git a/docs/channels/wecom/README.zh.md b/docs/channels/wecom/README.zh.md
index 736ef969a..8303a8f8a 100644
--- a/docs/channels/wecom/README.zh.md
+++ b/docs/channels/wecom/README.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../../README.zh.md)
+> 返回 [README](../../project/README.zh.md)
# 企业微信(WeCom)
diff --git a/docs/design/steering-spec.md b/docs/design/steering-spec.md
index 0951bf864..5fd8360b3 100644
--- a/docs/design/steering-spec.md
+++ b/docs/design/steering-spec.md
@@ -26,7 +26,8 @@ graph TD
subgraph AgentLoop
BUS[MessageBus]
- DRAIN[drainBusToSteering goroutine]
+ ROUTE{Session Routing}
+ WP[Worker Pool]
SQ[steeringQueue]
RLI[runLLMIteration]
TE[Tool Execution Loop]
@@ -37,8 +38,11 @@ graph TD
DC -->|PublishInbound| BUS
SL -->|PublishInbound| BUS
- BUS -->|ConsumeInbound while busy| DRAIN
- DRAIN -->|Steer| SQ
+ BUS -->|ConsumeInbound| ROUTE
+ ROUTE -->|no active turn| WP
+ ROUTE -->|active turn exists| SQ
+ WP -->|Steer| SQ
+ WP -->|process| RLI
RLI -->|1. initial poll| SQ
TE -->|2. poll after each tool| SQ
@@ -47,32 +51,34 @@ graph TD
RLI -->|inject into context| LLM
```
-### Bus drain mechanism
+### Message routing and worker pool
-Channels (Telegram, Discord, etc.) publish messages to the `MessageBus` via `PublishInbound`. Without additional wiring, these messages would sit in the bus buffer until the current `processMessage` finishes — meaning steering would never work for real users.
+Channels (Telegram, Discord, etc.) publish messages to the `MessageBus` via `PublishInbound`. The `Run()` loop consumes messages from the bus and routes each one based on its **session key**:
-The solution: when `Run()` starts processing a message, it spawns a **drain goroutine** (`drainBusToSteering`) that keeps consuming from the bus and calling `Steer()`. When `processMessage` returns, the drain is canceled and normal consumption resumes.
+- **No active turn for the session**: The session key is atomically reserved via `LoadOrStore(sessionKey, struct{}{})`, and a **worker goroutine** is spawned to process the full turn lifecycle.
+- **Active turn exists for the session**: The message is enqueued directly into the steering queue via `enqueueSteeringMessage`. It will be picked up by the existing worker's steering drain loop.
+- **Non-routable (system)**: Processed synchronously in the main loop.
+
+This enables **parallel processing of messages from different sessions** (up to `max_parallel_turns`) while keeping same-session messages strictly sequential.
```mermaid
sequenceDiagram
participant Bus
participant Run
- participant Drain
- participant AgentLoop
+ participant Worker
+ participant SQ
Run->>Bus: ConsumeInbound() → msg
- Run->>Drain: spawn drainBusToSteering(ctx)
- Run->>Run: processMessage(msg)
+ Run->>Run: resolveSteeringTarget(msg) → sessionKey
- Note over Drain: running concurrently
-
- Bus-->>Drain: ConsumeInbound() → newMsg
- Drain->>AgentLoop: al.transcribeAudioInMessage(ctx, newMsg)
- Drain->>AgentLoop: Steer(providers.Message{Content: newMsg.Content})
-
- Run->>Run: processMessage returns
- Run->>Drain: cancel context
- Note over Drain: exits
+ alt no active turn
+ Run->>Run: LoadOrStore(sessionKey, sentinel)
+ Run->>Worker: spawn worker goroutine
+ Worker->>Worker: processMessage(msg)
+ Worker->>SQ: drain steering after turn
+ else active turn exists
+ Run->>SQ: enqueueSteeringMessage(msg)
+ end
```
## Data Structures
@@ -121,7 +127,7 @@ A new field was added to `processOptions`:
| `Steer` | `Steer(msg providers.Message) error` | Enqueues a steering message. Returns an error if the queue is full or not initialized. Thread-safe, can be called from any goroutine. |
| `SteeringMode` | `SteeringMode() SteeringMode` | Returns the current dequeue mode. |
| `SetSteeringMode` | `SetSteeringMode(mode SteeringMode)` | Changes the dequeue mode at runtime. |
-| `Continue` | `Continue(ctx, sessionKey, channel, chatID) (string, error)` | Resumes an idle agent using pending steering messages. Returns `""` if queue is empty. |
+| `Continue` | `Continue(ctx, sessionKey, channel, chatID) (string, error)` | Resumes an idle agent using pending steering messages for the given session. Returns `""` if queue is empty. Uses session-aware active turn checking (won't block on unrelated sessions). |
## Integration into the Agent Loop
@@ -280,15 +286,17 @@ flowchart TD
{
"agents": {
"defaults": {
- "steering_mode": "one-at-a-time"
+ "steering_mode": "one-at-a-time",
+ "max_parallel_turns": 1
}
}
}
```
-| Field | Type | Default | Env var |
-|-------|------|---------|---------|
-| `steering_mode` | `string` | `"one-at-a-time"` | `PICOCLAW_AGENTS_DEFAULTS_STEERING_MODE` |
+| Field | Type | Default | Env var | Description |
+|-------|------|---------|---------|-------------|
+| `steering_mode` | `string` | `"one-at-a-time"` | `PICOCLAW_AGENTS_DEFAULTS_STEERING_MODE` | How the steering queue is drained per poll |
+| `max_parallel_turns` | `int` | `1` | `PICOCLAW_AGENTS_DEFAULTS_MAX_PARALLEL_TURNS` | Max concurrent turns. `0` or `1` = sequential; `>1` = parallel across sessions |
## Design decisions and trade-offs
@@ -300,7 +308,8 @@ flowchart TD
| `one-at-a-time` as default | Gives the model a chance to react to each steering message individually. More predictable behavior than dumping all messages at once. |
| Skipped tools get explicit error results | The LLM protocol requires a tool result for every tool call in the assistant message. Omitting them would cause API errors. The skip message also informs the model about what was not done. |
| `Continue()` uses `SkipInitialSteeringPoll` | Prevents race conditions and double-dequeuing when resuming an idle agent. |
-| Queue stored on `AgentLoop`, not `AgentInstance` | Steering is a loop-level concern (it affects the iteration flow), not a per-agent concern. All agents share the same steering queue since `processMessage` is sequential. |
-| Bus drain goroutine in `Run()` | Channels (Telegram, Discord, etc.) publish to the bus via `PublishInbound`. Without the drain, messages would queue in the bus channel buffer and only be consumed after `processMessage` returns — defeating the purpose of steering. The drain goroutine bridges the gap by consuming new bus messages and calling `Steer()` while the agent is busy. |
-| Audio transcription before steering | The drain goroutine calls `al.transcribeAudioInMessage(ctx, msg)` before steering, so voice messages are converted to text before the agent sees them. If transcription fails, the error is silently discarded and the original message is steered as-is. |
+| Queue stored on `AgentLoop`, not `AgentInstance` | Steering is a loop-level concern (it affects the iteration flow), not a per-agent concern. All agents share the steering queue since `processMessage` is sequential. |
+| Worker pool dispatch in `Run()` | Messages are dispatched to a worker pool instead of a single sequential loop. The session key is atomically reserved via `LoadOrStore` before the worker starts, preventing TOCTOU races. Messages from the same session are serialized; different sessions are processed in parallel (up to `max_parallel_turns`). |
+| No bus drain goroutine | The old `drainBusToSteering` goroutine has been removed. The main `Run()` loop now checks `activeTurnStates` for each inbound message: if a turn is active for the session, the message is enqueued directly to the steering queue; otherwise a new worker is spawned. This eliminates the complexity of drain cancellation and requeuing. |
+| Audio transcription in worker | Audio is transcribed within the worker that processes the turn, not in a separate drain goroutine. |
| `MaxQueueSize = 10` | Prevents unbounded memory growth if a user sends many messages while the agent is busy. Excess messages are dropped with a warning. |
diff --git a/docs/fr/ANTIGRAVITY_USAGE.md b/docs/guides/ANTIGRAVITY_USAGE.fr.md
similarity index 98%
rename from docs/fr/ANTIGRAVITY_USAGE.md
rename to docs/guides/ANTIGRAVITY_USAGE.fr.md
index d6d0a2bd4..5672952d3 100644
--- a/docs/fr/ANTIGRAVITY_USAGE.md
+++ b/docs/guides/ANTIGRAVITY_USAGE.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
# Utiliser le fournisseur Antigravity dans PicoClaw
diff --git a/docs/ja/ANTIGRAVITY_USAGE.md b/docs/guides/ANTIGRAVITY_USAGE.ja.md
similarity index 98%
rename from docs/ja/ANTIGRAVITY_USAGE.md
rename to docs/guides/ANTIGRAVITY_USAGE.ja.md
index c044c1970..bd221ed1c 100644
--- a/docs/ja/ANTIGRAVITY_USAGE.md
+++ b/docs/guides/ANTIGRAVITY_USAGE.ja.md
@@ -1,4 +1,4 @@
-> [README](../../README.ja.md) に戻る
+> [README](../project/README.ja.md) に戻る
# PicoClaw で Antigravity プロバイダーを使用する
diff --git a/docs/ANTIGRAVITY_USAGE.md b/docs/guides/ANTIGRAVITY_USAGE.md
similarity index 100%
rename from docs/ANTIGRAVITY_USAGE.md
rename to docs/guides/ANTIGRAVITY_USAGE.md
diff --git a/docs/pt-br/ANTIGRAVITY_USAGE.md b/docs/guides/ANTIGRAVITY_USAGE.pt-br.md
similarity index 98%
rename from docs/pt-br/ANTIGRAVITY_USAGE.md
rename to docs/guides/ANTIGRAVITY_USAGE.pt-br.md
index d4b681ad0..e5108916a 100644
--- a/docs/pt-br/ANTIGRAVITY_USAGE.md
+++ b/docs/guides/ANTIGRAVITY_USAGE.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
# Usando o provedor Antigravity no PicoClaw
diff --git a/docs/vi/ANTIGRAVITY_USAGE.md b/docs/guides/ANTIGRAVITY_USAGE.vi.md
similarity index 98%
rename from docs/vi/ANTIGRAVITY_USAGE.md
rename to docs/guides/ANTIGRAVITY_USAGE.vi.md
index 4a696f770..54b4a6add 100644
--- a/docs/vi/ANTIGRAVITY_USAGE.md
+++ b/docs/guides/ANTIGRAVITY_USAGE.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
# Sử dụng nhà cung cấp Antigravity trong PicoClaw
diff --git a/docs/zh/ANTIGRAVITY_USAGE.md b/docs/guides/ANTIGRAVITY_USAGE.zh.md
similarity index 98%
rename from docs/zh/ANTIGRAVITY_USAGE.md
rename to docs/guides/ANTIGRAVITY_USAGE.zh.md
index 2218618a9..b4dde6ea3 100644
--- a/docs/zh/ANTIGRAVITY_USAGE.md
+++ b/docs/guides/ANTIGRAVITY_USAGE.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
# 在 PicoClaw 中使用 Antigravity 提供商
diff --git a/docs/guides/README.md b/docs/guides/README.md
new file mode 100644
index 000000000..1a50a5062
--- /dev/null
+++ b/docs/guides/README.md
@@ -0,0 +1,15 @@
+# Guides
+
+Task-oriented guides for setup, configuration, and common PicoClaw workflows.
+
+- [Docker & Quick Start Guide](docker.md): install and run PicoClaw with Docker or the launcher.
+- [Configuration Guide](configuration.md): environment variables, workspace layout, routing, and sandbox settings.
+- [Session Guide](session-guide.md): how session scope affects memory sharing, summaries, and isolation.
+- [Routing Guide](routing-guide.md): agent dispatch, session overrides, and light-model routing.
+- [Chat Apps Configuration](chat-apps.md): supported chat platforms and channel-specific setup paths.
+- [Providers & Model Configuration](providers.md): `model_list`, providers, and model routing.
+- [Spawn & Async Tasks](spawn-tasks.md): background work, long-running tasks, and sub-agent orchestration.
+- [PicoClaw Hardware Compatibility List](hardware-compatibility.md): tested boards and platform notes.
+- [Using Antigravity Provider in PicoClaw](ANTIGRAVITY_USAGE.md): Google Cloud Code Assist setup and usage.
+
+Translations usually live beside the English source when available.
diff --git a/docs/fr/chat-apps.md b/docs/guides/chat-apps.fr.md
similarity index 96%
rename from docs/fr/chat-apps.md
rename to docs/guides/chat-apps.fr.md
index d6590f9ba..d9112c595 100644
--- a/docs/fr/chat-apps.md
+++ b/docs/guides/chat-apps.fr.md
@@ -1,6 +1,6 @@
# 💬 Configuration des Applications de Chat
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
## 💬 Applications de Chat
@@ -19,7 +19,7 @@ Communiquez avec votre PicoClaw via Telegram, Discord, WhatsApp, Matrix, QQ, Din
| **QQ** | ⭐⭐ Moyen | API bot officielle, communauté chinoise | [Documentation](../channels/qq/README.fr.md) |
| **DingTalk** | ⭐⭐ Moyen | Mode Stream (pas d'IP publique requise), entreprise | [Documentation](../channels/dingtalk/README.fr.md) |
| **LINE** | ⭐⭐⭐ Avancé | HTTPS Webhook requis | [Documentation](../channels/line/README.fr.md) |
-| **WeCom (企业微信)** | ⭐⭐⭐ Avancé | Bot groupe (Webhook), app personnalisée (API), AI Bot | [Bot](../channels/wecom/wecom_bot/README.fr.md) / [App](../channels/wecom/wecom_app/README.fr.md) / [AI Bot](../channels/wecom/wecom_aibot/README.fr.md) |
+| **WeCom (企业微信)** | ⭐⭐⭐ Avancé | Bot groupe (Webhook), app personnalisée (API), AI Bot | [Guide](../channels/wecom/README.fr.md) |
| **Feishu (飞书)** | ⭐⭐⭐ Avancé | Collaboration entreprise, fonctionnalités riches | [Documentation](../channels/feishu/README.fr.md) |
| **IRC** | ⭐⭐ Moyen | Serveur + configuration TLS | [Documentation](#irc) |
| **OneBot** | ⭐⭐ Moyen | Compatible NapCat/Go-CQHTTP, écosystème communautaire | [Documentation](../channels/onebot/README.fr.md) |
@@ -61,11 +61,19 @@ picoclaw gateway
**4. Menu de commandes Telegram (enregistré automatiquement au démarrage)**
-PicoClaw conserve les définitions de commandes dans un registre partagé unique. Au démarrage, Telegram enregistre automatiquement les commandes bot prises en charge (par exemple `/start`, `/help`, `/show`, `/list`) afin que le menu de commandes et le comportement à l'exécution restent synchronisés.
+PicoClaw conserve les définitions de commandes dans un registre partagé unique. Au démarrage, Telegram enregistre automatiquement les commandes bot prises en charge (par exemple `/start`, `/help`, `/show`, `/list`, `/use`, `/btw`) afin que le menu de commandes et le comportement à l'exécution restent synchronisés.
L'enregistrement du menu de commandes Telegram reste une découverte UX locale au canal ; l'exécution générique des commandes est gérée de manière centralisée dans la boucle agent via l'exécuteur de commandes.
Si l'enregistrement des commandes échoue (erreurs transitoires réseau/API), le canal démarre quand même et PicoClaw réessaie l'enregistrement en arrière-plan.
+Vous pouvez aussi gerer les competences installees directement depuis Telegram :
+
+- `/list skills`
+- `/use `
+- `/use ` puis envoyer la vraie requete dans le message suivant
+- `/use clear`
+- `/btw ` pour poser une question annexe immediate sans modifier l'historique actif de la session ; `/btw` est traite comme une requete directe sans outils et n'entre pas dans le flux normal d'execution des outils
+
@@ -383,7 +391,7 @@ PicoClaw prend en charge trois types d'intégration WeCom :
**Option 2 : WeCom App (Application personnalisée)** - Plus de fonctionnalités, messagerie proactive, chat privé uniquement
**Option 3 : WeCom AI Bot (Bot IA)** - Bot IA officiel, réponses en streaming, prend en charge les discussions de groupe et privées
-Voir le [Guide de Configuration WeCom AI Bot](../channels/wecom/wecom_aibot/README.fr.md) pour les instructions détaillées.
+Voir le [Guide de Configuration WeCom](../channels/wecom/README.fr.md) pour les instructions détaillées.
**Configuration rapide - WeCom Bot :**
diff --git a/docs/ja/chat-apps.md b/docs/guides/chat-apps.ja.md
similarity index 97%
rename from docs/ja/chat-apps.md
rename to docs/guides/chat-apps.ja.md
index 997748939..49c41a66e 100644
--- a/docs/ja/chat-apps.md
+++ b/docs/guides/chat-apps.ja.md
@@ -1,6 +1,6 @@
# 💬 チャットアプリ設定
-> [README](../../README.ja.md) に戻る
+> [README](../project/README.ja.md) に戻る
## 💬 チャットアプリ連携
@@ -21,7 +21,7 @@ PicoClaw は複数のチャットプラットフォームをサポートして
| **QQ** | ⭐⭐ 中程度 | 公式ボット API、中国コミュニティ向け | [ドキュメント](../channels/qq/README.ja.md) |
| **DingTalk** | ⭐⭐ 中程度 | Stream モード(公開 IP 不要)、企業向け | [ドキュメント](../channels/dingtalk/README.ja.md) |
| **LINE** | ⭐⭐⭐ やや難 | HTTPS Webhook が必要 | [ドキュメント](../channels/line/README.ja.md) |
-| **WeCom (企業微信)** | ⭐⭐⭐ やや難 | グループ Bot (Webhook)、カスタムアプリ (API)、AI Bot 対応 | [Bot](../channels/wecom/wecom_bot/README.ja.md) / [App](../channels/wecom/wecom_app/README.ja.md) / [AI Bot](../channels/wecom/wecom_aibot/README.ja.md) |
+| **WeCom (企業微信)** | ⭐⭐⭐ やや難 | グループ Bot (Webhook)、カスタムアプリ (API)、AI Bot 対応 | [ガイド](../channels/wecom/README.ja.md) |
| **Feishu (飛書)** | ⭐⭐⭐ やや難 | エンタープライズコラボレーション、機能豊富 | [ドキュメント](../channels/feishu/README.ja.md) |
| **IRC** | ⭐⭐ 中程度 | サーバー + TLS 設定 | [ドキュメント](#irc) |
| **OneBot** | ⭐⭐ 中程度 | NapCat/Go-CQHTTP 互換、コミュニティエコシステム充実 | [ドキュメント](../channels/onebot/README.ja.md) |
@@ -65,7 +65,7 @@ picoclaw gateway
**4. Telegram コマンドメニュー(起動時に自動登録)**
-PicoClaw は統一されたコマンド定義を使用します。起動時に Telegram がサポートするコマンド(例: `/start`、`/help`、`/show`、`/list`)を Bot コマンドメニューに自動登録し、メニュー表示と実際の動作を一致させます。
+PicoClaw は統一されたコマンド定義を使用します。起動時に Telegram がサポートするコマンド(例: `/start`、`/help`、`/show`、`/list`、`/use`、`/btw`)を Bot コマンドメニューに自動登録し、メニュー表示と実際の動作を一致させます。
Telegram 側はコマンドメニュー登録機能を保持し、汎用コマンドの実行は Agent Loop 内の commands executor で統一的に処理されます。
ネットワークや API の一時的なエラーで登録に失敗しても、チャネルの起動はブロックされません。システムがバックグラウンドで自動リトライします。
@@ -502,7 +502,7 @@ PicoClaw は 3 種類の WeCom 統合をサポートしています:
**方式 2: カスタムアプリ (App)** — より多機能、プロアクティブメッセージング、プライベートチャットのみ
**方式 3: AI Bot** — 公式 AI Bot、ストリーミング返信、グループ・プライベートチャット対応
-詳細なセットアップ手順は [WeCom AI Bot 設定ガイド](../channels/wecom/wecom_aibot/README.ja.md) を参照してください。
+詳細なセットアップ手順は [WeCom 設定ガイド](../channels/wecom/README.ja.md) を参照してください。
**クイックセットアップ — グループ Bot:**
diff --git a/docs/chat-apps.md b/docs/guides/chat-apps.md
similarity index 88%
rename from docs/chat-apps.md
rename to docs/guides/chat-apps.md
index ae98a7d9f..140a659d1 100644
--- a/docs/chat-apps.md
+++ b/docs/guides/chat-apps.md
@@ -10,20 +10,20 @@ Talk to your picoclaw through Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk,
| Channel | Difficulty | Description | Documentation |
| -------------------- | ------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
-| **Telegram** | ⭐ Easy | Recommended, voice-to-text, long polling (no public IP needed) | [Docs](channels/telegram/README.md) |
-| **Discord** | ⭐ Easy | Socket Mode, group/DM support, rich bot ecosystem | [Docs](channels/discord/README.md) |
+| **Telegram** | ⭐ Easy | Recommended, voice-to-text, long polling (no public IP needed) | [Docs](../channels/telegram/README.md) |
+| **Discord** | ⭐ Easy | Socket Mode, group/DM support, rich bot ecosystem | [Docs](../channels/discord/README.md) |
| **WhatsApp** | ⭐ Easy | Native (QR scan) or Bridge URL | [Docs](#whatsapp) |
| **Weixin** | ⭐ Easy | Native QR scan (Tencent iLink API) | [Docs](#weixin) |
-| **Slack** | ⭐ Easy | **Socket Mode** (no public IP needed), enterprise | [Docs](channels/slack/README.md) |
-| **Matrix** | ⭐⭐ Medium | Federated protocol, self-hosting supported | [Docs](channels/matrix/README.md) |
-| **QQ** | ⭐⭐ Medium | Official bot API, Chinese community | [Docs](channels/qq/README.md) |
-| **DingTalk** | ⭐⭐ Medium | Stream mode (no public IP needed), enterprise | [Docs](channels/dingtalk/README.md) |
-| **LINE** | ⭐⭐⭐ Advanced | HTTPS Webhook required | [Docs](channels/line/README.md) |
-| **WeCom (企业微信)** | ⭐⭐⭐ Advanced | Official AI Bot over WebSocket, streaming + media | [Docs](channels/wecom/README.md) |
-| **Feishu (飞书)** | ⭐⭐⭐ Advanced | Enterprise collaboration, feature-rich | [Docs](channels/feishu/README.md) |
+| **Slack** | ⭐ Easy | **Socket Mode** (no public IP needed), enterprise | [Docs](../channels/slack/README.md) |
+| **Matrix** | ⭐⭐ Medium | Federated protocol, self-hosting supported | [Docs](../channels/matrix/README.md) |
+| **QQ** | ⭐⭐ Medium | Official bot API, Chinese community | [Docs](../channels/qq/README.md) |
+| **DingTalk** | ⭐⭐ Medium | Stream mode (no public IP needed), enterprise | [Docs](../channels/dingtalk/README.md) |
+| **LINE** | ⭐⭐⭐ Advanced | HTTPS Webhook required | [Docs](../channels/line/README.md) |
+| **WeCom (企业微信)** | ⭐⭐⭐ Advanced | Official AI Bot over WebSocket, streaming + media | [Docs](../channels/wecom/README.md) |
+| **Feishu (飞书)** | ⭐⭐⭐ Advanced | Enterprise collaboration, feature-rich | [Docs](../channels/feishu/README.md) |
| **IRC** | ⭐⭐ Medium | Server + TLS configuration | [Docs](#irc) |
-| **OneBot** | ⭐⭐ Medium | NapCat/Go-CQHTTP compatible, community ecosystem | [Docs](channels/onebot/README.md) |
-| **MaixCam** | ⭐ Easy | Hardware integration channel for Sipeed AI cameras | [Docs](channels/maixcam/README.md) |
+| **OneBot** | ⭐⭐ Medium | NapCat/Go-CQHTTP compatible, community ecosystem | [Docs](../channels/onebot/README.md) |
+| **MaixCam** | ⭐ Easy | Hardware integration channel for Sipeed AI cameras | [Docs](../channels/maixcam/README.md) |
| **Pico** | ⭐ Easy | Native PicoClaw protocol channel | |
@@ -62,7 +62,7 @@ picoclaw gateway
**4. Telegram command menu (auto-registered at startup)**
-PicoClaw now keeps command definitions in one shared registry. On startup, Telegram will automatically register supported bot commands (for example `/start`, `/help`, `/show`, `/list`, `/use`) so command menu and runtime behavior stay in sync.
+PicoClaw now keeps command definitions in one shared registry. On startup, Telegram will automatically register supported bot commands (for example `/start`, `/help`, `/show`, `/list`, `/use`, `/btw`) so command menu and runtime behavior stay in sync.
Telegram command menu registration remains channel-local discovery UX; generic command execution is handled centrally in the agent loop via the commands executor.
If command registration fails (network/API transient errors), the channel still starts and PicoClaw retries registration in the background.
@@ -73,6 +73,7 @@ You can also manage installed skills directly from Telegram:
- `/use `
- `/use ` and then send the actual request in the next message
- `/use clear`
+- `/btw ` to ask an immediate side question without changing the active session history; `/btw` is handled as a no-tool query and does not enter the normal tool-execution flow
**4. Advanced Formatting**
You can set use_markdown_v2: true to enable enhanced formatting options. This allows the bot to utilize the full range of Telegram MarkdownV2 features, including nested styles, spoilers, and custom fixed-width blocks.
@@ -330,7 +331,7 @@ picoclaw gateway
picoclaw gateway
```
-For full options (`device_id`, `join_on_invite`, `group_trigger`, `placeholder`, `reasoning_channel_id`), see [Matrix Channel Configuration Guide](channels/matrix/README.md).
+For full options (`device_id`, `join_on_invite`, `group_trigger`, `placeholder`, `reasoning_channel_id`), see [Matrix Channel Configuration Guide](../channels/matrix/README.md).
@@ -391,7 +392,7 @@ picoclaw gateway
PicoClaw now exposes WeCom as a single AI Bot channel over WebSocket.
No public webhook callback URL is required.
-See [WeCom Configuration Guide](channels/wecom/README.md) for the full configuration reference and migration notes.
+See [WeCom Configuration Guide](../channels/wecom/README.md) for the full configuration reference and migration notes.
**Quick Setup - Recommended**
@@ -471,7 +472,7 @@ picoclaw gateway
Open Feishu, search for your bot name, and start chatting. You can also add the bot to a group — use `group_trigger.mention_only: true` to only respond when @mentioned.
-For full options, see [Feishu Channel Configuration Guide](channels/feishu/README.md).
+For full options, see [Feishu Channel Configuration Guide](../channels/feishu/README.md).
diff --git a/docs/my/chat-apps.md b/docs/guides/chat-apps.ms.md
similarity index 94%
rename from docs/my/chat-apps.md
rename to docs/guides/chat-apps.ms.md
index c42436139..6bfa7565e 100644
--- a/docs/my/chat-apps.md
+++ b/docs/guides/chat-apps.ms.md
@@ -1,6 +1,6 @@
# 💬 Konfigurasi Aplikasi Sembang
-> Kembali ke [README](../../README.my.md)
+> Kembali ke [README](../project/README.ms.md)
## 💬 Aplikasi Sembang
@@ -60,11 +60,19 @@ picoclaw gateway
**4. Menu arahan Telegram (auto-register semasa startup)**
-PicoClaw kini menyimpan definisi arahan dalam satu registry bersama. Semasa startup, Telegram akan mendaftarkan arahan bot yang disokong secara automatik (contohnya `/start`, `/help`, `/show`, `/list`) supaya menu arahan dan tingkah laku runtime sentiasa selari.
+PicoClaw kini menyimpan definisi arahan dalam satu registry bersama. Semasa startup, Telegram akan mendaftarkan arahan bot yang disokong secara automatik (contohnya `/start`, `/help`, `/show`, `/list`, `/use`, `/btw`) supaya menu arahan dan tingkah laku runtime sentiasa selari.
Pendaftaran menu arahan Telegram kekal sebagai UX penemuan setempat saluran; pelaksanaan arahan generik dikendalikan secara berpusat dalam gelung agen melalui commands executor.
Jika pendaftaran arahan gagal (ralat sementara rangkaian/API), saluran tetap akan bermula dan PicoClaw akan mencuba semula pendaftaran di latar belakang.
+Anda juga boleh mengurus skill yang dipasang terus dari Telegram:
+
+- `/list skills`
+- `/use `
+- `/use ` kemudian hantar permintaan sebenar dalam mesej seterusnya
+- `/use clear`
+- `/btw ` untuk bertanya soalan sampingan segera tanpa mengubah sejarah sesi aktif; `/btw` dikendalikan sebagai pertanyaan langsung tanpa tool dan tidak memasuki aliran pelaksanaan tool biasa
+
**4. Pemformatan Lanjutan**
Anda boleh menetapkan `use_markdown_v2: true` untuk mengaktifkan pilihan pemformatan yang lebih maju. Ini membolehkan bot menggunakan keseluruhan set ciri Telegram MarkdownV2, termasuk gaya bersarang, spoiler, dan blok lebar tetap tersuai.
@@ -271,7 +279,7 @@ picoclaw gateway
picoclaw gateway
```
-Untuk pilihan penuh (`device_id`, `join_on_invite`, `group_trigger`, `placeholder`, `reasoning_channel_id`), lihat [Panduan Konfigurasi Saluran Matrix](docs/channels/matrix/README.md).
+Untuk pilihan penuh (`device_id`, `join_on_invite`, `group_trigger`, `placeholder`, `reasoning_channel_id`), lihat [Panduan Konfigurasi Saluran Matrix](../channels/matrix/README.md).
@@ -333,7 +341,7 @@ PicoClaw menyokong tiga jenis integrasi WeCom:
**Pilihan 2: WeCom App (Custom App)** - Lebih banyak ciri, pemesejan proaktif, sembang peribadi sahaja
**Pilihan 3: WeCom AI Bot (AI Bot)** - AI Bot rasmi, balasan streaming, menyokong sembang kumpulan & peribadi
-Lihat [Panduan Konfigurasi WeCom AI Bot](docs/channels/wecom/wecom_aibot/README.zh.md) untuk arahan penyediaan terperinci.
+Lihat [Panduan Konfigurasi WeCom](../channels/wecom/README.zh.md) untuk arahan penyediaan terperinci.
**Quick Setup - WeCom Bot:**
diff --git a/docs/pt-br/chat-apps.md b/docs/guides/chat-apps.pt-br.md
similarity index 96%
rename from docs/pt-br/chat-apps.md
rename to docs/guides/chat-apps.pt-br.md
index 732cdb1dc..6d4fbdc23 100644
--- a/docs/pt-br/chat-apps.md
+++ b/docs/guides/chat-apps.pt-br.md
@@ -1,6 +1,6 @@
# 💬 Configuração de Aplicativos de Chat
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
## 💬 Aplicativos de Chat
@@ -19,7 +19,7 @@ Converse com seu picoclaw através do Telegram, Discord, WhatsApp, Matrix, QQ, D
| **QQ** | ⭐⭐ Médio | API bot oficial, comunidade chinesa | [Documentação](../channels/qq/README.pt-br.md) |
| **DingTalk** | ⭐⭐ Médio | Modo Stream (sem IP público), empresarial | [Documentação](../channels/dingtalk/README.pt-br.md) |
| **LINE** | ⭐⭐⭐ Avançado | HTTPS Webhook obrigatório | [Documentação](../channels/line/README.pt-br.md) |
-| **WeCom (企业微信)** | ⭐⭐⭐ Avançado | Bot de grupo (Webhook), app personalizado (API), AI Bot | [Bot](../channels/wecom/wecom_bot/README.pt-br.md) / [App](../channels/wecom/wecom_app/README.pt-br.md) / [AI Bot](../channels/wecom/wecom_aibot/README.pt-br.md) |
+| **WeCom (企业微信)** | ⭐⭐⭐ Avançado | Bot de grupo (Webhook), app personalizado (API), AI Bot | [Guia](../channels/wecom/README.pt-br.md) |
| **Feishu (飞书)** | ⭐⭐⭐ Avançado | Colaboração empresarial, rico em recursos | [Documentação](../channels/feishu/README.pt-br.md) |
| **IRC** | ⭐⭐ Médio | Servidor + configuração TLS | [Documentação](#irc) |
| **OneBot** | ⭐⭐ Médio | Compatível com NapCat/Go-CQHTTP, ecossistema comunitário | [Documentação](../channels/onebot/README.pt-br.md) |
@@ -61,11 +61,19 @@ picoclaw gateway
**4. Menu de comandos do Telegram (registrado automaticamente na inicialização)**
-O PicoClaw agora mantém definições de comandos em um registro compartilhado. Na inicialização, o Telegram registrará automaticamente os comandos de bot suportados (por exemplo `/start`, `/help`, `/show`, `/list`) para que o menu de comandos e o comportamento em tempo de execução permaneçam sincronizados.
+O PicoClaw agora mantém definições de comandos em um registro compartilhado. Na inicialização, o Telegram registrará automaticamente os comandos de bot suportados (por exemplo `/start`, `/help`, `/show`, `/list`, `/use`, `/btw`) para que o menu de comandos e o comportamento em tempo de execução permaneçam sincronizados.
O registro do menu de comandos do Telegram permanece como descoberta UX local do canal; a execução genérica de comandos é tratada centralmente no loop do agente via commands executor.
Se o registro de comandos falhar (erros transitórios de rede/API), o canal ainda inicia e o PicoClaw tenta novamente o registro em segundo plano.
+Voce tambem pode gerenciar skills instaladas diretamente pelo Telegram:
+
+- `/list skills`
+- `/use `
+- `/use ` e depois enviar a solicitacao real na proxima mensagem
+- `/use clear`
+- `/btw ` para fazer uma pergunta lateral imediata sem alterar o historico ativo da sessao; `/btw` e tratado como uma consulta direta sem ferramentas e nao entra no fluxo normal de execucao de ferramentas
+
@@ -408,7 +416,7 @@ O PicoClaw suporta três tipos de integração WeCom:
**Opção 2: WeCom App (App Personalizado)** - Mais recursos, mensagens proativas, apenas chat privado
**Opção 3: WeCom AI Bot (AI Bot)** - AI Bot oficial, respostas em streaming, suporta chat de grupo e privado
-Veja o [Guia de Configuração do WeCom AI Bot](../channels/wecom/wecom_aibot/README.pt-br.md) para instruções detalhadas de configuração.
+Veja o [Guia de Configuração do WeCom](../channels/wecom/README.pt-br.md) para instruções detalhadas de configuração.
**Configuração Rápida - WeCom Bot:**
diff --git a/docs/vi/chat-apps.md b/docs/guides/chat-apps.vi.md
similarity index 96%
rename from docs/vi/chat-apps.md
rename to docs/guides/chat-apps.vi.md
index 5eb7c9488..8d0b4ee32 100644
--- a/docs/vi/chat-apps.md
+++ b/docs/guides/chat-apps.vi.md
@@ -1,6 +1,6 @@
# 💬 Cấu Hình Ứng Dụng Chat
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
## 💬 Ứng Dụng Chat
@@ -19,7 +19,7 @@ Trò chuyện với picoclaw của bạn qua Telegram, Discord, WhatsApp, Matrix
| **QQ** | ⭐⭐ Trung bình | API bot chính thức, cộng đồng Trung Quốc | [Tài liệu](../channels/qq/README.vi.md) |
| **DingTalk** | ⭐⭐ Trung bình | Chế độ Stream (không cần IP công khai), doanh nghiệp | [Tài liệu](../channels/dingtalk/README.vi.md) |
| **LINE** | ⭐⭐⭐ Nâng cao | Yêu cầu HTTPS Webhook | [Tài liệu](../channels/line/README.vi.md) |
-| **WeCom (企业微信)** | ⭐⭐⭐ Nâng cao | Bot nhóm (Webhook), ứng dụng tùy chỉnh (API), AI Bot | [Bot](../channels/wecom/wecom_bot/README.vi.md) / [App](../channels/wecom/wecom_app/README.vi.md) / [AI Bot](../channels/wecom/wecom_aibot/README.vi.md) |
+| **WeCom (企业微信)** | ⭐⭐⭐ Nâng cao | Bot nhóm (Webhook), ứng dụng tùy chỉnh (API), AI Bot | [Hướng dẫn](../channels/wecom/README.vi.md) |
| **Feishu (飞书)** | ⭐⭐⭐ Nâng cao | Cộng tác doanh nghiệp, nhiều tính năng | [Tài liệu](../channels/feishu/README.vi.md) |
| **IRC** | ⭐⭐ Trung bình | Máy chủ + cấu hình TLS | [Tài liệu](#irc) |
| **OneBot** | ⭐⭐ Trung bình | Tương thích NapCat/Go-CQHTTP, hệ sinh thái cộng đồng | [Tài liệu](../channels/onebot/README.vi.md) |
@@ -61,11 +61,19 @@ picoclaw gateway
**4. Menu lệnh Telegram (tự động đăng ký khi khởi động)**
-PicoClaw hiện lưu trữ định nghĩa lệnh trong một registry chung. Khi khởi động, Telegram sẽ tự động đăng ký các lệnh bot được hỗ trợ (ví dụ `/start`, `/help`, `/show`, `/list`) để menu lệnh và hành vi runtime luôn đồng bộ.
+PicoClaw hiện lưu trữ định nghĩa lệnh trong một registry chung. Khi khởi động, Telegram sẽ tự động đăng ký các lệnh bot được hỗ trợ (ví dụ `/start`, `/help`, `/show`, `/list`, `/use`, `/btw`) để menu lệnh và hành vi runtime luôn đồng bộ.
Đăng ký menu lệnh Telegram vẫn là UX khám phá cục bộ của kênh; thực thi lệnh chung được xử lý tập trung trong vòng lặp agent qua commands executor.
Nếu đăng ký lệnh thất bại (lỗi tạm thời mạng/API), kênh vẫn khởi động và PicoClaw thử lại đăng ký trong nền.
+Ban cung co the quan ly skill da cai dat truc tiep tu Telegram:
+
+- `/list skills`
+- `/use `
+- `/use ` roi gui yeu cau that o tin nhan tiep theo
+- `/use clear`
+- `/btw ` de hoi them mot cau ngoai le ngay lap tuc ma khong thay doi lich su phien dang hoat dong; `/btw` duoc xu ly nhu mot truy van truc tiep khong dung cong cu va khong di vao luong thuc thi cong cu thong thuong
+
@@ -408,7 +416,7 @@ PicoClaw hỗ trợ ba loại tích hợp WeCom:
**Tùy chọn 2: WeCom App (App Tùy chỉnh)** - Nhiều tính năng hơn, nhắn tin chủ động, chỉ chat riêng
**Tùy chọn 3: WeCom AI Bot (AI Bot)** - AI Bot chính thức, phản hồi streaming, hỗ trợ chat nhóm & riêng
-Xem [Hướng Dẫn Cấu Hình WeCom AI Bot](../channels/wecom/wecom_aibot/README.vi.md) để biết hướng dẫn thiết lập chi tiết.
+Xem [Hướng Dẫn Cấu Hình WeCom](../channels/wecom/README.vi.md) để biết hướng dẫn thiết lập chi tiết.
**Thiết Lập Nhanh - WeCom Bot:**
diff --git a/docs/zh/chat-apps.md b/docs/guides/chat-apps.zh.md
similarity index 98%
rename from docs/zh/chat-apps.md
rename to docs/guides/chat-apps.zh.md
index 4a59d528f..b5891dc69 100644
--- a/docs/zh/chat-apps.md
+++ b/docs/guides/chat-apps.zh.md
@@ -1,6 +1,6 @@
# 💬 聊天应用配置
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
## 💬 聊天应用集成 (Chat Apps)
@@ -65,7 +65,7 @@ picoclaw gateway
**4. Telegram 命令菜单(启动时自动注册)**
-PicoClaw 使用统一的命令定义来源。启动时会自动将 Telegram 支持的命令(例如 `/start`、`/help`、`/show`、`/list`、`/use`)注册到 Bot 命令菜单,确保菜单展示与实际行为一致。
+PicoClaw 使用统一的命令定义来源。启动时会自动将 Telegram 支持的命令(例如 `/start`、`/help`、`/show`、`/list`、`/use`、`/btw`)注册到 Bot 命令菜单,确保菜单展示与实际行为一致。
Telegram 侧保留的是命令菜单注册能力;通用命令的实际执行统一走 Agent Loop 中的 commands executor。
如果注册因网络或 API 短暂异常失败,不会阻塞 channel 启动;系统会在后台自动重试。
@@ -76,6 +76,7 @@ Telegram 侧保留的是命令菜单注册能力;通用命令的实际执行
- `/use `
- `/use `,然后在下一条消息里发送真正的请求
- `/use clear`
+- `/btw `,用于发起一个不改动当前会话历史的即时旁支提问;`/btw` 会按一次无工具的直接问答处理,不会进入常规的工具执行流程
diff --git a/docs/fr/configuration.md b/docs/guides/configuration.fr.md
similarity index 91%
rename from docs/fr/configuration.md
rename to docs/guides/configuration.fr.md
index 7a57cceae..f147fea95 100644
--- a/docs/fr/configuration.md
+++ b/docs/guides/configuration.fr.md
@@ -1,6 +1,6 @@
# ⚙️ Guide de Configuration
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
## ⚙️ Configuration
@@ -80,10 +80,30 @@ Pour les configurations avancées/de test, vous pouvez remplacer la racine des c
export PICOCLAW_BUILTIN_SKILLS=/path/to/skills
```
+### Utiliser les Commandes Depuis les Canaux de Chat
+
+Une fois les compétences installées, vous pouvez aussi les inspecter et les activer directement depuis un canal de chat :
+
+- `/list skills` affiche les noms des compétences installées visibles pour l'agent courant.
+- `/use ` force une compétence pour une seule requête.
+- `/use ` prépare cette compétence pour votre prochain message dans la meme conversation.
+- `/use clear` annule une surcharge de compétence en attente creee via `/use `.
+- `/btw ` pose une question annexe immediate sans modifier l'historique courant de la session. `/btw` est traite comme une requete directe sans outils et n'entre pas dans le flux normal d'execution des outils.
+
+Exemples :
+
+```text
+/list skills
+/use git explique comment squash les 3 derniers commits
+/btw rappelle-moi ce qu'on a deja decide pour le plan de deploiement
+/use italiapersonalfinance
+dammi le ultime news
+```
+
### Politique Unifiée d'Exécution des Commandes
- Les commandes slash génériques sont exécutées via un chemin unique dans `pkg/agent/loop.go` via `commands.Executor`.
-- Les adaptateurs de canaux ne consomment plus les commandes génériques localement ; ils transmettent le texte entrant au chemin bus/agent. Telegram enregistre toujours automatiquement les commandes prises en charge au démarrage.
+- Les adaptateurs de canaux ne consomment plus les commandes génériques localement ; ils transmettent le texte entrant au chemin bus/agent. Telegram enregistre toujours automatiquement au démarrage les commandes prises en charge, comme `/start`, `/help`, `/show`, `/list`, `/use` et `/btw`.
- Une commande slash inconnue (par exemple `/foo`) passe au traitement LLM normal.
- Une commande enregistrée mais non prise en charge sur le canal actuel (par exemple `/show` sur WhatsApp) renvoie une erreur explicite à l'utilisateur et arrête le traitement ultérieur.
@@ -373,7 +393,7 @@ Les tâches planifiées persistent après redémarrage dans `~/.picoclaw/workspa
| Sujet | Description |
| ----- | ----------- |
-| [Système de Hooks](../hooks/README.md) | Hooks événementiels : observateurs, intercepteurs, hooks d'approbation |
-| [Steering](../steering.md) | Injecter des messages dans une boucle agent en cours d'exécution |
-| [SubTurn](../subturn.md) | Coordination de subagents, contrôle de concurrence, cycle de vie |
-| [Gestion du Contexte](../agent-refactor/context.md) | Détection des limites de contexte, compression |
+| [Système de Hooks](../architecture/hooks/README.md) | Hooks événementiels : observateurs, intercepteurs, hooks d'approbation |
+| [Steering](../architecture/steering.md) | Injecter des messages dans une boucle agent en cours d'exécution |
+| [SubTurn](../architecture/subturn.md) | Coordination de subagents, contrôle de concurrence, cycle de vie |
+| [Gestion du Contexte](../architecture/agent-refactor/context.md) | Détection des limites de contexte, compression |
diff --git a/docs/ja/configuration.md b/docs/guides/configuration.ja.md
similarity index 91%
rename from docs/ja/configuration.md
rename to docs/guides/configuration.ja.md
index 6d6290e8a..1940eacda 100644
--- a/docs/ja/configuration.md
+++ b/docs/guides/configuration.ja.md
@@ -1,6 +1,6 @@
# ⚙️ 設定ガイド
-> [README](../../README.ja.md) に戻る
+> [README](../project/README.ja.md) に戻る
## ⚙️ 設定詳細
@@ -81,10 +81,30 @@ PicoClaw は設定されたワークスペース(デフォルト: `~/.picoclaw
export PICOCLAW_BUILTIN_SKILLS=/path/to/skills
```
+### チャットチャネルからスキルとコマンドを使う
+
+スキルをインストールすると、チャットチャネルから直接確認したり明示的に適用したりできます:
+
+- `/list skills` は現在の Agent から見えるインストール済みスキル名を表示します。
+- `/use ` は 1 回のリクエストだけそのスキルを強制します。
+- `/use ` は同じチャット内の次のメッセージにそのスキルを予約します。
+- `/use clear` は `/use ` で設定した保留中のスキル上書きを解除します。
+- `/btw ` は現在のセッション履歴を変更せずに即時の横道の質問を送ります。`/btw` はツールなしの直接質問として処理され、通常のツール実行フローには入りません。
+
+例:
+
+```text
+/list skills
+/use git 直近 3 つのコミットを squash する方法を教えて
+/btw さっきのデプロイ方針の結論だけもう一度教えて
+/use italiapersonalfinance
+dammi le ultime news
+```
+
### 統一コマンド実行ポリシー
- 汎用スラッシュコマンドは `pkg/agent/loop.go` 内の `commands.Executor` を通じて統一的に実行されます。
-- チャネルアダプターはローカルで汎用コマンドを消費しなくなりました。受信テキストを bus/agent パスに転送するだけです。Telegram は起動時にサポートするコマンドメニューを自動登録します。
+- チャネルアダプターはローカルで汎用コマンドを消費しなくなりました。受信テキストを bus/agent パスに転送するだけです。Telegram は起動時に `/start`、`/help`、`/show`、`/list`、`/use`、`/btw` などのサポート済みコマンドを自動登録します。
- 未登録のスラッシュコマンド(例: `/foo`)は通常の LLM 処理にパススルーされます。
- 登録済みだが現在のチャネルでサポートされていないコマンド(例: WhatsApp での `/show`)は、明示的なユーザー向けエラーを返し、以降の処理を停止します。
@@ -374,7 +394,7 @@ PicoClaw は `cron` ツールを通じて cron スタイルのスケジュール
| トピック | 説明 |
| -------- | ---- |
-| [Hook システム](../hooks/README.md) | イベント駆動 Hook:オブザーバー、インターセプター、承認 Hook |
-| [Steering](../steering.md) | 実行中の Agent ループにメッセージを注入 |
-| [SubTurn](../subturn.md) | サブ Agent の調整、並行制御、ライフサイクル |
-| [コンテキスト管理](../agent-refactor/context.md) | コンテキスト境界検出、圧縮戦略 |
+| [Hook システム](../architecture/hooks/README.md) | イベント駆動 Hook:オブザーバー、インターセプター、承認 Hook |
+| [Steering](../architecture/steering.md) | 実行中の Agent ループにメッセージを注入 |
+| [SubTurn](../architecture/subturn.md) | サブ Agent の調整、並行制御、ライフサイクル |
+| [コンテキスト管理](../architecture/agent-refactor/context.md) | コンテキスト境界検出、圧縮戦略 |
diff --git a/docs/configuration.md b/docs/guides/configuration.md
similarity index 94%
rename from docs/configuration.md
rename to docs/guides/configuration.md
index e59d6a022..bb58d5081 100644
--- a/docs/configuration.md
+++ b/docs/guides/configuration.md
@@ -6,7 +6,7 @@
Config file: `~/.picoclaw/config.json`
-> **Security Configuration:** For storing API keys, tokens, and other sensitive data, see the [Security Configuration Guide](security_configuration.md).
+> **Security Configuration:** For storing API keys, tokens, and other sensitive data, see the [Security Configuration Guide](../security/security_configuration.md).
### Environment Variables
@@ -103,12 +103,14 @@ Once skills are installed, you can inspect and force them directly from a chat c
- `/use ` forces a specific skill for a single request.
- `/use ` arms that skill for your next message in the same chat session.
- `/use clear` cancels a pending skill override created by `/use `.
+- `/btw ` asks an immediate side question without changing the current session history. `/btw` is handled as a no-tool query and does not enter the normal tool-execution flow.
Examples:
```text
/list skills
/use git explain how to squash the last 3 commits
+/btw remind me what we already decided about the deploy plan
/use italiapersonalfinance
dammi le ultime news
```
@@ -116,10 +118,19 @@ dammi le ultime news
### Unified Command Execution Policy
- Generic slash commands are executed through a single path in `pkg/agent/loop.go` via `commands.Executor`.
-- Channel adapters no longer consume generic commands locally; they forward inbound text to the bus/agent path. Telegram still auto-registers supported commands at startup.
+- Channel adapters no longer consume generic commands locally; they forward inbound text to the bus/agent path. Telegram still auto-registers supported commands such as `/start`, `/help`, `/show`, `/list`, `/use`, and `/btw` at startup.
- Unknown slash command (for example `/foo`) passes through to normal LLM processing.
- Registered but unsupported command on the current channel (for example `/show` on WhatsApp) returns an explicit user-facing error and stops further processing.
+### Session Isolation
+
+Session scope controls how much memory is shared between chats, users, threads, and spaces.
+
+- Use `session.dimensions` for the global default.
+- Use `session_dimensions` on a dispatch rule for one routed exception.
+
+For step-by-step recipes and isolation patterns, see the [Session Guide](session-guide.md).
+
### Routing
Routing is configured through `agents.dispatch.rules`.
@@ -193,6 +204,8 @@ In the example above, the VIP rule must appear before the broader group rule.
Because routing is strictly ordered, more specific rules should be placed
earlier and broader fallback rules later.
+For more complete routing and model-tier examples, see the [Routing Guide](routing-guide.md).
+
### 🔒 Security Sandbox
PicoClaw runs in a sandboxed environment by default. The agent can only access files and execute commands within the configured workspace.
@@ -553,7 +566,7 @@ chmod 600 ~/.picoclaw/.security.yml
- If a field exists in both files, `.security.yml` value takes precedence
- You can mix direct values in config.json with security values
-For complete documentation, see [`security_configuration.md`](security_configuration.md).
+For complete documentation, see [`../security/security_configuration.md`](../security/security_configuration.md).
#### All Supported Vendors
@@ -823,7 +836,8 @@ This keeps the runtime lightweight while making new OpenAI-compatible backends m
"model": "glm-4.7",
"max_tokens": 8192,
"temperature": 0.7,
- "max_tool_iterations": 20
+ "max_tool_iterations": 20,
+ "max_parallel_turns": 1
}
},
"providers": {
@@ -836,6 +850,8 @@ This keeps the runtime lightweight while making new OpenAI-compatible backends m
```
> **Note**: The `providers` format is deprecated. Use the new `model_list` format with `.security.yml` for better security.
+>
+> **`max_parallel_turns`**: Controls concurrent processing of messages from different sessions. `1` (default) = sequential; `>1` = parallel. Messages from the same session are always serialized. See [Steering docs](../architecture/steering.md) for details.
@@ -901,9 +917,9 @@ Scheduled tasks persist across restarts and are stored in `~/.picoclaw/workspace
| Topic | Description |
| ----- | ----------- |
-| [Security Configuration](security_configuration.md) | Store API keys and secrets in separate `.security.yml` file |
-| [Sensitive Data Filtering](sensitive_data_filtering.md) | Filter API keys and tokens from tool results before sending to LLM |
-| [Hook System](hooks/README.md) | Event-driven hooks: observers, interceptors, approval hooks |
-| [Steering](steering.md) | Inject messages into a running agent loop between tool calls |
-| [SubTurn](subturn.md) | Subagent coordination, concurrency control, lifecycle |
-| [Context Management](agent-refactor/context.md) | Context boundary detection, proactive budget check, compression |
+| [Security Configuration](../security/security_configuration.md) | Store API keys and secrets in separate `.security.yml` file |
+| [Sensitive Data Filtering](../security/sensitive_data_filtering.md) | Filter API keys and tokens from tool results before sending to LLM |
+| [Hook System](../architecture/hooks/README.md) | Event-driven hooks: observers, interceptors, approval hooks |
+| [Steering](../architecture/steering.md) | Inject messages into a running agent loop between tool calls |
+| [SubTurn](../architecture/subturn.md) | Subagent coordination, concurrency control, lifecycle |
+| [Context Management](../architecture/agent-refactor/context.md) | Context boundary detection, proactive budget check, compression |
diff --git a/docs/my/configuration.md b/docs/guides/configuration.ms.md
similarity index 90%
rename from docs/my/configuration.md
rename to docs/guides/configuration.ms.md
index f798bd9bd..bcd17afa8 100644
--- a/docs/my/configuration.md
+++ b/docs/guides/configuration.ms.md
@@ -1,6 +1,6 @@
# ⚙️ Panduan Konfigurasi
-> Kembali ke [README](../../README.my.md)
+> Kembali ke [README](../project/README.ms.md)
## ⚙️ Konfigurasi
@@ -63,10 +63,30 @@ Untuk setup lanjutan/ujian, anda boleh menindih root builtin skills dengan:
export PICOCLAW_BUILTIN_SKILLS=/path/to/skills
```
+### Menggunakan Skill dan Arahan Dari Saluran Chat
+
+Selepas skill dipasang, anda boleh menyemak dan memaksanya terus dari saluran chat:
+
+- `/list skills` memaparkan nama skill dipasang yang kelihatan kepada agen semasa.
+- `/use ` memaksa satu skill untuk satu permintaan sahaja.
+- `/use ` menyediakan skill itu untuk mesej anda yang seterusnya dalam chat yang sama.
+- `/use clear` membatalkan skill override tertunda yang dibuat melalui `/use `.
+- `/btw ` bertanya soalan sampingan segera tanpa mengubah sejarah sesi semasa. `/btw` dikendalikan sebagai pertanyaan langsung tanpa tool dan tidak memasuki aliran pelaksanaan tool biasa.
+
+Contoh:
+
+```text
+/list skills
+/use git terangkan cara squash 3 commit terakhir
+/btw ingatkan saya semula apa keputusan tadi untuk pelan deploy
+/use italiapersonalfinance
+dammi le ultime news
+```
+
### Polisi Pelaksanaan Arahan Bersepadu
- Generic slash command dilaksanakan melalui satu laluan dalam `pkg/agent/loop.go` melalui `commands.Executor`.
-- Adapter saluran tidak lagi menggunakan generic command secara setempat; ia memajukan teks masuk ke laluan bus/agent. Telegram masih auto-register arahan yang disokong semasa startup.
+- Adapter saluran tidak lagi menggunakan generic command secara setempat; ia memajukan teks masuk ke laluan bus/agent. Telegram masih auto-register arahan yang disokong semasa startup seperti `/start`, `/help`, `/show`, `/list`, `/use`, dan `/btw`.
- Slash command yang tidak dikenali (contohnya `/foo`) akan diteruskan ke pemprosesan LLM biasa.
- Arahan yang didaftarkan tetapi tidak disokong pada saluran semasa (contohnya `/show` di WhatsApp) akan memulangkan ralat yang jelas kepada pengguna dan menghentikan pemprosesan lanjut.
diff --git a/docs/pt-br/configuration.md b/docs/guides/configuration.pt-br.md
similarity index 92%
rename from docs/pt-br/configuration.md
rename to docs/guides/configuration.pt-br.md
index 27cd6d21f..c47278484 100644
--- a/docs/pt-br/configuration.md
+++ b/docs/guides/configuration.pt-br.md
@@ -1,6 +1,6 @@
# ⚙️ Guia de Configuração
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
## ⚙️ Configuração
@@ -81,10 +81,30 @@ Para configurações avançadas/de teste, você pode substituir o diretório rai
export PICOCLAW_BUILTIN_SKILLS=/path/to/skills
```
+### Usando Skills e Comandos em Canais de Chat
+
+Depois que as skills estiverem instaladas, voce pode inspeciona-las e aplica-las diretamente de um canal de chat:
+
+- `/list skills` mostra os nomes das skills instaladas visiveis para o agente atual.
+- `/use ` força uma skill para uma unica requisicao.
+- `/use ` prepara essa skill para a sua proxima mensagem no mesmo chat.
+- `/use clear` cancela uma substituicao pendente criada por `/use `.
+- `/btw ` faz uma pergunta lateral imediata sem alterar o historico atual da sessao. `/btw` e tratado como uma consulta direta sem ferramentas e nao entra no fluxo normal de execucao de ferramentas.
+
+Exemplos:
+
+```text
+/list skills
+/use git explique como fazer squash dos ultimos 3 commits
+/btw me relembre o que ja decidimos sobre o plano de deploy
+/use italiapersonalfinance
+dammi le ultime news
+```
+
### Política Unificada de Execução de Comandos
- Comandos slash genéricos são executados através de um único caminho em `pkg/agent/loop.go` via `commands.Executor`.
-- Os adaptadores de canal não consomem mais comandos genéricos localmente; eles encaminham o texto de entrada para o caminho bus/agent. O Telegram ainda registra automaticamente os comandos suportados na inicialização.
+- Os adaptadores de canal não consomem mais comandos genéricos localmente; eles encaminham o texto de entrada para o caminho bus/agent. O Telegram ainda registra automaticamente na inicialização comandos suportados como `/start`, `/help`, `/show`, `/list`, `/use` e `/btw`.
- Comando slash desconhecido (por exemplo `/foo`) passa para o processamento normal do LLM.
- Comando registrado mas não suportado no canal atual (por exemplo `/show` no WhatsApp) retorna um erro explícito ao usuário e interrompe o processamento.
@@ -374,7 +394,7 @@ As tarefas agendadas persistem após reinicializações em `~/.picoclaw/workspac
| Tópico | Descrição |
| ------ | --------- |
-| [Sistema de Hooks](../hooks/README.md) | Hooks orientados a eventos: observadores, interceptores, hooks de aprovação |
-| [Steering](../steering.md) | Injetar mensagens em um loop de agente em execução |
-| [SubTurn](../subturn.md) | Coordenação de subagentes, controle de concorrência, ciclo de vida |
-| [Gerenciamento de Contexto](../agent-refactor/context.md) | Detecção de limites de contexto, compressão |
+| [Sistema de Hooks](../architecture/hooks/README.md) | Hooks orientados a eventos: observadores, interceptores, hooks de aprovação |
+| [Steering](../architecture/steering.md) | Injetar mensagens em um loop de agente em execução |
+| [SubTurn](../architecture/subturn.md) | Coordenação de subagentes, controle de concorrência, ciclo de vida |
+| [Gerenciamento de Contexto](../architecture/agent-refactor/context.md) | Detecção de limites de contexto, compressão |
diff --git a/docs/vi/configuration.md b/docs/guides/configuration.vi.md
similarity index 92%
rename from docs/vi/configuration.md
rename to docs/guides/configuration.vi.md
index 56eb8f557..9efeaa2b6 100644
--- a/docs/vi/configuration.md
+++ b/docs/guides/configuration.vi.md
@@ -1,6 +1,6 @@
# ⚙️ Hướng Dẫn Cấu Hình
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
## ⚙️ Cấu Hình
@@ -81,10 +81,30 @@ Cho thiết lập nâng cao/test, bạn có thể ghi đè thư mục gốc skil
export PICOCLAW_BUILTIN_SKILLS=/path/to/skills
```
+### Dung Skill va Lenh Tu Kenh Chat
+
+Sau khi cai dat skill, ban co the xem va ep dung truc tiep tu kenh chat:
+
+- `/list skills` hien ten cac skill da cai dat ma agent hien tai co the dung.
+- `/use ` ep dung mot skill cho duy nhat mot yeu cau.
+- `/use ` dat san skill do cho tin nhan tiep theo trong cung cuoc tro chuyen.
+- `/use clear` huy skill override dang cho duoc tao boi `/use `.
+- `/btw ` dat cau hoi phu ngay lap tuc ma khong thay doi lich su phien hien tai. `/btw` duoc xu ly nhu mot truy van truc tiep khong dung cong cu va khong di vao luong thuc thi cong cu thong thuong.
+
+Vi du:
+
+```text
+/list skills
+/use git giai thich cach squash 3 commit cuoi
+/btw nhac lai giup toi chung ta da chot gi cho ke hoach deploy
+/use italiapersonalfinance
+dammi le ultime news
+```
+
### Chính Sách Thực Thi Lệnh Thống Nhất
- Lệnh slash chung được thực thi qua một đường dẫn duy nhất trong `pkg/agent/loop.go` qua `commands.Executor`.
-- Adapter kênh không còn xử lý lệnh chung cục bộ; chúng chuyển tiếp văn bản đầu vào đến đường dẫn bus/agent. Telegram vẫn tự động đăng ký lệnh được hỗ trợ khi khởi động.
+- Adapter kênh không còn xử lý lệnh chung cục bộ; chúng chuyển tiếp văn bản đầu vào đến đường dẫn bus/agent. Telegram vẫn tự động đăng ký khi khởi động các lệnh được hỗ trợ như `/start`, `/help`, `/show`, `/list`, `/use`, va `/btw`.
- Lệnh slash không xác định (ví dụ `/foo`) được chuyển sang xử lý LLM bình thường.
- Lệnh đã đăng ký nhưng không được hỗ trợ trên kênh hiện tại (ví dụ `/show` trên WhatsApp) trả về lỗi rõ ràng cho người dùng và dừng xử lý tiếp.
@@ -374,7 +394,7 @@ Tác vụ đã lên lịch được lưu trữ bền vững sau khi khởi độ
| Chủ đề | Mô tả |
| ------ | ----- |
-| [Hệ Thống Hook](../hooks/README.md) | Hook hướng sự kiện: observer, interceptor, approval hook |
-| [Steering](../steering.md) | Chèn tin nhắn vào vòng lặp agent đang chạy |
-| [SubTurn](../subturn.md) | Điều phối subagent, kiểm soát đồng thời, vòng đời |
-| [Quản Lý Ngữ Cảnh](../agent-refactor/context.md) | Phát hiện ranh giới ngữ cảnh, nén |
+| [Hệ Thống Hook](../architecture/hooks/README.md) | Hook hướng sự kiện: observer, interceptor, approval hook |
+| [Steering](../architecture/steering.md) | Chèn tin nhắn vào vòng lặp agent đang chạy |
+| [SubTurn](../architecture/subturn.md) | Điều phối subagent, kiểm soát đồng thời, vòng đời |
+| [Quản Lý Ngữ Cảnh](../architecture/agent-refactor/context.md) | Phát hiện ranh giới ngữ cảnh, nén |
diff --git a/docs/zh/configuration.md b/docs/guides/configuration.zh.md
similarity index 88%
rename from docs/zh/configuration.md
rename to docs/guides/configuration.zh.md
index a628eaaa2..ecaef6eb7 100644
--- a/docs/zh/configuration.md
+++ b/docs/guides/configuration.zh.md
@@ -1,6 +1,6 @@
# ⚙️ 配置指南
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
## ⚙️ 配置详解
@@ -101,12 +101,14 @@ export PICOCLAW_BUILTIN_SKILLS=/path/to/skills
- `/use `:只对当前这一条请求强制使用指定技能。
- `/use `:为同一会话中的下一条消息预先启用该技能。
- `/use clear`:取消通过 `/use ` 设置的待应用技能。
+- `/btw `:发起一个即时的旁支提问,且不改动当前会话历史。`/btw` 会按一次无工具的直接问答处理,不会进入常规的工具执行流程。
示例:
```text
/list skills
/use git explain how to squash the last 3 commits
+/btw 帮我回顾一下刚才关于发布方案的结论
/use italiapersonalfinance
dammi le ultime news
```
@@ -114,10 +116,90 @@ dammi le ultime news
### 统一命令执行策略
- 通用斜杠命令通过 `pkg/agent/loop.go` 中的 `commands.Executor` 统一执行。
-- Channel 适配器不再在本地消费通用命令;它们只负责把入站文本转发到 bus/agent 路径。Telegram 仍会在启动时自动注册其支持的命令菜单。
+- Channel 适配器不再在本地消费通用命令;它们只负责把入站文本转发到 bus/agent 路径。Telegram 仍会在启动时自动注册其支持的命令菜单,例如 `/start`、`/help`、`/show`、`/list`、`/use` 和 `/btw`。
- 未注册的斜杠命令(例如 `/foo`)会透传给 LLM 按普通输入处理。
- 已注册但当前 channel 不支持的命令(例如 WhatsApp 上的 `/show`)会返回明确的用户可见错误,并停止后续处理。
+### Session 隔离
+
+Session scope 决定了聊天、用户、线程和 space 之间共享多少上下文。
+
+- 全局默认值使用 `session.dimensions`
+- 如果只想让某条路由例外,使用 dispatch rule 上的 `session_dimensions`
+
+如果你想看完整的隔离方案和配置配方,请看 [Session 使用指南](session-guide.zh.md)。
+
+### Routing
+
+Routing 通过 `agents.dispatch.rules` 配置。
+
+每条规则都针对 channel 归一化后的 inbound context 做匹配。
+规则按从上到下顺序检查,第一条命中的规则立即生效。若没有规则命中,PicoClaw 会回退到默认 agent。
+
+支持的匹配字段:
+
+* `channel`
+* `account`
+* `space`
+* `chat`
+* `topic`
+* `sender`
+* `mentioned`
+
+这些值使用和 session system 一致的归一化词汇:
+
+* `space`: `workspace:t001`、`guild:123456`
+* `chat`: `direct:user123`、`group:-100123`、`channel:c123`
+* `topic`: `topic:42`
+* `sender`: 平台归一化后的 sender 标识
+
+规则也可以通过 `session_dimensions` 覆盖全局 `session.dimensions`,这样路由和会话隔离就能保持一致,而不必回到旧的 `bindings` 或 `dm_scope` 配置。
+
+示例:
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" },
+ { "id": "sales" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "vip in support group",
+ "agent": "sales",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890",
+ "sender": "12345"
+ },
+ "session_dimensions": ["chat", "sender"]
+ },
+ {
+ "name": "telegram support group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890"
+ },
+ "session_dimensions": ["chat"]
+ }
+ ]
+ }
+ },
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+在这个例子里,VIP 规则必须放在更宽泛的群规则前面。
+因为 routing 是严格按顺序执行的,所以更具体的规则要放前面,兜底规则放后面。
+
+如果你想看更完整的 agent 路由和模型分层示例,请看 [路由使用指南](routing-guide.zh.md)。
+
### 🔒 安全沙箱 (Security Sandbox)
PicoClaw 默认在沙箱环境中运行。Agent 只能访问配置的工作区内的文件和执行命令。
@@ -668,8 +750,8 @@ PicoClaw 通过 `cron` 工具支持 cron 风格的定时任务。Agent 可以设
| 主题 | 说明 |
| ---- | ---- |
-| [敏感数据过滤](../sensitive_data_filtering.md) | 在发送给 LLM 前,从工具结果中过滤 API 密钥和令牌 |
-| [Hook 系统](../hooks/README.zh.md) | 事件驱动 Hook:观察者、拦截器、审批 Hook |
-| [Steering](../steering.md) | 在工具调用间向运行中的 Agent 注入消息 |
-| [SubTurn](../subturn.md) | 子 Agent 协调、并发控制、生命周期管理 |
-| [上下文管理](../agent-refactor/context.md) | 上下文边界检测、主动预算检查、压缩策略 |
+| [敏感数据过滤](../security/sensitive_data_filtering.zh.md) | 在发送给 LLM 前,从工具结果中过滤 API 密钥和令牌 |
+| [Hook 系统](../architecture/hooks/README.zh.md) | 事件驱动 Hook:观察者、拦截器、审批 Hook |
+| [Steering](../architecture/steering.md) | 在工具调用间向运行中的 Agent 注入消息 |
+| [SubTurn](../architecture/subturn.md) | 子 Agent 协调、并发控制、生命周期管理 |
+| [上下文管理](../architecture/agent-refactor/context.md) | 上下文边界检测、主动预算检查、压缩策略 |
diff --git a/docs/fr/docker.md b/docs/guides/docker.fr.md
similarity index 99%
rename from docs/fr/docker.md
rename to docs/guides/docker.fr.md
index 9605440bc..f8c821570 100644
--- a/docs/fr/docker.md
+++ b/docs/guides/docker.fr.md
@@ -1,6 +1,6 @@
# 🐳 Docker et Démarrage Rapide
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
## 🐳 Docker Compose
diff --git a/docs/ja/docker.md b/docs/guides/docker.ja.md
similarity index 97%
rename from docs/ja/docker.md
rename to docs/guides/docker.ja.md
index a585c5e80..f5885e775 100644
--- a/docs/ja/docker.md
+++ b/docs/guides/docker.ja.md
@@ -1,6 +1,6 @@
# 🐳 Docker とクイックスタート
-> [README](../../README.ja.md) に戻る
+> [README](../project/README.ja.md) に戻る
## 🐳 Docker Compose
@@ -143,7 +143,7 @@ picoclaw onboard
}
```
-> **新機能**: `model_list` 設定形式により、コード変更なしで provider を追加できます。詳細は[モデル設定](providers.md#モデル設定-model_list)を参照してください。
+> **新機能**: `model_list` 設定形式により、コード変更なしで provider を追加できます。詳細は[モデル設定](providers.ja.md#モデル設定-model_list)を参照してください。
> `request_timeout` はオプションで、単位は秒です。省略または `<= 0` に設定した場合、PicoClaw はデフォルトのタイムアウト(120 秒)を使用します。
**3. API Key の取得**
diff --git a/docs/docker.md b/docs/guides/docker.md
similarity index 100%
rename from docs/docker.md
rename to docs/guides/docker.md
diff --git a/docs/my/docker.md b/docs/guides/docker.ms.md
similarity index 99%
rename from docs/my/docker.md
rename to docs/guides/docker.ms.md
index 2f9cac3fd..05725e195 100644
--- a/docs/my/docker.md
+++ b/docs/guides/docker.ms.md
@@ -1,6 +1,6 @@
# 🐳 Panduan Docker & Quick Start
-> Kembali ke [README](../../README.my.md)
+> Kembali ke [README](../project/README.ms.md)
## 🐳 Docker Compose
diff --git a/docs/pt-br/docker.md b/docs/guides/docker.pt-br.md
similarity index 99%
rename from docs/pt-br/docker.md
rename to docs/guides/docker.pt-br.md
index a17dc64ec..46d273bee 100644
--- a/docs/pt-br/docker.md
+++ b/docs/guides/docker.pt-br.md
@@ -1,6 +1,6 @@
# 🐳 Docker e Início Rápido
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
## 🐳 Docker Compose
diff --git a/docs/vi/docker.md b/docs/guides/docker.vi.md
similarity index 99%
rename from docs/vi/docker.md
rename to docs/guides/docker.vi.md
index e6bc74b1a..716c81544 100644
--- a/docs/vi/docker.md
+++ b/docs/guides/docker.vi.md
@@ -1,6 +1,6 @@
# 🐳 Docker và Bắt Đầu Nhanh
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
## 🐳 Docker Compose
diff --git a/docs/zh/docker.md b/docs/guides/docker.zh.md
similarity index 97%
rename from docs/zh/docker.md
rename to docs/guides/docker.zh.md
index f840290a7..521747d16 100644
--- a/docs/zh/docker.md
+++ b/docs/guides/docker.zh.md
@@ -1,6 +1,6 @@
# 🐳 Docker 与快速开始
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
## 🐳 Docker Compose
@@ -143,7 +143,7 @@ picoclaw onboard
}
```
-> **新功能**: `model_list` 配置格式支持零代码添加 provider。详见[模型配置](providers.md#模型配置-model_list)章节。
+> **新功能**: `model_list` 配置格式支持零代码添加 provider。详见[模型配置](providers.zh.md#模型配置-model_list)章节。
> `request_timeout` 为可选项,单位为秒。若省略或设置为 `<= 0`,PicoClaw 使用默认超时(120 秒)。
**3. 获取 API Key**
diff --git a/docs/fr/hardware-compatibility.md b/docs/guides/hardware-compatibility.fr.md
similarity index 98%
rename from docs/fr/hardware-compatibility.md
rename to docs/guides/hardware-compatibility.fr.md
index c1f397e80..bb2d92d57 100644
--- a/docs/fr/hardware-compatibility.md
+++ b/docs/guides/hardware-compatibility.fr.md
@@ -1,4 +1,4 @@
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
# 🖥️ PicoClaw Liste de compatibilité matérielle
@@ -99,7 +99,7 @@ Produits grand public, routeurs et appareils industriels testés avec PicoClaw.
Tout téléphone Android ARM64 (2015+) avec 1 Go+ de RAM. Installez [Termux](https://github.com/termux/termux-app), utilisez `proot` pour exécuter PicoClaw.
-> Voir [README : Exécuter sur d'anciens téléphones Android](../../README.fr.md#-run-on-old-android-phones) pour les instructions de configuration.
+> Voir [README : Exécuter sur d'anciens téléphones Android](../project/README.fr.md#-run-on-old-android-phones) pour les instructions de configuration.
### Bureau / Serveur / Cloud
diff --git a/docs/ja/hardware-compatibility.md b/docs/guides/hardware-compatibility.ja.md
similarity index 98%
rename from docs/ja/hardware-compatibility.md
rename to docs/guides/hardware-compatibility.ja.md
index 96ccd1cd1..c86684f84 100644
--- a/docs/ja/hardware-compatibility.md
+++ b/docs/guides/hardware-compatibility.ja.md
@@ -1,4 +1,4 @@
-> [README](../../README.ja.md) に戻る
+> [README](../project/README.ja.md) に戻る
# 🖥️ PicoClaw ハードウェア互換性リスト
@@ -99,7 +99,7 @@ PicoClaw でテスト済みのコンシューマー製品、ルーター、産
1GB 以上の RAM を搭載した ARM64 Android スマートフォン(2015年以降)。[Termux](https://github.com/termux/termux-app) をインストールし、`proot` を使用して PicoClaw を実行します。
-> セットアップ手順は [README:古い Android スマートフォンで実行](../../README.ja.md#-run-on-old-android-phones) を参照してください。
+> セットアップ手順は [README:古い Android スマートフォンで実行](../project/README.ja.md#-run-on-old-android-phones) を参照してください。
### デスクトップ / サーバー / クラウド
diff --git a/docs/hardware-compatibility.md b/docs/guides/hardware-compatibility.md
similarity index 98%
rename from docs/hardware-compatibility.md
rename to docs/guides/hardware-compatibility.md
index c11849822..a07bb5116 100644
--- a/docs/hardware-compatibility.md
+++ b/docs/guides/hardware-compatibility.md
@@ -97,7 +97,7 @@ Consumer products, routers, and industrial devices that have been tested with Pi
Any ARM64 Android phone (2015+) with 1GB+ RAM. Install [Termux](https://github.com/termux/termux-app), use `proot` to run PicoClaw.
-> See [README: Run on old Android Phones](../README.md#-run-on-old-android-phones) for setup instructions.
+> See [README: Run on old Android Phones](../../README.md#-run-on-old-android-phones) for setup instructions.
### Desktop / Server / Cloud
diff --git a/docs/pt-br/hardware-compatibility.md b/docs/guides/hardware-compatibility.pt-br.md
similarity index 97%
rename from docs/pt-br/hardware-compatibility.md
rename to docs/guides/hardware-compatibility.pt-br.md
index 771621014..1fc8ee25e 100644
--- a/docs/pt-br/hardware-compatibility.md
+++ b/docs/guides/hardware-compatibility.pt-br.md
@@ -1,4 +1,4 @@
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
# 🖥️ PicoClaw Lista de compatibilidade de hardware
@@ -99,7 +99,7 @@ Produtos de consumo, roteadores e dispositivos industriais testados com o PicoCl
Qualquer celular Android ARM64 (2015+) com 1GB+ de RAM. Instale o [Termux](https://github.com/termux/termux-app), use `proot` para rodar o PicoClaw.
-> Veja [README: Rodar em celulares Android antigos](../../README.pt-br.md#-run-on-old-android-phones) para instruções de configuração.
+> Veja [README: Rodar em celulares Android antigos](../project/README.pt-br.md#-run-on-old-android-phones) para instruções de configuração.
### Desktop / Servidor / Nuvem
diff --git a/docs/vi/hardware-compatibility.md b/docs/guides/hardware-compatibility.vi.md
similarity index 97%
rename from docs/vi/hardware-compatibility.md
rename to docs/guides/hardware-compatibility.vi.md
index 8315c049e..5566a4248 100644
--- a/docs/vi/hardware-compatibility.md
+++ b/docs/guides/hardware-compatibility.vi.md
@@ -1,4 +1,4 @@
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
# 🖥️ PicoClaw Danh sách tương thích phần cứng
@@ -99,7 +99,7 @@ Sản phẩm tiêu dùng, router và thiết bị công nghiệp đã được k
Bất kỳ điện thoại Android ARM64 nào (2015+) với 1GB+ RAM. Cài đặt [Termux](https://github.com/termux/termux-app), sử dụng `proot` để chạy PicoClaw.
-> Xem [README: Chạy trên điện thoại Android cũ](../../README.vi.md#-run-on-old-android-phones) để biết hướng dẫn cài đặt.
+> Xem [README: Chạy trên điện thoại Android cũ](../project/README.vi.md#-run-on-old-android-phones) để biết hướng dẫn cài đặt.
### Desktop / Máy chủ / Đám mây
diff --git a/docs/zh/hardware-compatibility.md b/docs/guides/hardware-compatibility.zh.md
similarity index 97%
rename from docs/zh/hardware-compatibility.md
rename to docs/guides/hardware-compatibility.zh.md
index 66bd08072..d563f3ebe 100644
--- a/docs/zh/hardware-compatibility.md
+++ b/docs/guides/hardware-compatibility.zh.md
@@ -1,4 +1,4 @@
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
# 🖥️ PicoClaw 硬件兼容性列表
@@ -99,7 +99,7 @@ PicoClaw 几乎可以在任何 Linux 设备上运行。本页面记录了已验
任何 ARM64 Android 手机(2015 年以后),1GB 以上内存。安装 [Termux](https://github.com/termux/termux-app),使用 `proot` 运行 PicoClaw。
-> 参见 [README:在旧 Android 手机上运行](../../README.zh.md#-run-on-old-android-phones) 获取设置说明。
+> 参见 [README:在旧 Android 手机上运行](../project/README.zh.md#-run-on-old-android-phones) 获取设置说明。
### 桌面 / 服务器 / 云
diff --git a/docs/fr/providers.md b/docs/guides/providers.fr.md
similarity index 99%
rename from docs/fr/providers.md
rename to docs/guides/providers.fr.md
index f053d5d57..5e2700a01 100644
--- a/docs/fr/providers.md
+++ b/docs/guides/providers.fr.md
@@ -1,6 +1,6 @@
# 🔌 Fournisseurs et Configuration des Modèles
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
### Fournisseurs
@@ -454,5 +454,5 @@ picoclaw agent -m "Hello"
---
diff --git a/docs/providers.md b/docs/guides/providers.md
similarity index 99%
rename from docs/providers.md
rename to docs/guides/providers.md
index ca1678c7e..41f3caae0 100644
--- a/docs/providers.md
+++ b/docs/guides/providers.md
@@ -35,6 +35,8 @@
> **What's New?** PicoClaw now uses a **model-centric** configuration approach. Simply specify `vendor/model` format (e.g., `zhipu/glm-4.7`) to add new providers—**zero code changes required!**
+For agent dispatch and light-model routing examples, see the [Routing Guide](routing-guide.md).
+
This design also enables **multi-agent support** with flexible provider selection:
- **Different agents, different providers**: Each agent can use its own LLM provider
@@ -406,7 +408,7 @@ The old `providers` configuration is **deprecated** and has been removed in V2.
}
```
-For detailed migration guide, see [migration/model-list-migration.md](migration/model-list-migration.md).
+For detailed migration guide, see [migration/model-list-migration.md](../migration/model-list-migration.md).
### Provider Architecture
@@ -572,5 +574,5 @@ picoclaw agent -m "Hello"
---
-
+
diff --git a/docs/pt-br/providers.md b/docs/guides/providers.pt-br.md
similarity index 99%
rename from docs/pt-br/providers.md
rename to docs/guides/providers.pt-br.md
index ebe911b65..fedeec5c5 100644
--- a/docs/pt-br/providers.md
+++ b/docs/guides/providers.pt-br.md
@@ -1,6 +1,6 @@
# 🔌 Provedores e Configuração de Modelos
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
### Provedores
@@ -454,5 +454,5 @@ picoclaw agent -m "Hello"
---
-
+
diff --git a/docs/vi/providers.md b/docs/guides/providers.vi.md
similarity index 99%
rename from docs/vi/providers.md
rename to docs/guides/providers.vi.md
index 5178ad197..1bc76092d 100644
--- a/docs/vi/providers.md
+++ b/docs/guides/providers.vi.md
@@ -1,6 +1,6 @@
# 🔌 Nhà Cung Cấp và Cấu Hình Mô Hình
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
### Nhà Cung Cấp
@@ -454,5 +454,5 @@ picoclaw agent -m "Hello"
---
-
+
diff --git a/docs/zh/providers.md b/docs/guides/providers.zh.md
similarity index 99%
rename from docs/zh/providers.md
rename to docs/guides/providers.zh.md
index 155fbe11b..1f1031043 100644
--- a/docs/zh/providers.md
+++ b/docs/guides/providers.zh.md
@@ -1,6 +1,6 @@
# 🔌 提供商与模型配置
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
### 提供商 (Providers)
@@ -29,10 +29,13 @@
| `modelscope` | LLM (ModelScope 直连) | [modelscope.cn](https://modelscope.cn) |
| `mimo` | LLM (小米 MiMo 直连) | [platform.xiaomimimo.com](https://platform.xiaomimimo.com) |
+
### 模型配置 (model_list)
> **新功能!** PicoClaw 现在采用**以模型为中心**的配置方式。只需使用 `厂商/模型` 格式(如 `zhipu/glm-4.7`)即可添加新的 provider——**无需修改任何代码!**
+如果你想看 agent 分发和轻量模型路由的完整示例,请看 [路由使用指南](routing-guide.zh.md)。
+
该设计同时支持**多 Agent 场景**,提供灵活的 Provider 选择:
- **不同 Agent 使用不同 Provider**:每个 Agent 可以使用自己的 LLM provider
diff --git a/docs/guides/routing-guide.md b/docs/guides/routing-guide.md
new file mode 100644
index 000000000..abeaf0285
--- /dev/null
+++ b/docs/guides/routing-guide.md
@@ -0,0 +1,331 @@
+# Routing Guide
+
+> Back to [README](../README.md)
+
+In PicoClaw, routing has two user-facing parts:
+
+- **agent routing**: choose which agent should handle a message
+- **model routing**: choose whether a turn should use the primary model or the configured light model
+
+This guide explains how to configure both for real deployments.
+
+## Quick Start
+
+### Route one Telegram group to a support agent
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "telegram support group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890"
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+### Route only Slack mentions in one workspace
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "slack mentions",
+ "agent": "support",
+ "when": {
+ "channel": "slack",
+ "space": "workspace:t001",
+ "mentioned": true
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+### Use a light model for simple turns
+
+```json
+{
+ "model_list": [
+ {
+ "model_name": "gpt-main",
+ "model": "openai/gpt-5.4",
+ "api_keys": ["sk-main"]
+ },
+ {
+ "model_name": "flash-light",
+ "model": "gemini/gemini-2.0-flash-exp",
+ "api_keys": ["sk-light"]
+ }
+ ],
+ "agents": {
+ "defaults": {
+ "model_name": "gpt-main",
+ "routing": {
+ "enabled": true,
+ "light_model": "flash-light",
+ "threshold": 0.35
+ }
+ }
+ }
+}
+```
+
+## Agent Routing
+
+Agent routing is configured with:
+
+```text
+agents.dispatch.rules
+```
+
+Rules are evaluated from top to bottom.
+The **first matching rule wins**.
+If no rule matches, PicoClaw falls back to the default agent.
+
+## Supported Match Fields
+
+| Field | Meaning | Example |
+| --- | --- | --- |
+| `channel` | Channel name | `telegram`, `slack`, `discord` |
+| `account` | Normalized account ID | `default`, `bot2` |
+| `space` | Workspace, guild, or similar container | `workspace:t001`, `guild:123456` |
+| `chat` | Direct chat, group, or channel | `direct:user123`, `group:-100123`, `channel:c123` |
+| `topic` | Thread or topic | `topic:42` |
+| `sender` | Normalized sender identity | `12345`, `john` |
+| `mentioned` | Whether the bot was explicitly mentioned | `true` |
+
+Values must match the normalized runtime shape, not the raw incoming payload.
+
+## Rule Ordering
+
+Put more specific rules before broader rules.
+
+Good:
+
+1. VIP sender inside one group
+2. all traffic for that group
+3. channel-wide fallback
+
+Bad:
+
+1. all traffic for that group
+2. VIP sender inside the same group
+
+In the bad ordering, the broad rule wins first and the VIP rule never runs.
+
+## Session Interaction
+
+Routing and sessions are related but different.
+
+- routing decides which agent handles the message
+- session settings decide which messages share memory
+
+You can override the global `session.dimensions` value for one matched rule with `session_dimensions`.
+
+Example:
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" },
+ { "id": "sales" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "vip in support group",
+ "agent": "sales",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890",
+ "sender": "12345"
+ },
+ "session_dimensions": ["chat", "sender"]
+ },
+ {
+ "name": "support group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890"
+ },
+ "session_dimensions": ["chat"]
+ }
+ ]
+ }
+ },
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+In this configuration:
+
+- the VIP gets routed to `sales`
+- everyone else in the group goes to `support`
+- the VIP route also gets per-user session isolation
+
+## Identity Links
+
+`session.identity_links` also affects routing when you match on `sender`.
+Use it when the same real user may appear under multiple raw sender IDs.
+
+Example:
+
+```json
+{
+ "session": {
+ "identity_links": {
+ "john": ["slack:u123", "legacy-user-42"]
+ }
+ },
+ "agents": {
+ "dispatch": {
+ "rules": [
+ {
+ "name": "john goes to sales",
+ "agent": "sales",
+ "when": {
+ "sender": "john"
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+## Model Routing
+
+Model routing is configured under:
+
+```text
+agents.defaults.routing
+```
+
+Current fields:
+
+| Field | Meaning |
+| --- | --- |
+| `enabled` | Turn model routing on or off |
+| `light_model` | `model_name` from `model_list` used for simple turns |
+| `threshold` | Complexity cutoff in `[0, 1]` |
+
+Important behavior:
+
+- the light model must exist in `model_list`
+- PicoClaw resolves the light model at startup; if it is invalid, routing is disabled
+- one turn stays on one model tier, even if it later calls tools
+
+## What Affects The Complexity Score
+
+The current model router looks at structural signals such as:
+
+- message length
+- fenced code blocks
+- recent tool calls in the same session
+- conversation depth
+- media or attachments
+
+This means a "simple" turn may still go to the primary model if it includes:
+
+- code
+- images or audio
+- a very long prompt
+- a tool-heavy ongoing workflow
+
+## Choosing A Threshold
+
+Recommended starting point:
+
+```json
+{
+ "agents": {
+ "defaults": {
+ "routing": {
+ "enabled": true,
+ "light_model": "flash-light",
+ "threshold": 0.35
+ }
+ }
+ }
+}
+```
+
+General rule:
+
+- lower threshold: use the primary model more often
+- higher threshold: use the light model more aggressively
+
+Practical suggestions:
+
+- `0.25` if you want safer routing with fewer light-model turns
+- `0.35` as the default starting point
+- `0.50+` only if your light model is already strong enough for most chat traffic
+
+## Troubleshooting
+
+### A rule is not matching
+
+Check:
+
+- rule order
+- normalized value shape such as `group:-100123` instead of just `-100123`
+- whether the channel actually provides `space`, `topic`, or `mentioned`
+
+### The wrong agent handles a message
+
+The most common cause is ordering.
+Remember: first match wins.
+
+### The light model is never used
+
+Check:
+
+- `agents.defaults.routing.enabled` is `true`
+- `light_model` exists in `model_list`
+- the light model can actually initialize
+- your threshold is not too low
+
+### The primary model is still chosen for short messages
+
+That can still happen when the turn includes:
+
+- a code block
+- media or attachments
+- recent tool-heavy history
+
+### Routing works, but the conversation memory is still too shared
+
+Adjust `session.dimensions` globally or `session_dimensions` on the specific route.
+Routing chooses the agent, but sessions decide context sharing.
+
+## Related Guides
+
+- [Session Guide](session-guide.md)
+- [Configuration Guide](configuration.md)
+- [Providers & Model Configuration](providers.md)
diff --git a/docs/guides/routing-guide.zh.md b/docs/guides/routing-guide.zh.md
new file mode 100644
index 000000000..58c9f14e2
--- /dev/null
+++ b/docs/guides/routing-guide.zh.md
@@ -0,0 +1,331 @@
+# 路由使用指南
+
+> 返回 [README](../project/README.zh.md)
+
+PicoClaw 里用户能直接感知到的“路由”主要有两部分:
+
+- **agent 路由**:决定哪一个 agent 处理一条消息
+- **模型路由**:决定这一轮是走主模型,还是走轻量模型
+
+这份文档面向真实部署中的配置使用场景。
+
+## 快速开始
+
+### 把一个 Telegram 群路由给 support agent
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "telegram support group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890"
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+### 只处理某个 Slack workspace 里的 @提及
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "slack mentions",
+ "agent": "support",
+ "when": {
+ "channel": "slack",
+ "space": "workspace:t001",
+ "mentioned": true
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+### 给简单请求启用轻量模型
+
+```json
+{
+ "model_list": [
+ {
+ "model_name": "gpt-main",
+ "model": "openai/gpt-5.4",
+ "api_keys": ["sk-main"]
+ },
+ {
+ "model_name": "flash-light",
+ "model": "gemini/gemini-2.0-flash-exp",
+ "api_keys": ["sk-light"]
+ }
+ ],
+ "agents": {
+ "defaults": {
+ "model_name": "gpt-main",
+ "routing": {
+ "enabled": true,
+ "light_model": "flash-light",
+ "threshold": 0.35
+ }
+ }
+ }
+}
+```
+
+## Agent 路由
+
+Agent 路由通过下面这个配置项定义:
+
+```text
+agents.dispatch.rules
+```
+
+规则从上到下依次检查。
+**第一条匹配的规则直接生效**。
+如果没有规则命中,PicoClaw 会回退到默认 agent。
+
+## 支持的匹配字段
+
+| 字段 | 含义 | 示例 |
+| --- | --- | --- |
+| `channel` | Channel 名称 | `telegram`、`slack`、`discord` |
+| `account` | 归一化后的 account ID | `default`、`bot2` |
+| `space` | workspace、guild 等上层容器 | `workspace:t001`、`guild:123456` |
+| `chat` | 私聊、群或频道 | `direct:user123`、`group:-100123`、`channel:c123` |
+| `topic` | 线程或话题 | `topic:42` |
+| `sender` | 归一化后的发送者身份 | `12345`、`john` |
+| `mentioned` | 是否显式 @ 了 bot | `true` |
+
+注意,配置里要写的是运行时归一化后的值,不是原始 webhook / SDK payload。
+
+## 规则顺序
+
+把更具体的规则放前面,把更宽泛的规则放后面。
+
+正确顺序:
+
+1. 某个群里的 VIP 用户
+2. 这个群的全部消息
+3. 某个 channel 的更宽泛兜底
+
+错误顺序:
+
+1. 这个群的全部消息
+2. 同一个群里的 VIP 用户
+
+在错误顺序下,宽泛规则会先命中,VIP 规则永远不会生效。
+
+## 和 Session 的关系
+
+路由和 Session 是相关但不同的两件事:
+
+- 路由决定由哪个 agent 处理
+- Session 决定这些消息是否共享同一段记忆
+
+如果你想让某条命中的路由使用不同的会话策略,可以用 `session_dimensions` 覆盖全局 `session.dimensions`。
+
+示例:
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" },
+ { "id": "sales" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "vip in support group",
+ "agent": "sales",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890",
+ "sender": "12345"
+ },
+ "session_dimensions": ["chat", "sender"]
+ },
+ {
+ "name": "support group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890"
+ },
+ "session_dimensions": ["chat"]
+ }
+ ]
+ }
+ },
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+在这个配置里:
+
+- VIP 用户会被路由到 `sales`
+- 其他群成员会进入 `support`
+- VIP 路由还会额外按 `chat + sender` 做每用户隔离
+
+## Identity Links
+
+当你用 `sender` 做匹配时,`session.identity_links` 也会影响路由结果。
+适合这种场景:同一个真实用户可能出现为多个原始 sender ID。
+
+示例:
+
+```json
+{
+ "session": {
+ "identity_links": {
+ "john": ["slack:u123", "legacy-user-42"]
+ }
+ },
+ "agents": {
+ "dispatch": {
+ "rules": [
+ {
+ "name": "john goes to sales",
+ "agent": "sales",
+ "when": {
+ "sender": "john"
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+## 模型路由
+
+模型路由配置在:
+
+```text
+agents.defaults.routing
+```
+
+当前支持字段:
+
+| 字段 | 含义 |
+| --- | --- |
+| `enabled` | 开启或关闭模型路由 |
+| `light_model` | `model_list` 中用于简单请求的 `model_name` |
+| `threshold` | `[0, 1]` 范围内的复杂度阈值 |
+
+关键行为:
+
+- `light_model` 必须存在于 `model_list`
+- PicoClaw 会在启动时解析轻量模型;如果模型无效,路由会被禁用
+- 同一轮 turn 只会使用同一档模型,不会中途切档
+
+## 什么会影响复杂度分数
+
+当前模型路由会看一些结构化信号,例如:
+
+- 消息长度
+- fenced code block
+- 同一 session 最近是否频繁调用工具
+- 会话深度
+- 是否带有媒体或附件
+
+因此,看起来“很简单”的消息,在以下情况下仍可能走主模型:
+
+- 带代码
+- 带图片或音频
+- prompt 很长
+- 当前是一个工具调用很多的工作流
+
+## 阈值怎么选
+
+推荐起点:
+
+```json
+{
+ "agents": {
+ "defaults": {
+ "routing": {
+ "enabled": true,
+ "light_model": "flash-light",
+ "threshold": 0.35
+ }
+ }
+ }
+}
+```
+
+通用规律:
+
+- 阈值越低,越容易回到主模型
+- 阈值越高,越积极地使用轻量模型
+
+实用建议:
+
+- `0.25`:更保守,更少轻量模型 turn
+- `0.35`:默认推荐起点
+- `0.50+`:只有当你的轻量模型已经能覆盖大多数聊天任务时再考虑
+
+## 常见问题
+
+### 某条规则没有命中
+
+优先检查:
+
+- 规则顺序
+- 值的形状是否写成了归一化格式,例如 `group:-100123` 而不是裸 `-100123`
+- 当前 channel 是否真的提供了 `space`、`topic` 或 `mentioned`
+
+### 消息被错误的 agent 处理了
+
+最常见原因还是顺序。
+记住:第一条匹配的规则直接生效。
+
+### 轻量模型从来没有被用到
+
+检查:
+
+- `agents.defaults.routing.enabled` 是否为 `true`
+- `light_model` 是否存在于 `model_list`
+- 轻量模型能否成功初始化
+- 阈值是不是设得太低
+
+### 明明是短消息,还是走了主模型
+
+这通常是因为当前 turn 同时满足了其他“复杂”信号,例如:
+
+- 带代码块
+- 带媒体或附件
+- 最近的 session 历史里工具调用很多
+
+### 路由没问题,但上下文还是共享得太多
+
+去调整 `session.dimensions` 或某条 route 上的 `session_dimensions`。
+路由只决定“谁来处理”,session 才决定“记忆怎么共享”。
+
+## 相关文档
+
+- [Session 使用指南](session-guide.zh.md)
+- [配置指南](configuration.zh.md)
+- [Provider 与模型配置](providers.zh.md)
diff --git a/docs/guides/session-guide.md b/docs/guides/session-guide.md
new file mode 100644
index 000000000..3f3759260
--- /dev/null
+++ b/docs/guides/session-guide.md
@@ -0,0 +1,273 @@
+# Session Guide
+
+> Back to [README](../README.md)
+
+PicoClaw sessions decide which messages share the same conversation history.
+If your bot "remembers too much" or "forgets too much", the first thing to check is the session configuration.
+
+This guide is for users configuring session behavior in `config.json`.
+For implementation details, see the architecture docs instead.
+
+## What Sessions Control
+
+A session controls:
+
+- which previous messages are visible to the agent
+- when summarization starts for that conversation
+- whether two users in the same group share context
+- whether different chats, threads, or spaces stay isolated
+
+Session data is stored under your workspace, typically:
+
+```text
+~/.picoclaw/workspace/sessions/
+```
+
+## Quick Start
+
+### Default: one context per chat
+
+This is the default and is the right choice for most bots.
+
+```json
+{
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+Use this when:
+
+- each group/channel should have its own shared memory
+- each direct message should have its own separate memory
+
+### Separate each user inside a group
+
+If users in the same group should not share memory, add `sender`:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "sender"]
+ }
+}
+```
+
+Use this when:
+
+- one shared assistant sits in a busy group
+- each user should keep a private thread of context even inside the same room
+
+### Share one context across multiple rooms in the same workspace or guild
+
+If your channel exposes a `space` value, you can route by workspace or guild instead of by room:
+
+```json
+{
+ "session": {
+ "dimensions": ["space"]
+ }
+}
+```
+
+Use this when:
+
+- a Slack workspace assistant should share context across channels
+- a Discord guild assistant should share context across channels
+
+### Split by thread or forum topic
+
+If your channel exposes `topic`, you can isolate per thread:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "topic"]
+ }
+}
+```
+
+Use this when:
+
+- each forum topic should keep its own history
+- each threaded discussion should stay separate
+
+## Available Dimensions
+
+| Dimension | What it means | Good for |
+| --- | --- | --- |
+| `space` | Workspace, guild, or similar top-level container | One shared assistant across many rooms |
+| `chat` | Direct chat, group, or channel | Default per-room isolation |
+| `topic` | Thread, topic, or forum sub-channel | Keep threaded discussions separate |
+| `sender` | The message sender after normalization | Per-user context inside shared rooms |
+
+Not every channel provides every field.
+If a channel does not supply `space` or `topic`, those dimensions simply have no effect for that message.
+
+## Important Behavior
+
+### Sessions are always separated by agent
+
+Even if two agents receive messages from the same chat, they do not share one session.
+
+### Sessions are still separated by channel and account
+
+`session.dimensions` adds finer-grained isolation, but PicoClaw still keeps a baseline separation by:
+
+- agent
+- channel
+- account
+
+That means an empty or very small `dimensions` list does **not** create one global memory across every platform.
+
+### Telegram forum topics already stay isolated in the default `chat` mode
+
+Telegram forum messages keep topic isolation by default even when `dimensions` only contains `chat`.
+You usually do not need a special workaround for Telegram forums.
+
+### Summaries happen per session
+
+`summarize_message_threshold` and `summarize_token_percent` apply inside each session independently.
+If you create smaller sessions, summarization also happens on smaller per-session histories.
+
+## Common Recipes
+
+### One shared assistant per group or direct chat
+
+```json
+{
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+### One context per user inside each chat
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "sender"]
+ }
+}
+```
+
+### One context per sender across one workspace or guild
+
+```json
+{
+ "session": {
+ "dimensions": ["space", "sender"]
+ }
+}
+```
+
+This is useful for workspace-wide assistants where each user should keep their own memory while moving across rooms in the same workspace.
+
+### Use a different session policy for one routed agent only
+
+You can keep the global default and override it for one dispatch rule:
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "support group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890"
+ },
+ "session_dimensions": ["chat", "sender"]
+ }
+ ]
+ }
+ },
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+In this example:
+
+- most traffic uses one shared context per chat
+- the support group uses one context per user inside that chat
+
+## Identity Links
+
+`session.identity_links` helps when the same user may appear under multiple raw sender IDs and you want PicoClaw to treat them as one sender identity.
+
+Example:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "sender"],
+ "identity_links": {
+ "john": ["slack:u123", "u123", "legacy-user-42"]
+ }
+ }
+}
+```
+
+This is mainly useful for:
+
+- migrated sender IDs
+- platform-specific ID aliases
+- cleanup after changing channel adapters or account naming
+
+Current limitation:
+
+- `identity_links` does not make one user share memory across different channels automatically
+- channel and account remain part of the baseline session scope
+
+## Troubleshooting
+
+### Users in one group are sharing memory
+
+Your current session is probably keyed only by `chat`.
+Switch to:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "sender"]
+ }
+}
+```
+
+### The same user does not share memory across Slack and Telegram
+
+That is expected.
+PicoClaw still separates sessions by channel even if you use `sender`.
+
+### Threads are mixing together
+
+Add `topic` when the channel provides one:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "topic"]
+ }
+}
+```
+
+### Old sessions seem to use legacy keys
+
+That is normal during migration.
+PicoClaw keeps compatibility with older `agent:...` session keys while moving runtime storage to opaque canonical keys.
+
+## Related Guides
+
+- [Configuration Guide](configuration.md)
+- [Routing Guide](routing-guide.md)
+- [Providers & Model Configuration](providers.md)
diff --git a/docs/guides/session-guide.zh.md b/docs/guides/session-guide.zh.md
new file mode 100644
index 000000000..679a7f68d
--- /dev/null
+++ b/docs/guides/session-guide.zh.md
@@ -0,0 +1,273 @@
+# Session 使用指南
+
+> 返回 [README](../project/README.zh.md)
+
+PicoClaw 的 Session 决定了哪些消息会共享同一段对话历史。
+如果你的 bot 表现为“记得太多”或“忘得太快”,首先就该检查 session 配置。
+
+这份文档面向编辑 `config.json` 的普通用户。
+如果你想看内部实现细节,请看 architecture 文档,而不是这里。
+
+## Session 控制什么
+
+一个 session 会影响:
+
+- Agent 能看到哪些历史消息
+- 这段对话何时开始触发摘要
+- 同一个群里的不同用户是否共享上下文
+- 不同聊天、不同线程、不同空间是否保持隔离
+
+Session 数据保存在工作区目录下,通常是:
+
+```text
+~/.picoclaw/workspace/sessions/
+```
+
+## 快速开始
+
+### 默认:每个 chat 一段上下文
+
+这是默认值,也是大多数 bot 的正确起点。
+
+```json
+{
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+适用场景:
+
+- 每个群 / 频道都有自己的共享记忆
+- 每个私聊都有各自独立的记忆
+
+### 在同一个群里按用户分开
+
+如果同一个群里的不同用户不应该共享上下文,增加 `sender`:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "sender"]
+ }
+}
+```
+
+适用场景:
+
+- 一个群里挂着一个共享 assistant,但不希望用户之间串上下文
+- 希望每个用户在同一个房间里保留自己的独立记忆
+
+### 在同一个 workspace / guild 下跨多个房间共享上下文
+
+如果你的 channel 会提供 `space`,可以按 workspace 或 guild 共享,而不是按单个房间共享:
+
+```json
+{
+ "session": {
+ "dimensions": ["space"]
+ }
+}
+```
+
+适用场景:
+
+- Slack workspace 里的 assistant 想跨多个 channel 共享上下文
+- Discord guild 里的 assistant 想跨多个 channel 共享上下文
+
+### 按线程或论坛 topic 隔离
+
+如果 channel 会提供 `topic`,可以显式按线程隔离:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "topic"]
+ }
+}
+```
+
+适用场景:
+
+- 每个论坛 topic 都要保留独立历史
+- 每个 threaded discussion 都不能串上下文
+
+## 可用维度
+
+| 维度 | 含义 | 适合什么场景 |
+| --- | --- | --- |
+| `space` | workspace、guild 或类似的上层容器 | 一个 assistant 跨多个房间共享上下文 |
+| `chat` | 私聊、群聊或频道 | 默认按房间隔离 |
+| `topic` | 线程、topic 或 forum 子通道 | 让 threaded discussion 保持隔离 |
+| `sender` | 归一化后的消息发送者 | 在共享房间内按用户隔离 |
+
+并不是每个 channel 都会提供全部字段。
+如果某个 channel 没有 `space` 或 `topic`,对应维度对那条消息就不会生效。
+
+## 关键行为
+
+### Session 总是按 agent 分开
+
+即使两个 agent 处理同一个 chat,它们也不会共享同一段 session。
+
+### Session 仍然会按 channel 和 account 分开
+
+`session.dimensions` 只是添加更细的隔离维度,PicoClaw 仍然保留一层基础隔离:
+
+- agent
+- channel
+- account
+
+这意味着即使 `dimensions` 为空,系统也**不会**把所有平台的消息都混成一个全局记忆。
+
+### Telegram forum topic 在默认 `chat` 模式下也会保持隔离
+
+Telegram forum 消息在默认 `chat` 模式下就会保留 topic 隔离。
+通常不需要额外为 Telegram forum 单独写 workaround。
+
+### 摘要是按 session 触发的
+
+`summarize_message_threshold` 和 `summarize_token_percent` 都是针对单个 session 生效。
+如果你把 session 切得更小,摘要也会按更小的历史范围触发。
+
+## 常见配置方案
+
+### 每个群 / 私聊共享一段上下文
+
+```json
+{
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+### 每个 chat 内再按用户拆分
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "sender"]
+ }
+}
+```
+
+### 在同一个 workspace / guild 内按用户保留上下文
+
+```json
+{
+ "session": {
+ "dimensions": ["space", "sender"]
+ }
+}
+```
+
+这适合做 workspace 级 assistant:用户在同一个 workspace 里跨多个房间移动,但仍保留自己的上下文。
+
+### 只给某个路由出来的 agent 覆盖 session 策略
+
+你可以保留全局默认值,再在某条 dispatch rule 上单独覆盖:
+
+```json
+{
+ "agents": {
+ "list": [
+ { "id": "main", "default": true },
+ { "id": "support" }
+ ],
+ "dispatch": {
+ "rules": [
+ {
+ "name": "support group",
+ "agent": "support",
+ "when": {
+ "channel": "telegram",
+ "chat": "group:-1001234567890"
+ },
+ "session_dimensions": ["chat", "sender"]
+ }
+ ]
+ }
+ },
+ "session": {
+ "dimensions": ["chat"]
+ }
+}
+```
+
+在这个例子里:
+
+- 大部分流量仍然按 `chat` 共享上下文
+- 只有 support 群按 `chat + sender` 拆成每人一段上下文
+
+## Identity Links
+
+`session.identity_links` 适合处理这种场景:同一个人可能会以多个原始 sender ID 出现,但你希望 PicoClaw 把它们视为同一个发送者身份。
+
+示例:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "sender"],
+ "identity_links": {
+ "john": ["slack:u123", "u123", "legacy-user-42"]
+ }
+ }
+}
+```
+
+这主要适用于:
+
+- sender ID 迁移
+- 同一平台下的多个 ID 别名
+- 调整 channel adapter 或 account 命名后的兼容清理
+
+当前限制:
+
+- `identity_links` 不会自动让同一个用户跨不同 channel 共享记忆
+- channel 和 account 仍然属于基础 session scope 的一部分
+
+## 常见问题
+
+### 同一个群里的用户在共享记忆
+
+大概率是当前 session 只按 `chat` 建。
+改成:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "sender"]
+ }
+}
+```
+
+### 同一个用户在 Slack 和 Telegram 之间没有共享记忆
+
+这是当前实现下的预期行为。
+即使使用了 `sender`,PicoClaw 仍然会按 channel 做基础隔离。
+
+### 不同线程混在一起了
+
+如果这个 channel 提供 `topic`,加上它:
+
+```json
+{
+ "session": {
+ "dimensions": ["chat", "topic"]
+ }
+}
+```
+
+### 升级后看到旧的 session key
+
+这属于正常兼容行为。
+PicoClaw 在迁移到新的 opaque canonical key 时,仍会兼容旧的 `agent:...` session key。
+
+## 相关文档
+
+- [配置指南](configuration.zh.md)
+- [路由指南](routing-guide.zh.md)
+- [Provider 与模型配置](providers.zh.md)
diff --git a/docs/fr/spawn-tasks.md b/docs/guides/spawn-tasks.fr.md
similarity index 97%
rename from docs/fr/spawn-tasks.md
rename to docs/guides/spawn-tasks.fr.md
index 5635cd645..40a7a3ded 100644
--- a/docs/fr/spawn-tasks.md
+++ b/docs/guides/spawn-tasks.fr.md
@@ -1,6 +1,6 @@
# 🔄 Tâches Asynchrones et Spawn
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
## Tâches Rapides (réponse directe)
diff --git a/docs/ja/spawn-tasks.md b/docs/guides/spawn-tasks.ja.md
similarity index 98%
rename from docs/ja/spawn-tasks.md
rename to docs/guides/spawn-tasks.ja.md
index a13aab9eb..598654242 100644
--- a/docs/ja/spawn-tasks.md
+++ b/docs/guides/spawn-tasks.ja.md
@@ -1,6 +1,6 @@
# 🔄 非同期タスクと Spawn
-> [README](../../README.ja.md) に戻る
+> [README](../project/README.ja.md) に戻る
### Spawn を使用した非同期タスク
diff --git a/docs/spawn-tasks.md b/docs/guides/spawn-tasks.md
similarity index 100%
rename from docs/spawn-tasks.md
rename to docs/guides/spawn-tasks.md
diff --git a/docs/my/spawn-tasks.md b/docs/guides/spawn-tasks.ms.md
similarity index 97%
rename from docs/my/spawn-tasks.md
rename to docs/guides/spawn-tasks.ms.md
index c0c3e8f92..055ebf20d 100644
--- a/docs/my/spawn-tasks.md
+++ b/docs/guides/spawn-tasks.ms.md
@@ -1,6 +1,6 @@
# 🔄 Spawn & Tugasan Async
-> Kembali ke [README](../../README.my.md)
+> Kembali ke [README](../project/README.ms.md)
## Tugasan Cepat (balas terus)
diff --git a/docs/pt-br/spawn-tasks.md b/docs/guides/spawn-tasks.pt-br.md
similarity index 97%
rename from docs/pt-br/spawn-tasks.md
rename to docs/guides/spawn-tasks.pt-br.md
index d6b539cb1..0de929821 100644
--- a/docs/pt-br/spawn-tasks.md
+++ b/docs/guides/spawn-tasks.pt-br.md
@@ -1,6 +1,6 @@
# 🔄 Tarefas Assíncronas e Spawn
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
## Tarefas Rápidas (resposta direta)
diff --git a/docs/vi/spawn-tasks.md b/docs/guides/spawn-tasks.vi.md
similarity index 97%
rename from docs/vi/spawn-tasks.md
rename to docs/guides/spawn-tasks.vi.md
index 78f728040..e8533750b 100644
--- a/docs/vi/spawn-tasks.md
+++ b/docs/guides/spawn-tasks.vi.md
@@ -1,6 +1,6 @@
# 🔄 Tác Vụ Bất Đồng Bộ và Spawn
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
## Tác Vụ Nhanh (phản hồi trực tiếp)
diff --git a/docs/zh/spawn-tasks.md b/docs/guides/spawn-tasks.zh.md
similarity index 98%
rename from docs/zh/spawn-tasks.md
rename to docs/guides/spawn-tasks.zh.md
index 781462af2..ee5f1580e 100644
--- a/docs/zh/spawn-tasks.md
+++ b/docs/guides/spawn-tasks.zh.md
@@ -1,6 +1,6 @@
# 🔄 异步任务与 Spawn
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
PicoClaw 通过 `spawn` 工具支持**异步任务执行**。主要由 **Heartbeat(心跳)** 系统使用,在不阻塞主 Agent 循环的情况下运行耗时任务。
diff --git a/docs/migration/README.md b/docs/migration/README.md
new file mode 100644
index 000000000..eb37eec20
--- /dev/null
+++ b/docs/migration/README.md
@@ -0,0 +1,5 @@
+# Migration
+
+Migration notes for major configuration and behavior changes across PicoClaw versions.
+
+- [Migration Guide: From `providers` to `model_list`](model-list-migration.md): update legacy provider config to the current `model_list` format.
diff --git a/docs/operations/README.md b/docs/operations/README.md
new file mode 100644
index 000000000..b775ca3d9
--- /dev/null
+++ b/docs/operations/README.md
@@ -0,0 +1,6 @@
+# Operations
+
+Operational docs for debugging, diagnosis, and production troubleshooting.
+
+- [Troubleshooting](troubleshooting.md): common failures, symptoms, and recovery steps.
+- [Debugging PicoClaw](debug.md): logs, runtime visibility, and debugging workflow.
diff --git a/docs/fr/debug.md b/docs/operations/debug.fr.md
similarity index 97%
rename from docs/fr/debug.md
rename to docs/operations/debug.fr.md
index 5753ccf8c..331f7c4ba 100644
--- a/docs/fr/debug.md
+++ b/docs/operations/debug.fr.md
@@ -1,6 +1,6 @@
# Débogage de PicoClaw
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
PicoClaw effectue de multiples interactions complexes en arrière-plan pour chaque requête qu'il reçoit — du routage des messages et de l'évaluation de la complexité, à l'exécution des outils et à l'adaptation aux défaillances de modèle. Pouvoir voir exactement ce qui se passe est crucial, non seulement pour résoudre les problèmes potentiels, mais aussi pour véritablement comprendre le fonctionnement de l'agent.
diff --git a/docs/ja/debug.md b/docs/operations/debug.ja.md
similarity index 97%
rename from docs/ja/debug.md
rename to docs/operations/debug.ja.md
index ecc52f454..5b3365bf8 100644
--- a/docs/ja/debug.md
+++ b/docs/operations/debug.ja.md
@@ -1,6 +1,6 @@
# PicoClaw のデバッグ
-> [README](../../README.ja.md) に戻る
+> [README](../project/README.ja.md) に戻る
PicoClaw は、受信するすべてのリクエストに対して、メッセージのルーティングや複雑度の評価、ツールの実行、モデル障害への適応など、多くの複雑な処理をバックグラウンドで実行しています。何が起きているかを正確に把握できることは、潜在的な問題のトラブルシューティングだけでなく、エージェントの動作を真に理解するためにも非常に重要です。
diff --git a/docs/debug.md b/docs/operations/debug.md
similarity index 100%
rename from docs/debug.md
rename to docs/operations/debug.md
diff --git a/docs/my/debug.md b/docs/operations/debug.ms.md
similarity index 100%
rename from docs/my/debug.md
rename to docs/operations/debug.ms.md
diff --git a/docs/pt-br/debug.md b/docs/operations/debug.pt-br.md
similarity index 97%
rename from docs/pt-br/debug.md
rename to docs/operations/debug.pt-br.md
index 8614cd5ed..655385840 100644
--- a/docs/pt-br/debug.md
+++ b/docs/operations/debug.pt-br.md
@@ -1,6 +1,6 @@
# Depuração do PicoClaw
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
O PicoClaw realiza múltiplas interações complexas nos bastidores para cada requisição que recebe — desde o roteamento de mensagens e avaliação de complexidade, até a execução de ferramentas e adaptação a falhas de modelo. Poder ver exatamente o que está acontecendo é crucial, não apenas para solucionar problemas potenciais, mas também para realmente entender como o agente opera.
diff --git a/docs/vi/debug.md b/docs/operations/debug.vi.md
similarity index 97%
rename from docs/vi/debug.md
rename to docs/operations/debug.vi.md
index 69583d486..76d555648 100644
--- a/docs/vi/debug.md
+++ b/docs/operations/debug.vi.md
@@ -1,6 +1,6 @@
# Gỡ lỗi PicoClaw
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
PicoClaw thực hiện nhiều tương tác phức tạp ở hậu trường cho mỗi yêu cầu nhận được — từ định tuyến tin nhắn và đánh giá độ phức tạp, đến thực thi công cụ và thích ứng với lỗi mô hình. Khả năng xem chính xác những gì đang xảy ra là rất quan trọng, không chỉ để khắc phục các sự cố tiềm ẩn, mà còn để thực sự hiểu cách agent hoạt động.
diff --git a/docs/zh/debug.md b/docs/operations/debug.zh.md
similarity index 97%
rename from docs/zh/debug.md
rename to docs/operations/debug.zh.md
index e7f20d777..8e544c03b 100644
--- a/docs/zh/debug.md
+++ b/docs/operations/debug.zh.md
@@ -1,6 +1,6 @@
# 调试 PicoClaw
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
PicoClaw 在处理每一个请求时,都会在后台执行多个复杂的交互操作——从消息路由和复杂度评估,到工具执行和模型故障适配。能够准确地看到正在发生什么至关重要,这不仅有助于排查潜在问题,也有助于真正理解代理的运作方式。
diff --git a/docs/fr/troubleshooting.md b/docs/operations/troubleshooting.fr.md
similarity index 97%
rename from docs/fr/troubleshooting.md
rename to docs/operations/troubleshooting.fr.md
index d2d099ad3..630f69627 100644
--- a/docs/fr/troubleshooting.md
+++ b/docs/operations/troubleshooting.fr.md
@@ -1,6 +1,6 @@
# 🐛 Dépannage
-> Retour au [README](../../README.fr.md)
+> Retour au [README](../project/README.fr.md)
## "model ... not found in model_list" ou OpenRouter "free is not a valid model ID"
diff --git a/docs/ja/troubleshooting.md b/docs/operations/troubleshooting.ja.md
similarity index 97%
rename from docs/ja/troubleshooting.md
rename to docs/operations/troubleshooting.ja.md
index f18b456db..f1d244c92 100644
--- a/docs/ja/troubleshooting.md
+++ b/docs/operations/troubleshooting.ja.md
@@ -1,6 +1,6 @@
# 🐛 トラブルシューティング
-> [README](../../README.ja.md) に戻る
+> [README](../project/README.ja.md) に戻る
## "model ... not found in model_list" または OpenRouter "free is not a valid model ID"
diff --git a/docs/troubleshooting.md b/docs/operations/troubleshooting.md
similarity index 100%
rename from docs/troubleshooting.md
rename to docs/operations/troubleshooting.md
diff --git a/docs/my/troubleshooting.md b/docs/operations/troubleshooting.ms.md
similarity index 100%
rename from docs/my/troubleshooting.md
rename to docs/operations/troubleshooting.ms.md
diff --git a/docs/pt-br/troubleshooting.md b/docs/operations/troubleshooting.pt-br.md
similarity index 96%
rename from docs/pt-br/troubleshooting.md
rename to docs/operations/troubleshooting.pt-br.md
index 286ad2ac8..eec64d9d8 100644
--- a/docs/pt-br/troubleshooting.md
+++ b/docs/operations/troubleshooting.pt-br.md
@@ -1,6 +1,6 @@
# 🐛 Solução de Problemas
-> Voltar ao [README](../../README.pt-br.md)
+> Voltar ao [README](../project/README.pt-br.md)
## "model ... not found in model_list" ou OpenRouter "free is not a valid model ID"
diff --git a/docs/vi/troubleshooting.md b/docs/operations/troubleshooting.vi.md
similarity index 97%
rename from docs/vi/troubleshooting.md
rename to docs/operations/troubleshooting.vi.md
index 961c932aa..8aa5e2ae4 100644
--- a/docs/vi/troubleshooting.md
+++ b/docs/operations/troubleshooting.vi.md
@@ -1,6 +1,6 @@
# 🐛 Khắc Phục Sự Cố
-> Quay lại [README](../../README.vi.md)
+> Quay lại [README](../project/README.vi.md)
## "model ... not found in model_list" hoặc OpenRouter "free is not a valid model ID"
diff --git a/docs/zh/troubleshooting.md b/docs/operations/troubleshooting.zh.md
similarity index 97%
rename from docs/zh/troubleshooting.md
rename to docs/operations/troubleshooting.zh.md
index be4d4f5d7..fd519a8b2 100644
--- a/docs/zh/troubleshooting.md
+++ b/docs/operations/troubleshooting.zh.md
@@ -1,6 +1,6 @@
# 🐛 疑难解答
-> 返回 [README](../../README.zh.md)
+> 返回 [README](../project/README.zh.md)
## "model ... not found in model_list" 或 OpenRouter "free is not a valid model ID"
diff --git a/CONTRIBUTING.zh.md b/docs/project/CONTRIBUTING.zh.md
similarity index 100%
rename from CONTRIBUTING.zh.md
rename to docs/project/CONTRIBUTING.zh.md
diff --git a/README.fr.md b/docs/project/README.fr.md
similarity index 82%
rename from README.fr.md
rename to docs/project/README.fr.md
index 8fa67fa02..1e2f59bee 100644
--- a/README.fr.md
+++ b/docs/project/README.fr.md
@@ -1,5 +1,5 @@
@@ -72,7 +72,7 @@
2026-02-26 🎉 PicoClaw atteint **20K Stars** en seulement 17 jours ! L'orchestration automatique des channels et les interfaces de capacités sont disponibles.
-2026-02-16 🎉 PicoClaw dépasse 12K Stars en une semaine ! Rôles de mainteneurs communautaires et [Roadmap](ROADMAP.md) officiellement lancés.
+2026-02-16 🎉 PicoClaw dépasse 12K Stars en une semaine ! Rôles de mainteneurs communautaires et [Roadmap](../../ROADMAP.md) officiellement lancés.
2026-02-13 🎉 PicoClaw dépasse 5000 Stars en 4 jours ! Roadmap du projet et groupes de développeurs en cours.
@@ -110,14 +110,14 @@ _*Les builds récents peuvent utiliser 10-20 Mo en raison des fusions rapides de
| **Temps de démarrage**(cœur 0,8 GHz) | >500s | >30s | **<1s** |
| **Coût** | Mac Mini $599 | La plupart des cartes Linux ~$50 | **N'importe quelle carte Linux****à partir de $10** |
-
+
-> **[Liste de compatibilité matérielle](docs/fr/hardware-compatibility.md)** — Voir toutes les cartes testées, du RISC-V à $5 au Raspberry Pi en passant par les téléphones Android. Votre carte n'est pas listée ? Soumettez une PR !
+> **[Liste de compatibilité matérielle](../guides/hardware-compatibility.fr.md)** — Voir toutes les cartes testées, du RISC-V à $5 au Raspberry Pi en passant par les téléphones Android. Votre carte n'est pas listée ? Soumettez une PR !
-
+
## 🦾 Démonstration
@@ -131,9 +131,9 @@ _*Les builds récents peuvent utiliser 10-20 Mo en raison des fusions rapides de
Recherche Web & Apprentissage
-
-
-
+
+
+
Développer · Déployer · Mettre à l'échelle
@@ -223,7 +223,7 @@ picoclaw-launcher
> ```
-
+
**Pour commencer :**
@@ -277,7 +277,7 @@ macOS peut bloquer `picoclaw-launcher` au premier lancement car il est télécha
**Étape 1 :** Double-cliquez sur `picoclaw-launcher`. Un avertissement de sécurité s'affiche :
-
+
> *"picoclaw-launcher" n'a pas pu être ouvert — Apple n'a pas pu vérifier que "picoclaw-launcher" ne contient pas de logiciel malveillant susceptible de nuire à votre Mac ou de compromettre votre confidentialité.*
@@ -285,7 +285,7 @@ macOS peut bloquer `picoclaw-launcher` au premier lancement car il est télécha
**Étape 2 :** Ouvrez **Réglages Système** → **Confidentialité et sécurité** → faites défiler jusqu'à la section **Sécurité** → cliquez sur **Ouvrir quand même** → confirmez en cliquant sur **Ouvrir quand même** dans la boîte de dialogue.
-
+
Après cette étape unique, `picoclaw-launcher` s'ouvrira normalement lors des lancements suivants.
@@ -301,7 +301,7 @@ picoclaw-launcher-tui
```
-
+
**Pour commencer :**
@@ -310,6 +310,7 @@ Utilisez les menus TUI pour : **1)** Configurer un Provider -> **2)** Configurer
Pour la documentation détaillée du TUI, voir [docs.picoclaw.io](https://docs.picoclaw.io).
+
### 📱 Android
Donnez une seconde vie à votre téléphone vieux de dix ans ! Transformez-le en assistant IA intelligent avec PicoClaw.
@@ -320,10 +321,10 @@ Aperçu :
-
-
-
-
+
+
+
+
@@ -347,7 +348,7 @@ termux-chroot ./picoclaw onboard # chroot fournit une arborescence Linux stand
Suivez ensuite la section Terminal Launcher ci-dessous pour terminer la configuration.
-
+
Pour les environnements minimaux où seul le binaire principal `picoclaw` est disponible (sans Launcher UI), vous pouvez tout configurer via la ligne de commande et un fichier de configuration JSON.
@@ -454,7 +455,7 @@ PicoClaw supporte plus de 30 providers LLM via la configuration `model_list`. Ut
}
```
-Pour les détails complets de configuration des providers, voir [Providers & Models](docs/fr/providers.md).
+Pour les détails complets de configuration des providers, voir [Providers & Models](../guides/providers.fr.md).
@@ -464,28 +465,28 @@ Parlez à votre PicoClaw via plus de 17 plateformes de messagerie :
| Channel | Configuration | Protocole | Docs |
|---------|---------------|-----------|------|
-| **Telegram** | Facile (token bot) | Long polling | [Guide](docs/channels/telegram/README.fr.md) |
-| **Discord** | Facile (token bot + intents) | WebSocket | [Guide](docs/channels/discord/README.fr.md) |
-| **WhatsApp** | Facile (scan QR ou URL bridge) | Natif / Bridge | [Guide](docs/fr/chat-apps.md#whatsapp) |
-| **Weixin** | Facile (scan QR natif) | iLink API | [Guide](docs/fr/chat-apps.md#weixin) |
-| **QQ** | Facile (AppID + AppSecret) | WebSocket | [Guide](docs/channels/qq/README.fr.md) |
-| **Slack** | Facile (token bot + app) | Socket Mode | [Guide](docs/channels/slack/README.fr.md) |
-| **Matrix** | Moyen (homeserver + token) | Sync API | [Guide](docs/channels/matrix/README.fr.md) |
-| **DingTalk** | Moyen (identifiants client) | Stream | [Guide](docs/channels/dingtalk/README.fr.md) |
-| **Feishu / Lark** | Moyen (App ID + Secret) | WebSocket/SDK | [Guide](docs/channels/feishu/README.fr.md) |
-| **LINE** | Moyen (identifiants + webhook) | Webhook | [Guide](docs/channels/line/README.fr.md) |
-| **WeCom** | Facile (QR login ou manuel) | WebSocket | [Guide](docs/channels/wecom/README.md) |
-| **IRC** | Moyen (serveur + pseudo) | Protocole IRC | [Guide](docs/fr/chat-apps.md#irc) |
-| **OneBot** | Moyen (URL WebSocket) | OneBot v11 | [Guide](docs/channels/onebot/README.fr.md) |
-| **MaixCam** | Facile (activer) | Socket TCP | [Guide](docs/channels/maixcam/README.fr.md) |
+| **Telegram** | Facile (token bot) | Long polling | [Guide](../channels/telegram/README.fr.md) |
+| **Discord** | Facile (token bot + intents) | WebSocket | [Guide](../channels/discord/README.fr.md) |
+| **WhatsApp** | Facile (scan QR ou URL bridge) | Natif / Bridge | [Guide](../guides/chat-apps.fr.md#whatsapp) |
+| **Weixin** | Facile (scan QR natif) | iLink API | [Guide](../guides/chat-apps.fr.md#weixin) |
+| **QQ** | Facile (AppID + AppSecret) | WebSocket | [Guide](../channels/qq/README.fr.md) |
+| **Slack** | Facile (token bot + app) | Socket Mode | [Guide](../channels/slack/README.fr.md) |
+| **Matrix** | Moyen (homeserver + token) | Sync API | [Guide](../channels/matrix/README.fr.md) |
+| **DingTalk** | Moyen (identifiants client) | Stream | [Guide](../channels/dingtalk/README.fr.md) |
+| **Feishu / Lark** | Moyen (App ID + Secret) | WebSocket/SDK | [Guide](../channels/feishu/README.fr.md) |
+| **LINE** | Moyen (identifiants + webhook) | Webhook | [Guide](../channels/line/README.fr.md) |
+| **WeCom** | Facile (QR login ou manuel) | WebSocket | [Guide](../channels/wecom/README.fr.md) |
+| **IRC** | Moyen (serveur + pseudo) | Protocole IRC | [Guide](../guides/chat-apps.fr.md#irc) |
+| **OneBot** | Moyen (URL WebSocket) | OneBot v11 | [Guide](../channels/onebot/README.fr.md) |
+| **MaixCam** | Facile (activer) | Socket TCP | [Guide](../channels/maixcam/README.fr.md) |
| **Pico** | Facile (activer) | Protocole natif | Intégré |
| **Pico Client** | Facile (URL WebSocket) | WebSocket | Intégré |
> Tous les channels basés sur webhook partagent un seul serveur HTTP Gateway (`gateway.host`:`gateway.port`, par défaut `127.0.0.1:18790`). Feishu utilise le mode WebSocket/SDK et n'utilise pas le serveur HTTP partagé.
-> La verbosité des logs est contrôlée par `gateway.log_level` (par défaut : `warn`). Valeurs supportées : `debug`, `info`, `warn`, `error`, `fatal`. Peut aussi être défini via `PICOCLAW_LOG_LEVEL`. Voir [Configuration](docs/fr/configuration.md#niveau-de-log-du-gateway) pour plus de détails.
+> La verbosité des logs est contrôlée par `gateway.log_level` (par défaut : `warn`). Valeurs supportées : `debug`, `info`, `warn`, `error`, `fatal`. Peut aussi être défini via `PICOCLAW_LOG_LEVEL`. Voir [Configuration](../guides/configuration.fr.md#niveau-de-log-du-gateway) pour plus de détails.
-Pour les instructions détaillées de configuration des channels, voir [Configuration des applications de chat](docs/fr/chat-apps.md).
+Pour les instructions détaillées de configuration des channels, voir [Configuration des applications de chat](../guides/chat-apps.fr.md).
## 🔧 Outils
@@ -505,7 +506,7 @@ PicoClaw peut effectuer des recherches sur le web pour fournir des informations
### ⚙️ Autres outils
-PicoClaw inclut des outils intégrés pour les opérations sur fichiers, l'exécution de code, la planification et plus encore. Voir [Configuration des outils](docs/fr/tools_configuration.md) pour les détails.
+PicoClaw inclut des outils intégrés pour les opérations sur fichiers, l'exécution de code, la planification et plus encore. Voir [Configuration des outils](../reference/tools_configuration.fr.md) pour les détails.
## 🎯 Skills
@@ -535,7 +536,7 @@ Ajoutez à votre `config.json` :
}
```
-Pour plus de détails, voir [Configuration des outils - Skills](docs/fr/tools_configuration.md#skills-tool).
+Pour plus de détails, voir [Configuration des outils - Skills](../reference/tools_configuration.fr.md#skills-tool).
## 🔗 MCP (Model Context Protocol)
@@ -558,9 +559,9 @@ PicoClaw supporte nativement [MCP](https://modelcontextprotocol.io/) — connect
}
```
-Pour la configuration MCP complète (transports stdio, SSE, HTTP, Tool Discovery), voir [Configuration des outils - MCP](docs/fr/tools_configuration.md#mcp-tool).
+Pour la configuration MCP complète (transports stdio, SSE, HTTP, Tool Discovery), voir [Configuration des outils - MCP](../reference/tools_configuration.fr.md#mcp-tool).
-## Rejoignez le réseau social des Agents
+## Rejoignez le réseau social des Agents
Connectez PicoClaw au réseau social des Agents simplement en envoyant un seul message via le CLI ou n'importe quelle application de chat intégrée.
@@ -601,23 +602,23 @@ Pour des guides détaillés au-delà de ce README :
| Sujet | Description |
|-------|-------------|
-| [Docker & Démarrage rapide](docs/fr/docker.md) | Configuration Docker Compose, modes Launcher/Agent |
-| [Applications de chat](docs/fr/chat-apps.md) | Guides de configuration pour les 17+ channels |
-| [Configuration](docs/fr/configuration.md) | Variables d'environnement, structure du workspace, sandbox de sécurité |
-| [Providers & Modèles](docs/fr/providers.md) | 30+ providers LLM, routage de modèles, configuration model_list |
-| [Spawn & Tâches asynchrones](docs/fr/spawn-tasks.md) | Tâches rapides, tâches longues avec spawn, orchestration de sous-agents asynchrones |
-| [Hooks](docs/hooks/README.md) | Système de hooks événementiels : observateurs, intercepteurs, hooks d'approbation |
-| [Steering](docs/steering.md) | Injecter des messages dans une boucle agent en cours d'exécution |
-| [SubTurn](docs/subturn.md) | Coordination de subagents, contrôle de concurrence, cycle de vie |
-| [Dépannage](docs/fr/troubleshooting.md) | Problèmes courants et solutions |
-| [Configuration des outils](docs/fr/tools_configuration.md) | Activation/désactivation par outil, politiques d'exécution, MCP, Skills |
-| [Compatibilité matérielle](docs/fr/hardware-compatibility.md) | Cartes testées, exigences minimales |
+| [Docker & Démarrage rapide](../guides/docker.fr.md) | Configuration Docker Compose, modes Launcher/Agent |
+| [Applications de chat](../guides/chat-apps.fr.md) | Guides de configuration pour les 17+ channels |
+| [Configuration](../guides/configuration.fr.md) | Variables d'environnement, structure du workspace, sandbox de sécurité |
+| [Providers & Modèles](../guides/providers.fr.md) | 30+ providers LLM, routage de modèles, configuration model_list |
+| [Spawn & Tâches asynchrones](../guides/spawn-tasks.fr.md) | Tâches rapides, tâches longues avec spawn, orchestration de sous-agents asynchrones |
+| [Hooks](../architecture/hooks/README.md) | Système de hooks événementiels : observateurs, intercepteurs, hooks d'approbation |
+| [Steering](../architecture/steering.md) | Injecter des messages dans une boucle agent en cours d'exécution |
+| [SubTurn](../architecture/subturn.md) | Coordination de subagents, contrôle de concurrence, cycle de vie |
+| [Dépannage](../operations/troubleshooting.fr.md) | Problèmes courants et solutions |
+| [Configuration des outils](../reference/tools_configuration.fr.md) | Activation/désactivation par outil, politiques d'exécution, MCP, Skills |
+| [Compatibilité matérielle](../guides/hardware-compatibility.fr.md) | Cartes testées, exigences minimales |
## 🤝 Contribuer & Roadmap
Les PRs sont les bienvenues ! Le code source est intentionnellement petit et lisible.
-Consultez notre [Roadmap communautaire](https://github.com/sipeed/picoclaw/issues/988) et [CONTRIBUTING.md](CONTRIBUTING.md) pour les directives.
+Consultez notre [Roadmap communautaire](https://github.com/sipeed/picoclaw/issues/988) et [CONTRIBUTING.md](../../CONTRIBUTING.md) pour les directives.
Groupe de développeurs en construction, rejoignez-le après votre première PR fusionnée !
@@ -626,4 +627,4 @@ Groupes d'utilisateurs :
Discord :
WeChat :
-
+
diff --git a/README.id.md b/docs/project/README.id.md
similarity index 83%
rename from README.id.md
rename to docs/project/README.id.md
index 525d4dc72..244e6e49a 100644
--- a/README.id.md
+++ b/docs/project/README.id.md
@@ -1,5 +1,5 @@
@@ -71,7 +71,7 @@
2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi channel otomatis dan antarmuka kapabilitas kini aktif.
-2026-02-16 🎉 PicoClaw menembus 12K Stars dalam satu minggu! Peran maintainer komunitas dan [Roadmap](ROADMAP.md) resmi diluncurkan.
+2026-02-16 🎉 PicoClaw menembus 12K Stars dalam satu minggu! Peran maintainer komunitas dan [Roadmap](../../ROADMAP.md) resmi diluncurkan.
2026-02-13 🎉 PicoClaw menembus 5000 Stars dalam 4 hari! Roadmap proyek dan grup pengembang sedang dalam proses.
@@ -108,14 +108,14 @@ _*Build terbaru mungkin menggunakan 10-20MB karena penggabungan PR yang cepat. O
| **Waktu Boot**(core 0,8GHz) | >500d | >30d | **<1d** |
| **Biaya** | Mac Mini $599 | Kebanyakan board Linux ~$50 | **Board Linux mana pun****mulai $10** |
-
+
-> **[Daftar Kompatibilitas Hardware](docs/hardware-compatibility.md)** — Lihat semua board yang telah diuji, dari RISC-V $5 hingga Raspberry Pi hingga ponsel Android. Board Anda belum terdaftar? Kirim PR!
+> **[Daftar Kompatibilitas Hardware](../guides/hardware-compatibility.md)** — Lihat semua board yang telah diuji, dari RISC-V $5 hingga Raspberry Pi hingga ponsel Android. Board Anda belum terdaftar? Kirim PR!
-
+
## 🦾 Demonstrasi
@@ -129,9 +129,9 @@ _*Build terbaru mungkin menggunakan 10-20MB karena penggabungan PR yang cepat. O
Pencarian Web & Pembelajaran
-
-
-
+
+
+
Develop · Deploy · Scale
@@ -220,7 +220,7 @@ picoclaw-launcher
> ```
-
+
**Memulai:**
@@ -274,7 +274,7 @@ macOS mungkin memblokir `picoclaw-launcher` saat pertama kali diluncurkan karena
**Langkah 1:** Klik dua kali `picoclaw-launcher`. Anda akan melihat peringatan keamanan:
-
+
> *"picoclaw-launcher" Tidak Dapat Dibuka — Apple tidak dapat memverifikasi bahwa "picoclaw-launcher" bebas dari malware yang dapat membahayakan Mac Anda atau mengancam privasi Anda.*
@@ -282,7 +282,7 @@ macOS mungkin memblokir `picoclaw-launcher` saat pertama kali diluncurkan karena
**Langkah 2:** Buka **Pengaturan Sistem** → **Privasi & Keamanan** → gulir ke bawah ke bagian **Keamanan** → klik **Tetap Buka** → konfirmasi dengan mengklik **Tetap Buka** pada dialog.
-
+
Setelah langkah satu kali ini, `picoclaw-launcher` akan terbuka secara normal pada peluncuran berikutnya.
@@ -298,7 +298,7 @@ picoclaw-launcher-tui
```
-
+
**Memulai:**
@@ -317,10 +317,10 @@ Pratinjau:
-
-
-
-
+
+
+
+
@@ -344,7 +344,7 @@ termux-chroot ./picoclaw onboard # chroot menyediakan tata letak filesystem Li
Kemudian ikuti bagian Terminal Launcher di bawah untuk menyelesaikan konfigurasi.
-
+
Untuk lingkungan minimal di mana hanya binary inti `picoclaw` yang tersedia (tanpa Launcher UI), Anda dapat mengonfigurasi semuanya melalui command line dan file konfigurasi JSON.
@@ -450,7 +450,7 @@ PicoClaw mendukung 30+ provider LLM melalui konfigurasi `model_list`. Gunakan fo
}
```
-Untuk detail konfigurasi provider lengkap, lihat [Providers & Models](docs/providers.md).
+Untuk detail konfigurasi provider lengkap, lihat [Providers & Models](../guides/providers.md).
@@ -460,28 +460,28 @@ Bicara dengan PicoClaw Anda melalui 17+ platform pesan:
| Channel | Pengaturan | Protocol | Dokumentasi |
|---------|------------|----------|-------------|
-| **Telegram** | Mudah (bot token) | Long polling | [Panduan](docs/channels/telegram/README.md) |
-| **Discord** | Mudah (bot token + intents) | WebSocket | [Panduan](docs/channels/discord/README.md) |
-| **WhatsApp** | Mudah (scan QR atau bridge URL) | Native / Bridge | [Panduan](docs/chat-apps.md#whatsapp) |
-| **Weixin** | Mudah (scan QR native) | iLink API | [Panduan](docs/chat-apps.md#weixin) |
-| **QQ** | Mudah (AppID + AppSecret) | WebSocket | [Panduan](docs/channels/qq/README.md) |
-| **Slack** | Mudah (bot + app token) | Socket Mode | [Panduan](docs/channels/slack/README.md) |
-| **Matrix** | Sedang (homeserver + token) | Sync API | [Panduan](docs/channels/matrix/README.md) |
-| **DingTalk** | Sedang (client credentials) | Stream | [Panduan](docs/channels/dingtalk/README.md) |
-| **Feishu / Lark** | Sedang (App ID + Secret) | WebSocket/SDK | [Panduan](docs/channels/feishu/README.md) |
-| **LINE** | Sedang (credentials + webhook) | Webhook | [Panduan](docs/channels/line/README.md) |
-| **WeCom** | Mudah (login QR atau manual) | WebSocket | [Panduan](docs/channels/wecom/README.md) |
-| **IRC** | Sedang (server + nick) | IRC protocol | [Panduan](docs/chat-apps.md#irc) |
-| **OneBot** | Sedang (WebSocket URL) | OneBot v11 | [Panduan](docs/channels/onebot/README.md) |
-| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](docs/channels/maixcam/README.md) |
+| **Telegram** | Mudah (bot token) | Long polling | [Panduan](../channels/telegram/README.md) |
+| **Discord** | Mudah (bot token + intents) | WebSocket | [Panduan](../channels/discord/README.md) |
+| **WhatsApp** | Mudah (scan QR atau bridge URL) | Native / Bridge | [Panduan](../guides/chat-apps.md#whatsapp) |
+| **Weixin** | Mudah (scan QR native) | iLink API | [Panduan](../guides/chat-apps.md#weixin) |
+| **QQ** | Mudah (AppID + AppSecret) | WebSocket | [Panduan](../channels/qq/README.md) |
+| **Slack** | Mudah (bot + app token) | Socket Mode | [Panduan](../channels/slack/README.md) |
+| **Matrix** | Sedang (homeserver + token) | Sync API | [Panduan](../channels/matrix/README.md) |
+| **DingTalk** | Sedang (client credentials) | Stream | [Panduan](../channels/dingtalk/README.md) |
+| **Feishu / Lark** | Sedang (App ID + Secret) | WebSocket/SDK | [Panduan](../channels/feishu/README.md) |
+| **LINE** | Sedang (credentials + webhook) | Webhook | [Panduan](../channels/line/README.md) |
+| **WeCom** | Mudah (login QR atau manual) | WebSocket | [Panduan](../channels/wecom/README.md) |
+| **IRC** | Sedang (server + nick) | IRC protocol | [Panduan](../guides/chat-apps.md#irc) |
+| **OneBot** | Sedang (WebSocket URL) | OneBot v11 | [Panduan](../channels/onebot/README.md) |
+| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](../channels/maixcam/README.md) |
| **Pico** | Mudah (aktifkan) | Native protocol | Bawaan |
| **Pico Client** | Mudah (WebSocket URL) | WebSocket | Bawaan |
> Semua channel berbasis webhook berbagi satu server HTTP Gateway (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu menggunakan mode WebSocket/SDK dan tidak menggunakan server HTTP bersama.
-> Verbositas log dikontrol oleh `gateway.log_level` (default: `warn`). Nilai yang didukung: `debug`, `info`, `warn`, `error`, `fatal`. Juga dapat diatur melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](docs/configuration.md#gateway-log-level) untuk detail.
+> Verbositas log dikontrol oleh `gateway.log_level` (default: `warn`). Nilai yang didukung: `debug`, `info`, `warn`, `error`, `fatal`. Juga dapat diatur melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](../guides/configuration.md#gateway-log-level) untuk detail.
-Untuk instruksi pengaturan channel lengkap, lihat [Konfigurasi Aplikasi Chat](docs/chat-apps.md).
+Untuk instruksi pengaturan channel lengkap, lihat [Konfigurasi Aplikasi Chat](../guides/chat-apps.md).
## 🔧 Tools
@@ -501,7 +501,7 @@ PicoClaw dapat mencari web untuk memberikan informasi terkini. Konfigurasi di `t
### ⚙️ Tools Lainnya
-PicoClaw menyertakan tools bawaan untuk operasi file, eksekusi kode, penjadwalan, dan lainnya. Lihat [Konfigurasi Tools](docs/tools_configuration.md) untuk detail.
+PicoClaw menyertakan tools bawaan untuk operasi file, eksekusi kode, penjadwalan, dan lainnya. Lihat [Konfigurasi Tools](../reference/tools_configuration.md) untuk detail.
## 🎯 Skills
@@ -531,7 +531,7 @@ Tambahkan ke `config.json` Anda:
}
```
-Untuk detail lebih lanjut, lihat [Konfigurasi Tools - Skills](docs/tools_configuration.md#skills-tool).
+Untuk detail lebih lanjut, lihat [Konfigurasi Tools - Skills](../reference/tools_configuration.md#skills-tool).
## 🔗 MCP (Model Context Protocol)
@@ -554,9 +554,9 @@ PicoClaw mendukung [MCP](https://modelcontextprotocol.io/) secara native — hub
}
```
-Untuk konfigurasi MCP lengkap (transport stdio, SSE, HTTP, Tool Discovery), lihat [Konfigurasi Tools - MCP](docs/tools_configuration.md#mcp-tool).
+Untuk konfigurasi MCP lengkap (transport stdio, SSE, HTTP, Tool Discovery), lihat [Konfigurasi Tools - MCP](../reference/tools_configuration.md#mcp-tool).
-## Bergabung dengan Jaringan Sosial Agent
+## Bergabung dengan Jaringan Sosial Agent
Hubungkan PicoClaw ke Jaringan Sosial Agent hanya dengan mengirim satu pesan melalui CLI atau Aplikasi Chat terintegrasi mana pun.
@@ -597,23 +597,23 @@ Untuk panduan lengkap di luar README ini:
| Topik | Deskripsi |
|-------|-----------|
-| [Docker & Panduan Cepat](docs/docker.md) | Pengaturan Docker Compose, mode Launcher/Agent |
-| [Aplikasi Chat](docs/chat-apps.md) | Semua 17+ panduan pengaturan channel |
-| [Konfigurasi](docs/configuration.md) | Variabel environment, tata letak workspace, sandbox keamanan |
-| [Providers & Models](docs/providers.md) | 30+ provider LLM, routing model, konfigurasi model_list |
-| [Spawn & Tugas Async](docs/spawn-tasks.md) | Tugas cepat, tugas panjang dengan spawn, orkestrasi sub-agent async |
-| [Hooks](docs/hooks/README.md) | Sistem hook berbasis event: observer, interceptor, approval hook |
-| [Steering](docs/steering.md) | Menyuntikkan pesan ke dalam loop agent yang sedang berjalan |
-| [SubTurn](docs/subturn.md) | Koordinasi subagent, kontrol konkurensi, siklus hidup |
-| [Pemecahan Masalah](docs/troubleshooting.md) | Masalah umum dan solusinya |
-| [Konfigurasi Tools](docs/tools_configuration.md) | Aktifkan/nonaktifkan per-tool, kebijakan exec, MCP, Skills |
-| [Kompatibilitas Hardware](docs/hardware-compatibility.md) | Board yang telah diuji, persyaratan minimum |
+| [Docker & Panduan Cepat](../guides/docker.md) | Pengaturan Docker Compose, mode Launcher/Agent |
+| [Aplikasi Chat](../guides/chat-apps.md) | Semua 17+ panduan pengaturan channel |
+| [Konfigurasi](../guides/configuration.md) | Variabel environment, tata letak workspace, sandbox keamanan |
+| [Providers & Models](../guides/providers.md) | 30+ provider LLM, routing model, konfigurasi model_list |
+| [Spawn & Tugas Async](../guides/spawn-tasks.md) | Tugas cepat, tugas panjang dengan spawn, orkestrasi sub-agent async |
+| [Hooks](../architecture/hooks/README.md) | Sistem hook berbasis event: observer, interceptor, approval hook |
+| [Steering](../architecture/steering.md) | Menyuntikkan pesan ke dalam loop agent yang sedang berjalan |
+| [SubTurn](../architecture/subturn.md) | Koordinasi subagent, kontrol konkurensi, siklus hidup |
+| [Pemecahan Masalah](../operations/troubleshooting.md) | Masalah umum dan solusinya |
+| [Konfigurasi Tools](../reference/tools_configuration.md) | Aktifkan/nonaktifkan per-tool, kebijakan exec, MCP, Skills |
+| [Kompatibilitas Hardware](../guides/hardware-compatibility.md) | Board yang telah diuji, persyaratan minimum |
## 🤝 Kontribusi & Roadmap
PR sangat diterima! Codebase sengaja dibuat kecil dan mudah dibaca.
-Lihat [Roadmap Komunitas](https://github.com/sipeed/picoclaw/issues/988) dan [CONTRIBUTING.md](CONTRIBUTING.md) untuk panduan.
+Lihat [Roadmap Komunitas](https://github.com/sipeed/picoclaw/issues/988) dan [CONTRIBUTING.md](../../CONTRIBUTING.md) untuk panduan.
Grup pengembang sedang dibangun, bergabunglah setelah PR pertama Anda di-merge!
@@ -622,4 +622,4 @@ Grup Pengguna:
Discord:
WeChat:
-
+
diff --git a/README.it.md b/docs/project/README.it.md
similarity index 82%
rename from README.it.md
rename to docs/project/README.it.md
index c560976cf..eb2f7c95b 100644
--- a/README.it.md
+++ b/docs/project/README.it.md
@@ -1,5 +1,5 @@
@@ -71,7 +71,7 @@
2026-02-26 🎉 PicoClaw raggiunge **20K stelle** in soli 17 giorni! Orchestrazione automatica dei canali e interfacce di capacità sono attive.
-2026-02-16 🎉 PicoClaw supera 12K stelle in una settimana! Ruoli di maintainer della community e [Roadmap](ROADMAP.md) pubblicati ufficialmente.
+2026-02-16 🎉 PicoClaw supera 12K stelle in una settimana! Ruoli di maintainer della community e [Roadmap](../../ROADMAP.md) pubblicati ufficialmente.
2026-02-13 🎉 PicoClaw supera 5000 stelle in 4 giorni! Roadmap del progetto e gruppi sviluppatori in fase di avvio.
@@ -108,14 +108,14 @@ _*Le build recenti potrebbero usare 10-20MB a causa delle fusioni rapide di PR.
| **Avvio**(core 0,8 GHz) | >500s | >30s | **<1s** |
| **Costo** | Mac Mini $599 | La maggior parte degli SBC Linux ~$50 | **Qualsiasi scheda Linux****a partire da $10** |
-
+
-> **[Lista di Compatibilità Hardware](docs/hardware-compatibility.md)** — Vedi tutte le schede testate, dai $5 RISC-V al Raspberry Pi ai telefoni Android. La tua scheda non è elencata? Invia una PR!
+> **[Lista di Compatibilità Hardware](../guides/hardware-compatibility.md)** — Vedi tutte le schede testate, dai $5 RISC-V al Raspberry Pi ai telefoni Android. La tua scheda non è elencata? Invia una PR!
-
+
## 🦾 Dimostrazione
@@ -129,9 +129,9 @@ _*Le build recenti potrebbero usare 10-20MB a causa delle fusioni rapide di PR.
Ricerca Web & Apprendimento
-
-
-
+
+
+
Sviluppa · Distribuisci · Scala
@@ -220,7 +220,7 @@ picoclaw-launcher
> ```
-
+
**Per iniziare:**
@@ -274,7 +274,7 @@ macOS potrebbe bloccare `picoclaw-launcher` al primo avvio perché è stato scar
**Passo 1:** Fai doppio clic su `picoclaw-launcher`. Verrà visualizzato un avviso di sicurezza:
-
+
> *"picoclaw-launcher" Non Aperto — Apple non è riuscita a verificare che "picoclaw-launcher" sia privo di malware che potrebbe danneggiare il Mac o compromettere la privacy.*
@@ -282,7 +282,7 @@ macOS potrebbe bloccare `picoclaw-launcher` al primo avvio perché è stato scar
**Passo 2:** Apri **Impostazioni di Sistema** → **Privacy e sicurezza** → scorri fino alla sezione **Sicurezza** → clicca su **Apri comunque** → conferma cliccando su **Apri comunque** nella finestra di dialogo.
-
+
Dopo questo passaggio una tantum, `picoclaw-launcher` si aprirà normalmente ai lanci successivi.
@@ -298,7 +298,7 @@ picoclaw-launcher-tui
```
@@ -344,7 +344,7 @@ termux-chroot ./picoclaw onboard # chroot fornisce un layout standard del file
Poi segui la sezione Terminal Launcher qui sotto per completare la configurazione.
-
+
Per ambienti minimali dove è disponibile solo il binario core `picoclaw` (senza Launcher UI), puoi configurare tutto tramite riga di comando e un file di configurazione JSON.
@@ -450,7 +450,7 @@ PicoClaw supporta 30+ provider LLM tramite la configurazione `model_list`. Usa i
}
```
-Per i dettagli completi sulla configurazione dei provider, vedi [Provider & Modelli](docs/providers.md).
+Per i dettagli completi sulla configurazione dei provider, vedi [Provider & Modelli](../guides/providers.md).
@@ -460,28 +460,28 @@ Parla con il tuo PicoClaw attraverso 17+ piattaforme di messaggistica:
| Channel | Configurazione | Protocollo | Docs |
|---------|----------------|------------|------|
-| **Telegram** | Facile (bot token) | Long polling | [Guida](docs/channels/telegram/README.md) |
-| **Discord** | Facile (bot token + intents) | WebSocket | [Guida](docs/channels/discord/README.md) |
-| **WhatsApp** | Facile (QR scan o bridge URL) | Nativo / Bridge | [Guida](docs/chat-apps.md#whatsapp) |
-| **Weixin** | Facile (scan QR nativo) | iLink API | [Guida](docs/chat-apps.md#weixin) |
-| **QQ** | Facile (AppID + AppSecret) | WebSocket | [Guida](docs/channels/qq/README.md) |
-| **Slack** | Facile (bot + app token) | Socket Mode | [Guida](docs/channels/slack/README.md) |
-| **Matrix** | Medio (homeserver + token) | Sync API | [Guida](docs/channels/matrix/README.md) |
-| **DingTalk** | Medio (credenziali client) | Stream | [Guida](docs/channels/dingtalk/README.md) |
-| **Feishu / Lark** | Medio (App ID + Secret) | WebSocket/SDK | [Guida](docs/channels/feishu/README.md) |
-| **LINE** | Medio (credenziali + webhook) | Webhook | [Guida](docs/channels/line/README.md) |
-| **WeCom** | Facile (login QR o manuale) | WebSocket | [Guida](docs/channels/wecom/README.md) |
-| **IRC** | Medio (server + nick) | Protocollo IRC | [Guida](docs/chat-apps.md#irc) |
-| **OneBot** | Medio (WebSocket URL) | OneBot v11 | [Guida](docs/channels/onebot/README.md) |
-| **MaixCam** | Facile (abilita) | TCP socket | [Guida](docs/channels/maixcam/README.md) |
+| **Telegram** | Facile (bot token) | Long polling | [Guida](../channels/telegram/README.md) |
+| **Discord** | Facile (bot token + intents) | WebSocket | [Guida](../channels/discord/README.md) |
+| **WhatsApp** | Facile (QR scan o bridge URL) | Nativo / Bridge | [Guida](../guides/chat-apps.md#whatsapp) |
+| **Weixin** | Facile (scan QR nativo) | iLink API | [Guida](../guides/chat-apps.md#weixin) |
+| **QQ** | Facile (AppID + AppSecret) | WebSocket | [Guida](../channels/qq/README.md) |
+| **Slack** | Facile (bot + app token) | Socket Mode | [Guida](../channels/slack/README.md) |
+| **Matrix** | Medio (homeserver + token) | Sync API | [Guida](../channels/matrix/README.md) |
+| **DingTalk** | Medio (credenziali client) | Stream | [Guida](../channels/dingtalk/README.md) |
+| **Feishu / Lark** | Medio (App ID + Secret) | WebSocket/SDK | [Guida](../channels/feishu/README.md) |
+| **LINE** | Medio (credenziali + webhook) | Webhook | [Guida](../channels/line/README.md) |
+| **WeCom** | Facile (login QR o manuale) | WebSocket | [Guida](../channels/wecom/README.md) |
+| **IRC** | Medio (server + nick) | Protocollo IRC | [Guida](../guides/chat-apps.md#irc) |
+| **OneBot** | Medio (WebSocket URL) | OneBot v11 | [Guida](../channels/onebot/README.md) |
+| **MaixCam** | Facile (abilita) | TCP socket | [Guida](../channels/maixcam/README.md) |
| **Pico** | Facile (abilita) | Protocollo nativo | Integrato |
| **Pico Client** | Facile (WebSocket URL) | WebSocket | Integrato |
> Tutti i channel basati su webhook condividono un singolo server HTTP Gateway (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu usa la modalità WebSocket/SDK e non usa il server HTTP condiviso.
-> La verbosità dei log è controllata da `gateway.log_level` (default: `warn`). Valori supportati: `debug`, `info`, `warn`, `error`, `fatal`. Può essere impostato anche tramite `PICOCLAW_LOG_LEVEL`. Vedi [Configurazione](docs/configuration.md#gateway-log-level) per i dettagli.
+> La verbosità dei log è controllata da `gateway.log_level` (default: `warn`). Valori supportati: `debug`, `info`, `warn`, `error`, `fatal`. Può essere impostato anche tramite `PICOCLAW_LOG_LEVEL`. Vedi [Configurazione](../guides/configuration.md#gateway-log-level) per i dettagli.
-Per istruzioni dettagliate sulla configurazione dei channel, vedi [Configurazione App di Chat](docs/chat-apps.md).
+Per istruzioni dettagliate sulla configurazione dei channel, vedi [Configurazione App di Chat](../guides/chat-apps.md).
## 🔧 Strumenti
@@ -501,7 +501,7 @@ PicoClaw può cercare sul web per fornire informazioni aggiornate. Configura in
### ⚙️ Altri Strumenti
-PicoClaw include strumenti integrati per operazioni su file, esecuzione di codice, pianificazione e altro. Vedi [Configurazione degli Strumenti](docs/tools_configuration.md) per i dettagli.
+PicoClaw include strumenti integrati per operazioni su file, esecuzione di codice, pianificazione e altro. Vedi [Configurazione degli Strumenti](../reference/tools_configuration.md) per i dettagli.
## 🎯 Skill
@@ -531,7 +531,7 @@ Aggiungi al tuo `config.json`:
}
```
-Per maggiori dettagli, vedi [Configurazione degli Strumenti - Skill](docs/tools_configuration.md#skills-tool).
+Per maggiori dettagli, vedi [Configurazione degli Strumenti - Skill](../reference/tools_configuration.md#skills-tool).
## 🔗 MCP (Model Context Protocol)
@@ -554,9 +554,9 @@ PicoClaw supporta nativamente [MCP](https://modelcontextprotocol.io/) — connet
}
```
-Per la configurazione MCP completa (trasporti stdio, SSE, HTTP, Tool Discovery), vedi [Configurazione degli Strumenti - MCP](docs/tools_configuration.md#mcp-tool).
+Per la configurazione MCP completa (trasporti stdio, SSE, HTTP, Tool Discovery), vedi [Configurazione degli Strumenti - MCP](../reference/tools_configuration.md#mcp-tool).
-## Unisciti al Social Network degli Agent
+## Unisciti al Social Network degli Agent
Connetti PicoClaw al Social Network degli Agent semplicemente inviando un singolo messaggio tramite CLI o qualsiasi app di chat integrata.
@@ -597,23 +597,23 @@ Per guide dettagliate oltre questo README:
| Argomento | Descrizione |
|-----------|-------------|
-| [Docker & Avvio Rapido](docs/docker.md) | Configurazione Docker Compose, modalità Launcher/Agent |
-| [App di Chat](docs/chat-apps.md) | Tutte le guide di configurazione per 17+ channel |
-| [Configurazione](docs/configuration.md) | Variabili d'ambiente, struttura del workspace, sandbox di sicurezza |
-| [Provider & Modelli](docs/providers.md) | 30+ provider LLM, routing dei modelli, configurazione model_list |
-| [Spawn & Task Asincroni](docs/spawn-tasks.md) | Task veloci, task lunghi con spawn, orchestrazione asincrona di sub-agent |
-| [Hooks](docs/hooks/README.md) | Sistema di hook event-driven: observer, interceptor, approval hook |
-| [Steering](docs/steering.md) | Iniettare messaggi in un loop agent in esecuzione |
-| [SubTurn](docs/subturn.md) | Coordinamento subagent, controllo concorrenza, ciclo di vita |
-| [Risoluzione Problemi](docs/troubleshooting.md) | Problemi comuni e soluzioni |
-| [Configurazione degli Strumenti](docs/tools_configuration.md) | Abilitazione/disabilitazione per strumento, politiche exec, MCP, Skill |
-| [Compatibilità Hardware](docs/hardware-compatibility.md) | Schede testate, requisiti minimi |
+| [Docker & Avvio Rapido](../guides/docker.md) | Configurazione Docker Compose, modalità Launcher/Agent |
+| [App di Chat](../guides/chat-apps.md) | Tutte le guide di configurazione per 17+ channel |
+| [Configurazione](../guides/configuration.md) | Variabili d'ambiente, struttura del workspace, sandbox di sicurezza |
+| [Provider & Modelli](../guides/providers.md) | 30+ provider LLM, routing dei modelli, configurazione model_list |
+| [Spawn & Task Asincroni](../guides/spawn-tasks.md) | Task veloci, task lunghi con spawn, orchestrazione asincrona di sub-agent |
+| [Hooks](../architecture/hooks/README.md) | Sistema di hook event-driven: observer, interceptor, approval hook |
+| [Steering](../architecture/steering.md) | Iniettare messaggi in un loop agent in esecuzione |
+| [SubTurn](../architecture/subturn.md) | Coordinamento subagent, controllo concorrenza, ciclo di vita |
+| [Risoluzione Problemi](../operations/troubleshooting.md) | Problemi comuni e soluzioni |
+| [Configurazione degli Strumenti](../reference/tools_configuration.md) | Abilitazione/disabilitazione per strumento, politiche exec, MCP, Skill |
+| [Compatibilità Hardware](../guides/hardware-compatibility.md) | Schede testate, requisiti minimi |
## 🤝 Contribuisci & Roadmap
Le PR sono benvenute! Il codice è volutamente piccolo e leggibile.
-Consulta la nostra [Roadmap della Community](https://github.com/sipeed/picoclaw/issues/988) e [CONTRIBUTING.md](CONTRIBUTING.md) per le linee guida.
+Consulta la nostra [Roadmap della Community](https://github.com/sipeed/picoclaw/issues/988) e [CONTRIBUTING.md](../../CONTRIBUTING.md) per le linee guida.
Gruppo sviluppatori in costruzione, unisciti dopo la tua prima PR accettata!
@@ -622,4 +622,4 @@ Gruppi utenti:
Discord:
WeChat:
-
+
diff --git a/README.ja.md b/docs/project/README.ja.md
similarity index 84%
rename from README.ja.md
rename to docs/project/README.ja.md
index d09eb436d..66d06ba5e 100644
--- a/README.ja.md
+++ b/docs/project/README.ja.md
@@ -1,5 +1,5 @@
@@ -71,7 +71,7 @@
2026-02-26 🎉 PicoClaw가 단 17일 만에 **20K 스타**를 달성했습니다! 채널 자동 오케스트레이션과 기능 인터페이스가 적용되었습니다.
-2026-02-16 🎉 PicoClaw가 1주일 만에 **12K 스타**를 돌파했습니다! 커뮤니티 메인터너 역할과 [로드맵](ROADMAP.md)이 공식적으로 공개되었습니다.
+2026-02-16 🎉 PicoClaw가 1주일 만에 **12K 스타**를 돌파했습니다! 커뮤니티 메인터너 역할과 [로드맵](../../ROADMAP.md)이 공식적으로 공개되었습니다.
2026-02-13 🎉 PicoClaw가 4일 만에 **5000 스타**를 돌파했습니다! 프로젝트 로드맵과 개발자 그룹이 준비 중입니다.
@@ -108,14 +108,14 @@ _*최근 빌드는 급격한 PR 병합으로 인해 10~20MB를 사용할 수 있
| **부팅 시간**(0.8GHz 코어) | >500초 | >30초 | **<1초** |
| **비용** | Mac Mini $599 | 대부분의 Linux 보드 ~$50 | **모든 Linux 보드****최저 $10부터** |
-
+
-> **[하드웨어 호환 목록](docs/hardware-compatibility.md)** — 테스트된 모든 보드를 확인하세요. $5 RISC-V 보드부터 Raspberry Pi, Android 스마트폰까지 포함됩니다. 사용 중인 보드가 없나요? PR을 보내주세요!
+> **[하드웨어 호환 목록](../guides/hardware-compatibility.md)** — 테스트된 모든 보드를 확인하세요. $5 RISC-V 보드부터 Raspberry Pi, Android 스마트폰까지 포함됩니다. 사용 중인 보드가 없나요? PR을 보내주세요!
-
+
## 🦾 데모
@@ -129,9 +129,9 @@ _*최근 빌드는 급격한 PR 병합으로 인해 10~20MB를 사용할 수 있
웹 검색 및 학습
-
-
-
+
+
+
개발 · 배포 · 확장
@@ -220,7 +220,7 @@ picoclaw-launcher
> ```
-
+
**시작 방법:**
@@ -274,7 +274,7 @@ macOS에서는 인터넷에서 다운로드한 앱이고 Mac App Store 공증을
**1단계:** `picoclaw-launcher`를 더블클릭합니다. 그러면 보안 경고가 표시됩니다.
-
+
> *"picoclaw-launcher"을(를) 열 수 없습니다. Apple에서 이 앱이 악성 소프트웨어가 없으며 Mac이나 개인 정보를 해치지 않는다고 확인할 수 없습니다.*
@@ -282,7 +282,7 @@ macOS에서는 인터넷에서 다운로드한 앱이고 Mac App Store 공증을
**2단계:** **시스템 설정** -> **개인정보 보호 및 보안** 으로 이동한 뒤 **보안** 섹션까지 스크롤하여 **그래도 열기(Open Anyway)** 를 클릭하고, 대화상자에서 다시 한 번 **그래도 열기**를 확인합니다.
-
+
이 과정을 한 번만 거치면 이후에는 `picoclaw-launcher`가 정상적으로 열립니다.
@@ -298,7 +298,7 @@ picoclaw-launcher-tui
```
-
+
**시작 방법:**
@@ -317,10 +317,10 @@ TUI 메뉴를 사용해 다음 순서로 진행하세요. **1)** 프로바이더
-
-
-
-
+
+
+
+
@@ -344,7 +344,7 @@ termux-chroot ./picoclaw onboard # chroot가 표준 Linux 파일시스템 레
그다음 아래의 터미널 런처 섹션을 따라 설정을 마무리하세요.
-
+
런처 UI 없이 `picoclaw` 코어 바이너리만 있는 최소 환경에서는 명령줄과 JSON 설정 파일만으로도 모든 설정을 마칠 수 있습니다.
@@ -377,7 +377,7 @@ picoclaw onboard
> 사용 가능한 모든 옵션이 포함된 전체 설정 템플릿은 저장소의 `config/config.example.json`을 참고하세요.
>
-> 참고: `config.example.json` 형식은 버전 0이며 민감 정보가 포함되어 있습니다. 실행 시 자동으로 버전 1+로 마이그레이션되며, 이후 `config.json`에는 비민감 정보만 저장되고 민감 정보는 `.security.yml`에 저장됩니다. 민감 정보를 직접 수정해야 한다면 `docs/security_configuration.md`를 참고하세요.
+> 참고: `config.example.json` 형식은 버전 0이며 민감 정보가 포함되어 있습니다. 실행 시 자동으로 버전 1+로 마이그레이션되며, 이후 `config.json`에는 비민감 정보만 저장되고 민감 정보는 `.security.yml`에 저장됩니다. 민감 정보를 직접 수정해야 한다면 `../security/security_configuration.md`를 참고하세요.
**3. 채팅**
@@ -455,7 +455,7 @@ PicoClaw는 `model_list` 설정을 통해 30개 이상의 LLM 프로바이더를
}
```
-프로바이더 전체 설정은 [프로바이더와 모델](docs/providers.md)을 참고하세요.
+프로바이더 전체 설정은 [프로바이더와 모델](../guides/providers.md)을 참고하세요.
@@ -465,29 +465,29 @@ PicoClaw는 `model_list` 설정을 통해 30개 이상의 LLM 프로바이더를
| 채널 | 설정 | 프로토콜 | 문서 |
|---------|------|----------|------|
-| **Telegram** | 쉬움(봇 토큰) | Long polling | [가이드](docs/channels/telegram/README.md) |
-| **Discord** | 쉬움(봇 토큰 + intents) | WebSocket | [가이드](docs/channels/discord/README.md) |
-| **WhatsApp** | 쉬움(QR 스캔 또는 브리지 URL) | Native / Bridge | [가이드](docs/chat-apps.md#whatsapp) |
-| **Weixin** | 쉬움(네이티브 QR 스캔) | iLink API | [가이드](docs/chat-apps.md#weixin) |
-| **QQ** | 쉬움(AppID + AppSecret) | WebSocket | [가이드](docs/channels/qq/README.md) |
-| **Slack** | 쉬움(봇 + 앱 토큰) | Socket Mode | [가이드](docs/channels/slack/README.md) |
-| **Matrix** | 중간(homeserver + 토큰) | Sync API | [가이드](docs/channels/matrix/README.md) |
-| **DingTalk** | 중간(클라이언트 자격 증명) | Stream | [가이드](docs/channels/dingtalk/README.md) |
-| **Feishu / Lark** | 중간(App ID + Secret) | WebSocket/SDK | [가이드](docs/channels/feishu/README.md) |
-| **LINE** | 중간(인증 정보 + webhook) | Webhook | [가이드](docs/channels/line/README.md) |
-| **WeCom** | 쉬움(QR 로그인 또는 수동 설정) | WebSocket | [가이드](docs/channels/wecom/README.md) |
-| **VK** | 쉬움(그룹 토큰) | Long Poll | [가이드](docs/channels/vk/README.md) |
-| **IRC** | 중간(서버 + 닉네임) | IRC protocol | [가이드](docs/chat-apps.md#irc) |
-| **OneBot** | 중간(WebSocket URL) | OneBot v11 | [가이드](docs/channels/onebot/README.md) |
-| **MaixCam** | 쉬움(활성화) | TCP socket | [가이드](docs/channels/maixcam/README.md) |
+| **Telegram** | 쉬움(봇 토큰) | Long polling | [가이드](../channels/telegram/README.md) |
+| **Discord** | 쉬움(봇 토큰 + intents) | WebSocket | [가이드](../channels/discord/README.md) |
+| **WhatsApp** | 쉬움(QR 스캔 또는 브리지 URL) | Native / Bridge | [가이드](../guides/chat-apps.md#whatsapp) |
+| **Weixin** | 쉬움(네이티브 QR 스캔) | iLink API | [가이드](../guides/chat-apps.md#weixin) |
+| **QQ** | 쉬움(AppID + AppSecret) | WebSocket | [가이드](../channels/qq/README.md) |
+| **Slack** | 쉬움(봇 + 앱 토큰) | Socket Mode | [가이드](../channels/slack/README.md) |
+| **Matrix** | 중간(homeserver + 토큰) | Sync API | [가이드](../channels/matrix/README.md) |
+| **DingTalk** | 중간(클라이언트 자격 증명) | Stream | [가이드](../channels/dingtalk/README.md) |
+| **Feishu / Lark** | 중간(App ID + Secret) | WebSocket/SDK | [가이드](../channels/feishu/README.md) |
+| **LINE** | 중간(인증 정보 + webhook) | Webhook | [가이드](../channels/line/README.md) |
+| **WeCom** | 쉬움(QR 로그인 또는 수동 설정) | WebSocket | [가이드](../channels/wecom/README.md) |
+| **VK** | 쉬움(그룹 토큰) | Long Poll | [가이드](../channels/vk/README.md) |
+| **IRC** | 중간(서버 + 닉네임) | IRC protocol | [가이드](../guides/chat-apps.md#irc) |
+| **OneBot** | 중간(WebSocket URL) | OneBot v11 | [가이드](../channels/onebot/README.md) |
+| **MaixCam** | 쉬움(활성화) | TCP socket | [가이드](../channels/maixcam/README.md) |
| **Pico** | 쉬움(활성화) | 네이티브 프로토콜 | 내장 |
| **Pico Client** | 쉬움(WebSocket URL) | WebSocket | 내장 |
> webhook 기반 채널은 모두 하나의 게이트웨이 HTTP 서버(`gateway.host`:`gateway.port`, 기본값 `127.0.0.1:18790`)를 공유합니다. Feishu는 WebSocket/SDK 모드를 사용하며 이 공용 HTTP 서버를 사용하지 않습니다.
-> 로그 상세도는 `gateway.log_level`(기본값: `warn`)로 제어됩니다. 지원 값은 `debug`, `info`, `warn`, `error`, `fatal`입니다. `PICOCLAW_LOG_LEVEL` 환경 변수로도 설정할 수 있습니다. 자세한 내용은 [설정 문서](docs/configuration.md#gateway-log-level)를 참고하세요.
+> 로그 상세도는 `gateway.log_level`(기본값: `warn`)로 제어됩니다. 지원 값은 `debug`, `info`, `warn`, `error`, `fatal`입니다. `PICOCLAW_LOG_LEVEL` 환경 변수로도 설정할 수 있습니다. 자세한 내용은 [설정 문서](../guides/configuration.md#gateway-log-level)를 참고하세요.
-자세한 채널 설정 방법은 [채팅 앱 설정 가이드](docs/chat-apps.md)를 참고하세요.
+자세한 채널 설정 방법은 [채팅 앱 설정 가이드](../guides/chat-apps.md)를 참고하세요.
## 🔧 도구
@@ -507,7 +507,7 @@ PicoClaw는 최신 정보를 제공하기 위해 웹 검색을 수행할 수 있
### ⚙️ 기타 도구
-PicoClaw에는 파일 작업, 코드 실행, 스케줄링 등을 위한 내장 도구가 포함되어 있습니다. 자세한 내용은 [도구 설정](docs/tools_configuration.md)을 참고하세요.
+PicoClaw에는 파일 작업, 코드 실행, 스케줄링 등을 위한 내장 도구가 포함되어 있습니다. 자세한 내용은 [도구 설정](../reference/tools_configuration.md)을 참고하세요.
## 🎯 스킬
@@ -537,7 +537,7 @@ picoclaw skills install
}
```
-자세한 내용은 [도구 설정 - 스킬](docs/tools_configuration.md#skills-tool)를 참고하세요.
+자세한 내용은 [도구 설정 - 스킬](../reference/tools_configuration.md#skills-tool)를 참고하세요.
## 🔗 MCP (Model Context Protocol)
@@ -560,9 +560,9 @@ PicoClaw는 [MCP](https://modelcontextprotocol.io/)를 기본 지원합니다.
}
```
-MCP 전체 설정(stdio, SSE, HTTP 전송 방식, 도구 탐색)은 [도구 설정 - MCP](docs/tools_configuration.md#mcp-tool)를 참고하세요.
+MCP 전체 설정(stdio, SSE, HTTP 전송 방식, 도구 탐색)은 [도구 설정 - MCP](../reference/tools_configuration.md#mcp-tool)를 참고하세요.
-## 에이전트 소셜 네트워크 참여하기
+## 에이전트 소셜 네트워크 참여하기
CLI 또는 통합된 채팅 앱에서 메시지를 한 번만 보내면 PicoClaw를 에이전트 소셜 네트워크에 연결할 수 있습니다.
@@ -597,7 +597,7 @@ PicoClaw는 `cron` 도구를 통해 예약 리마인더와 반복 작업을 지
* **반복 작업**: "2시간마다 알려줘" -> 2시간마다 실행
* **Cron 표현식**: "매일 오전 9시에 알려줘" -> cron 표현식 사용
-현재 지원하는 스케줄 유형, 실행 모드, 명령 작업 게이트, 저장 방식은 [docs/cron.md](docs/cron.md)를 참고하세요.
+현재 지원하는 스케줄 유형, 실행 모드, 명령 작업 게이트, 저장 방식은 [docs/reference/cron.md](../reference/cron.md)를 참고하세요.
## 📚 문서
@@ -605,24 +605,24 @@ PicoClaw는 `cron` 도구를 통해 예약 리마인더와 반복 작업을 지
| 주제 | 설명 |
|------|------|
-| [도커 & 빠른 시작](docs/docker.md) | Docker Compose 설정, 런처/에이전트 모드 |
-| [채팅 앱](docs/chat-apps.md) | 17개 이상의 채널 설정 가이드 |
-| [설정](docs/configuration.md) | 환경 변수, 워크스페이스 레이아웃, 보안 샌드박스 |
-| [예약 작업과 Cron](docs/cron.md) | Cron 스케줄 유형, 전달 모드, 명령 게이트, 작업 저장 |
-| [프로바이더와 모델](docs/providers.md) | 30개 이상의 LLM 프로바이더, 모델 라우팅, model_list 설정 |
-| [Spawn & 비동기 작업](docs/spawn-tasks.md) | 빠른 작업, spawn을 이용한 장기 작업, 비동기 서브에이전트 오케스트레이션 |
-| [Hooks](docs/hooks/README.md) | 이벤트 기반 Hook 시스템: 관찰자, 인터셉터, 승인 훅 |
-| [Steering](docs/steering.md) | 실행 중인 에이전트 루프에서 도구 호출 사이에 메시지 주입 |
-| [SubTurn](docs/subturn.md) | 서브에이전트 조정, 동시성 제어, 생명주기 |
-| [문제 해결](docs/troubleshooting.md) | 자주 발생하는 문제와 해결 방법 |
-| [도구 설정](docs/tools_configuration.md) | 도구별 활성화/비활성화, exec 정책, MCP, 스킬 |
-| [하드웨어 호환성](docs/hardware-compatibility.md) | 테스트된 보드, 최소 요구사항 |
+| [도커 & 빠른 시작](../guides/docker.md) | Docker Compose 설정, 런처/에이전트 모드 |
+| [채팅 앱](../guides/chat-apps.md) | 17개 이상의 채널 설정 가이드 |
+| [설정](../guides/configuration.md) | 환경 변수, 워크스페이스 레이아웃, 보안 샌드박스 |
+| [예약 작업과 Cron](../reference/cron.md) | Cron 스케줄 유형, 전달 모드, 명령 게이트, 작업 저장 |
+| [프로바이더와 모델](../guides/providers.md) | 30개 이상의 LLM 프로바이더, 모델 라우팅, model_list 설정 |
+| [Spawn & 비동기 작업](../guides/spawn-tasks.md) | 빠른 작업, spawn을 이용한 장기 작업, 비동기 서브에이전트 오케스트레이션 |
+| [Hooks](../architecture/hooks/README.md) | 이벤트 기반 Hook 시스템: 관찰자, 인터셉터, 승인 훅 |
+| [Steering](../architecture/steering.md) | 실행 중인 에이전트 루프에서 도구 호출 사이에 메시지 주입 |
+| [SubTurn](../architecture/subturn.md) | 서브에이전트 조정, 동시성 제어, 생명주기 |
+| [문제 해결](../operations/troubleshooting.md) | 자주 발생하는 문제와 해결 방법 |
+| [도구 설정](../reference/tools_configuration.md) | 도구별 활성화/비활성화, exec 정책, MCP, 스킬 |
+| [하드웨어 호환성](../guides/hardware-compatibility.md) | 테스트된 보드, 최소 요구사항 |
## 🤝 기여 & 로드맵
PR은 언제든 환영합니다! 코드베이스는 의도적으로 작고 읽기 쉽게 유지하고 있습니다.
-가이드라인은 [커뮤니티 로드맵](https://github.com/sipeed/picoclaw/issues/988)과 [CONTRIBUTING.md](CONTRIBUTING.md)를 참고하세요.
+가이드라인은 [커뮤니티 로드맵](https://github.com/sipeed/picoclaw/issues/988)과 [CONTRIBUTING.md](../../CONTRIBUTING.md)를 참고하세요.
개발자 그룹도 준비 중입니다. 첫 PR이 머지되면 함께할 수 있습니다!
@@ -631,4 +631,4 @@ PR은 언제든 환영합니다! 코드베이스는 의도적으로 작고 읽
Discord:
WeChat:
-
+
diff --git a/README.my.md b/docs/project/README.ms.md
similarity index 85%
rename from README.my.md
rename to docs/project/README.ms.md
index bbe003deb..f8c9e95e7 100644
--- a/README.my.md
+++ b/docs/project/README.ms.md
@@ -1,5 +1,5 @@
@@ -71,7 +71,7 @@
2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi saluran automatik dan antara muka keupayaan kini aktif.
-2026-02-16 🎉 PicoClaw melepasi 12K Stars dalam seminggu! Peranan penyelenggara komuniti dan [Peta Jalan](ROADMAP.md) dilancarkan secara rasmi.
+2026-02-16 🎉 PicoClaw melepasi 12K Stars dalam seminggu! Peranan penyelenggara komuniti dan [Peta Jalan](../../ROADMAP.md) dilancarkan secara rasmi.
2026-02-13 🎉 PicoClaw melepasi 5000 Stars dalam 4 hari! Peta jalan projek dan kumpulan pembangun sedang dalam proses.
@@ -108,14 +108,14 @@ _*Binaan terkini mungkin menggunakan 10-20MB disebabkan penggabungan PR yang pes
| **Masa Boot** (teras 0.8GHz) | >500s | >30s | **<1s** |
| **Kos** | Mac Mini $599 | Kebanyakan papan Linux ~$50 | **Mana-mana papan Linux dari $10** |
-
+
-> **[Senarai Keserasian Perkakasan](docs/hardware-compatibility.md)** — Lihat semua papan yang diuji, dari RISC-V $5 hingga Raspberry Pi hingga telefon Android.
+> **[Senarai Keserasian Perkakasan](../guides/hardware-compatibility.md)** — Lihat semua papan yang diuji, dari RISC-V $5 hingga Raspberry Pi hingga telefon Android.
-
+
## 🦾 Demonstrasi
@@ -129,9 +129,9 @@ _*Binaan terkini mungkin menggunakan 10-20MB disebabkan penggabungan PR yang pes
Carian Web & Pembelajaran
-
-
-
+
+
+
Bangun · Deploy · Skala
@@ -220,7 +220,7 @@ picoclaw-launcher
> ```
-
+
**Memulakan:** Buka WebUI, kemudian: **1)** Konfigurasikan Penyedia (tambah kunci API LLM) -> **2)** Konfigurasikan Saluran (cth. Telegram) -> **3)** Mulakan Gateway -> **4)** Sembang!
@@ -271,7 +271,7 @@ macOS mungkin menyekat `picoclaw-launcher` pada pelancaran pertama kerana ia dim
**Langkah 1:** Klik dua kali `picoclaw-launcher`. Anda akan melihat amaran keselamatan:
-
+
> *"picoclaw-launcher" Tidak Dibuka — Apple tidak dapat mengesahkan "picoclaw-launcher" bebas daripada perisian hasad yang mungkin membahayakan Mac anda atau menjejaskan privasi anda.*
@@ -279,7 +279,7 @@ macOS mungkin menyekat `picoclaw-launcher` pada pelancaran pertama kerana ia dim
**Langkah 2:** Buka **Tetapan Sistem** → **Privasi & Keselamatan** → tatal ke bawah ke bahagian **Keselamatan** → klik **Buka Juga** → sahkan dengan mengklik **Buka Juga** dalam dialog.
-
+
Selepas langkah sekali ini, `picoclaw-launcher` akan dibuka secara normal pada pelancaran seterusnya.
@@ -295,7 +295,7 @@ picoclaw-launcher-tui
```
-
+
**Memulakan:**
@@ -314,10 +314,10 @@ Pratonton:
-
-
-
-
+
+
+
+
@@ -341,7 +341,7 @@ termux-chroot ./picoclaw onboard # chroot menyediakan susun atur sistem fail L
Kemudian ikuti bahagian Pelancar Terminal di bawah untuk melengkapkan konfigurasi.
-
+
Untuk persekitaran minimal di mana hanya binari teras `picoclaw` tersedia (tiada UI Pelancar), anda boleh mengkonfigurasi semua melalui baris arahan dan fail konfigurasi JSON.
@@ -449,7 +449,7 @@ PicoClaw menyokong 30+ penyedia LLM melalui konfigurasi `model_list`. Gunakan fo
}
```
-Untuk butiran konfigurasi penyedia penuh, lihat [Penyedia & Model](docs/providers.md).
+Untuk butiran konfigurasi penyedia penuh, lihat [Penyedia & Model](../guides/providers.md).
@@ -460,28 +460,28 @@ Bercakap dengan PicoClaw anda melalui 17+ platform pemesejan:
| Saluran | Persediaan | Protokol | Dok |
|---------|-----------|----------|-----|
-| **Telegram** | Mudah (token bot) | Long polling | [Panduan](docs/channels/telegram/README.md) |
-| **Discord** | Mudah (token bot + intents) | WebSocket | [Panduan](docs/channels/discord/README.md) |
-| **WhatsApp** | Mudah (imbas QR atau URL jambatan) | Natif / Jambatan | [Panduan](docs/chat-apps.md#whatsapp) |
-| **Weixin** | Mudah (imbas QR natif) | iLink API | [Panduan](docs/chat-apps.md#weixin) |
-| **QQ** | Mudah (AppID + AppSecret) | WebSocket | [Panduan](docs/channels/qq/README.md) |
-| **Slack** | Mudah (token bot + app) | Socket Mode | [Panduan](docs/channels/slack/README.md) |
-| **Matrix** | Sederhana (homeserver + token) | Sync API | [Panduan](docs/channels/matrix/README.md) |
-| **DingTalk** | Sederhana (kelayakan klien) | Stream | [Panduan](docs/channels/dingtalk/README.md) |
-| **Feishu / Lark** | Sederhana (App ID + Secret) | WebSocket/SDK | [Panduan](docs/channels/feishu/README.md) |
-| **LINE** | Sederhana (kelayakan + webhook) | Webhook | [Panduan](docs/channels/line/README.md) |
-| **WeCom** | Mudah (log masuk QR atau manual) | WebSocket | [Panduan](docs/channels/wecom/README.md) |
-| **IRC** | Sederhana (pelayan + nick) | Protokol IRC | [Panduan](docs/chat-apps.md#irc) |
-| **OneBot** | Sederhana (URL WebSocket) | OneBot v11 | [Panduan](docs/channels/onebot/README.md) |
-| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](docs/channels/maixcam/README.md) |
+| **Telegram** | Mudah (token bot) | Long polling | [Panduan](../channels/telegram/README.md) |
+| **Discord** | Mudah (token bot + intents) | WebSocket | [Panduan](../channels/discord/README.md) |
+| **WhatsApp** | Mudah (imbas QR atau URL jambatan) | Natif / Jambatan | [Panduan](../guides/chat-apps.ms.md#whatsapp) |
+| **Weixin** | Mudah (imbas QR natif) | iLink API | [Panduan](../guides/chat-apps.ms.md#weixin) |
+| **QQ** | Mudah (AppID + AppSecret) | WebSocket | [Panduan](../channels/qq/README.md) |
+| **Slack** | Mudah (token bot + app) | Socket Mode | [Panduan](../channels/slack/README.md) |
+| **Matrix** | Sederhana (homeserver + token) | Sync API | [Panduan](../channels/matrix/README.md) |
+| **DingTalk** | Sederhana (kelayakan klien) | Stream | [Panduan](../channels/dingtalk/README.md) |
+| **Feishu / Lark** | Sederhana (App ID + Secret) | WebSocket/SDK | [Panduan](../channels/feishu/README.md) |
+| **LINE** | Sederhana (kelayakan + webhook) | Webhook | [Panduan](../channels/line/README.md) |
+| **WeCom** | Mudah (log masuk QR atau manual) | WebSocket | [Panduan](../channels/wecom/README.md) |
+| **IRC** | Sederhana (pelayan + nick) | Protokol IRC | [Panduan](../guides/chat-apps.ms.md#irc) |
+| **OneBot** | Sederhana (URL WebSocket) | OneBot v11 | [Panduan](../channels/onebot/README.md) |
+| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](../channels/maixcam/README.md) |
| **Pico** | Mudah (aktifkan) | Protokol natif | Terbina dalam |
| **Pico Client** | Mudah (URL WebSocket) | WebSocket | Terbina dalam |
> Semua saluran berasaskan webhook berkongsi satu pelayan HTTP Gateway (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). Feishu menggunakan mod WebSocket/SDK dan tidak menggunakan pelayan HTTP yang dikongsi.
-> Tahap perincian log dikawal oleh `gateway.log_level` (lalai: `warn`). Nilai yang disokong: `debug`, `info`, `warn`, `error`, `fatal`. Boleh juga ditetapkan melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](docs/configuration.md#gateway-log-level) untuk butiran.
+> Tahap perincian log dikawal oleh `gateway.log_level` (lalai: `warn`). Nilai yang disokong: `debug`, `info`, `warn`, `error`, `fatal`. Boleh juga ditetapkan melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](../guides/configuration.ms.md#gateway-log-level) untuk butiran.
-Untuk arahan persediaan saluran terperinci, lihat [Konfigurasi Aplikasi Sembang](docs/my/chat-apps.md).
+Untuk arahan persediaan saluran terperinci, lihat [Konfigurasi Aplikasi Sembang](../guides/chat-apps.ms.md).
## 🔧 Alat
@@ -501,7 +501,7 @@ PicoClaw boleh mencari web untuk menyediakan maklumat terkini. Konfigurasikan da
### ⚙️ Alat Lain
-PicoClaw menyertakan alat terbina dalam untuk operasi fail, pelaksanaan kod, penjadualan, dan banyak lagi. Lihat [Konfigurasi Alat](docs/tools_configuration.md) untuk butiran.
+PicoClaw menyertakan alat terbina dalam untuk operasi fail, pelaksanaan kod, penjadualan, dan banyak lagi. Lihat [Konfigurasi Alat](../reference/tools_configuration.md) untuk butiran.
## 🎯 Kemahiran
@@ -531,7 +531,7 @@ Tambah ke `config.json` anda:
}
```
-Untuk butiran lanjut, lihat [Konfigurasi Alat - Kemahiran](docs/tools_configuration.md#skills-tool).
+Untuk butiran lanjut, lihat [Konfigurasi Alat - Kemahiran](../reference/tools_configuration.md#skills-tool).
## 🔗 MCP (Protokol Konteks Model)
@@ -554,9 +554,9 @@ PicoClaw menyokong [MCP](https://modelcontextprotocol.io/) secara natif — samb
}
```
-Untuk konfigurasi MCP penuh (pengangkutan stdio, SSE, HTTP, Penemuan Alat), lihat [Konfigurasi Alat - MCP](docs/tools_configuration.md#mcp-tool).
+Untuk konfigurasi MCP penuh (pengangkutan stdio, SSE, HTTP, Penemuan Alat), lihat [Konfigurasi Alat - MCP](../reference/tools_configuration.md#mcp-tool).
-## Sertai Rangkaian Sosial Agent
+## Sertai Rangkaian Sosial Agent
Sambungkan PicoClaw ke Rangkaian Sosial Agent dengan menghantar satu mesej melalui CLI atau mana-mana Aplikasi Sembang yang disepadukan.
@@ -597,20 +597,20 @@ Untuk panduan terperinci melebihi README ini:
| Topik | Penerangan |
|-------|------------|
-| [Docker & Permulaan Pantas](docs/my/docker.md) | Persediaan Docker Compose, mod Launcher/Agent |
-| [Aplikasi Sembang](docs/my/chat-apps.md) | Panduan persediaan 17+ saluran |
-| [Konfigurasi](docs/my/configuration.md) | Pemboleh ubah persekitaran, susun atur ruang kerja |
-| [Penyedia & Model](docs/providers.md) | 30+ penyedia LLM, penghalaan model |
-| [Spawn & Tugasan Async](docs/my/spawn-tasks.md) | Tugasan pantas, tugasan panjang dengan spawn |
-| [Penyelesaian Masalah](docs/my/troubleshooting.md) | Isu biasa dan penyelesaian |
-| [Konfigurasi Alat](docs/tools_configuration.md) | Aktif/nyahaktif alat, dasar exec, MCP, Kemahiran |
-| [Keserasian Perkakasan](docs/hardware-compatibility.md) | Papan yang diuji, keperluan minimum |
+| [Docker & Permulaan Pantas](../guides/docker.ms.md) | Persediaan Docker Compose, mod Launcher/Agent |
+| [Aplikasi Sembang](../guides/chat-apps.ms.md) | Panduan persediaan 17+ saluran |
+| [Konfigurasi](../guides/configuration.ms.md) | Pemboleh ubah persekitaran, susun atur ruang kerja |
+| [Penyedia & Model](../guides/providers.md) | 30+ penyedia LLM, penghalaan model |
+| [Spawn & Tugasan Async](../guides/spawn-tasks.ms.md) | Tugasan pantas, tugasan panjang dengan spawn |
+| [Penyelesaian Masalah](../operations/troubleshooting.ms.md) | Isu biasa dan penyelesaian |
+| [Konfigurasi Alat](../reference/tools_configuration.md) | Aktif/nyahaktif alat, dasar exec, MCP, Kemahiran |
+| [Keserasian Perkakasan](../guides/hardware-compatibility.md) | Papan yang diuji, keperluan minimum |
## 🤝 Sumbangan & Peta Jalan
PR dialu-alukan! Kod sumber sengaja dibuat kecil dan mudah dibaca.
-Lihat [Peta Jalan Komuniti](https://github.com/sipeed/picoclaw/issues/988) dan [CONTRIBUTING.md](CONTRIBUTING.md) untuk panduan.
+Lihat [Peta Jalan Komuniti](https://github.com/sipeed/picoclaw/issues/988) dan [CONTRIBUTING.md](../../CONTRIBUTING.md) untuk panduan.
Kumpulan pembangun sedang dibina, sertai selepas PR pertama anda digabungkan!
@@ -619,4 +619,4 @@ Kumpulan Pengguna:
Discord:
WeChat:
-
+
diff --git a/README.pt-br.md b/docs/project/README.pt-br.md
similarity index 82%
rename from README.pt-br.md
rename to docs/project/README.pt-br.md
index 25f82a180..56d4ddd63 100644
--- a/README.pt-br.md
+++ b/docs/project/README.pt-br.md
@@ -1,5 +1,5 @@
@@ -71,7 +71,7 @@
2026-02-26 🎉 O PicoClaw atinge **20K Stars** em apenas 17 dias! Orquestração automática de channels e interfaces de capacidade estão disponíveis.
-2026-02-16 🎉 O PicoClaw ultrapassa 12K Stars em uma semana! Funções de mantenedor da comunidade e [Roadmap](ROADMAP.md) lançados oficialmente.
+2026-02-16 🎉 O PicoClaw ultrapassa 12K Stars em uma semana! Funções de mantenedor da comunidade e [Roadmap](../../ROADMAP.md) lançados oficialmente.
2026-02-13 🎉 O PicoClaw ultrapassa 5000 Stars em 4 dias! Roadmap do projeto e grupos de desenvolvedores em andamento.
@@ -108,14 +108,14 @@ _*Builds recentes podem usar 10-20MB devido a merges rápidos de PRs. Otimizaç
| **Tempo de boot**(core 0,8GHz) | >500s | >30s | **<1s** |
| **Custo** | Mac Mini $599 | Maioria das placas Linux ~$50 | **Qualquer placa Linux****a partir de $10** |
-
+
-> **[Lista de Compatibilidade de Hardware](docs/pt-br/hardware-compatibility.md)** — Veja todas as placas testadas, de RISC-V de $5 ao Raspberry Pi e celulares Android. Sua placa não está listada? Envie um PR!
+> **[Lista de Compatibilidade de Hardware](../guides/hardware-compatibility.pt-br.md)** — Veja todas as placas testadas, de RISC-V de $5 ao Raspberry Pi e celulares Android. Sua placa não está listada? Envie um PR!
-
+
## 🦾 Demonstração
@@ -129,9 +129,9 @@ _*Builds recentes podem usar 10-20MB devido a merges rápidos de PRs. Otimizaç
Busca na Web e Aprendizado
-
-
-
+
+
+
Desenvolver · Implantar · Escalar
@@ -220,7 +220,7 @@ picoclaw-launcher
> ```
-
+
**Primeiros passos:**
@@ -274,7 +274,7 @@ O macOS pode bloquear o `picoclaw-launcher` no primeiro lançamento porque ele f
**Passo 1:** Dê um duplo clique em `picoclaw-launcher`. Você verá um aviso de segurança:
-
+
> *"picoclaw-launcher" não foi aberto — A Apple não conseguiu verificar se "picoclaw-launcher" está livre de malware que possa prejudicar seu Mac ou comprometer sua privacidade.*
@@ -282,7 +282,7 @@ O macOS pode bloquear o `picoclaw-launcher` no primeiro lançamento porque ele f
**Passo 2:** Abra **Configurações do Sistema** → **Privacidade e Segurança** → role até a seção **Segurança** → clique em **Abrir Mesmo Assim** → confirme clicando em **Abrir Mesmo Assim** na caixa de diálogo.
-
+
Após esta etapa única, o `picoclaw-launcher` abrirá normalmente nos lançamentos seguintes.
@@ -298,7 +298,7 @@ picoclaw-launcher-tui
```
-
+
**Primeiros passos:**
@@ -307,6 +307,7 @@ Use os menus do TUI para: **1)** Configurar um Provider -> **2)** Configurar um
Para documentação detalhada do TUI, veja [docs.picoclaw.io](https://docs.picoclaw.io).
+
### 📱 Android
Dê uma segunda vida ao seu celular de uma década! Transforme-o em um Assistente de IA inteligente com o PicoClaw.
@@ -317,10 +318,10 @@ Pré-visualização:
-
-
-
-
+
+
+
+
@@ -344,7 +345,7 @@ termux-chroot ./picoclaw onboard # chroot fornece um layout padrão de sistema
Em seguida, siga a seção Terminal Launcher abaixo para concluir a configuração.
-
+
Para ambientes mínimos onde apenas o binário principal `picoclaw` está disponível (sem Launcher UI), você pode configurar tudo via linha de comando e um arquivo de configuração JSON.
@@ -450,7 +451,7 @@ O PicoClaw suporta mais de 30 providers de LLM através da configuração `model
}
```
-Para detalhes completos de configuração de providers, veja [Providers & Models](docs/pt-br/providers.md).
+Para detalhes completos de configuração de providers, veja [Providers & Models](../guides/providers.pt-br.md).
@@ -460,28 +461,28 @@ Converse com seu PicoClaw por meio de mais de 17 plataformas de mensagens:
| Channel | Configuração | Protocolo | Docs |
|---------|--------------|-----------|------|
-| **Telegram** | Fácil (bot token) | Long polling | [Guia](docs/channels/telegram/README.pt-br.md) |
-| **Discord** | Fácil (bot token + intents) | WebSocket | [Guia](docs/channels/discord/README.pt-br.md) |
-| **WhatsApp** | Fácil (QR scan ou bridge URL) | Nativo / Bridge | [Guia](docs/pt-br/chat-apps.md#whatsapp) |
-| **Weixin** | Fácil (scan QR nativo) | iLink API | [Guia](docs/pt-br/chat-apps.md#weixin) |
-| **QQ** | Fácil (AppID + AppSecret) | WebSocket | [Guia](docs/channels/qq/README.pt-br.md) |
-| **Slack** | Fácil (bot + app token) | Socket Mode | [Guia](docs/channels/slack/README.pt-br.md) |
-| **Matrix** | Médio (homeserver + token) | Sync API | [Guia](docs/channels/matrix/README.pt-br.md) |
-| **DingTalk** | Médio (credenciais do cliente) | Stream | [Guia](docs/channels/dingtalk/README.pt-br.md) |
-| **Feishu / Lark** | Médio (App ID + Secret) | WebSocket/SDK | [Guia](docs/channels/feishu/README.pt-br.md) |
-| **LINE** | Médio (credenciais + webhook) | Webhook | [Guia](docs/channels/line/README.pt-br.md) |
-| **WeCom** | Fácil (login QR ou manual) | WebSocket | [Guia](docs/channels/wecom/README.md) |
-| **IRC** | Médio (servidor + nick) | Protocolo IRC | [Guia](docs/pt-br/chat-apps.md#irc) |
-| **OneBot** | Médio (WebSocket URL) | OneBot v11 | [Guia](docs/channels/onebot/README.pt-br.md) |
-| **MaixCam** | Fácil (habilitar) | TCP socket | [Guia](docs/channels/maixcam/README.pt-br.md) |
+| **Telegram** | Fácil (bot token) | Long polling | [Guia](../channels/telegram/README.pt-br.md) |
+| **Discord** | Fácil (bot token + intents) | WebSocket | [Guia](../channels/discord/README.pt-br.md) |
+| **WhatsApp** | Fácil (QR scan ou bridge URL) | Nativo / Bridge | [Guia](../guides/chat-apps.pt-br.md#whatsapp) |
+| **Weixin** | Fácil (scan QR nativo) | iLink API | [Guia](../guides/chat-apps.pt-br.md#weixin) |
+| **QQ** | Fácil (AppID + AppSecret) | WebSocket | [Guia](../channels/qq/README.pt-br.md) |
+| **Slack** | Fácil (bot + app token) | Socket Mode | [Guia](../channels/slack/README.pt-br.md) |
+| **Matrix** | Médio (homeserver + token) | Sync API | [Guia](../channels/matrix/README.pt-br.md) |
+| **DingTalk** | Médio (credenciais do cliente) | Stream | [Guia](../channels/dingtalk/README.pt-br.md) |
+| **Feishu / Lark** | Médio (App ID + Secret) | WebSocket/SDK | [Guia](../channels/feishu/README.pt-br.md) |
+| **LINE** | Médio (credenciais + webhook) | Webhook | [Guia](../channels/line/README.pt-br.md) |
+| **WeCom** | Fácil (login QR ou manual) | WebSocket | [Guia](../channels/wecom/README.pt-br.md) |
+| **IRC** | Médio (servidor + nick) | Protocolo IRC | [Guia](../guides/chat-apps.pt-br.md#irc) |
+| **OneBot** | Médio (WebSocket URL) | OneBot v11 | [Guia](../channels/onebot/README.pt-br.md) |
+| **MaixCam** | Fácil (habilitar) | TCP socket | [Guia](../channels/maixcam/README.pt-br.md) |
| **Pico** | Fácil (habilitar) | Protocolo nativo | Integrado |
| **Pico Client** | Fácil (WebSocket URL) | WebSocket | Integrado |
> Todos os channels baseados em webhook compartilham um único servidor HTTP do Gateway (`gateway.host`:`gateway.port`, padrão `127.0.0.1:18790`). O Feishu usa modo WebSocket/SDK e não utiliza o servidor HTTP compartilhado.
-> A verbosidade dos logs é controlada por `gateway.log_level` (padrão: `warn`). Valores suportados: `debug`, `info`, `warn`, `error`, `fatal`. Também pode ser definido via `PICOCLAW_LOG_LEVEL`. Veja [Configuração](docs/pt-br/configuration.md#nível-de-log-do-gateway) para detalhes.
+> A verbosidade dos logs é controlada por `gateway.log_level` (padrão: `warn`). Valores suportados: `debug`, `info`, `warn`, `error`, `fatal`. Também pode ser definido via `PICOCLAW_LOG_LEVEL`. Veja [Configuração](../guides/configuration.pt-br.md#nível-de-log-do-gateway) para detalhes.
-Para instruções detalhadas de configuração de channels, veja [Configuração de Apps de Chat](docs/pt-br/chat-apps.md).
+Para instruções detalhadas de configuração de channels, veja [Configuração de Apps de Chat](../guides/chat-apps.pt-br.md).
## 🔧 Ferramentas
@@ -501,7 +502,7 @@ O PicoClaw pode pesquisar na web para fornecer informações atualizadas. Config
### ⚙️ Outras Ferramentas
-O PicoClaw inclui ferramentas integradas para operações de arquivo, execução de código, agendamento e mais. Veja [Configuração de Ferramentas](docs/pt-br/tools_configuration.md) para detalhes.
+O PicoClaw inclui ferramentas integradas para operações de arquivo, execução de código, agendamento e mais. Veja [Configuração de Ferramentas](../reference/tools_configuration.pt-br.md) para detalhes.
## 🎯 Skills
@@ -531,7 +532,7 @@ Adicione ao seu `config.json`:
}
```
-Para mais detalhes, veja [Configuração de Ferramentas - Skills](docs/pt-br/tools_configuration.md#skills-tool).
+Para mais detalhes, veja [Configuração de Ferramentas - Skills](../reference/tools_configuration.pt-br.md#skills-tool).
## 🔗 MCP (Model Context Protocol)
@@ -554,9 +555,9 @@ O PicoClaw suporta nativamente o [MCP](https://modelcontextprotocol.io/) — con
}
```
-Para configuração completa de MCP (transportes stdio, SSE, HTTP, Tool Discovery), veja [Configuração de Ferramentas - MCP](docs/pt-br/tools_configuration.md#mcp-tool).
+Para configuração completa de MCP (transportes stdio, SSE, HTTP, Tool Discovery), veja [Configuração de Ferramentas - MCP](../reference/tools_configuration.pt-br.md#mcp-tool).
-## Junte-se à Rede Social de Agents
+## Junte-se à Rede Social de Agents
Conecte o PicoClaw à Rede Social de Agents simplesmente enviando uma única mensagem via CLI ou qualquer App de Chat integrado.
@@ -597,23 +598,23 @@ Para guias detalhados além deste README:
| Tópico | Descrição |
|--------|-----------|
-| [Docker & Início Rápido](docs/pt-br/docker.md) | Configuração do Docker Compose, modos Launcher/Agent |
-| [Apps de Chat](docs/pt-br/chat-apps.md) | Guias de configuração para todos os 17+ channels |
-| [Configuração](docs/pt-br/configuration.md) | Variáveis de ambiente, layout do workspace, sandbox de segurança |
-| [Providers & Models](docs/pt-br/providers.md) | 30+ providers de LLM, roteamento de modelos, configuração de model_list |
-| [Spawn & Tarefas Assíncronas](docs/pt-br/spawn-tasks.md) | Tarefas rápidas, tarefas longas com spawn, orquestração assíncrona de sub-agents |
-| [Hooks](docs/hooks/README.md) | Sistema de hooks orientado a eventos: observadores, interceptores, hooks de aprovação |
-| [Steering](docs/steering.md) | Injetar mensagens em um loop de agente em execução |
-| [SubTurn](docs/subturn.md) | Coordenação de subagentes, controle de concorrência, ciclo de vida |
-| [Solução de Problemas](docs/pt-br/troubleshooting.md) | Problemas comuns e soluções |
-| [Configuração de Ferramentas](docs/pt-br/tools_configuration.md) | Habilitar/desabilitar por ferramenta, políticas de exec, MCP, Skills |
-| [Compatibilidade de Hardware](docs/pt-br/hardware-compatibility.md) | Placas testadas, requisitos mínimos |
+| [Docker & Início Rápido](../guides/docker.pt-br.md) | Configuração do Docker Compose, modos Launcher/Agent |
+| [Apps de Chat](../guides/chat-apps.pt-br.md) | Guias de configuração para todos os 17+ channels |
+| [Configuração](../guides/configuration.pt-br.md) | Variáveis de ambiente, layout do workspace, sandbox de segurança |
+| [Providers & Models](../guides/providers.pt-br.md) | 30+ providers de LLM, roteamento de modelos, configuração de model_list |
+| [Spawn & Tarefas Assíncronas](../guides/spawn-tasks.pt-br.md) | Tarefas rápidas, tarefas longas com spawn, orquestração assíncrona de sub-agents |
+| [Hooks](../architecture/hooks/README.md) | Sistema de hooks orientado a eventos: observadores, interceptores, hooks de aprovação |
+| [Steering](../architecture/steering.md) | Injetar mensagens em um loop de agente em execução |
+| [SubTurn](../architecture/subturn.md) | Coordenação de subagentes, controle de concorrência, ciclo de vida |
+| [Solução de Problemas](../operations/troubleshooting.pt-br.md) | Problemas comuns e soluções |
+| [Configuração de Ferramentas](../reference/tools_configuration.pt-br.md) | Habilitar/desabilitar por ferramenta, políticas de exec, MCP, Skills |
+| [Compatibilidade de Hardware](../guides/hardware-compatibility.pt-br.md) | Placas testadas, requisitos mínimos |
## 🤝 Contribuir & Roadmap
PRs são bem-vindos! O código-fonte é intencionalmente pequeno e legível.
-Veja nosso [Roadmap da Comunidade](https://github.com/sipeed/picoclaw/issues/988) e [CONTRIBUTING.md](CONTRIBUTING.md) para diretrizes.
+Veja nosso [Roadmap da Comunidade](https://github.com/sipeed/picoclaw/issues/988) e [CONTRIBUTING.md](../../CONTRIBUTING.md) para diretrizes.
Grupo de desenvolvedores em formação, entre após seu primeiro PR mesclado!
@@ -622,4 +623,4 @@ Grupos de Usuários:
Discord:
WeChat:
-
+
diff --git a/README.vi.md b/docs/project/README.vi.md
similarity index 84%
rename from README.vi.md
rename to docs/project/README.vi.md
index 98e0b9bc9..52a56796b 100644
--- a/README.vi.md
+++ b/docs/project/README.vi.md
@@ -1,5 +1,5 @@
@@ -71,7 +71,7 @@
2026-02-26 🎉 PicoClaw đạt **20K Stars** chỉ trong 17 ngày! Tự động điều phối Channel và giao diện khả năng đã hoạt động.
-2026-02-16 🎉 PicoClaw vượt 12K Stars trong một tuần! Vai trò người duy trì cộng đồng và [Lộ trình](ROADMAP.md) chính thức ra mắt.
+2026-02-16 🎉 PicoClaw vượt 12K Stars trong một tuần! Vai trò người duy trì cộng đồng và [Lộ trình](../../ROADMAP.md) chính thức ra mắt.
2026-02-13 🎉 PicoClaw vượt 5000 Stars trong 4 ngày! Lộ trình dự án và nhóm nhà phát triển đang được xây dựng.
@@ -108,14 +108,14 @@ _*Các bản build gần đây có thể dùng 10-20MB do merge PR nhanh. Tối
| **Thời gian khởi động**(lõi 0.8GHz) | >500s | >30s | **<1s** |
| **Chi phí** | Mac Mini $599 | Hầu hết board Linux ~$50 | **Bất kỳ board Linux****từ $10** |
-
+
-> **[Danh sách Tương thích Phần cứng](docs/vi/hardware-compatibility.md)** — Xem tất cả các board đã được kiểm tra, từ RISC-V $5 đến Raspberry Pi đến điện thoại Android. Board của bạn chưa có trong danh sách? Gửi PR!
+> **[Danh sách Tương thích Phần cứng](../guides/hardware-compatibility.vi.md)** — Xem tất cả các board đã được kiểm tra, từ RISC-V $5 đến Raspberry Pi đến điện thoại Android. Board của bạn chưa có trong danh sách? Gửi PR!
-
+
## 🦾 Minh họa
@@ -129,9 +129,9 @@ _*Các bản build gần đây có thể dùng 10-20MB do merge PR nhanh. Tối
Tìm kiếm Web & Học tập
-
-
-
+
+
+
Phát triển · Triển khai · Mở rộng
@@ -220,7 +220,7 @@ picoclaw-launcher
> ```
-
+
**Bắt đầu:**
@@ -274,7 +274,7 @@ macOS có thể chặn `picoclaw-launcher` khi khởi chạy lần đầu vì n
**Bước 1:** Nhấp đúp vào `picoclaw-launcher`. Bạn sẽ thấy cảnh báo bảo mật:
-
+
> *"picoclaw-launcher" Không Mở Được — Apple không thể xác minh "picoclaw-launcher" không chứa phần mềm độc hại có thể gây hại cho Mac hoặc xâm phạm quyền riêng tư của bạn.*
@@ -282,7 +282,7 @@ macOS có thể chặn `picoclaw-launcher` khi khởi chạy lần đầu vì n
**Bước 2:** Mở **Cài đặt Hệ thống** → **Quyền riêng tư & Bảo mật** → cuộn xuống phần **Bảo mật** → nhấp **Vẫn Mở** → xác nhận bằng cách nhấp **Vẫn Mở** trong hộp thoại.
-
+
Sau bước này, `picoclaw-launcher` sẽ mở bình thường trong các lần khởi chạy tiếp theo.
@@ -298,7 +298,7 @@ picoclaw-launcher-tui
```
-
+
**Bắt đầu:**
@@ -307,6 +307,7 @@ Sử dụng menu TUI để: **1)** Cấu hình Provider -> **2)** Cấu hình Ch
Để biết tài liệu TUI chi tiết, xem [docs.picoclaw.io](https://docs.picoclaw.io).
+
### 📱 Android
Hãy cho chiếc điện thoại cũ của bạn một cuộc sống mới! Biến nó thành Trợ lý AI thông minh với PicoClaw.
@@ -317,10 +318,10 @@ Xem trước:
-
-
-
-
+
+
+
+
@@ -344,7 +345,7 @@ termux-chroot ./picoclaw onboard # chroot provides a standard Linux filesystem
Sau đó làm theo phần Terminal Launcher bên dưới để hoàn tất cấu hình.
-
+
Đối với các môi trường tối giản chỉ có binary lõi `picoclaw` (không có Launcher UI), bạn có thể cấu hình mọi thứ qua dòng lệnh và tệp cấu hình JSON.
@@ -450,7 +451,7 @@ PicoClaw hỗ trợ 30+ Provider LLM thông qua cấu hình `model_list`. Sử d
}
```
-Để biết chi tiết cấu hình provider đầy đủ, xem [Providers & Models](docs/vi/providers.md).
+Để biết chi tiết cấu hình provider đầy đủ, xem [Providers & Models](../guides/providers.vi.md).
@@ -460,28 +461,28 @@ Trò chuyện với PicoClaw của bạn qua 17+ nền tảng nhắn tin:
| Channel | Thiết lập | Protocol | Tài liệu |
|---------|-----------|----------|----------|
-| **Telegram** | Dễ (bot token) | Long polling | [Hướng dẫn](docs/channels/telegram/README.vi.md) |
-| **Discord** | Dễ (bot token + intents) | WebSocket | [Hướng dẫn](docs/channels/discord/README.vi.md) |
-| **WhatsApp** | Dễ (quét QR hoặc bridge URL) | Native / Bridge | [Hướng dẫn](docs/vi/chat-apps.md#whatsapp) |
-| **Weixin** | Dễ (quét QR gốc) | iLink API | [Hướng dẫn](docs/vi/chat-apps.md#weixin) |
-| **QQ** | Dễ (AppID + AppSecret) | WebSocket | [Hướng dẫn](docs/channels/qq/README.vi.md) |
-| **Slack** | Dễ (bot + app token) | Socket Mode | [Hướng dẫn](docs/channels/slack/README.vi.md) |
-| **Matrix** | Trung bình (homeserver + token) | Sync API | [Hướng dẫn](docs/channels/matrix/README.vi.md) |
-| **DingTalk** | Trung bình (client credentials) | Stream | [Hướng dẫn](docs/channels/dingtalk/README.vi.md) |
-| **Feishu / Lark** | Trung bình (App ID + Secret) | WebSocket/SDK | [Hướng dẫn](docs/channels/feishu/README.vi.md) |
-| **LINE** | Trung bình (credentials + webhook) | Webhook | [Hướng dẫn](docs/channels/line/README.vi.md) |
-| **WeCom** | Dễ (đăng nhập QR hoặc thủ công) | WebSocket | [Hướng dẫn](docs/channels/wecom/README.md) |
-| **IRC** | Trung bình (server + nick) | IRC protocol | [Hướng dẫn](docs/vi/chat-apps.md#irc) |
-| **OneBot** | Trung bình (WebSocket URL) | OneBot v11 | [Hướng dẫn](docs/channels/onebot/README.vi.md) |
-| **MaixCam** | Dễ (bật) | TCP socket | [Hướng dẫn](docs/channels/maixcam/README.vi.md) |
+| **Telegram** | Dễ (bot token) | Long polling | [Hướng dẫn](../channels/telegram/README.vi.md) |
+| **Discord** | Dễ (bot token + intents) | WebSocket | [Hướng dẫn](../channels/discord/README.vi.md) |
+| **WhatsApp** | Dễ (quét QR hoặc bridge URL) | Native / Bridge | [Hướng dẫn](../guides/chat-apps.vi.md#whatsapp) |
+| **Weixin** | Dễ (quét QR gốc) | iLink API | [Hướng dẫn](../guides/chat-apps.vi.md#weixin) |
+| **QQ** | Dễ (AppID + AppSecret) | WebSocket | [Hướng dẫn](../channels/qq/README.vi.md) |
+| **Slack** | Dễ (bot + app token) | Socket Mode | [Hướng dẫn](../channels/slack/README.vi.md) |
+| **Matrix** | Trung bình (homeserver + token) | Sync API | [Hướng dẫn](../channels/matrix/README.vi.md) |
+| **DingTalk** | Trung bình (client credentials) | Stream | [Hướng dẫn](../channels/dingtalk/README.vi.md) |
+| **Feishu / Lark** | Trung bình (App ID + Secret) | WebSocket/SDK | [Hướng dẫn](../channels/feishu/README.vi.md) |
+| **LINE** | Trung bình (credentials + webhook) | Webhook | [Hướng dẫn](../channels/line/README.vi.md) |
+| **WeCom** | Dễ (đăng nhập QR hoặc thủ công) | WebSocket | [Hướng dẫn](../channels/wecom/README.vi.md) |
+| **IRC** | Trung bình (server + nick) | IRC protocol | [Hướng dẫn](../guides/chat-apps.vi.md#irc) |
+| **OneBot** | Trung bình (WebSocket URL) | OneBot v11 | [Hướng dẫn](../channels/onebot/README.vi.md) |
+| **MaixCam** | Dễ (bật) | TCP socket | [Hướng dẫn](../channels/maixcam/README.vi.md) |
| **Pico** | Dễ (bật) | Native protocol | Tích hợp sẵn |
| **Pico Client** | Dễ (WebSocket URL) | WebSocket | Tích hợp sẵn |
> Tất cả các Channel dựa trên webhook dùng chung một Gateway HTTP server (`gateway.host`:`gateway.port`, mặc định `127.0.0.1:18790`). Feishu sử dụng chế độ WebSocket/SDK và không dùng HTTP server chung.
-> Mức độ chi tiết log được kiểm soát bởi `gateway.log_level` (mặc định: `warn`). Các giá trị được hỗ trợ: `debug`, `info`, `warn`, `error`, `fatal`. Cũng có thể đặt qua `PICOCLAW_LOG_LEVEL`. Xem [Cấu hình](docs/vi/configuration.md#mức-log-của-gateway) để biết thêm chi tiết.
+> Mức độ chi tiết log được kiểm soát bởi `gateway.log_level` (mặc định: `warn`). Các giá trị được hỗ trợ: `debug`, `info`, `warn`, `error`, `fatal`. Cũng có thể đặt qua `PICOCLAW_LOG_LEVEL`. Xem [Cấu hình](../guides/configuration.vi.md#mức-log-của-gateway) để biết thêm chi tiết.
-Để biết hướng dẫn thiết lập Channel chi tiết, xem [Cấu hình Ứng dụng Chat](docs/vi/chat-apps.md).
+Để biết hướng dẫn thiết lập Channel chi tiết, xem [Cấu hình Ứng dụng Chat](../guides/chat-apps.vi.md).
## 🔧 Tools
@@ -501,7 +502,7 @@ PicoClaw có thể tìm kiếm web để cung cấp thông tin cập nhật. C
### ⚙️ Các Tools Khác
-PicoClaw bao gồm các tool tích hợp sẵn cho thao tác tệp, thực thi mã, lên lịch và nhiều hơn nữa. Xem [Cấu hình Tools](docs/vi/tools_configuration.md) để biết chi tiết.
+PicoClaw bao gồm các tool tích hợp sẵn cho thao tác tệp, thực thi mã, lên lịch và nhiều hơn nữa. Xem [Cấu hình Tools](../reference/tools_configuration.vi.md) để biết chi tiết.
## 🎯 Skills
@@ -531,7 +532,7 @@ Thêm vào `config.json` của bạn:
}
```
-Để biết thêm chi tiết, xem [Cấu hình Tools - Skills](docs/vi/tools_configuration.md#skills-tool).
+Để biết thêm chi tiết, xem [Cấu hình Tools - Skills](../reference/tools_configuration.vi.md#skills-tool).
## 🔗 MCP (Model Context Protocol)
@@ -554,9 +555,9 @@ PicoClaw hỗ trợ [MCP](https://modelcontextprotocol.io/) gốc — kết nố
}
```
-Để biết cấu hình MCP đầy đủ (stdio, SSE, HTTP transports, Tool Discovery), xem [Cấu hình Tools - MCP](docs/vi/tools_configuration.md#mcp-tool).
+Để biết cấu hình MCP đầy đủ (stdio, SSE, HTTP transports, Tool Discovery), xem [Cấu hình Tools - MCP](../reference/tools_configuration.vi.md#mcp-tool).
-## Tham gia Mạng xã hội Agent
+## Tham gia Mạng xã hội Agent
Kết nối PicoClaw với Mạng xã hội Agent chỉ bằng cách gửi một tin nhắn duy nhất qua CLI hoặc bất kỳ Ứng dụng Chat nào đã tích hợp.
@@ -597,23 +598,23 @@ PicoClaw hỗ trợ nhắc nhở đã lên lịch và tác vụ định kỳ th
| Chủ đề | Mô tả |
|--------|-------|
-| [Docker & Khởi động Nhanh](docs/vi/docker.md) | Thiết lập Docker Compose, chế độ Launcher/Agent |
-| [Ứng dụng Chat](docs/vi/chat-apps.md) | Hướng dẫn thiết lập 17+ Channel |
-| [Cấu hình](docs/vi/configuration.md) | Biến môi trường, bố cục workspace, sandbox bảo mật |
-| [Providers & Models](docs/vi/providers.md) | 30+ Provider LLM, định tuyến mô hình, cấu hình model_list |
-| [Spawn & Tác vụ Bất đồng bộ](docs/vi/spawn-tasks.md) | Tác vụ nhanh, tác vụ dài với spawn, điều phối sub-agent bất đồng bộ |
-| [Hooks](docs/hooks/README.md) | Hệ thống hook hướng sự kiện: observer, interceptor, approval hook |
-| [Steering](docs/steering.md) | Chèn tin nhắn vào vòng lặp agent đang chạy |
-| [SubTurn](docs/subturn.md) | Điều phối subagent, kiểm soát đồng thời, vòng đời |
-| [Khắc phục sự cố](docs/vi/troubleshooting.md) | Các vấn đề thường gặp và giải pháp |
-| [Cấu hình Tools](docs/vi/tools_configuration.md) | Bật/tắt từng tool, chính sách exec, MCP, Skills |
-| [Tương thích Phần cứng](docs/vi/hardware-compatibility.md) | Các board đã kiểm tra, yêu cầu tối thiểu |
+| [Docker & Khởi động Nhanh](../guides/docker.vi.md) | Thiết lập Docker Compose, chế độ Launcher/Agent |
+| [Ứng dụng Chat](../guides/chat-apps.vi.md) | Hướng dẫn thiết lập 17+ Channel |
+| [Cấu hình](../guides/configuration.vi.md) | Biến môi trường, bố cục workspace, sandbox bảo mật |
+| [Providers & Models](../guides/providers.vi.md) | 30+ Provider LLM, định tuyến mô hình, cấu hình model_list |
+| [Spawn & Tác vụ Bất đồng bộ](../guides/spawn-tasks.vi.md) | Tác vụ nhanh, tác vụ dài với spawn, điều phối sub-agent bất đồng bộ |
+| [Hooks](../architecture/hooks/README.md) | Hệ thống hook hướng sự kiện: observer, interceptor, approval hook |
+| [Steering](../architecture/steering.md) | Chèn tin nhắn vào vòng lặp agent đang chạy |
+| [SubTurn](../architecture/subturn.md) | Điều phối subagent, kiểm soát đồng thời, vòng đời |
+| [Khắc phục sự cố](../operations/troubleshooting.vi.md) | Các vấn đề thường gặp và giải pháp |
+| [Cấu hình Tools](../reference/tools_configuration.vi.md) | Bật/tắt từng tool, chính sách exec, MCP, Skills |
+| [Tương thích Phần cứng](../guides/hardware-compatibility.vi.md) | Các board đã kiểm tra, yêu cầu tối thiểu |
## 🤝 Đóng góp & Lộ trình
PR luôn được chào đón! Codebase được thiết kế nhỏ gọn và dễ đọc.
-Xem [Lộ trình Cộng đồng](https://github.com/sipeed/picoclaw/issues/988) và [CONTRIBUTING.md](CONTRIBUTING.md) để biết hướng dẫn.
+Xem [Lộ trình Cộng đồng](https://github.com/sipeed/picoclaw/issues/988) và [CONTRIBUTING.md](../../CONTRIBUTING.md) để biết hướng dẫn.
Nhóm nhà phát triển đang được xây dựng, tham gia sau khi PR đầu tiên của bạn được merge!
@@ -622,4 +623,4 @@ Nhóm Người dùng:
Discord:
WeChat:
-
+
diff --git a/README.zh.md b/docs/project/README.zh.md
similarity index 82%
rename from README.zh.md
rename to docs/project/README.zh.md
index 1a0659e22..a4fc892bd 100644
--- a/README.zh.md
+++ b/docs/project/README.zh.md
@@ -1,5 +1,5 @@