From 28dc095dc5fbe3a773ae4bf333f0a25f2b04be2b Mon Sep 17 00:00:00 2001 From: JianFeeeee <109188060+JianFeeeee@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:29:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=A4=9A=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E6=96=87=E6=A1=A3=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 docs/zh/ 和 docs/en/ 目录结构 - 迁移文档至多语言目录,添加中英文切换链接 - 创建 README_EN.md 英文版本 - 更新 README.md 添加语言切换链接 - 删除旧文档和临时文件 --- README.md | 10 +- README_EN.md | 185 +++++++++++++ docs/en/ADAPTER.md | 104 ++++++++ docs/en/ARCHITECTURE.md | 347 +++++++++++++++++++++++++ docs/en/OVERVIEW.md | 74 ++++++ docs/en/PLUGIN_DEV.md | 477 ++++++++++++++++++++++++++++++++++ docs/{ => zh}/ADAPTER.md | 2 + docs/{ => zh}/ARCHITECTURE.md | 3 + docs/{ => zh}/OVERVIEW.md | 4 +- docs/{ => zh}/PLUGIN_DEV.md | 4 +- 迭代文档.md | 200 -------------- 11 files changed, 1204 insertions(+), 206 deletions(-) create mode 100644 README_EN.md create mode 100644 docs/en/ADAPTER.md create mode 100644 docs/en/ARCHITECTURE.md create mode 100644 docs/en/OVERVIEW.md create mode 100644 docs/en/PLUGIN_DEV.md rename docs/{ => zh}/ADAPTER.md (98%) rename docs/{ => zh}/ARCHITECTURE.md (99%) rename docs/{ => zh}/OVERVIEW.md (97%) rename docs/{ => zh}/PLUGIN_DEV.md (99%) delete mode 100644 迭代文档.md diff --git a/README.md b/README.md index de52141..3b189f1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # HomeAgent +> **English**: [README_EN.md](./README_EN.md) + 首个提出**核心域与应用域分离**的 Agent 框架。内核零 IO,一切外界交互由插件承载——WebUI、QQ、命令行、文件操作、网络搜索、备忘,全部是插件,内核不碰任何 IO。 配合**三层记忆架构**(Context → Document → Graph),单对话长期稳定运行,记忆不衰减。 @@ -166,10 +168,10 @@ internal/ ## 文档 -- [项目概览](docs/OVERVIEW.md) -- [技术架构](docs/ARCHITECTURE.md) -- [插件开发指南](docs/PLUGIN_DEV.md) -- [Lua Adapter](docs/ADAPTER.md) +- [项目概览](docs/zh/OVERVIEW.md) | [English](docs/en/OVERVIEW.md) +- [技术架构](docs/zh/ARCHITECTURE.md) | [English](docs/en/ARCHITECTURE.md) +- [插件开发指南](docs/zh/PLUGIN_DEV.md) | [English](docs/en/PLUGIN_DEV.md) +- [Lua Adapter](docs/zh/ADAPTER.md) | [English](docs/en/ADAPTER.md) - [知识库演示](knowledge/homeagent_architecture/content.md) ## 构建 diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..3a7aabc --- /dev/null +++ b/README_EN.md @@ -0,0 +1,185 @@ +# HomeAgent + +> **中文**: [README.md](./README.md) + +The first Agent framework to propose **separation of core domain and application domain**. The kernel performs zero IO — all external interaction is handled by plugins: WebUI, QQ, CLI, file operations, web search, memos — everything is a plugin, the kernel doesn't touch any IO. + +Combined with a **three-layer memory architecture** (Context → Document → Graph), it achieves stable long-running single-conversation operation without memory decay. + +```go +homed (kernel, zero IO) ← PluginSDK → plugins (all IO capabilities) +``` + +## Key Innovations + +**Separation of Core Domain and Application Domain** — The kernel only handles LLM orchestration, memory management, and knowledge retrieval; all IO capabilities (sending/receiving messages, reading/writing files, network requests, hardware interaction) are implemented by plugins. Plugins can be hot-loaded, independently developed, and independently released. This is not a microservice split of an RPC framework, but a domain-level separation in Agent framework design. + +**Three-Layer Memory Architecture** — Solves the memory decay problem for long-running agents: +- **Context Layer**: TF-IDF relevance-scored event window, maintains recent topK context entries +- **Document Layer**: Temporary memory with automatic cold data sinking, also supports user-initiated submissions +- **Graph Layer**: SQLite graph database, persists entity relationships and semantic memory, supports distillation pipelines to extract triples from conversations + +## Architecture Diagrams + +### 1. Message Processing Sequence + +```mermaid +sequenceDiagram + participant U as User/Plugin + participant IO as IOManager + participant EV as eventLoop + participant CTX as RelevanceContext + participant LLM as LLM+Tool Loop + participant ST as StageHost + participant MEM as Three-Layer Memory + + U->>IO: InjectInput(type, payload) + IO->>EV: inputCh + rect lavender + Note over EV: processTextInput + EV->>ST: StageOnInput Plugin can rewrite/short-circuit + EV->>CTX: Prune(input,topK) TF-IDF pruning + CTX->>MEM: Low-score events archived to Document + EV->>CTX: Append(input) 5s debounce write + end + rect lightgreen + Note over EV,LLM: process() + EV->>MEM: buildMemoryContext Indexer recalls from Graph + EV->>MEM: buildSystemPrompt Persona+Memory+Skills injection + EV->>ST: StagePreAction Plugin can pre-intercept + loop Tool loop + LLM->>LLM: drainInterrupts + LLM->>LLM: LLM Chat + LLM->>ST: StagePostAction Plugin can modify/short-circuit + alt No tool call + LLM-->>EV: Returns response + else + loop Each tool + ST->>ST: StageBeforeToolcall Plugin can reject + LLM->>LLM: executeToolCall + ST->>ST: StageAfterToolcall + end + end + end + end + rect lightpink + Note over EV: emitResponse + CTX->>CTX: Append(response) + ST->>ST: StageBeforeOutput Plugin can rewrite + EV-->>U: ResponseCh CLI sync + EV-->>EV: Event bus WebUI SSE + ST->>ST: StageAfterOutput Read-only + EV->>MEM: emitMemoryCandidate + end +``` + +### 2. Stage Pipeline + +```mermaid +flowchart LR + S1[① on_input] --> S2[② pre_action] + S2 --> S3[③ post_action] + S3 --> Q{Has tool?} + Q -->|Yes| S4[④ before_toolcall] + S4 --> T[executeToolCall] + T --> S5[⑤ after_toolcall] + S5 --> S3 + Q -->|No| S6[⑥ before_output] + S6 --> S7[⑦ after_output] + style S1 fill:#e1f5fe + style S3 fill:#fff3e0 + style S6 fill:#e8f5e9 +``` + +### 3. Three-Layer Memory + +```mermaid +flowchart TB + subgraph C[① Context Working Window] + RC[RelevanceContext] + A[Append] -->|Vectorize char 1-2gram| RC + P[Prune TF-IDF Cosine] -->|Low score| D + P -->|Keep| TL[timeline→system prompt] + end + subgraph D[② Document File Memory] + DS[DocStore JSON+TF-IDF] + Q1[Query auto-inject] -->|[Related Memory Docs]| SP + Q2[doc_query LLM active] -->|Consume+delete| DS + Q2 -->|Original timestamp write| RC + CD[FindColdDocs 72h] -->|docToTriples| G + end + subgraph G[③ Graph Database] + DB[(SQLite)] + IDX[Indexer BFS depth=2] -->|[Memory Index]| SP + MEM[memory_recall/commit] + SOC[person_query/set_trait] + end + subgraph H[④ Heartbeat Distillation] + REORG -->|Step3 Cold docs| CD + REORG -->|Step4 Bigram Jaccard| CONS[consolidation] + PIPE[Pipeline regex] -->|Name/Address/Likes/Age/Job| DB + end + SP[System Prompt] -->|Sequential assembly| LLM + LLM[LLM] -->|doc_query| Q2 + LLM -->|memory_recall| MEM +``` + +See [`docs/en/ARCHITECTURE.md`](docs/en/ARCHITECTURE.md) for details. + +## Quick Start + +```bash +make build build-cli +./build/homed -data /tmp/ha +``` + +```bash +# Interactive mode +./build/waiter + +# Or single message +echo "Hello, remember that I like coffee" | ./build/waiter +``` + +API keys are configured via WebUI `http://localhost:8080` settings page, persisted in SQLite. + +## Code Structure + +``` +cmd/homed/ Daemon entry, assembles all subsystems +cmd/waiter/ CLI client (Unix socket) +internal/ +├── agent/core/ Agent core: event loop, LLM tool loop, 7-stage pipeline +├── agent/api/ LLM Provider + 8 Lua adapters +├── memory/ Three-layer memory: Graph(SQLite) / Document(JSON+TF-IDF) / Text(JSONL) +├── knowledge/ Knowledge base (filesystem + TF-IDF) +├── plugin/ Plugin registry + .so/.dll dynamic loader +├── plugins/ 10 built-in plugins (webui/cli/timer/cmd/mcp/openclaw/agentcli/healthcheck/pluginmgr/files) +├── internal/sdk/ PluginSDK (Tool/Stage/Event three channels) +├── config/ SQLite config center +├── events/ Event bus +└── lua/adapters/ 8 LLM protocol adapter scripts +External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo, use `plugindev` toolchain, refer to Go and Lua examples in `example/` +``` + +## Project Status + +Core is functional, plugin system and SDK are ready. 10 built-in plugins. External plugin development via [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo using `plugindev` toolchain. + +## Documentation + +- [Project Overview](docs/en/OVERVIEW.md) | [中文](docs/zh/OVERVIEW.md) +- [Technical Architecture](docs/en/ARCHITECTURE.md) | [中文](docs/zh/ARCHITECTURE.md) +- [Plugin Development Guide](docs/en/PLUGIN_DEV.md) | [中文](docs/zh/PLUGIN_DEV.md) +- [Lua Adapter](docs/en/ADAPTER.md) | [中文](docs/zh/ADAPTER.md) +- [Knowledge Base Demo](knowledge/homeagent_architecture/content.md) + +## Build + +```bash +make build build-cli # Build daemon + CLI +make test # go test ./... +make install # Install to system +``` + +Dependencies: Go 1.21+, CGo (go-sqlite3), Linux/Windows. diff --git a/docs/en/ADAPTER.md b/docs/en/ADAPTER.md new file mode 100644 index 0000000..7b9d09e --- /dev/null +++ b/docs/en/ADAPTER.md @@ -0,0 +1,104 @@ +**中文** | [English](../zh/ADAPTER.md) + +# Lua Adapter — LLM Source Adaptation Guide + +Each LLM API source corresponds to a Lua script, responsible for request transformation (Go unified format → API format) and response transformation (API format → Go unified format). + +## Adapter Contract + +The Lua script must return a table containing the following fields and functions: + +```lua +local adapter = {} + +-- Metadata +adapter.name = "my_provider" -- Unique identifier, matches adapter field in config +adapter.version = "2.0.0" +adapter.endpoint = "/v1/chat/completions" -- API path, appended to base_url +adapter.headers = {} -- Additional HTTP request headers + +-- Request transformation: Go → API +function adapter.transform_request(raw_json) + -- raw_json: Go's CompletionRequest JSON string + -- Returns: JSON string to send to API + return transformed_json +end + +-- Response transformation: API → Go +function adapter.transform_response(raw_json) + -- raw_json: API's raw response JSON string + -- Returns: Unified CompletionResponse JSON string + -- Unified format: + -- { content: "", finish_reason: "", token_usage: { prompt: N, completion: N, total: N }, tool_calls?: [...] } + return unified_json +end + +-- Stream chunk transformation (optional) +function adapter.transform_stream_chunk(raw_line) + -- raw_line: JSON string after data: in SSE + -- Returns: JSON of { content: "", done: bool }, return "" to skip this chunk + return chunk_json +end + +return adapter +``` + +## Unified CompletionRequest Format (Go → Adapter) + +```json +{ + "model": "deepseek-v4-flash", + "messages": [ + { "role": "system", "content": "..." }, + { "role": "user", "content": "..." }, + { "role": "assistant", "content": "...", "tool_calls": [...] } + ], + "temperature": 0.7, + "max_tokens": 4096, + "stream": false, + "tools": [...], + "tool_choice": "auto" +} +``` + +## Unified CompletionResponse Format (Adapter → Go) + +```json +{ + "content": "Response content", + "finish_reason": "stop", + "token_usage": { "prompt": 10, "completion": 20, "total": 30 }, + "tool_calls": [ + { "id": "call_xxx", "type": "function", "name": "tool_name", "arguments": { "key": "val" } } + ] +} +``` + +## Lua VM Built-in Functions + +`json.encode(table)` — Encode Lua table to JSON string + +`json.decode(string)` — Decode JSON string to Lua table + +`log(level, message)` — Output log (level: info/warn/error) + +## Adapting Typical APIs + +| API | endpoint | auth method | Format differences | +|-----|----------|-------------|-------------------| +| **OpenAI** | `/chat/completions` | `Authorization: Bearer ` | Standard OpenAI format | +| **DeepSeek** | `/chat/completions` | `Authorization: Bearer ` | OpenAI compatible, forces temperature=0 | +| **Anthropic** | `/v1/messages` | `x-api-key: ` | Messages API, system message separated, content as block array | +| **Gemini** | `/v1/models/{model}:generateContent` | `?key=` or Bearer | contents/parts format, role uses model instead of assistant | +| **Mistral** | `/v1/chat/completions` | `Authorization: Bearer ` | OpenAI compatible | +| **Groq** | `/openai/v1/chat/completions` | `Authorization: Bearer ` | OpenAI compatible | +| **GitHub Models** | `/chat/completions` | `Authorization: Bearer ` | OpenAI compatible | +| **Ollama** | `/api/chat` | None | Different options format | + +## Steps to Add a New Source + +1. Create `.lua` under `internal/lua/adapters/` +2. Script defines `transform_request` and `transform_response` +3. (Optional) Define `transform_stream_chunk` for streaming support +4. Build verification: `go build ./cmd/homed/` +5. Test verification: `go test ./...` diff --git a/docs/en/ARCHITECTURE.md b/docs/en/ARCHITECTURE.md new file mode 100644 index 0000000..df2885b --- /dev/null +++ b/docs/en/ARCHITECTURE.md @@ -0,0 +1,347 @@ +**中文** | [English](../zh/ARCHITECTURE.md) + +# HomeAgent Architecture + +The kernel performs zero IO; all external interaction comes from plugins. + +## Message Processing Flow + +### Full Pipeline + +``` +External input (via plugin InjectInput) + │ + ▼ +eventLoop() → processTextInput() + │ + ├── on_input stage Plugins can intercept/rewrite/short-circuit + ├── Context.Append Record to context window + ├── Context.Prune Low-relevance events archived to Document + ├── buildMemoryContext() Indexer recall → GraphDB BFS traversal + │ + ├── pre_action stage Plugins can inject system messages + │ + ├── [Tool Loop] process() + │ ├── buildSystemPrompt Persona + Memory + Knowledge + Context + │ ├── buildToolDefs Built-in tools + Plugin tools + │ ├── provider.Chat() LLM call + │ ├── post_action stage Plugins see LLM output + tool list + │ ├── Has tools? + │ │ ├── before_toolcall Plugins can reject/modify params + │ │ ├── executeToolCall Route to plugin/built-in + │ │ ├── after_toolcall Plugins can modify results + │ │ └── → back to post_action + │ └── No tools → exit loop + │ + ├── Context.Append(response) + ├── before_output stage Plugins can modify final text + ├── emitResponse() Send via output_send + └── after_output stage Read-only, cleanup +``` + +Code: `internal/agent/core/agent.go` — `process()` is the main tool loop + +### 7 Stage Hooks + +| Stage | Trigger | Plugin Capabilities | +|-------|---------|---------------------| +| `on_input` | Message arrives at Agent, zero processing | Blacklist/rate-limit/short-circuit reply | +| `pre_action` | Context ready, before LLM call | Inject external data into context | +| `post_action` | LLM returns text + tool list | Sensitive word filter/forced redirect | +| `before_toolcall` | Before single tool execution | Audit/reject/modify params | +| `after_toolcall` | After single tool execution | Desensitize/sort results | +| `before_output` | Final text ready, before sending | Format adaptation | +| `after_output` | Already sent | Statistics/logging | + +Code: `internal/agent/core/stages.go` — `StageHost` orchestration + +### Loop Rules + +`post_action → [before_toolcall → execute → after_toolcall] → post_action` forms the inner loop. +Exit conditions: LLM has no tool calls / all rejected / exceeded limit. + +### Short-Circuit Rules + +Setting `ctx.Response` at any stage jumps to `after_output`. + +## Three-Layer Memory + +### Memory Flow + +``` +① Context (Working Window) + RelevanceContext — In-memory events[] + JSON persistence + Append: Each input, Vectorize(char 1-2gram TF-IDF) + Prune: TF-IDF CosineSimilarity, keep topK + last 10 + ├── Keep → timeline → system prompt (sorted by time) + └── Low score → Document layer archive (ContextToDoc) + Save: 5s debounce write to disk + + ↓ Prune archive ↑ LLM active recall + +② Document (File Memory) + DocStore — JSON files + TF-IDF InvertedIndex + Write: Prune archive / doc_commit / Graph snapshot (syncGraphToDocs) + Read: + ├── Auto-inject: Query(input, top3) → [Related Memory Docs] → system prompt (read-only, update AccessCount) + └── LLM active: doc_query → Consume(read and delete) + → context.Append{Timestamp: d.CreatedAt, Source: "cold_storage"} per doc + → Docs written to context timeline with original timestamps, deleted from docStore + Cold: FindColdDocs(72h, ≤2 accesses) → docToTriples → Graph + + ↓ Cold doc distillation ↑ Auto recall + +③ Graph (Graph Database) + SQLite — entities + relations tables + Write: memory_commit / cold doc distillation / Pipeline rule distillation + Read: + ├── Auto recall: Indexer.BuildContext(input) + │ → TF-IDF entity name search → BFS depth=2 + │ → [Memory Index] → system prompt + └── LLM active: memory_recall / doc_query + Social: person_query / set_trait / relate (wraps GraphDB) + +④ Distillation Pipeline (30min heartbeat) + distillContext → window > 2×maxSize → force Prune + syncGraphToDocs → Graph snapshot to Document (cross-layer searchable) + reorgGraph: + Step1: indexer.Sync — rebuild entity TF-IDF vector index + Step2: docStore.Reindex — rebuild document TF-IDF vector index + Step3: Cold docs → docToTriples → GraphDB.Commit + Step4: Entity similarity (Bigram Jaccard > 0.75) → consolidation → LLM decides merge + Step5: evaluateGraphQuality → LLM decides keep/delete + +⑤ Pipeline Rule Distiller (every heartbeat) + distillOnce → regex match personal info: + 我叫X / 我住在X / 我喜欢X / 我X岁 / 我的工作是X + → triples → GraphDB.Commit +``` + +### TF-IDF Vectorization (char 1-2 gram) + +TF-IDF is the core algorithm running through all three memory layers, used in 4 independent locations: + +| Location | File | Purpose | Algorithm | +|----------|------|---------|-----------| +| Context Prune | `context.go:162` | Trim low-relevance context events | CosineSimilarity(queryVec, evt.Vector) | +| DocStore Query | `document.go:205` | Recall related content from document memory | InvertedIndex + CosineSimilarity | +| Indexer Entity Search | `indexer.go:149` | Recall related entities from Graph | InvertedIndex + CosineSimilarity | +| Entity Similarity Detection | `agent.go:2297` | Detect similar entities in Graph | Bigram Jaccard (>0.75 → consolidation) | + +### Context Layer + +`internal/agent/core/context.go` — `RelevanceContext` +- Maintains recent event list, writes JSON on each Append/Prune to prevent data loss +- TF-IDF relevance scoring on user input, keeps topK + +### Document Layer + +`internal/memory/document/doc.go` — `Store` +- Consume-on-read mode: deleted after `doc_query` retrieval +- TF-IDF index with character bigram + inverted index + +### Graph Layer + +`internal/memory/graph.go` — `GraphDB` +- SQLite WAL mode, two tables +- `Commit(triples)` — UPSERT entities + INSERT relations +- `Recall(keywords, depth)` — Keyword LIKE search + BFS traversal + +### Memory Tools (LLM-callable) + +| Tool | Purpose | +|------|---------| +| `memory_recall` | Recall from Graph | +| `memory_commit` | Write triples to Graph | +| `memory_introspect` | View memory statistics | +| `doc_query` | Search from Document | +| `doc_commit` | Write to Document | + +### Other Memory Layers + +- **Social** (`internal/memory/social/social.go`) — Persona traits and relationship network, wraps GraphDB entity types +- **Text Memory** (`internal/memory/text/text.go`) — Raw conversation JSONL logs, rotation strategy +- **Memory Indexer** (`internal/memory/indexer.go`) — Entity vectorization, auto-inject into system prompt + +### Distillation Pipeline + +`internal/memory/pipeline/pipeline.go` +- 10-minute tick, 7-day retention +- Rule-based triple extraction (name / location / likes / age / job patterns) +- Writes to GraphDB + +### Context Pruning + +``` +Heartbeat 30min: + ├── distillContext() — Distill current context + ├── syncGraphToDocs() — Graph → Document sync + └── reorgGraph() + ├── Indexer.Sync() + ├── DocStore.Reindex() + ├── Cold docs → Graph + └── Entity conflicts → enqueueConsolidationTask() + │ + selfInputCh → LLM decides merge/skip +``` + +Entity conflict detection heuristic (bigram Jaccard > 0.5), routed through `selfInputCh` internal channel, LLM makes the final merge decision. + +## Knowledge Base + +`internal/knowledge/knowledge.go` +- File directory `knowledge//content.md` +- Independent TF-IDF index, separate from memory system +- `knowledge_search` / `knowledge_create` / `knowledge_list` + +## Provider & Lua Adapter Layer + +``` +Agent + │ + ▼ +Provider Interface (Name / Chat / ChatStream) + │ + ├── OpenAIProvider — Standard OpenAI API + ├── OllamaProvider — Local Ollama + └── LuaAdaptedProvider (primary) + ├── Serialize CompletionRequest → JSON + ├── adapter.transform_request() → API format + ├── HTTP request + adapter.headers + ├── adapter.transform_response() → unified format + └── Deserialize +``` + +Code: `internal/agent/api/provider.go` + +ProviderManager manages multiple sources, fallback in registration order. Lua adapters at `internal/lua/adapters/`, each `.lua` script defines `transform_request` / `transform_response` / `transform_stream_chunk`. + +VM built-ins: `json.encode` / `json.decode` / `log` / `http_get` / `http_post`. + +## Plugin System + +### Three Loading Methods + +| Method | Registration Mechanism | Compilation | Usage | +|--------|----------------------|-------------|-------| +| Built-in | `init()` → `RegisterFactory` | `internal/plugins/` compiled into kernel | webui/cli/timer/mcp etc. | +| External `.so`/`.dll` | `plugin.Open` dynamic loading | `-buildmode=plugin` | qq/files/web/memo etc. | +| Lua script plugin | Parse `main.lua` to register tools | No compilation, hot-reload | luaplugintest/testlua etc. | +| SKILL plugin | Parse `SKILL.md` | Markdown definition | OpenClaw compatible | + +Built-in plugin registration: `internal/plugins/all.go` blank imports → each plugin `init()` → `Registry.Load()` scans directory to match factory. +External plugin loading: `internal/plugin/dynamic.go` → copy to SHA256 temp path (bypass `plugin.Open` path cache) → `Open` + `Lookup("NewPlugin")`. +Lua script plugin loading: `internal/lua/` → parse `main.lua` via Lua VM, call `start()` to register tools. + +### PluginSDK Three Channels + +``` +Plugin ──→ Kernel + +RegisterTool(name, fn) ──→ buildToolDefs() / executeToolCall() +RegisterStage(stage, fn) ──→ runStage() called at corresponding phase +Subscribe(event, fn) ──→ Publish() notify all subscribers +``` + +`internal/sdk/` bridges external SDK interface to kernel, defines complete PluginSDK: + +```go +sdk.RegisterTool(name, def, handler) +sdk.RegisterStage(stage, handler) +sdk.Publish(event) +sdk.InjectInput(source, channel, payload) +sdk.InjectInterrupt(source, channel, payload) +sdk.Memory().Recall/Commit +sdk.Knowledge().Search/Create +sdk.Settings().Get/Set/List +``` + +### Plugin Interface + +```go +type Plugin interface { + Name() string + Start(sdk *PluginSDK) error + Stop() error +} +``` + +## Interrupt Mechanism + +``` +interceptLoop (goroutine) + ├── InputInterruptChan() ← Timer/message notifications + ├── (a) cancelLLM() → Cancel Provider HTTP request + ├── (b) interceptCh → process() pre-loop read [interrupt message] + └── (c) InjectInput() → Trigger new processing when idle +``` + +Three delivery paths: + +| Path | Effect | Timing | +|------|--------|--------| +| cancelLLM | Cancel current HTTP request | On context.Canceled | +| interceptCh | Insert `[interrupt message]` in process() | Before each LLM call | +| InjectInput | Trigger new processing when eventLoop is idle | No ongoing request | + +Code: `internal/agent/core/agent.go` — `interceptLoop` / `drainInterrupt` + +## Configuration System + +`internal/config/registry.go` — ConfigRegistry + +- SQLite storage, `config` table + `config_` independent tables +- Namespaces: `core.*` / `plugin..*` +- `RegisterDefault` inserts ~80 default keys (seeds for 8 LLM sources) +- WebUI settings page `/api/v1/settings` for read/write + +## Code Structure + +``` +cmd/homed/main.go — Entry: assembles all subsystems +cmd/waiter/main.go — CLI client (Unix socket) +internal/ +├── agent/ +│ ├── core/ — Agent core (eventLoop/process/stages/context) +│ ├── api/ — Provider interface + LuaAdaptedProvider +│ ├── io/ — IOManager (queue/interrupt/output) +│ └── personal.go — Persona loading +├── plugin/ +│ ├── registry.go — Registry + lifecycle +│ ├── dynamic.go — .so dynamic loader +│ └── manifest.go — plugin.json metadata +├── plugins/ — Built-in plugin implementations +│ ├── all.go — Blank imports +│ ├── webui/ — HTTP server + embedded SPA +│ ├── cli/ — Unix socket CLI +│ ├── timer/ — Timer +│ ├── cmd/ — Command execution +│ ├── mcp/ — MCP protocol +│ ├── openclaw/ — OpenClaw compatible +│ ├── agentcli/ — PTY terminal +│ ├── healthcheck/ — Health check +│ └── pluginmgr/ — Plugin manager +├── internal/sdk/ — PluginSDK definitions +│ ├── plugin.go — Plugin interface + PluginSDK +│ ├── memory.go — MemoryAPI +│ ├── knowledge.go — KnowledgeAPI +│ ├── settings.go — SettingsAPI +│ └── llm.go — LLMAPI +├── memory/ +│ ├── graph.go — SQLite graph database +│ ├── indexer.go — Graph → vector index +│ ├── vector/store.go — TF-IDF vector engine +│ ├── document/doc.go — Document memory +│ ├── text/text.go — Text logs +│ └── pipeline/ — Distiller +├── knowledge/knowledge.go — Knowledge base +├── lua/ +│ ├── vm.go — Lua VM (json/log/http) +│ └── adapters/ — 8 LLM adapter scripts +├── config/registry.go — SQLite config center +├── events/bus.go — Event bus +├── tracker/ — OverlayFS change tracking +├── supervisor/ — Daemon management +├── snapshot/ — Snapshots +└── tokenizer/ — Chinese tokenization (jieba wrapper) +``` diff --git a/docs/en/OVERVIEW.md b/docs/en/OVERVIEW.md new file mode 100644 index 0000000..0003134 --- /dev/null +++ b/docs/en/OVERVIEW.md @@ -0,0 +1,74 @@ +**中文** | [English](../zh/OVERVIEW.md) + +# HomeAgent — Project Overview + +## What Is This + +HomeAgent is a continuously-running personal intelligent Agent framework. + +Core architecture: a long-running kernel process (`homed`) that connects to various IO channels (QQ, Web, CLI, etc.) through a plugin system. The kernel handles LLM orchestration, memory management, and knowledge retrieval; plugins handle all external IO — sending/receiving messages, file operations, web search, etc. + +### Key Innovations + +**Separation of Core Domain and Application Domain** — This is the first Agent framework to explicitly make this distinction. The kernel (core domain) performs zero IO; all IO capabilities belong to plugins (application domain). The boundary is clearly defined through PluginSDK: +- Plugins register tools (Tool) with the kernel for LLM invocation +- Plugins hook into the processing pipeline (Stage) to intercept/rewrite message flow at various phases +- Plugins subscribe/publish events (Event) for loosely-coupled communication +- Plugins queue or interrupt input delivery through IO API + +The significance: the kernel stays pure (zero IO, only orchestration and memory), plugins stay flexible (each does its job, hot-loadable), with no cross-contamination. + +**Three-Layer Memory Architecture** — Solves the memory decay problem for long-running agents: +- **Context Layer**: In-memory TF-IDF scored event window, maintains recent context in real-time, low-relevance events automatically sink to the next layer +- **Document Layer**: JSON files + TF-IDF vector-indexed temporary memory, supports explicit submission and implicit archival, cold data distills to Graph +- **Graph Layer**: SQLite graph database, persists entities and relations, BFS traversal recall, distillation pipeline extracts triples from conversations + +Three progressive layers: context → cold archive → long-term graph memory, ensuring the agent doesn't degrade over time. + +## What It Actually Does + +Code is in the project root, implemented in Go. + +**Kernel** (`internal/agent/core/agent.go`): +- Maintains a message loop (`eventLoop`), queuing input from the IO layer +- Each input goes through the full processing pipeline: memory recall → persona injection → LLM call → tool execution → output delivery +- LLM calls abstracted through Provider interface, supports 8 LLM sources with automatic fallback +- Context management (`context.go`) based on TF-IDF scoring, automatic pruning of low-relevance events + +**Memory System** (`internal/memory/`): +- **GraphDB** (`graph.go`) — SQLite, entities + relations tables, BFS traversal +- **Document Store** (`document/doc.go`) — Temporary memory, JSON files + TF-IDF vector index, consume-on-read +- **Text Memory** (`text/text.go`) — Raw conversation logs, JSONL file rotation +- **Social Store** (`social/social.go`) — Persona traits + relationship network, wraps GraphDB +- **Memory Indexer** (`indexer.go`) — Auto-vectorizes GraphDB entities, recalls and injects into system prompt on user input + +**Knowledge Base** (`internal/knowledge/knowledge.go`): +- File system directory `knowledge//content.md` +- TF-IDF vector search, independent index instance from the memory system +- LLM operates via three tools: `knowledge_search` / `knowledge_create` / `knowledge_list` + +**Plugin System** (`internal/plugin/`): +- Built-in plugins: Go `init()` self-registration, compiled into kernel +- External plugins: Go `-buildmode=plugin` compiled to `.so`/`.dll`, dynamically loaded via `plugin.Open`; also supports Lua script plugins +- PluginSDK (`internal/sdk/`) defines three channels: RegisterTool / RegisterStage / Subscribe +- 7 stage hooks: on_input → pre_action → post_action → before_toolcall → after_toolcall → before_output → after_output + +**LLM Provider** (`internal/agent/api/provider.go`): +- Provider interface: Name / Chat / ChatStream +- Three implementations: OpenAIProvider (standard OpenAI API), OllamaProvider (local), LuaAdaptedProvider (Lua adapter) +- LuaAdapter located at `internal/lua/adapters/`, each LLM source has a corresponding `.lua` script +- 8 built-in adapters: deepseek / openai / anthropic / gemini / mistral / groq / github / ollama + +**WebUI** (`internal/plugins/webui/`): +- Embedded SPA dashboard (`dashboard.html` packaged via `//go:embed`) +- REST API: status query, configuration management, memory operations, knowledge management, plugin management +- OpenAI API-compatible `/v1/chat/completions` endpoint +- SSE event stream `/api/v1/chat/events` + +## Project Status + +Core functionality is operational. Plugin system and SDK are ready for independent external plugin development. + +- Built-in plugins: webui / cli / timer / cmd / mcp / agentcli / healthcheck / pluginmgr / openclaw / files +- External plugin examples ([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo `example/`, both Go and Lua types): qq / files / web / memo / bili / editdoc / a2a / ocr / sanitizer / luaplugintest / testlua +- Distribution: `.hmap` plugin package format, installable via WebUI diff --git a/docs/en/PLUGIN_DEV.md b/docs/en/PLUGIN_DEV.md new file mode 100644 index 0000000..e26614d --- /dev/null +++ b/docs/en/PLUGIN_DEV.md @@ -0,0 +1,477 @@ +**中文** | [English](../zh/PLUGIN_DEV.md) + +# HomeAgent Plugin Development Guide + +## Overview + +All external interaction capabilities of HomeAgent comes from plugins. Plugins interact with the kernel through `PluginSDK` (Go API). + +**SDK Repository**: Plugin development tools, template code, and example plugins are hosted in the [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repository. + +```bash +git clone https://gitcode.com/JianFeeeee/homeagent-sdk.git +cd homeagent-sdk +``` + +Each plugin implements a three-method interface: + +```go +type Plugin interface { + Name() string + Start(sdk *PluginSDK) error + Stop() error +} +``` + +### Three Development Methods + +| Method | Use Case | Complexity | +|--------|----------|------------| +| **Dynamic .so/.dll plugin (recommended)** | Independently distributed third-party plugins | Medium, generated using `plugindev` toolchain | +| **Built-in plugin** | Released with HomeAgent | Simple, requires merging into main repo | +| **Lua script plugin** | Lightweight rapid prototyping | Simple, generated using `plugindev init --lua` | + +--- + +## 1. Quick Start: Using the plugindev Toolchain + +`plugindev` is the unified plugin development toolchain provided in the SDK repository, supporting both Go and Lua plugin types. + +### Installation + +```bash +cd homeagent-sdk/tools/plugindev +go build -o plugindev.exe +# Add plugindev.exe to PATH or use directly +``` + +### Creating a Go Plugin + +```bash +plugindev init myplugin +cd myplugin +# Edit plugin code +vim plugin.go +# Build and package +plugindev build +# Output: dist/myplugin_linux_amd64.hmap (or windows_amd64) +``` + +### Creating a Lua Plugin + +```bash +plugindev init myluaplugin --lua +cd myluaplugin +# Edit plugin code +vim main.lua +# Local test +lua main.lua +# Build and package +plugindev build +# Output: dist/myluaplugin_lua.hmap +``` + +### Template Project Structure + +**Go plugin**: + +``` +myplugin/ +├── plg.json — Plugin metadata (name, version, entry, target platform) +├── main.go — Entry point (compiled for non-Windows or non-cgo) +├── plugin.go — Plugin implementation (Plugin interface) +├── go.mod — Go module definition +└── README.md — Documentation +``` + +**Lua plugin**: + +``` +myluaplugin/ +├── plg.json — Plugin metadata (entry: "main.lua", targets: "lua") +├── main.lua — Plugin implementation (Lua version of Plugin interface) +├── sdk.lua — SDK mock layer (supports `lua main.lua` standalone testing) +└── README.md — Documentation +``` + +### Build & Package + +`plugindev build` automatically handles compilation and packaging: + +```bash +cd myplugin +plugindev build +``` + +Execution process: +1. Reads `plg.json` to determine target platform +2. **Go plugin**: Runs `go build -buildmode=plugin` (Linux) or `-buildmode=c-shared` (Windows) +3. **Lua plugin**: Packages source code directly, no compilation needed +4. Generates `plugin.json` manifest file +5. Packages as `.hmap` distribution (zip format, containing `plugin.json` + `plugin.so`/`plugin.dll`/`main.lua`) + +Output in `dist/` directory: +``` +dist/ +├── myplugin_linux_amd64.hmap # Go plugin Linux version +├── myplugin_windows_amd64.hmap # Go plugin Windows version +└── myplugin_lua.hmap # Lua plugin +``` + +### Deployment + +Install via PluginMgr HTTP API: + +```bash +# Kernel PluginMgr listens on :9876 +curl -X POST http://127.0.0.1:9876/plugins \ + -F "file=@dist/myplugin_linux_amd64.hmap" +``` + +Or upload via WebUI plugin management page. + +--- + +## 2. Go Plugin Development in Detail + +### Plugin Interface + +```go +package main + +import "gitcode.com/JianFeeeee/homeagent-sdk/sdk" + +type Plugin struct { + name string + sdk *sdk.PluginSDK +} + +func (p *Plugin) Name() string { return p.name } + +func (p *Plugin) Start(s *sdk.PluginSDK) error { + p.sdk = s + // Register config items, tools, stage hooks, etc. + return nil +} + +func (p *Plugin) Stop() error { + // Clean up resources + return nil +} + +// NewPluginFactory creates plugin instance (called by main.go or Windows bridge) +func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) { + return &Plugin{name: name}, nil +} +``` + +### Entry Point + +`main.go` provides the `NewPlugin` export function, which is the entry point when the kernel loads the plugin: + +```go +//go:build !windows || !cgo + +package main + +import "gitcode.com/JianFeeeee/homeagent-sdk/sdk" + +func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { + return NewPluginFactory(name, config) +} +``` + +For Windows `-buildmode=c-shared`, `plugindev build` auto-generates C ABI bridge code, no manual handling needed. + +### PluginSDK Core API + +#### Tool Registration — Make your capabilities callable by LLM + +```go +s.RegisterTool("weather_query", sdk.ToolDef{ + Name: "weather_query", + Description: "Query weather for a specified city", + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "city": map[string]interface{}{ + "type": "string", + "description": "City name, e.g. Beijing", + }, + }, + "required": []string{"city"}, + }, +}, func(args map[string]interface{}) (interface{}, error) { + city, _ := args["city"].(string) + return map[string]interface{}{ + "city": city, + "temp": 25, + "weather": "Sunny", + }, nil +}) +``` + +#### Stage Hooks — Intervene in message processing flow + +7 stages: + +| Stage | Timing | Purpose | +|-------|--------|---------| +| `on_input` | Message just arrived at Agent | Blacklist, rate-limit, short-circuit | +| `pre_action` | About to call LLM | Inject context | +| `post_action` | LLM returned results | Modify output/tool list | +| `before_toolcall` | Before tool execution | Audit, reject, modify params | +| `after_toolcall` | After tool execution | Desensitize, rewrite results | +| `before_output` | Before output | Format adaptation | +| `after_output` | After output | Statistics/logging | + +```go +s.RegisterStage(sdk.StagePreAction, func(ctx *sdk.StageContext) error { + ctx.Lock() + ctx.ContextMsgs = append(ctx.ContextMsgs, map[string]interface{}{ + "role": "system", + "content": "Injected context content", + }) + ctx.Unlock() + return nil +}) +``` + +#### Configuration Management + +```go +// Register config definition +s.Settings().RegisterDef(sdk.ConfigDef{ + Key: "plugin.myplugin.api_key", + Default: "", + Type: "string", + DisplayName: "API Key", + Description: "API key", + Category: "myplugin", +}) + +// Read/write config +val, err := s.Settings().Get("api_key") +s.Settings().Set("api_key", "new-value") + +// Read core config +s.Settings().GetCore("llm.model") + +// Read other plugin's config +s.Settings().GetPlugin("other_plugin", "some_key") +``` + +#### Input Delivery + +```go +// Queued delivery (processed in order) +s.InjectInput(source, channel string, payload map[string]interface{}) + +// Interrupt delivery (can interrupt current LLM processing) +s.InjectInterrupt(source, channel string, payload map[string]interface{}) + +// Shortcuts +s.InjectText(source, channel, text string) +s.InjectInterruptText(source, channel, text string) +``` + +#### Event Subscription + +```go +unsub := s.Subscribe("tool_call", func(evt *events.Event) { + log.Printf("Tool was called: %v", evt.Payload) +}) +defer unsub() +``` + +#### Capability Access + +```go +// Memory +s.Memory().Recall(query string) ([]MemItem, error) +s.Memory().Commit(triples []Triple) error + +// Knowledge +s.Knowledge().Search(query string) ([]string, error) + +// LLM source management +s.LLM().ListSources() []SourceInfo +s.LLM().SetSource(name string) error +``` + +--- + +## 3. Lua Plugin Development in Detail + +Lua plugins are suitable for lightweight rapid prototyping, requiring no Go compilation environment. Changes take effect after kernel restart. + +### Plugin Structure + +```lua +-- main.lua +local plugin = { + name = "myluaplugin" +} + +function plugin.start(sdk) + sdk.log("info", "myluaplugin starting...") + + sdk.register_tool("myluaplugin_hello", { + description = "A hello world tool", + parameters = { + type = "object", + properties = {} + } + }, function(args) + return { content = "Hello from myluaplugin plugin!" } + end) + + sdk.log("info", "myluaplugin started") +end + +function plugin.stop() + sdk.log("info", "myluaplugin stopped") +end + +return plugin +``` + +### SDK Mock Layer + +`sdk.lua` provides a pure Lua SDK mock implementation, supporting `lua main.lua` standalone testing: + +```bash +lua main.lua +# Output: +# [lua-plugin] info: myluaplugin starting... +# [lua-plugin] register_tool: myluaplugin_hello +# [lua-plugin] info: myluaplugin started +``` + +When running inside the kernel, `sdk.*` global variables are injected by the Go layer, and all functions marked with `-- !impl` are replaced with real implementations. + +### Lua SDK API + +| Function | Description | +|----------|-------------| +| `sdk.log(level, msg)` | Log output | +| `sdk.register_tool(name, def, handler)` | Register tool | +| `sdk.register_stage(stage, handler)` | Register stage hook | +| `sdk.register_api(name)` | Register API | +| `sdk.get_setting(key)` | Read config | +| `sdk.set_setting(key, value)` | Write config | +| `sdk.inject_text(source, channel, text)` | Deliver text message | +| `sdk.inject_interrupt(source, channel, text)` | Interrupt delivery | +| `sdk.json.encode(val)` | JSON encode | +| `sdk.json.decode(str)` | JSON decode | +| `sdk.http.get(url)` | HTTP GET request (`-- !impl`) | +| `sdk.http.post(url, body, content_type)` | HTTP POST request (`-- !impl`) | + +--- + +## 4. Built-in Plugins + +Built-in plugins use `init()` self-registration, compiled into the kernel, no separate deployment needed. + +### Directory Structure + +``` +internal/plugins/yourplugin/ + plugin.go — Plugin main file +``` + +### Minimal Plugin Example + +```go +package yourplugin + +import ( + "gitcode.com/JianFeeeee/HomeAgent/internal/plugin" + sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk" +) + +func init() { + plugin.RegisterFactory("yourplugin", func(name string, config map[string]interface{}) (sdk.Plugin, error) { + return New(name), nil + }) +} + +type Plugin struct { + name string +} + +func New(name string) *Plugin { + return &Plugin{name: name} +} + +func (p *Plugin) Name() string { return p.name } + +func (p *Plugin) Start(s *sdk.PluginSDK) error { + // Initialize plugin here: start goroutines, register tools, subscribe events, etc. + return nil +} + +func (p *Plugin) Stop() error { + // Clean up resources + return nil +} +``` + +### Register with Kernel + +Add blank import in `internal/plugins/all.go`: + +```go +package plugins + +import ( + _ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/yourplugin" + // ... other plugins +) +``` + +--- + +## 5. Best Practices + +1. `Start()` is non-blocking — start long tasks in goroutines, don't block Start +2. `Stop()` cleans up resources — close connections, stop goroutines, cancel subscriptions +3. Unique tool names — use plugin name prefix to avoid conflicts +4. When handler returns `error`, LLM will receive it and may retry +5. Use `InjectInterruptText` for interrupts, `InjectText` for normal delivery +6. Use `Settings().Get/Set` for config, don't hardcode +7. External Go plugins compile independently, not tied to kernel version; only built-in plugins need recompilation with kernel + +--- + +## 6. Example Plugin Reference + +### SDK Repository Examples (`homeagent-sdk/example/`) + +| Example | Type | Features | +|---------|------|----------| +| [memo](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/memo) | Go | Memo management, PreAction injection + timed interrupt dual reminder | +| [files](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/files) | Go | File system operations, 4 write modes, sandbox isolation | +| [web](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/web) | Go | DuckDuckGo search + web scraping, SSRF protection | +| [qq](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/qq) | Go | NapCat OneBot integration, 17 tools | +| [bili](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/bili) | Go | Bilibili video download (you-get) | +| [editdoc](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/editdoc) | Go | Office document editing and format conversion | +| [a2a](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/a2a) | Go | Agent-to-Agent protocol | +| [ocr](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/ocr) | Go | Offline text recognition (Tesseract) | +| [sanitizer](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/sanitizer) | Go | Output sanitizer filter | +| [luaplugintest](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/luaplugintest) | Lua | Lua plugin Hello World | +| [testlua](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/testlua) | Lua | Lua plugin example | + +### Built-in Plugins + +| Plugin | Location | Features | +|--------|----------|----------| +| Timer | `internal/plugins/timer/` | Simplest complete example, registers one tool + interrupt feedback | +| CLI | `internal/plugins/cli/` | Unix socket listener + synchronous request-response | +| WebUI | `internal/plugins/webui/` | HTTP service + dependency injection | + +--- + +*Want to understand the project goals? See [OVERVIEW.md](OVERVIEW.md).* +*Want to understand the architecture? See [ARCHITECTURE.md](ARCHITECTURE.md).* +*SDK repository and development tools? See [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk).* diff --git a/docs/ADAPTER.md b/docs/zh/ADAPTER.md similarity index 98% rename from docs/ADAPTER.md rename to docs/zh/ADAPTER.md index bf80d94..982aea6 100644 --- a/docs/ADAPTER.md +++ b/docs/zh/ADAPTER.md @@ -1,3 +1,5 @@ +[English](../en/ADAPTER.md) | **中文** + # Lua Adapter — LLM 源适配指南 每个 LLM API 源对应一个 Lua 脚本,负责请求转换(Go 统一格式 → API 格式)和响应转换(API 格式 → Go 统一格式)。 diff --git a/docs/ARCHITECTURE.md b/docs/zh/ARCHITECTURE.md similarity index 99% rename from docs/ARCHITECTURE.md rename to docs/zh/ARCHITECTURE.md index ee6166c..50df7fa 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/zh/ARCHITECTURE.md @@ -1,3 +1,5 @@ +[English](../en/ARCHITECTURE.md) | **中文** + # HomeAgent 架构 内核零 IO,一切外界交互来自插件。 @@ -342,3 +344,4 @@ internal/ ├── supervisor/ — 守护进程管理 ├── snapshot/ — 快照 └── tokenizer/ — 中文分词 (jieba 包装) +``` diff --git a/docs/OVERVIEW.md b/docs/zh/OVERVIEW.md similarity index 97% rename from docs/OVERVIEW.md rename to docs/zh/OVERVIEW.md index fe59e96..ec6c925 100644 --- a/docs/OVERVIEW.md +++ b/docs/zh/OVERVIEW.md @@ -1,3 +1,5 @@ +[English](../en/OVERVIEW.md) | **中文** + # HomeAgent — 项目概览 ## 这是什么 @@ -69,4 +71,4 @@ HomeAgent 是一个持续运行的个人智能 Agent 框架。 - 内置插件:webui / cli / timer / cmd / mcp / agentcli / healthcheck / pluginmgr / openclaw / files - 外部插件示例([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库 `example/`,含 Go 和 Lua 两种类型):qq / files / web / memo / bili / editdoc / a2a / ocr / sanitizer / luaplugintest / testlua -- 打包分发:`.hmap` 插件包格式,通过 WebUI 安装 \ No newline at end of file +- 打包分发:`.hmap` 插件包格式,通过 WebUI 安装 diff --git a/docs/PLUGIN_DEV.md b/docs/zh/PLUGIN_DEV.md similarity index 99% rename from docs/PLUGIN_DEV.md rename to docs/zh/PLUGIN_DEV.md index 9ed3ce2..e691d0c 100644 --- a/docs/PLUGIN_DEV.md +++ b/docs/zh/PLUGIN_DEV.md @@ -1,3 +1,5 @@ +[English](../en/PLUGIN_DEV.md) | **中文** + # HomeAgent 插件开发指南 ## 概述 @@ -473,4 +475,4 @@ import ( *了解项目整体目标?查看 [OVERVIEW.md](OVERVIEW.md)。* *了解技术架构?查看 [ARCHITECTURE.md](ARCHITECTURE.md)。* -*SDK 仓库与开发工具?查看 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk)。* \ No newline at end of file +*SDK 仓库与开发工具?查看 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk)。* diff --git a/迭代文档.md b/迭代文档.md deleted file mode 100644 index bd99b7f..0000000 --- a/迭代文档.md +++ /dev/null @@ -1,200 +0,0 @@ -# 迭代文档 - -## 插件错误分析与修改建议(2026-07-11) - -基于本机 homeagent 实例 24h systemd 日志分析。 - ---- - -### 1. LLM Provider Fallback 链 - -#### 现象 -DeepSeek 网络不稳定时,依次尝试 8 个 provider,全部失败耗时 ~10s: -``` -provider "lua_deepseek" failed: TLS handshake timeout -provider "lua_anthropic" failed: 401 Invalid bearer token -provider "lua_gemini" failed: 404 -provider "lua_github" failed: 401 Bad credentials -provider "lua_groq" failed: 401 Invalid API Key -provider "lua_mistral" failed: TLS handshake timeout / x509 cert mismatch -provider "lua_ollama" failed: 404 model 'llama3' not found -provider "lua_openai" failed: TLS handshake timeout -→ all 8 providers failed -``` - -#### 根因定位 -- fallback 逻辑在 `internal/agent/core/agent.go:671-698`:`OrderedProviders()` 遍历所有已注册 provider -- provider 来自 `cfg.LLM.Sources`(`cmd/homed/main.go:260-273`),**不是硬编码的循环**,是按配置依次尝试 -- **但是**默认配置 `config/config.go:32-41` 的 `DefaultConfig()` 硬编码了 8 个 source。本机 `/etc/homeagent/config.yaml` 不存在,所以 fallback 到默认配置的 8 个 sources -- 本机启动日志确认:`sources=8 adapters=8` -- 认证错误(401/403/404)的 provider 每次 fallback 都重新尝试,没有缓存/跳过机制 - -#### 修改建议 - -**homeagent(高优先级)** -- `internal/agent/core/agent.go` fallback 循环: - - 对返回 401/403 的 provider 标记为 `auth_failed`,本轮不再重试 - - 增加断路器:连续 N 次失败的 provider 进入 cooldown(如 5 分钟不选) -- `internal/agent/api/provider.go` `ProviderManager`: - - 增加 `MarkUnavailable(name string)` / `IsAvailable(name string)` 方法 -- `config/config.go` `DefaultConfig()`: - - 默认只保留 primary source(deepseek),其余 7 个不作为默认配置 - - 用户如果需要 fallback 应自行配置 - ---- - -### 2. QQ 插件 — `parse get_file ... bad response` - -#### 现象 -日志中出现频率最高,占全部 plugin 错误的 ~90%。NapCat 收到图片/文件事件后,homeagent 尝试下载但解析失败。 - -#### 根因定位 -- 运行中的 QQ 插件是 `/home/newqqagent/plugins/qq/plugin.so`(13MB 编译产物) -- 源码 `.tmp-plugins/qq/plugin.go`(1051 行)与运行版本不一致(运行版本 line 1356 有此错误) -- 错误信息仅包含文件名,不包含 NapCat 原始响应,无法定位是 NapCat 返回了非 JSON 还是 URL 无效 - -#### 修改建议 - -**QQ 插件(高优先级)** -- `handleGetGroupFiles` download 分支(当前 line 892-933): - - JSON 解析失败时,将 NapCat 原始 body 截断后一并返回 - - 对 NapCat 返回的错误状态码(如 `retcode != 0`)明确提示 - - 例:`{"error": "parse response failed", "raw": "", "file_id": "xxx"}` - -**homeagent SDK(中优先级)** -- `internal/sdk/plugin.go` 增加 `LogError(plugin, action, err, context)` 方法 -- 统一插件错误日志格式:`[plugin] : | ` - ---- - -### 3. QQ 插件 — `qq_web_fetch` 内容不完整 - -#### 现象 -工具仅返回页面 `` 内容(`标题: xxx`),部分站点: -- `502 Bad Gateway` — 目标站不可用 -- `百度安全验证` — 触发反爬 -- `www.zhihu.com` — 只有域名无内容 - -#### 根因 -运行中的 QQ plugin.so 包含 `qq_web_fetch` 工具(源码未在仓库中),实现简陋,只提取 title 标签。 - -#### 修改建议 -- 升级为完整正文提取(readability 算法) -- 增加 User-Agent 轮换 -- 对非 200 状态码返回明确错误 -- 考虑将 web_fetch 功能移至独立的 `web` 插件(已定义 manifest 但未编译 plugin.so) - ---- - -### 4. A2A 插件 — discover 超时 - -#### 现象 -``` -a2a_a2a_discover → http://192.168.1.100:8080/a2a/agent-card -→ context deadline exceeded -``` - -#### 根因 -- 目标地址 `192.168.1.100:8080` 不可达 -- 错误信息只有"连接Agent失败",无超时/拒绝等分类 - -#### 修改建议 -- A2A 插件的 discover 错误应区分:超时、连接拒绝、DNS 解析失败 -- Agent Card URL 应从配置读取,而非硬编码 - ---- - -### 5. AgentCLI — SSH 终端超时 - -#### 现象 -``` -terminal_create: ssh ... timeout=15s -terminal_read: 终端 term_2 不存在或已关闭 -``` - -#### 根因 -SSH 命令 timeout 仅 15s,连接建立后很快超时被清理。 - -#### 修改建议 -- `terminal_create` 根据命令类型给出默认超时建议(SSH 建议 60-300s) -- 终端关闭时主动发送中断通知,而非让 agent 下次 read 才知道 - ---- - -### 6. LLM 思维泄漏 — 工具调用残留混入 QQ 消息内容 - -#### 现象 -LLM 通过 `qq_send_private_msg` 发送的消息中,包含了原始工具调用描述。用户在 QQ 上收到类似: -``` -好嘞老大,我找找日志给你发过来!😊 - -<tool_call> -cmd_run find / -name "*.log" 2>/dev/null | head -20 -</tool_call> -``` - -LLM 在同一轮生成中同时输出文本(`content`)和工具调用(`tool_calls`),但文本内容里也描述/复现了它准备调用的工具。 - -#### 根因 -- LLM API 返回格式:`{content: "文本...", tool_calls: [...]}` — content 和 tool_calls 是并列关系 -- 部分 LLM(尤其是 thinking/reasoning 模型)会在 content 中输出推理过程,包括"我需要调 xxx 工具"的描述 -- `qq_send_private_msg` 的 `message` 参数直接取 LLM 的 `content` 原文,未做清洗 -- 工具调用残留不是框架 bug,是 LLM 输出内容的"思维泄漏" - -#### 修改建议 - -**QQ 插件 `beforeOwnToolcall`(高优先级)** -- 对 `qq_send_private_msg` / `qq_send_group_msg` 的 `message` 参数做正则清洗: - - 过滤 `<tool_call>...</tool_call>`、`<invoke>...</invoke>` 等已知的 tool call 标记 - - 过滤 `cmd_run`、`terminal_create` 等工具名后跟命令的文本模式 - - 过滤 markdown 代码块中疑似 shell 命令的内容 -- 清洗规则应可配置/可扩展 - -**homeagent 核心层 `post_action` 阶段(中优先级)** -- `internal/agent/core/agent.go` 中 `post_action` stage 后,对 LLM 返回的 `content` 增加通用清洗管道: - - 移除 ````xml <tool_call>...` 等已知模式 - - 移除 `【tool_call】...` 等自定义标记 -- 清洗后的 content 写入上下文 + 输出通道,原始 content 保留供调试 - ---- - -### 7. Files 插件 — `not a directory` 提示不友好 - -#### 现象 -``` -files_ls /tmp/hello_world.pptx → "not a directory: /tmp/hello_world.pptx" -``` - -#### 修改建议 -- `handleLs` 遇到文件路径时,返回"这是一个文件,不是目录。如需读取文件内容,请使用 files_read 工具" - ---- - -### 8. 未编译插件 - -`/home/newqqagent/plugins/` 中以下插件有 `plugin.json` 但无 `plugin.so`: -- `web` — 声明提供 `web_search` / `web_fetch` -- `browser` — 声明提供 headless browser -- `bili` — B 站相关 -- `memo` — 备忘录 -- `editdoc` — 文档编辑 - -需要找到对应 Go 源码并编译部署。 - ---- - -### 附录:相关源码位置 - -| 组件 | 路径 | 关键行 | -|------|------|--------| -| Provider 注册 | `cmd/homed/main.go` | 259-276 | -| ProviderManager | `internal/agent/api/provider.go` | 581-676 | -| Fallback 循环 | `internal/agent/core/agent.go` | 661-711 | -| 默认配置 | `config/config.go` | 15-67 | -| QQ 插件源码 | `.tmp-plugins/qq/plugin.go` | 全部(1051行) | -| QQ 插件运行版 | `/home/newqqagent/plugins/qq/plugin.so` | — | -| 插件注册 | `internal/plugin/registry.go` | 全部 | -| PluginSDK | `internal/sdk/plugin.go` | 全部 | -| AgentCLI 终端 | `internal/plugins/agentcli/plugin.go` | 全部 | -| Files 插件 | `internal/plugins/files/plugin.go` | 全部 | -| Cmd 插件 | `internal/plugins/cmd/plugin.go` | 全部 |