release: v1.6.0

This commit is contained in:
matevip 2026-06-22 15:07:05 +08:00
parent 2664b26763
commit 2cf08683a4
49 changed files with 735 additions and 358 deletions

View File

@ -107,7 +107,7 @@ Change an agent's type at any time. Same system prompt works reasonably in both
## Multi-agent parallel delegation
An agent doesn't work alone. One agent can delegate to another — or to **three at once**.
An agent doesn't work alone. One agent can delegate to another — or to **multiple agents at once** (up to 8).
- **Single delegation** — hand a sub-task to a specific agent; it runs in an isolated session, results stream back
- **Parallel delegation** — fan out to multiple agents at once, each in its own session
@ -130,8 +130,9 @@ Three delegation tools, one per cadence:
Children deny a default set of tools so the tree can't run away:
- `delegateToAgent` / `delegateParallel` (recursion guard — children can't launch their own synchronous/parallel delegations, avoiding a delegation storm)
- the `setGoal` family + the `remember` family (goal and memory ownership stays with the parent)
- `delegateToAgent` / `delegateParallel` / `listAvailableAgents` (recursion guard — children can't launch their own synchronous/parallel delegations and can't enumerate sibling agents)
- `setGoal` / `addGoalCriterion` / `completeGoal` / `getGoalStatus` (goal ownership stays with the parent)
- `remember` / `remember_structured` / `forget_structured` (children can't write into the parent's long-term memory)
- `create_employee` (children can't conjure new employees)
This default deny list is tunable via `mateclaw.delegation.child-denied-tools`.
@ -150,6 +151,41 @@ The ChatConsole draws the whole delegation tree, not a flat log:
---
## Plan Kanban
::: tip New
The `Digital Employees` page now has a three-way toggle at the top: **Roster / Live / Plan Kanban**. The Kanban surfaces every plan produced by every employee in the workspace, sorted by status into a single board so you can see at a glance who's doing what and where things are stuck. (Visible to admins only.)
:::
The board is a global view of **Plan-and-Execute plans**, with four columns that plans fall into automatically:
| Column | Meaning |
|--------|---------|
| **Pending** | Plan generated, first step hasn't started yet |
| **Running** | First step has started |
| **Done** | All steps completed |
| **Failed** | A step failed and won't be retried |
The layout is **swimlane-style**: each employee that has plans gets its own row, ordered by most-recent activity, with a top-of-page dropdown to filter to a single employee. Multiple re-plans for the same goal collapse into **one card + ×N badge** — no stacking. Each card shows the goal text, a progress bar (completed / total steps), and step-distribution chips (N pending / M running / K done).
The board is **read-only** — state is driven by execution, not drag-and-drop. Click a card and a **plan detail panel** slides in from the right: assigned employee, status, KPIs (step count / progress / creation date), execution output (Markdown rendered), and an expandable step timeline. A "Goals" button at the top links directly to the active [Goals](./goals) list.
REST: `GET /api/v1/plans?limit=N` (most-recent N plans across all employees), `GET /api/v1/plans?agentId=...` (by employee), `GET /api/v1/plans/{id}` (with step detail).
### Per-step delegation to specialist employees
::: tip New
A multi-step plan doesn't have to be run by a single employee from start to finish. When generating a plan, the planner can assign **individual steps** to more-specialized employees in the workspace.
:::
The mechanism is **automatic** — no manual wiring required. During planning, the system shows the planner every other enabled employee in the workspace (name and description included); the planner marks a step for a specialist employee when that step clearly falls within the specialist's domain, leaving the remaining steps to itself. Most steps typically need no delegation.
- Delegation is recorded in `mate_sub_plan.assigned_agent_id`; a blue badge — **"Delegated to <employee name>"** — appears below the step in the plan detail panel
- Delegated steps execute in a **sub-conversation** scoped to the parent plan's conversation — they do **not** leak into the top-level conversation list as independent sessions
- Step-level delegation shares the same semantics as the [Goals](./goals) system and the [multi-level delegation tree](#multi-level-subagent-delegation-tree) above: the parent breaks up the work, specialists do their part
---
## Build a team from one sentence: the digital-employee builder skill
::: tip New in 1.4.0
@ -167,6 +203,24 @@ The companion tool **`list_capability_catalog`** lets the skill survey which too
---
## Single-employee creation wizard
::: tip New
The team-builder skill above creates a whole team in one shot. If you only need **one** employee and don't want to fill in every field by hand, use the **Create Wizard** button in the top-right corner of the employee list — describe what you want in a sentence, and the AI drafts the employee for you to tweak before saving.
:::
This is a separate three-step UI wizard (`Digital Employees → Create Wizard`), distinct from the team-builder skill: the skill outputs a team through a chat interface; the wizard outputs a single employee through a dedicated page.
1. **Describe** — type a natural-language sentence in the input box ("an operations assistant that tracks competitor news and writes a daily brief"). Example chips below the box let you fill one in with a single click
2. **Review** — the AI returns a draft: name, avatar emoji, role, goal, system prompt, type (`react` / `plan_execute`), suggested opening question, tags, and **recommended tool / skill / knowledge-base bindings**. Every field is editable; the capabilities list uses a searchable picker
3. **Publish** — confirm and the employee is created along with all tool / skill / KB bindings in one go; you're offered "Start chatting / Create another / Back to list"
**Hallucination prevention** is the key design decision here: the AI can only suggest tools, skills, and KBs that **actually exist** in your deployment — anything the model invents that doesn't match a real capability is verified and discarded server-side during generation, before the draft ever reaches the wizard. Every binding shown in the draft is immediately usable.
Backend endpoint: `POST /api/v1/agents/generate`, request body `{ "requirement": "your one-sentence description" }`, response is a validated draft.
---
## Deep thinking
Not every question deserves deep reasoning, but some do. MateClaw lets you turn on deep thinking per agent, per conversation:
@ -188,7 +242,7 @@ Not every question deserves deep reasoning, but some do. MateClaw lets you turn
5. Choose the type (`react` or `plan_execute`)
6. Write (or edit) the system prompt (role / goal / backstory get auto-appended — don't repeat them)
7. Pick which tools they can use, bind any knowledge bases they should read
8. Set `max_iterations` (default 10)
8. Set `max_iterations` (default 100)
9. Save
Live immediately. Call them from chat or via API.
@ -250,6 +304,27 @@ When an employee invokes a wiki tool, the resolution order is:
Migration note: early versions persisted the binding on `mate_wiki_knowledge_base.agent_id` (one-to-one, exclusive semantics). Starting with the V130 migration, every legacy `kb.agent_id` is backfilled into the corresponding `agent.primary_kb_id`; the old column stays around as a read-only fallback, but new writes only touch `agent.primary_kb_id`. If you relied on `kb.agent_id` to isolate a KB to a specific agent, revisit those bindings in the editor — KBs are now visible to every employee in the workspace.
#### Disable knowledge bases entirely for an employee
::: tip New
The top of the "Knowledge Base" tab now has a toggle: **This employee does not use any knowledge base**. It is the symmetric counterpart to the tool-disable and skill-disable opt-out switches.
:::
There are two distinct meanings of "no KB selected":
- **Selector left empty** = "I haven't specified one" → at runtime, the employee **inherits all workspace KBs** (the default behavior)
- **Toggle switched on** = "I explicitly want zero KBs" → at runtime, the employee's visible KB set is treated as **empty**
After saving with the toggle on, the employee's KB binding is cleared and marked as "explicitly KB-free"; a **Disabled** badge appears on the tab. The effect:
- `wiki_read_page` / `wiki_search_pages` / `wiki_semantic_search` and every other wiki tool return `"no knowledge base"` — the tools are still in the toolset, they just produce no results
- The webchat `/wiki/pages` endpoint returns an empty list for this employee
- All KB injection and grounding is off
**Off by default** — all existing employees are unaffected. The toggle can be removed at any time: selecting at least one KB in the picker and saving automatically clears the flag (a non-empty binding takes precedence over the opt-out, preventing contradictory state).
The flag lives in `mate_agent.wiki_disabled` (V154 migration, covering H2 / MySQL / KingbaseES).
### System prompt best practices
The system prompt is the employee's voice, priorities, and constraints. **Role / Goal / Backstory**, skill instructions, and workspace memory all get automatically appended to the final prompt — you don't write those yourself.
@ -309,6 +384,10 @@ Why the turn ended:
| `SUMMARIZED` | Completed after a context-compression pass |
| `MAX_ITERATIONS_REACHED` | Forced convergence at iteration limit |
| `ERROR_FALLBACK` | Degraded answer after an error |
| `INCOMPLETE` | Response did not finish; needs retry or continuation |
| `EVIDENCE_INSUFFICIENT` | Final answer cited facts not verified by any tool result |
| `STOPPED` | User actively stopped the turn |
| `RETURN_DIRECT` | A tool with `returnDirect=true` short-circuited the loop; result delivered without re-entering the LLM |
---
@ -317,12 +396,14 @@ Why the turn ended:
These are things the runtime does so agents don't fail in ways you'd have to debug:
- **Context pruning** — when the context window gets too full, earlier turns get summarized by the LLM and the summary replaces them. Cached for 30 minutes. Injected as a user message, not a system message, to prevent prompt injection from historical content.
- **Structured compaction (on prompt-too-long)** — when the model returns "prompt too long," the runtime walks a four-stage escalation: **soft trim → hard clear → pre-prune → LLM structured summary**. At every stage it **always preserves the prefix** — the system prompt + the goal anchor stay intact — and injects the final summary as a UserMessage. Delegation tool results are **never compacted** (they're a child's hard-won output; lose them and they're gone). After a failed summary there's a **10-minute cooldown**, so the runtime won't keep hammering the LLM inside the same over-budget turn.
- **Structured compaction (on prompt-too-long)** — when the model returns "prompt too long," the runtime walks a four-stage escalation: **soft trim → hard clear → pre-prune → LLM structured summary**. At every stage it **always preserves the prefix** — the system prompt + the goal anchor stay intact — and injects the final summary as a UserMessage. Delegation tool results are **never compacted** (they're a child's hard-won output; lose them and they're gone). After a PTL-triggered compaction there's a **1-minute cooldown**, so the runtime won't keep hammering the LLM inside the same over-budget turn.
- **Thinking recovery** — if a stream breaks mid-response, the partial thinking and content persist and show up when the conversation reloads.
- **Iteration limit handler** — instead of crashing when `max_iterations` is hit, the runtime forces a best-effort summary answer.
- **Stale stream cleanup** — every open SSE stream is tracked, abandoned ones are reaped automatically.
- **429 retry** — LLM rate-limit errors trigger automatic retries with backoff.
- **Repetition detection** — agents looping on the same tool call get forced out.
- **Stall detection + re-planning** — in Plan-and-Execute mode, when a step throws an exception or repeatedly fails inside a tool loop, the runtime discards the current plan, carries the failure reason back to the planning node, and **re-plans** to route around the broken step — rather than pushing a garbage result forward. See [Goals · Stall detection and re-planning](./goals#stall-detection-and-re-planning).
- **Hard continuation on the iteration cap** — an employee with an active goal that hits its iteration limit can **resume with a full fresh iteration budget** instead of stopping and waiting for you to send another message. See [Goals · Hard continuation](./goals#hard-continuation-on-the-iteration-cap).
- **Configurable tool timeouts** — one slow tool can't freeze a turn.
- **Channel health monitor** — failing channel adapters restart with exponential backoff.

View File

@ -109,7 +109,7 @@ Only MateClaw fills out the right column completely, because only MateClaw has a
- **Multi-agent runtime** (ReAct + Plan-Execute)
- **Cron scheduling + retry**
- **9 IM channel adapters** with exponential-backoff reconnect
- **8 IM channel adapters** with exponential-backoff reconnect
- **Persistent memory** ([Memory](./memory) — Dreaming makes it know you better every day)
- **Wiki knowledge layer** ([LLM Wiki](./wiki) — gives the agent something to base research on)
- **Tool Guard** ([Security](./security) — sensitive ops still ask you first)

View File

@ -404,12 +404,12 @@ Total routes extracted: 406.
| `GET` | `/api/v1/skills/{id}` | `Get` |
| `PUT` | `/api/v1/skills/{id}` | `Update` |
| `POST` | `/api/v1/skills/{id}/archive` | `Archive` |
| `GET` | `/api/v1/skills/{id}/employees` | `List agents that can use this skill (RFC-090 §14.2)` |
| `GET` | `/api/v1/skills/{id}/employees` | `List agents that can use this skill` |
| `POST` | `/api/v1/skills/{id}/export-workspace` | `Export To Workspace` |
| `GET` | `/api/v1/skills/{id}/lessons` | `Read per-skill LESSONS.md (RFC-090 §11.4)` |
| `POST` | `/api/v1/skills/{id}/lessons/clear` | `Clear all lessons for a skill (RFC-090 §11.4)` |
| `GET` | `/api/v1/skills/{id}/lessons` | `Read per-skill LESSONS.md` |
| `POST` | `/api/v1/skills/{id}/lessons/clear` | `Clear all lessons for a skill` |
| `POST` | `/api/v1/skills/{id}/pin` | `Pin` |
| `GET` | `/api/v1/skills/{id}/requirements` | `Pre-flight requirement statuses for a skill (RFC-090)` |
| `GET` | `/api/v1/skills/{id}/requirements` | `Pre-flight requirement statuses for a skill` |
| `POST` | `/api/v1/skills/{id}/rescan` | `Rescan` |
| `POST` | `/api/v1/skills/{id}/restore` | `Restore` |
| `POST` | `/api/v1/skills/{id}/sync-files` | `Re-sync this skill's bundle files from DB → local workspace cache` |
@ -423,7 +423,7 @@ Total routes extracted: 406.
| Method | Path | Purpose / handler |
|---|---|---|
| `GET` | `/api/v1/skill-templates` | `List skill templates (RFC-091)` |
| `GET` | `/api/v1/skill-templates` | `List skill templates` |
| `GET` | `/api/v1/skill-templates/{id}` | `Get a single skill template` |
| `POST` | `/api/v1/skill-templates/{id}/instantiate` | `Instantiate a template into a skill` |

View File

@ -258,10 +258,10 @@ curl -X POST http://localhost:18088/api/v1/channels \
"type": "feishu",
"agentId": 1,
"config": {
"appId": "cli_your_app_id",
"appSecret": "your-app-secret",
"verificationToken": "your-verification-token",
"encryptKey": "your-encrypt-key"
"app_id": "cli_your_app_id",
"app_secret": "your-app-secret",
"verification_token": "your-verification-token",
"encrypt_key": "your-encrypt-key"
},
"enabled": true
}'
@ -349,8 +349,8 @@ curl -X POST http://localhost:18088/api/v1/channels \
"type": "feishu",
"agentId": 1,
"config": {
"appId": "cli_your_app_id",
"appSecret": "your-app-secret",
"app_id": "cli_your_app_id",
"app_secret": "your-app-secret",
"card_format": "auto",
"card_header": "AI 助手",
"card_streaming_enabled": true,
@ -384,11 +384,8 @@ curl -X POST http://localhost:18088/api/v1/channels \
"type": "wecom",
"agentId": 1,
"config": {
"corpId": "your-corp-id",
"wecomAgentId": "1000002",
"secret": "your-secret",
"token": "your-token",
"encodingAesKey": "your-encoding-aes-key"
"bot_id": "your-bot-id",
"secret": "your-secret"
},
"enabled": true
}'
@ -396,8 +393,6 @@ curl -X POST http://localhost:18088/api/v1/channels \
![Start Chat](/images/channels/wecom/07-chat.png)
Webhook URL: `https://your-domain/api/v1/channels/webhook/wecom`
::: tip Want WeCom to actually run smoothly?
Group multi-user collaboration, quoted messages, appmsg parsing, upload constraints, aibot_respond_msg routing, self-loop detection, TLS retry, platform-level permission locks — every non-obvious optimization and corner case is collected in [WeCom Deep Tuning](./wecom-tuning).
:::
@ -529,8 +524,8 @@ curl -X POST http://localhost:18088/api/v1/channels \
"type": "qq",
"agentId": 1,
"config": {
"appId": "your-app-id",
"appSecret": "your-app-secret"
"app_id": "your-app-id",
"client_secret": "your-app-secret"
},
"enabled": true
}'
@ -563,8 +558,7 @@ curl -X POST http://localhost:18088/api/v1/channels \
"agentId": 1,
"config": {
"bot_token": "xoxb-...",
"app_token": "xapp-...",
"mode": "socket"
"app_token": "xapp-..."
},
"enabled": true
}'
@ -603,7 +597,7 @@ curl -X POST http://localhost:18088/api/v1/channels \
"name": "WeChat Personal",
"type": "weixin",
"agentId": 1,
"config": {"botToken": "your-bot-token"},
"config": {"bot_token": "your-bot-token"},
"enabled": true
}'
```

View File

@ -21,6 +21,8 @@ Segments arrive progressively. They persist to the database in real time — mea
This used to not be true. Now it is.
References in the reply are live too: `[[slug]]` wikilinks and the `[1]` / `[2]` **source citation markers** that appear when the agent answers from a knowledge base are all clickable — each one navigates directly to the corresponding wiki page. Every line of the "Sources:" list at the end of a reply is also fully clickable. See [LLM Wiki · Click-through from chat](./wiki#click-through-from-chat).
---
## The task list, for plans that take time

View File

@ -125,65 +125,56 @@ mate:
```yaml
mate:
wiki:
chunk-size: 1200
chunk-overlap: 200
digestion-concurrency: 2
llm-model-config-id: 1
min-concept-occurrences: 2
max-page-backlinks: 50
lock-on-manual-edit: true
rebuild-sources-on-update: true
enabled: true
max-chunk-size: 30000
max-context-chars: 10000
max-pages-per-raw: 15
max-parallel-raw-materials: 3
max-parallel-phase-b-pages: 3
auto-process-on-upload: true
upload-dir: ./data/wiki-uploads
```
Eight knobs. Details in [LLM Wiki](./wiki).
Details in [LLM Wiki](./wiki).
### Tool Guard (rule-based)
```yaml
mateclaw:
tool:
guard:
enabled: true
default-policy: require_approval # `allow` / `deny` / `require_approval`
approval-timeout-seconds: 600
rules:
- tool: ShellExecuteTool
arg-pattern: "^(ls|cat|grep|find)\\s"
action: allow
priority: 100
- tool: ShellExecuteTool
action: require_approval
priority: 50
```
Tool Guard's global switch, default policy, and rules are **not configured in application.yml** — they live in the database (`mate_tool_guard_config` / `mate_tool_guard_rule`) and are edited from the admin **Security** page or via REST:
Details in [Security & Approval](./security).
| Method | Path | What it does |
|---|---|---|
| `GET` / `PUT` | `/api/v1/security/guard/config` | Global switch + default policy (`allow` / `deny` / `require_approval`) |
| `GET` | `/api/v1/security/guard/rules/builtin` | Built-in rules |
| `GET` / `POST` | `/api/v1/security/guard/rules` | List / create custom rules |
| `PUT` | `/api/v1/security/guard/rules/{ruleId}` | Update a rule |
| `PUT` | `/api/v1/security/guard/rules/{ruleId}/toggle` | Enable/disable a single rule |
Each rule matches on tool name + argument pattern and yields an `allow` / `deny` / `require_approval` action, ordered by priority. Details in [Security & Approval](./security).
### File Guard
File Guard has two layers:
1. **Allowed / denied path rules** — like Tool Guard, stored in the database and edited from the admin **Security** page; REST is `GET` / `PUT /api/v1/security/guard/config/file-guard`. **Not in application.yml.**
2. **Global fallback sandbox root** — the only piece that lives in application.yml. When a conversation has no per-workspace base path configured, file/shell tools are confined to this root (fail-closed default):
```yaml
mateclaw:
security:
file-guard:
enabled: true
allowed-paths:
- "${user.dir}/workspace"
- "${java.io.tmpdir}/mateclaw"
denied-paths:
- "/etc"
- "/usr"
- "${user.home}/.ssh"
- "${user.home}/.config"
workspace:
sandbox:
enabled: true # set false to restore the legacy unconstrained behaviour
root: ${user.dir}/data/workspace # fallback sandbox root, created at startup
```
Environment overrides: `MATECLAW_WORKSPACE_SANDBOX_ENABLED` / `MATECLAW_WORKSPACE_SANDBOX_ROOT`.
### JWT authentication
```yaml
mateclaw:
auth:
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-characters-long}
expiration: 86400000
sliding-window: true
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-characters-long}
expiration: 86400000
```
::: warning

View File

@ -211,7 +211,7 @@ Frontend assets can be **hot-updated independently** — a frontend-only fix doe
|-----|------|
| macOS | `~/Library/Application Support/MateClaw/data/` |
| Windows | `%APPDATA%/MateClaw/data/` |
| Linux | `~/.local/share/MateClaw/data/` |
| Linux | `~/.config/MateClaw/data/` |
Logs, workspace files, skill scripts, wiki content all live alongside the database in the same user directory. Back it up before major changes.
@ -257,9 +257,9 @@ The desktop app reads env vars the same way the standalone backend does. But the
1. Installed app bundles JRE — you don't need Java. Dev build from source: verify `java -version` shows 21+.
2. Check logs:
- macOS: `~/Library/Logs/MateClaw/`
- macOS: `~/Library/Application Support/MateClaw/logs/`
- Windows: `%APPDATA%/MateClaw/logs/`
- Linux: `~/.local/share/MateClaw/logs/`
- Linux: `~/.config/MateClaw/logs/`
3. Launch from terminal to see console output
4. Confirm backend port isn't blocked

View File

@ -226,7 +226,7 @@ You want an **allow rule**, not a blanket approval. `Settings → Security & App
### How long do pending approvals stay pending?
Default 10 minutes, then they expire and become `rejected`. Configure with `mateclaw.tool.guard.approval-timeout-seconds`.
Default 30 minutes, after which they expire and become `timeout` (the agent treats it as a denial). The timeout is set in the Tool Guard config on the admin Security page, not in application.yml.
---

View File

@ -119,6 +119,39 @@ Feels like: the worker answers a segment → pauses a beat → **keeps going**
---
## Getting unstuck
::: tip New
Long tasks don't stall because they're hard — they stall because they **get stuck**: hitting the iteration cap with nothing left to show, spinning on a broken tool until the budget is gone, or crashing a plan at a bad step and stopping dead. This group of mechanisms lets the worker pick itself back up, route around failures, and keep going without waiting for your next message.
:::
### Hard continuation on the iteration cap
Previously, if a ReAct loop ran out of `max_iterations` (finish reason `MAX_ITERATIONS_REACHED`), the goal subsystem **skipped** that run entirely — no evaluation, no continuation, the task just stopped there. Now it takes a **hard-continuation** path: it resets the iteration counter, clears the "over-limit draft", and gives the worker a **fresh full iteration budget** to carry on.
This is different from the auto-followup described above. Auto-followup triggers when the evaluator decides "not yet done." Hard continuation triggers specifically when the worker **hits the iteration cap** — it resets the iteration budget itself. Each hard continuation consumes one full iteration quota, so there is a limit: by default, at most **1** per run (compile-time hard ceiling of 3). Set to `0` to disable and restore the old behaviour (hitting the cap ends that run).
### Stall detection and re-planning
In Plan-Execute mode, an individual step may **throw an exception** or fall into a **stall** — repeating the same tool call that keeps failing, or getting back identical "no new information" results each time, burning through the tool budget and then "completing" with an empty result that poisons every downstream step that depended on it.
The runtime signs each tool response and runs a two-level check:
- **WARN**: after the same call fails several times in a row, a system hint is injected telling the model to try a different approach (each unique call gets at most one warning).
- **HALT**: if the call continues to fail after the warning, the step is marked stuck and the inner loop exits.
When a step is HALTed or throws an exception, the runtime triggers **re-planning**: the current plan is cleared, and a "completed-steps summary + failure reason + skip the bad step" context is passed back to the planning node to generate a new plan. Re-planning happens at most **1 time per run**. The UI receives a `plan_replan` event carrying the failed step index and the reason.
### Meta-tool turns don't count (iteration refunds)
Progressive disclosure tools such as `load_skill` / `enable_tool` are **configuration actions**, not real work. When every tool call in a ReAct turn is one of these meta-tools, that turn's iteration counter **is not incremented** (the iteration is refunded), preventing a model focused on loading skills from burning through its entire budget on setup steps alone. At most 3 refunds per run.
### Auto-deriving a goal from a multi-step plan
When a Plan-Execute plan has **two or more steps** and the current conversation has no active goal, the planning node **automatically creates a goal**, using the plan's steps as exit criteria, and broadcasts a `goal_created` event so the UI's goal panel refreshes. This means long plans are naturally held under the goal system's "follow-through to completion" semantics. Controlled by `mateclaw.goal.auto-goal-from-plan` (on by default).
---
## A goal is a checklist (1.5.0+)
In 1.4.0 the evaluator gave a completion score (01) and a one-line "what's missing" each turn. The problem: **what does 0.8 mean** — which boxes are done, which aren't? You couldn't see it.
@ -275,6 +308,10 @@ mateclaw:
auto-followup-cooldown-seconds: 0
# Hard cap on auto-followups within a single graph run (per-message safety net; overall budget is turnBudget).
max-followups-per-run: 8
# Max hard continuations when the iteration cap is hit per run (0 = disabled; compile-time ceiling is 3).
max-hard-continuations-per-run: 1
# Automatically derive a goal from a multi-step Plan-Execute plan when no active goal exists.
auto-goal-from-plan: true
# Model used by the evaluator. Empty = same model as the chat agent.
# Recommended: a cheap model like qwen-turbo / glm-4-flash.
evaluator-model: ""
@ -293,7 +330,7 @@ Two tables, all `mate_`-prefixed:
| `mate_agent_goal` | Goal itself; status / budgets / dual LLM counters / auto-followup config |
| `mate_agent_goal_event` | Append-only event log; powers the timeline view |
Flyway migration `V120__agent_goal.sql` (H2 + MySQL dialects).
Flyway migration `V120__agent_goal.sql` (H2 / MySQL / KingbaseES dialects).
---

View File

@ -279,10 +279,10 @@ Before v1.2.0, all employees could call every MCP tool by default — it was a g
### Three problems it solves
**Problem 1: Tool namespace collisions.**
Two MCP servers both expose `read_file` — which one wins? v1.3.0 internally uses a **stable server-prefixed callback name** (`{serverName}__{toolName}`) and persists it to `mate_mcp_server.cached_tools`. The picker shows them as `serverA__read_file` and `serverB__read_file`; the agent's prompt maps them back to original names to save tokens and avoid LLM confusion.
Two MCP servers both expose `read_file` — which one wins? v1.3.0 internally uses a **stable server-prefixed callback name** (`{serverName}__{toolName}`) and persists it to `mate_mcp_server.tools_cache_json`. The picker shows them as `serverA__read_file` and `serverB__read_file`; the agent's prompt maps them back to original names to save tokens and avoid LLM confusion.
**Problem 2: MCP server / tool rename breaks bindings.**
In v1.2.0, renaming a server orphaned every employee bound to it. v1.3.0 introduces a **persistent tool cache**: every successful list-tools writes tool metadata to a `cached_tools` JSON column on `mate_mcp_server`. When validating bindings and the server is temporarily unreachable, the cache is consulted as fallback — bindings stay marked `stale` and become live again the moment the server reconnects.
In v1.2.0, renaming a server orphaned every employee bound to it. v1.3.0 introduces a **persistent tool cache**: every successful list-tools writes tool metadata to a `tools_cache_json` JSON column on `mate_mcp_server`. When validating bindings and the server is temporarily unreachable, the cache is consulted as fallback — bindings stay marked `stale` and become live again the moment the server reconnects.
**Problem 3: Save silently accepted non-existent tool references.**
A typo'd `nonexistent-server.weird-tool` would save fine and blow up at runtime. v1.3.0 runs `AgentBindingService.validate(...)` on save:
@ -300,7 +300,7 @@ A typo'd `nonexistent-server.weird-tool` would save fine and blow up at runtime.
### Data contract
- `mate_mcp_server.cached_tools` (new column in v1.3.0): JSON array, each element `{name, description, inputSchema, lastSeenAt}`
- `mate_mcp_server.tools_cache_json` (new column in v1.3.0): JSON array, each element `{name, description, inputSchema, lastSeenAt}`
- `mate_agent_tool.tool_name`: stores the **prefixed callback name** `{serverName}__{toolName}` rather than the raw name, so a server rename surfaces immediately as an observable join miss
- `AgentBindingService.getEffectiveToolNames(agentId)` is the single source of truth for tool dispatch — runs every turn, ensuring the editor view and the runtime view always agree

View File

@ -16,7 +16,7 @@ Everything else in MateClaw is static the moment you configure it. Agents, tools
::: tip Your AI dreams about you while you sleep
That's not a marketing line. It's literal code in the `memory/dreaming/` package.
Every night at 2 AM (default; configurable) a scheduled job runs — its name is **Dreaming**. It walks every agent's conversation trail from the day, consolidates scattered signals into a coherent understanding of you, filters out one-offs and contradictions and stale facts, promotes recurring patterns into `MEMORY.md`, and appends "what it saw, what it concluded, what it rewrote" to `DREAMS.md` — a human-readable audit trail of how memory got to where it is today.
Every night at 3 AM (default; configurable) a scheduled job runs — its name is **Dreaming**. It walks every agent's conversation trail from the day, consolidates scattered signals into a coherent understanding of you, filters out one-offs and contradictions and stale facts, promotes recurring patterns into `MEMORY.md`, and appends "what it saw, what it concluded, what it rewrote" to `DREAMS.md` — a human-readable audit trail of how memory got to where it is today.
When you open MateClaw the next morning, it **picks up where yesterday left off** — not from zero.
@ -45,7 +45,7 @@ This page covers the four layers that make up memory, the files the system write
│ Updated: asynchronously, after each meaningful chat │
└────────────────────────────────────────────────────────────┘
▼ (daily at 2:00 AM, configurable)
▼ (daily at 3:00 AM, configurable)
┌────────────────────────────────────────────────────────────┐
│ 3. Nightly consolidation (Dreaming) │
│ Scans recent daily notes, finds recurring patterns, │
@ -157,7 +157,7 @@ From v1.3.0, the [workflow](./workflow) `write_memory` step can write the run's
### Daily notes
Conversation highlights archived by date, in append mode — multiple conversations in one day concatenate into the same file. Not injected into the system prompt (`enabled=false`). They exist so the consolidator has something to scan at 2 AM.
Conversation highlights archived by date, in append mode — multiple conversations in one day concatenate into the same file. Not injected into the system prompt (`enabled=false`). They exist so the consolidator has something to scan at 3 AM.
---
@ -197,7 +197,7 @@ Only files with `enabled=true` are included.
Three-stage defense:
**Stage 1 — proactive compression.** When estimated total exceeds 75% of the budget (default window 128k tokens), the system calls the LLM to summarize earlier turns. The most recent 2 turns (4 messages) survive verbatim. The summary is cached for 30 minutes.
**Stage 1 — proactive compression.** When estimated total exceeds 75% of the budget (default window 128k tokens), the system calls the LLM to summarize earlier turns. The tail is retained dynamically based on a token budget, with a floor controlled by `preserve-recent-pairs` and `protect-last-min-messages` (whichever is larger; defaults to at least 10 messages). The summary is cached for 30 minutes.
**Stage 2 — emergency recovery.** If the LLM still returns context-too-large, the system stops calling the LLM. It discards older messages, keeps the last 2 turns, and retries once.
@ -281,7 +281,7 @@ The third layer runs on a schedule. Its job is to watch daily notes pile up and
### Trigger methods
- **Automatic** — every agent has a row in the system's scheduled jobs, set to run nightly at 2 AM
- **Automatic** — every agent has a row in the system's scheduled jobs, set to run nightly at 3 AM
- **Manual**`POST /api/v1/memory/{agentId}/emergence`
### Why it's not recursive
@ -327,18 +327,70 @@ What it does:
- **Monthly archive** — old reports roll into a compressed monthly archive, browsable in the timeline
- **Memory Browser** — timeline, facts, contradictions, diff viewer, and a trust bar across the top
Enable in `application.yml`:
Enable in `application.yml` (these flags all live under `mate.memory`, grouped by phase):
```yaml
mateclaw:
mate:
memory:
dream-v2:
enabled: true
fact-projection: true
contradictions: true
morning-card: true
# Phase 1: turn-by-turn lifecycle bus
lifecycle-mediator-enabled: true
dream:
focused-enabled: true # focused dream endpoint
archive-enabled: true # monthly archive rotation
archive-keep-days: 30
max-candidates-per-dream: 100
# Phase 2: SOUL auto-evolution
soul-update-interval: 20 # one SOUL.md rewrite every 20 writes (0 = off)
# Phase 3: fact projection
fact:
projection-enabled: true
projection-rebuild-cron: "0 */30 * * * ?"
contradiction-check-enabled: false # contradiction detection (experimental, off by default)
trust-half-life-days: 60
forget-enabled: true # the "Forget" button in the UI
```
> The morning card is an endpoint (`GET /api/v1/memory/{agentId}/dream/morning-card`), not a standalone flag — it has data as long as the fact-projection + dream lifecycle is on.
---
## Bounding always-on memory size
::: tip New
The memory that gets injected into the system prompt on every turn — `user` / `feedback` structured entries, `PROFILE.md`, `MEMORY.md` — has a silent problem: **it only ever grows**. As entries accumulate, each round's token cost climbs steadily. This group of mechanisms puts deterministic size limits on always-on memory.
:::
Three layers, each covering a different stage:
### Injection budget (truncate at inject time, disk untouched)
When `user` / `feedback` structured entries are injected into the system prompt they are sorted by their `Updated:` date (LRU) and only the most recent N are kept. Entries beyond the limit are **discarded at inject time** — the on-disk file is not modified — and the block footer discloses how many were omitted.
- `mate.memory.system-block-max-chars` (default `4000`): character cap for the always-on structured block; when exceeded, entries are dropped oldest-first. `0` = unlimited.
- `mate.memory.system-block-max-entries-per-type` (default `40`): maximum entries injected per type (`user` / `feedback`). `0` = unlimited.
### Nightly consolidation (shrink files at the storage layer)
The injection budget truncates at inject time, but the on-disk files keep growing. **Consolidation** compacts them at the storage layer: a nightly job (default 03:30, on its own schedule independent of [Dreaming](#consolidation-and-dreaming)) walks each agent's shared bucket and all per-owner buckets, and when entry count exceeds the threshold it calls the LLM to merge near-duplicate or stale entries and writes the result back.
One **safety invariant**: the entry count after consolidation can only decrease — if the model hallucinates additional entries, that write is skipped entirely.
- `mate.memory.structured-consolidation-enabled` (default `true`): when off, only the injection budget applies — no storage-side merging.
- `mate.memory.structured-consolidation-min-entries` (default `8`): buckets with fewer entries than this skip the LLM call to save cost.
- `mate.memory.structured-consolidation-cron` (default `"0 30 3 * * ?"`): independent schedule; does not affect Dreaming.
- `mate.memory.structured-consolidation-max-owners-per-run` (default `50`): maximum owner buckets processed per agent per run; the rest are deferred to the next run. `0` = unlimited.
Manual trigger: `POST /api/v1/memory/{agentId}/structured-consolidation` — returns stats including `ownersConsolidated`, `updated`, `entriesBefore`, and `entriesAfter`.
> Don't confuse this with [Dreaming](#consolidation-and-dreaming): Dreaming merges daily notes into `MEMORY.md` (promoting what matters); consolidation deduplicates and trims `user` / `feedback` structured entries. Two different jobs, two different schedules.
### File ceiling (deterministic hard cap at rewrite time)
`PROFILE.md` and `MEMORY.md` are fully rewritten by the LLM. The prompt asks for conciseness, but there is no hard constraint, so files can still grow unbounded. The file ceiling is the **deterministic fallback at write time**: if the content exceeds the budget it is truncated at the last `##` section boundary that still fits (preserving the head of the file), and a truncation marker is appended.
- `mate.memory.profile-max-chars` (default `4000`): hard character cap for PROFILE.md. `0` = unlimited.
- `mate.memory.memory-md-max-chars` (default `8000`): hard character cap for MEMORY.md. `0` = unlimited.
---
## Agents reading and writing their own memory
@ -470,6 +522,19 @@ mate:
# PERSONAL memory and recall filters by owner_key. Set false for the old shared behavior (all writes
# to TEAM). The bare Java-property default is false.
lifecycle-mediator-enabled: true
# --- always-on memory size bounds ---
# Injection budget: always-on user/feedback structured block (LRU truncation at inject time, 0 = unlimited)
system-block-max-chars: 4000
system-block-max-entries-per-type: 40
# Nightly consolidation: merge/deduplicate user/feedback entries at the storage layer (independent of dreaming)
structured-consolidation-enabled: true
structured-consolidation-min-entries: 8
structured-consolidation-cron: "0 30 3 * * ?"
structured-consolidation-max-owners-per-run: 50
# File ceiling: hard cap applied when PROFILE.md / MEMORY.md are rewritten (section boundary, 0 = unlimited)
profile-max-chars: 4000
memory-md-max-chars: 8000
```
Prefix: `mate.memory`.
@ -495,6 +560,7 @@ mate:
|--------|------|---------|
| POST | `/api/v1/memory/{agentId}/emergence` | Manually trigger consolidation |
| POST | `/api/v1/memory/{agentId}/summarize/{conversationId}` | Manually trigger extraction |
| POST | `/api/v1/memory/{agentId}/structured-consolidation` | Manually trigger user/feedback structured-entry consolidation |
| GET | `/api/v1/memory/{agentId}/dreaming/status` | Last run, next run, latest DREAMS.md entry |
---

View File

@ -23,8 +23,8 @@ MateClaw doesn't care which LLM you use. It talks to every mainstream provider t
| **xAI / Grok** | Grok 3, Grok 4 | openai | OpenAI-compatible (base URL + API key); xAI brand icon in the UI |
| **DeepSeek** | deepseek-chat, deepseek-coder, **DeepSeek V4 flash + pro** (thinking-mode) | openai | OpenAI-compatible |
| **Kimi (Moonshot)** | moonshot-v1-8k/32k/128k | openai | OpenAI-compatible |
| **Zhipu AI** | GLM-5-Turbo, GLM-5V-Turbo, GLM-5, GLM-5.1 | openai | OpenAI-compatible |
| **MiniMax** | abab6.5, abab5.5; expanded video catalog + CN endpoint | openai | OpenAI-compatible |
| **Zhipu AI** | GLM-5-Turbo, GLM-5V-Turbo, GLM-5, GLM-5.1, **GLM-5.2** | openai | OpenAI-compatible; CN + international standard endpoints plus two Coding Plan subscription endpoints |
| **MiniMax** | abab6.5, abab5.5; expanded video catalog + CN endpoint | anthropic | Anthropic Messages API-compatible (endpoint `/anthropic`) |
| **SiliconFlow CN/INTL** | Routed inference across hosted models | openai | Two endpoints, OpenAI-compatible |
| **OpenCode** | Code-tuned routing | openai | OpenAI-compatible |
| **OpenRouter** | 200+ models with free tier | openai | Routes to any upstream with one key |
@ -45,8 +45,8 @@ Five protocols cover everything:
| Protocol | Used by |
|----------|---------|
| **OpenAI** | OpenAI, Kimi, DeepSeek, MiniMax, Zhipu, OpenRouter, LM Studio, llama.cpp, MLX |
| **Anthropic** | Claude family |
| **OpenAI** | OpenAI, Kimi, DeepSeek, Zhipu, OpenRouter, LM Studio, llama.cpp, MLX |
| **Anthropic** | Claude family, MiniMax |
| **DashScope** | Qwen family |
| **Gemini** | Google Gemini family |
| **Ollama** | Locally hosted models via Ollama |
@ -387,7 +387,7 @@ Every provider you add joins an `AvailableProviderPool` that's probed at startup
- **Automatic fallback** — if the primary provider returns an `AUTH_ERROR`, `BILLING`, `MODEL_NOT_FOUND`, `NETWORK`, or `5xx`, the runtime rolls forward to the next provider in the chain instead of bubbling up the error
- **Per-agent priority** — bind an agent to "OpenAI first, then Anthropic, then DashScope" via the drag-to-reorder editor in `Settings → Models`
- **Live pool state** — green / amber / red badges show each provider's health
- **4-protocol probe** — DashScope, OpenAI-compatible, Anthropic, Ollama-style
- **5-protocol probe** — DashScope, OpenAI-compatible, Anthropic, Gemini, Ollama-style
- **Manual reprobe + auto-reprobe on config change** — no restart after rotating a key
- **Egress sanitizer** — provider-specific options (e.g., `reasoning_effort` for OpenAI reasoning models) are stripped at egress when failing over to a provider that doesn't support them, so leaked options can't 400 the fallback
- **UI distinguishes 401 from session expiry** — provider auth errors and user session expiry now show different messages with different remediation

View File

@ -2,7 +2,7 @@
Speech, music, images, video — all first-class in MateClaw, not tacked on.
Most AI products treat multimodal generation as a plugin you bolt on later. MateClaw ships with it as core infrastructure: **six image providers, four video providers, three TTS backends, three STT backends, and two music providers**, all unified behind a single tool interface so agents can call any of them without knowing which vendor is underneath.
Most AI products treat multimodal generation as a plugin you bolt on later. MateClaw ships with it as core infrastructure: **six image providers, six video providers, three TTS backends, two STT backends, and two music providers**, all unified behind a single tool interface so agents can call any of them without knowing which vendor is underneath.
Configure once. Use everywhere.
@ -100,7 +100,7 @@ Text-to-3D and image-to-3D both work; output is a `.glb` rendered inline by `<mo
- **DashScope CosyVoice** — Chinese + English, natural prosody
- **OpenAI TTS** — alloy, echo, fable, onyx, nova, shimmer
- **MiniMax T2A** — Chinese voices with emotion tags
- **Edge TTS** — free, no API key required; wide voice selection
Click the speaker icon on any assistant message to read it aloud. The voice is whichever TTS provider is active in Settings.
@ -143,12 +143,8 @@ Every multimodal capability is exposed as a tool:
| Tool | Signature |
|------|-----------|
| `image_generate` | `(prompt, style?, size?)` |
| `image_edit` | `(image_id, prompt)` — where the provider supports it |
| `video_generate` | `(prompt, duration?)` |
| `video_from_image` | `(image_id, prompt)` |
| `music_generate` | `(prompt, style?, lyrics?)` |
| `tts_synthesize` | `(text, voice?)` |
| `stt_transcribe` | `(audio_id, language?)` |
Agents call them exactly like any other tool. The tool layer handles provider selection, retries, async polling, and attachment binding.

View File

@ -68,7 +68,7 @@ Each of those has its own page in the sidebar when you're ready to go deeper.
First run should Just Work. If it didn't:
- **Installer won't launch** — On Windows, right-click → Properties → Unblock. On macOS, allow the unsigned app in System Settings → Privacy & Security.
- **Backend never boots** — Check `~/.mateclaw/logs/app.log` (Windows: `%USERPROFILE%\.mateclaw\logs\`). Nine times out of ten it's a port conflict on 18088.
- **Backend never boots** — Check the log file (macOS: `~/Library/Application Support/MateClaw/logs/mateclaw.log`; Windows: `%APPDATA%\MateClaw\logs\mateclaw.log`). The desktop app picks a dynamic port — any port conflict is reported clearly in the log.
- **Model call fails** — Wrong API key or network can't reach the provider. Go back to Settings, re-verify the key, or try a different provider.
- **UI is blank** — Hard-refresh with Ctrl/Cmd+Shift+R. Electron caches aggressively.
- **Still broken** — Open an issue on [GitHub](https://github.com/matevip/mateclaw/issues) with the tail of `app.log`. We read them.

View File

@ -10,7 +10,7 @@ For historical diffs, check the corresponding git tag. For the "why" behind a fe
| Version | Date | Highlights |
|---------|------|------------|
| [v1.6.0](./releases/1.6.0) | 2026-06-14 | Runs on domestic databases — KingbaseES (人大金仓) + PostgreSQL (one shared PostgreSQL-family migration tree · opt-in Kingbase driver · least-privilege Docker roles) · New senses & hands (image kept in context across turns + `image_analyze` · `execute_code` runs agent-authored code) · You shape the employee (AGENTS.md editor + About You identity + runtime model identity + KB-scope binding + roster tags) · Wiki Sources tab (raw materials + watcher unified, per-KB auto-sync, multi-path/glob, pageType form editor) · Global outbound HTTP/SOCKS proxy · Deterministic Markdown answers · Claude Fable 5 |
| [v1.6.0](./releases/1.6.0) | 2026-06-22 | Runs on domestic databases — KingbaseES (人大金仓) + PostgreSQL (one shared PostgreSQL-family migration tree · opt-in Kingbase driver · least-privilege Docker roles) · New senses & hands (image kept in context across turns + `image_analyze` · `execute_code` runs agent-authored code) · You shape the employee (AGENTS.md editor + About You identity + runtime model identity + KB-scope binding + roster tags) · Wiki Sources tab (raw materials + watcher unified, per-KB auto-sync, multi-path/glob, pageType form editor) · Global outbound HTTP/SOCKS proxy · Deterministic Markdown answers · Claude Fable 5 |
| [v1.5.0](./releases/1.5.0) | 2026-06-04 | Goals grew a checklist — from "a score" to "ticked boxes" (checklist + Evaluator SPI + deterministic completion) · The Wiki learned to maintain itself (`[[wikilinks]]` + cascade rename/delete link-fix + broken-link lint · fact/experience layers + staleness propagation · pageType profiles & per-agent permissions · processing pipelines · local-directory knowledge source with scheduled incremental sync) · Per-owner memory isolation (owner_key + personal/team/global scopes + third-party endUserId passthrough) · Each employee binds a primary KB · Preferred provider drives the primary model + Claude Opus 4.8 |
| [v1.4.0](./releases/1.4.0) | 2026-05-23 | Persistent Goals — an employee locks a goal and follows it to done on its own · Subagent delegation became a tree (recursive 3 levels + async + digital-employee builder) · Progressive tool/skill disclosure (`enable_tool` + `load_skill`) · Workspace RBAC (4 roles + capability gating) · Feishu as a first-class citizen (interactive / approval / streaming cards + voice / file / audio / video + channel-native tools) |
| [v1.3.0](./releases/1.3.0) | 2026-05-13 | Year one of workflow — 7 step modes assemble employees into business processes · 6 trigger patterns make events drive workflows · Wiki promoted from search index to processing pipeline (user templates + cross-material aggregator + reverse citations) · Per-agent MCP tool binding + multimodal sidecar routing · 4 JVM-native document generation tools + image edit |

View File

@ -73,11 +73,10 @@ MateClaw does sliding-window token renewal. When a token's remaining lifetime fa
```yaml
mateclaw:
auth:
jwt:
secret: your-secret-key-must-be-at-least-32-characters-long
expiration: 86400000 # 24h in milliseconds
sliding-window: true
jwt:
secret: your-secret-key-must-be-at-least-32-characters-long
expiration: 86400000 # token lifetime (ms, default 24h)
renewal-threshold: 7200000 # sliding renewal when remaining lifetime drops below this (ms)
```
::: warning
@ -267,7 +266,7 @@ The current web path has no write-style `POST /api/v1/approvals/{id}/resolve` en
| `tool_name` | The tool being called |
| `tool_args` | JSON of the actual arguments |
| `rule_id` | Which rule triggered the approval |
| `status` | `pending` / `approved` / `rejected` / `expired` |
| `status` | `pending` / `approved` / `denied` / `consumed` / `timeout` / `superseded` |
| `requested_at` | When the approval was created |
| `resolved_at` | When the user decided |
| `resolved_by` | Who decided |
@ -279,7 +278,7 @@ Sometimes the agent's tool arguments contain placeholders — a computed file pa
### Timeouts
Pending approvals expire after a configurable timeout (default: 10 minutes). Expired approvals become `rejected`, and the agent treats expiry the same as user rejection.
Pending approvals expire after a configurable timeout (default: 30 minutes). Expired approvals become `timeout`, and the agent treats expiry the same as user rejection.
### Notifications
@ -348,20 +347,14 @@ Allow / Deny
### Configuration
Allowed / denied path rules live in the database and are managed from the admin Security page or `GET` / `PUT /api/v1/security/guard/config/file-guard`**not application.yml**. The only YAML piece is the **global fallback sandbox root** that file/shell tools are confined to when a conversation has no per-workspace base path:
```yaml
mateclaw:
security:
file-guard:
enabled: true
allowed-paths:
- "${user.dir}/workspace"
- "${java.io.tmpdir}/mateclaw"
denied-paths:
- "/etc"
- "/usr"
- "${user.home}/.ssh"
- "${user.home}/.config"
- "${user.home}/.env"
workspace:
sandbox:
enabled: true # set false to restore the legacy unconstrained behaviour
root: ${user.dir}/data/workspace # fallback sandbox root, created at startup
```
Visual editor on `Settings → Security & Approval → File Guard`.
@ -534,42 +527,31 @@ server {
## Security configuration reference
application.yml carries **only two** security-related blocks — JWT and the filesystem sandbox:
```yaml
mateclaw:
auth:
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-chars}
expiration: 86400
sliding-window-ratio: 0.5
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-chars}
expiration: 86400000 # token lifetime (milliseconds)
renewal-threshold: 7200000 # sliding renewal when remaining lifetime drops below this (ms)
tool:
guard:
# Global fallback sandbox for file/shell tools: when a conversation has no
# per-workspace base path, all file/shell operations are confined to this
# root (fail-closed default)
workspace:
sandbox:
enabled: true
default-policy: require_approval
approval-timeout-seconds: 600
notifications:
email-enabled: false
dingtalk-enabled: false
security:
file-guard:
enabled: true
allowed-paths:
- "${user.dir}/workspace"
denied-paths:
- "/etc"
- "${user.home}/.ssh"
audit-log:
enabled: true
retention-days: 90
skill:
security-scan:
enabled: true
block-critical: true
root: ${user.dir}/data/workspace
```
**Everything else is managed in the database — from the admin Security page (or `/api/v1/security/guard/*`), not application.yml:**
- **Tool Guard** switch, default policy, rules, approval timeout (default 30 minutes), notification channels → `mate_tool_guard_config` / `mate_tool_guard_rule`
- **File Guard** allowed / denied path rules → `GET` / `PUT /api/v1/security/guard/config/file-guard`
- **Audit log** is always on, written row by row to `mate_tool_guard_audit_log`, exportable as CSV
- **Skill security scan** findings surface during skill installation; CRITICAL findings are blocked by default
---
## Next

View File

@ -281,10 +281,8 @@ curl -X POST http://localhost:18088/api/v1/skills \
}'
# Enable / disable
curl -X PUT http://localhost:18088/api/v1/skills/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"enabled": true}'
curl -X PUT "http://localhost:18088/api/v1/skills/1/toggle?enabled=true" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Delete
curl -X DELETE http://localhost:18088/api/v1/skills/1 \

View File

@ -6,7 +6,7 @@ Left to its own devices, a language model is a pattern-matcher wrapped in text.
Tools are how MateClaw fixes this. Each tool is a concrete operation the agent is allowed to invoke — read a file, search the web, execute a shell command, extract text from a PDF, delegate to another agent. When the agent decides it needs one, it emits a **tool call**, the runtime executes it, and the result comes back as an **observation**.
Fourteen tools ship built-in. Unlimited more can be added through MCP servers, custom skill scripts, or your own `@Tool`-annotated Spring beans.
Twenty tools ship built-in. Unlimited more can be added through MCP servers, custom skill scripts, or your own `@Tool`-annotated Spring beans.
---
@ -319,10 +319,8 @@ curl http://localhost:18088/api/v1/tools \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Enable / disable
curl -X PUT http://localhost:18088/api/v1/tools/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"enabled": false}'
curl -X PUT "http://localhost:18088/api/v1/tools/1/toggle?enabled=false" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Set disclosure tier for a builtin or channel tool
curl -X PUT http://localhost:18088/api/v1/tools/1/disclosure-tier \

View File

@ -51,7 +51,7 @@ Implemented in `TriggerPatternMatcher.java`. Each pattern matches its `pattern_j
| `cron` | On a cron expression (**does not flow through ingest**; runs from the scheduler) | `cronExpression`, `timezone` | Reuses the `cron/` module's ShedLock + Spring TaskScheduler; **does NOT write into mate_cron_job, does NOT call CronJobService** |
| `webhook` | Generic event passthrough (**v0 does no further filtering** — secret check happens at the channel layer; the trigger itself just matches `patternType=webhook`) | (none in v0) | Through the unified `POST /api/v1/triggers/events` entry + envelope wrap |
| `channel_message` | Channel receives a message | `channelType` (optional, compared against envelope `data.channelType`), `senderEquals` (optional, exact sender id match) | Side-channel through `ChannelWebhookController`; original routing unaffected |
| `agent_lifecycle` | Agent lifecycle events | `agentId` (optional), `phase` (optional: `spawned` / `terminated` / `crashed`) | Hangs off `ReActLifecycleListener` |
| `agent_lifecycle` | Agent lifecycle events | `agentId` (optional), `phase` (optional: `spawned` / `enabled` / `disabled` / `terminated`; `crashed` reserved for a future release) | Hangs off `AgentLifecycleEventBridge` |
| `content_match` | Substring must appear in the envelope content | `substring` (**required**, case-insensitive contains-match against envelope `data.content`) | Generic content filter; the event source is whatever fed the envelope |
| `workflow_completion` | A workflow run reaches a terminal state | `sourceWorkflowId` (optional), `stateFilter` (optional: `completed` / `failed` / `any`) | Listens to `WorkflowEngine` terminal events; recursion guard below |
@ -118,7 +118,7 @@ The drawer has structured forms per pattern type — no hand-written `pattern_js
- `cron` → cron expression input + timezone dropdown + next-fire preview. The expression can be typed by hand, or click the edit button beside the input to open the **visual cron editor** (see below)
- `channel_message` → channel type (optional) + sender id exact-match (optional)
- `agent_lifecycle` → agent (optional) + phase: `spawned` / `terminated` / `crashed` (optional)
- `agent_lifecycle` → agent (optional) + phase: `spawned` / `enabled` / `disabled` / `terminated` (optional)
- `content_match` → substring (**required**), matched case-insensitively against envelope `data.content`
- `workflow_completion` → upstream workflow (optional) + state filter: `completed` / `failed` / `any` (optional)
- `webhook` → no extra fields in v0 (transparent passthrough)
@ -272,7 +272,7 @@ v0 deliberately **does not persist envelopes inside `mate_trigger_event`** — f
- **No visualization of trigger → workflow chains** — multiple triggers dispatching to the same workflow appear as two independent lists in the UI
- **No inter-trigger priority / dependency** — when an event hits multiple triggers, dispatches are serialized by ascending DB id
- **No dedicated webhook entry / IP allowlist** — there's no `/webhook/{slug}` route in v0; `/events` is the unified entry. Stricter IP control belongs at the front-door nginx / gateway
- **`agent_lifecycle` granularity is `spawned` / `terminated` / `crashed`** — not "started / completed / failed" per step
- **`agent_lifecycle` phase only covers CRUD operations** — the phases actually emitted are `spawned` / `enabled` / `disabled` / `terminated`; `crashed` (runtime error hook) is reserved for a future release and is never fired today
- **No event replay**`mate_trigger_event` only persists dedup metadata, not envelopes; "redispatch this event" requires the upstream source to re-emit
---
@ -284,7 +284,7 @@ v0 deliberately **does not persist envelopes inside `mate_trigger_event`** — f
| Cron trigger doesn't fire | 1) `enabled=true`? 2) Does the cron expression + timezone parse to a next-fire time? The editor previews it. 3) Is the ShedLock held by another instance? Check the `shedlock` table. |
| `POST /events` returns 200 but no dispatch happens | The response body contains a per-trigger fire / drop summary — look for `BOT_SELF` / `RATE_LIMITED` / `DEDUPED` / `PATTERN_MISMATCH` |
| `channel_message` doesn't fire | 1) Does the envelope's `data.channelType` match this trigger's `pattern_json.channelType`? 2) `bot_self_filter=true` and a non-default `BotSelfFilter` is filtering it? 3) For `content_match`, the `substring` field must actually appear in `data.content` |
| `agent_lifecycle` doesn't fire | Confirm `pattern_json.phase` is `spawned` / `terminated` / `crashed` (not `started` / `completed` / `failed`) |
| `agent_lifecycle` doesn't fire | Confirm `pattern_json.phase` is one of `spawned` / `enabled` / `disabled` / `terminated` (not `started` / `completed` / `failed`); `crashed` is reserved for a future release and is never emitted today |
| Cron trigger stops firing after restart | Look at startup log for `syncFromDatabase()` errors; common cause is corrupted `pattern_json` failing deserialization |
| `mate_trigger.last_error` reads `"rate-limited"` | Raise `rate_limit_per_min`, or split the trigger into multiple ones partitioned by group |
| `bot_self_filter=true` doesn't seem to filter | Confirm a non-noop `BotSelfFilter` Spring Bean is registered — the default `NoopBotSelfFilter` always returns `false` |

View File

@ -182,7 +182,7 @@ Create a role-specific agent → install skills → connect MCP servers → conf
| Symptom | Most likely cause |
|---------|-------------------|
| Backend won't start | Port 18088 is taken. Check `~/.mateclaw/logs/app.log` |
| Backend won't start | Port 18088 is taken. Check `<userData>/logs/mateclaw.log` (macOS: `~/Library/Application Support/MateClaw/logs/mateclaw.log`) |
| Model call fails | Wrong API key or network issue. Go back to Settings |
| UI is blank | Ctrl+Shift+R to hard-refresh |
| Ollama says "does not support tools" | Switch to a function-calling model (qwen3, llama3.1:8b+) |

View File

@ -111,7 +111,7 @@ Fix: magic-byte sniff:
- Other common formats (PNG / JPEG / MP4 / MP3 / WAV) all recognized
- Truly unknown → keep `.bin`, don't pretend it's something else
Implemented in `WeComChannelAdapter.sniffMagic()` + `refineZipKind()`.
Implemented in `MediaTypeSniffer.sniff()` + `MediaTypeSniffer.refineZipKind()`, called from `InboundMediaDownloader.download()`.
---

View File

@ -91,7 +91,7 @@ Ingestion is idempotent. Re-run it on the same material and existing pages get u
Eager ingest runs in two phases for an order-of-magnitude speedup:
- **Phase A (route)** — extracts metadata and concept routing, deciding which pages each chunk feeds into.
- **Phase B (merge)** — generates pages in parallel, 60+ at a time. Each raw material gets its own **progress bar** — no more staring at "processing…" wondering what's happening.
- **Phase B (merge)** — generates pages in parallel across multiple raw materials simultaneously; the degree of concurrency is tunable. Each raw material gets its own **progress bar** — no more staring at "processing…" wondering what's happening.
**Resumable**: interrupted mid-import? Hit "Reprocess" and only the unfinished pages re-run; everything already produced stays put. Documents larger than the embedding model's context get mean-pool sub-segmented automatically.
@ -285,7 +285,7 @@ The bound KB doesn't just contribute summaries — it also contributes a small,
- **Recent changes** — page creations and compilations since the last rebuild
- **Active threads** — open questions and unresolved decisions
The rebuilder fires asynchronously when a conversation ends (`ConversationCompletedEvent`), debounced inside a configurable window (default ~30 s) so a flurry of short turns doesn't churn LLM calls. An admin can also trigger a rebuild manually — that path bypasses the debounce.
The rebuilder fires asynchronously when a conversation ends (`ConversationCompletedEvent`), debounced inside a configurable window (default 5 min) so a flurry of short turns doesn't churn LLM calls. An admin can also trigger a rebuild manually — that path bypasses the debounce.
The injection is gated by the `wiki.hot_cache.enabled` feature flag (off → empty injection) and is capped at the **two highest-priority KBs** per agent so the system prompt stays small.
@ -389,8 +389,8 @@ that teaches wikilink syntax doesn't accidentally lint itself).
Each KB shows a banner at the top of the workspace. Click "Scan dead
links" to start a job:
| Method | Path | What it does |
|---|---|---|
| Endpoint | What it does |
|---|---|
| `POST /api/v1/wiki/knowledge-bases/{kbId}/lint/broken-links` | Starts a job (async, job-based). Returns `{jobId, status, startedAt}`. Idempotent — repeat POSTs while a job is in flight return the same id |
| `GET .../lint/broken-links` | Returns the latest completed scan as a per-page aggregate |
| `GET .../lint/broken-links/jobs/{jobId}` | Status check for a specific job |
@ -454,6 +454,12 @@ lookup is strict case-insensitive exact (no canonical fuzzing), so
if the LLM wrote a slug that doesn't exist you see the toast rather
than getting silently redirected to a similarly-named page.
### `[n]` citation markers are clickable too
When an agent answers using wiki retrieval, the reply ends with a "Sources:" list (`[1] Title — Section — page N`). The **inline citation markers** (`[1]`, `[2]`, etc.) are now themselves clickable, and every line of the sources list is also fully clickable — either takes you directly to the corresponding wiki page. The navigation logic is shared with wikilinks above: title-based cross-KB lookup, with 0 / 1 / multiple-hit behaviour of toast / direct navigation / picker respectively.
The backend normalises source lines into a canonical format (adding the "Sources:" header when missing, rewriting legacy formats in-place) so the frontend can reliably identify them and wire up the `[n]` markers as links. This requires the KB to have Wiki enabled and the material to have been ingested.
### Phase roadmap (all phases landed)
| Phase | Key changes |
@ -579,7 +585,7 @@ When the `wiki.ocr.enabled` feature flag is on, MateClaw runs every uploaded ima
|---|---|---|
| `dashscope-vision` | `qwen-vl-max` | DashScope OpenAI-compatible endpoint; reuses the DashScope provider configured in the UI |
| `zhipu-vision` | `glm-5v-turbo` | Zhipu BigModel; OpenAI-compatible |
| `volcano-doubao-vision` | configurable | ByteDance Volcano Doubao vision |
| `doubao-vision` | configurable | ByteDance Volcano Doubao vision |
Providers are auto-detected by order. Configure their keys / base URLs in `Settings → Models` like any other provider — the vision pipeline picks up the credentials from there.
@ -625,13 +631,47 @@ This UI used to be cosmetic — the Java side dropped the config on the floor an
---
## Knowledge graph: the entity layer
::: tip New
The page layer answers "which page covers this topic." The **entity layer** answers "who relates to whom, and how." During ingest, alongside chunking, embedding, and page writing, the system can run an additional **entity extraction** pass: pulling out named entities — people, organisations, locations, events, products, concepts — and the typed relationships between them, connecting everything into a navigable knowledge graph.
:::
### What gets extracted, and when
Two kinds of objects are produced:
- **Entities (nodes)** — each entity has a canonical name, aliases, a description, a salience score, a mention count, and an embedding vector used for near-duplicate merging. Six built-in types: `person` / `organization` / `location` / `event` / `product` / `concept`.
- **Relations (edges)** — subject → predicate → object triples, where the predicate is a snake\_case phrase (`works_for`, `located_in`, `founded`, etc.). Each relation carries an evidence quotation.
Extraction runs after embeddings are written, as an **independent async pass** that does not block page generation. It is **incremental** by default — chunks that have already been processed are skipped. Entity normalisation works in three tiers: an in-process runtime cache → exact database key lookup → cosine similarity against stored embeddings (threshold 0.92) to merge near-synonyms. "阿里巴巴" and "Alibaba" collapse to the same node.
Extraction only runs when **entity extraction is enabled** in the KB configuration. To force a full re-extraction immediately: `POST /api/v1/wiki/kb/{kbId}/entities/extract?force=true` — force mode captures a new graph before replacing the old one, so a complete LLM failure leaves the existing graph intact.
### Configuring entity types
In `Wiki → Config → Entity Extraction`: toggling the switch on reveals a tag editor (multi-select, searchable, inline create). The six built-in types are suggested by default; you can type a custom type (e.g. `technology`, `law`) and press Enter to add it. Leaving the list empty falls back to the built-in six. The type list is stored in the KB's `configContent` JSON under the `entityTypes` key.
### Exploring the graph
The Wiki graph view toolbar gains a **Page graph / Entity graph** toggle. In entity graph mode:
- The full graph is loaded in one call (`GET /api/v1/wiki/kb/{kbId}/entity-graph`). Nodes are coloured by type; labels are always visible.
- A **type legend** at the top lists every entity type present in the graph. Clicking a type label toggles that type's nodes on or off — useful when the graph is large.
- Clicking a node loads its **ego-graph**: the right-hand panel lists the entity's aliases, its relations, and the **wiki pages that mention it** (each is a clickable link).
- Colours follow a shared earthy palette that matches the page-type graph. Because the graph renders on canvas and cannot read CSS variables, the palette is resolved from the current theme's computed styles at runtime, so both light and dark mode display correct label colours.
The three underlying tables are described in the [Data model](#data-model-if-you-re-curious) section below.
---
## Data model (if you're curious)
Nine tables:
Core tables (see feature sections for the complete list):
| Table | Purpose |
|---|---|
| `mate_wiki_knowledge_base` | One row per KB. Owner, name, description, config JSON (`ingestMode`, `wikiDefaultModelId`, `stepModels`, fallback chain). |
| `mate_wiki_knowledge_base` | One row per KB. Owner, name, description, config JSON (`ingestMode`, `wikiDefaultModelId`, `stepModels`, `entityExtractionEnabled`, `entityTypes`, fallback chain). |
| `mate_wiki_raw_material` | One row per upload. Status, byte hash, source path, last successfully-processed hash. |
| `mate_wiki_page` | One row per generated page. Title, summary, body, `source_raw_ids` (provenance), `page_type`, `locked`, version, plus `embedding` / `embedding_model` / `embedding_text_version` so transformation synthesis pages enter semantic search directly. |
| `mate_wiki_chunk` | One row per chunk. content + hash + offsets + embedding, plus `page_number`, `header_breadcrumb`, `source_section`, `token_count`. |
@ -640,6 +680,9 @@ Nine tables:
| `mate_wiki_image_caption_cache` | SHA-256 keyed cache of vision-extracted captions. `caption`, `visible_text`, `mime_type`, `capture_model`, `provider_id`, `duration_ms`, `hit_count`. |
| `mate_wiki_transformation` | One row per transformation template. `name`, `title`, `description`, `prompt_template`, `model_id`, `apply_default`, `output_target`, `output_format`, `output_schema`. `kb_id=NULL` = workspace-wide. |
| `mate_wiki_transformation_run` | One row per template execution. `status`, `output`, `error`, `duration_ms`, `model_id`, `triggered_by`, `input_tokens`, `output_tokens`, `total_tokens`, `output_page_id`. |
| `mate_wiki_entity` (V148) | One row per entity. Canonical name, type, aliases JSON, `salience`, `mention_count`, `embedding` (used for near-duplicate merging). |
| `mate_wiki_entity_mention` (V149) | One occurrence of an entity in a chunk. `entity_id`, `chunk_id`, `page_id` (back-reference to the wiki page), `surface_form`, `evidence`. |
| `mate_wiki_entity_relation` (V150) | Entity relation triple. `subject_entity_id`, `predicate`, `object_entity_id`, `evidence`, `evidence_chunk_id`. |
`mate_wiki_page` also carries two protection flags:

View File

@ -290,7 +290,6 @@ curl -X PUT http://localhost:18088/api/v1/workspaces/1/members/42 \
| `workspace_id` | FK to `mate_workspace` |
| `user_id` | FK to `mate_user` |
| `role` | `owner` / `admin` / `member` / `viewer` |
| `joined_at` | When the user joined this workspace |
| `create_time` / `update_time` | Timestamps |
---

View File

@ -107,7 +107,7 @@ head:
## 多 Agent 并行委派
一个 Agent 不是孤军作战。一个 Agent 可以把任务委派给另一个——或者**同时委派给三个**
一个 Agent 不是孤军作战。一个 Agent 可以把任务委派给另一个——或者**同时委派给多个**(最多 8 个)
- **单点委派** —— 把一个子任务交给指定 Agent在独立会话中执行结果流式回传
- **并行委派** —— 同时委派给多个 Agent每个在自己的隔离会话里跑
@ -130,8 +130,9 @@ head:
子员工默认被拒绝一组工具,保证树不失控:
- `delegateToAgent` / `delegateParallel`(递归护栏——子员工不能再发起同步/并行委派,避免委派风暴)
- `setGoal` 系列 + `remember` 系列(目标与记忆的所有权留在父员工手里)
- `delegateToAgent` / `delegateParallel` / `listAvailableAgents`(递归护栏——子员工不能再发起同步/并行委派,也不能枚举兄弟员工)
- `setGoal` / `addGoalCriterion` / `completeGoal` / `getGoalStatus`(目标所有权留在父员工手里)
- `remember` / `remember_structured` / `forget_structured`(子员工不能写入父员工的长期记忆)
- `create_employee`(子员工不能凭空造新员工)
这组默认拒绝列表可通过 `mateclaw.delegation.child-denied-tools` 调整。
@ -150,6 +151,41 @@ ChatConsole 把整棵委派树画出来,不是一串扁平日志:
---
## 计划看板Kanban
::: tip 新增
`数字员工` 页头多了一个三段切换:**花名册 / 实时 / 计划看板**。计划看板把整个工作空间里员工跑出来的计划,按状态摊成一块看板,一眼看清谁在做什么、卡在哪。(仅管理员可见。)
:::
看板是 **Plan-and-Execute 计划**的全局视图,分四列,按计划状态自动归位:
| 列 | 含义 |
|----|------|
| **待执行** | 计划已生成,第一步还没开始 |
| **执行中** | 第一步已启动 |
| **已完成** | 全部步骤跑完 |
| **失败** | 有步骤失败且不再重试 |
布局是**泳道式**:每个有计划的员工占一行,按最近活动排序,顶部下拉可只看某一个员工。同一个目标的多次重新规划会折叠成**一张卡片 + ×N 徽标**,不会堆成一串。每张卡显示目标文字、进度条(已完成 / 总步数、以及步骤分布芯片N 待执行 / M 执行中 / K 已完成)。
看板是**只读**的——状态由执行驱动,不能拖拽。点一张卡,右侧滑出**计划详情面板**受派员工、状态、KPI步数 / 进度 / 创建日期、执行产出Markdown 渲染)、可展开的步骤时间线。顶部还有「目标」按钮,直接打开活跃[目标](./goals)列表。
REST`GET /api/v1/plans?limit=N`(跨员工最近 N 条)、`GET /api/v1/plans?agentId=...`(按员工)、`GET /api/v1/plans/{id}`(含步骤详情)。
### 按步骤委派给专职员工
::: tip 新增
一个多步计划,不必由一个员工从头跑到尾。生成计划时,规划员可以把**单个步骤**指派给工作空间里更对口的员工去执行。
:::
机制是**自动的**,不需要手动配置:规划阶段,系统把工作空间里其他已启用员工(连名字带描述)摊给规划员看;规划员判断某一步明显属于某员工的专长时,就在计划里把那一步标给它,其余步骤留给自己。多数步骤通常不需要委派。
- 委派关系存在 `mate_sub_plan.assigned_agent_id`,计划详情面板的步骤下方会显示蓝色徽标 **「委派给 &lt;员工名&gt;」**
- 被委派步骤在**子会话**里执行,结果回流到主计划——这条子会话归属到原计划的对话之下,**不会**作为独立顶层会话泄漏进会话列表
- 步骤委派和[目标系统](./goals)、上面的[多级委派树](#多级子员工委派树)是一套语义:父员工拆活、专职员工干活
---
## 一句话造一支团队:数字员工搭建技能
::: tip 1.4.0 新增
@ -167,6 +203,24 @@ ChatConsole 把整棵委派树画出来,不是一串扁平日志:
---
## 一句话创建员工向导(单个员工)
::: tip 新增
上面那条造的是**一整支团队**,走的是对话式技能。如果你只想要**一个**员工,又懒得逐项填表单,用列表页右上角的 **创建向导**一句话描述AI 把草稿生成出来,你改两下就上线。
:::
这是一个独立的三步 UI 向导(`数字员工 → 创建向导`),和上面的团队技能是两个东西——一个出团队、一个出单人,一个在聊天框里、一个是专门的页面。
1. **描述**——在输入框里用一句自然语言说清楚你要什么("一个帮我盯竞品动态、每天写简报的运营助手")。下方有示例 chip点一下就填好
2. **确认**——AI 回来一份草稿:名字、头像 emoji、角色、目标、system prompt、类型`react` / `plan_execute`)、推荐首问、标签,以及**建议绑定的工具 / 技能 / 知识库**。每个字段都能改,能力用可搜索的 picker 增删
3. **上线**——确认后一次性创建员工并完成工具 / 技能 / 知识库绑定,给你"开始聊天 / 再建一个 / 回列表"三个去处
**防幻觉**是这里的关键设计AI 只能从你这套部署**真实存在**的能力目录里挑工具、技能、知识库——模型凭空编出来的工具名、技能 ID、KB ID 在生成阶段就被逐项反查丢弃,永远到不了向导界面。所以草稿里出现的每一项绑定都是当场可用的。
后端入口:`POST /api/v1/agents/generate`,请求体 `{ "requirement": "你的一句话" }`,返回一份校验过的草稿。
---
## 深度思考
不是所有问题都值得深度推理但有些问题需要。MateClaw 支持按 Agent、按对话打开深度思考模式
@ -188,7 +242,7 @@ ChatConsole 把整棵委派树画出来,不是一串扁平日志:
5. 选类型(`react` 或 `plan_execute`
6. 写或改 system prompt角色 / 目标 / 背景故事会自动拼接进来,不用重复写)
7. 勾选它能用的工具,绑定它该读的知识库
8. 设置 `max_iterations`(默认 10
8. 设置 `max_iterations`(默认 100
9. 保存
立刻生效。从聊天 UI 或 API 开始用。
@ -250,6 +304,27 @@ UI 入口:`员工 → 选员工 → 编辑 → 知识库`。
迁移备注:早期版本的"绑定"是写在 `mate_wiki_knowledge_base.agent_id` 上的(一对一独占语义)。从 V130 迁移开始,所有老的 `kb.agent_id` 都被回填到 `agent.primary_kb_id`,老字段保留作 fallback 读取,但新的写入只走 `agent.primary_kb_id`。如果你之前依赖"KB 只给某个 agent 看"的隔离请到员工管理面板重新审视一遍——KB 现在对 workspace 内全员可见。
#### 让某个员工彻底不碰知识库
::: tip 新增
"知识库"标签页顶部有个开关:**此智能体不使用任何知识库**。它和"工具禁用""技能禁用"是对称的三个 opt-out 开关。
:::
这里要分清两种"空"
- **选择器留空** = "我没特别指定" → 运行时按**继承工作空间全部 KB**处理(默认行为)
- **打开这个开关** = "我明确不要任何 KB" → 运行时把该员工的可见 KB 直接判为**零**
打开后保存,员工的 KB 绑定被清空并标记为「显式无知识库」,标签页上出现「已禁用」徽标。效果:
- `wiki_read_page` / `wiki_search_pages` / `wiki_semantic_search` 等全系 wiki 工具一律返回 `"no knowledge base"`(工具还在工具集里,只是执行结果为空)
- webchat 的 `/wiki/pages` 端点对该员工返回空列表
- 知识库注入 / grounding 全部关闭
**默认关闭**,所有存量员工行为不变。开关可随时解除——只要回到 KB 选择器勾上任意一个知识库再保存,这个标志会自动清掉(非空绑定优先于 opt-out避免状态矛盾
字段落在 `mate_agent.wiki_disabled`V154 迁移H2 / MySQL / KingbaseES 三套齐备)。
### System Prompt 最佳实践
System prompt 是数字员工的声音、优先级、约束的来源。**角色 / 目标 / 背景故事**和技能指令、工作空间记忆系统会自动拼接到最终 prompt 里——这些部分你不用自己写。
@ -309,6 +384,10 @@ System prompt 是数字员工的声音、优先级、约束的来源。**角色
| `SUMMARIZED` | 上下文压缩之后正常完成 |
| `MAX_ITERATIONS_REACHED` | 到达迭代上限被强制收敛 |
| `ERROR_FALLBACK` | 出错后降级的答案 |
| `INCOMPLETE` | 响应未完整完成,需要继续生成或重试 |
| `EVIDENCE_INSUFFICIENT` | 最终回答引用了未被工具结果验证的事实 |
| `STOPPED` | 用户主动停止 |
| `RETURN_DIRECT` | 带 `returnDirect=true` 的工具短路了循环,结果直接发给用户 |
---
@ -317,12 +396,14 @@ System prompt 是数字员工的声音、优先级、约束的来源。**角色
这些是运行时自己在做的事,目的是让 Agent 在你不想去 debug 的那种地方不脆弱:
- **上下文修剪**——上下文窗口快满时,早期轮次由 LLM 总结、摘要替换原文。缓存 30 分钟。摘要以用户消息形式注入,不是系统消息——防止历史内容被提升成系统级指令的注入风险。
- **结构化压缩prompt 过长时)**——当模型返回"prompt 过长"时,运行时走一条四级递进的结构化压缩链:**软裁剪 → 硬清理 → 预修剪 → LLM 结构化摘要**。无论走到哪一级,都**永远保留前缀**——system prompt + 目标锚点不动;最终摘要以 UserMessage 形式注入。委派工具的返回结果**永远不会被压缩**(它们是子员工的成果,丢了就找不回来)。某次摘要失败后有 **10 分钟冷却**,避免在同一个超限回合里反复硬调 LLM。
- **结构化压缩prompt 过长时)**——当模型返回"prompt 过长"时,运行时走一条四级递进的结构化压缩链:**软裁剪 → 硬清理 → 预修剪 → LLM 结构化摘要**。无论走到哪一级,都**永远保留前缀**——system prompt + 目标锚点不动;最终摘要以 UserMessage 形式注入。委派工具的返回结果**永远不会被压缩**(它们是子员工的成果,丢了就找不回来)。PTL 紧急压缩后有 **1 分钟冷却**,避免在同一个超限回合里反复硬调 LLM。
- **思考恢复**——流式中途断了,已经写出的思考和内容会持久化,会话重载时还在。
- **迭代上限处理**——到达 `max_iterations` 不会崩溃,而是强制让 LLM 用现有信息生成一个尽力而为的总结答案。
- **僵尸流清理**——后台跟踪每一个打开的 SSE 流,被遗弃的会被自动回收。
- **429 重试**——LLM 限流错误会触发带退避的自动重试。
- **重复检测**——抓住那些反复在同一个工具调用上打转的 Agent强行把它拉出循环。
- **停滞检测 + 重新规划**——Plan-Execute 模式下,某一步抛异常或在工具内循环里反复失败时,运行时会清掉当前计划、带着失败原因回到规划节点**重新规划**,绕开坏掉的那一步,而不是带着垃圾结果硬推下去。详见[目标 · 停滞检测与重规划](./goals#停滞检测与重新规划)。
- **目标硬延续**——锁了目标的员工撞到迭代上限时,可以**续一次满额迭代**接着干,而不是停在那里等你再发消息。详见[目标 · 硬延续](./goals#撞到迭代上限的硬延续)。
- **工具超时可配置**——一个慢工具不会冻结整个回合。
- **渠道健康监控**——失败的渠道适配器走指数退避重启。

View File

@ -109,7 +109,7 @@ MateClaw 的答案是:**你团队已经在用的所有聊天软件,就是那
- **多 Agent 引擎**ReAct + Plan-Execute
- **Cron 调度 + 失败重试**
- **9 个 IM 渠道适配器**(每个都有指数退避重连)
- **8 个 IM 渠道适配器**(每个都有指数退避重连)
- **持久化记忆**[Memory](./memory)Dreaming 之后越用越懂你)
- **Wiki 知识层**[LLM Wiki](./wiki),让调研有依据)
- **Tool Guard**[Security](./security),敏感操作问你一句再执行)

View File

@ -285,7 +285,7 @@ curl -N -X POST http://localhost:18088/api/v1/chat/stream \
| `GET` | `/api/v1/models/active` | `获取当前激活模型` |
| `PUT` | `/api/v1/models/active` | `设置当前激活模型` |
| `GET` | `/api/v1/models/by-type` | `按类型筛选模型chat / embedding可选 modality 过滤` |
| `GET` | `/api/v1/models/catalog` | `RFC-074: 获取 Provider 全量目录(含未启用),供 Add Provider 抽屉使用` |
| `GET` | `/api/v1/models/catalog` | `获取 Provider 全量目录(含未启用),供 Add Provider 抽屉使用` |
| `DELETE` | `/api/v1/models/custom-providers` | `删除自定义 Provider查询参数变体兼容含特殊字符的旧 ID` |
| `POST` | `/api/v1/models/custom-providers` | `创建自定义 Provider` |
| `DELETE` | `/api/v1/models/custom-providers/{providerId}` | `删除自定义 Provider` |
@ -299,10 +299,10 @@ curl -N -X POST http://localhost:18088/api/v1/chat/stream \
| `PUT` | `/api/v1/models/{id}` | `更新模型` |
| `POST` | `/api/v1/models/{id}/default` | `设置默认模型` |
| `PUT` | `/api/v1/models/{providerId}/config` | `更新 Provider 配置` |
| `POST` | `/api/v1/models/{providerId}/disable` | `RFC-074: 禁用 Provider如其下模型为当前默认会自动切换` |
| `POST` | `/api/v1/models/{providerId}/disable` | `禁用 Provider如其下模型为当前默认会自动切换` |
| `POST` | `/api/v1/models/{providerId}/discover` | `发现远端模型` |
| `POST` | `/api/v1/models/{providerId}/discover/apply` | `批量添加发现的模型` |
| `POST` | `/api/v1/models/{providerId}/enable` | `RFC-074: 启用 Provider` |
| `POST` | `/api/v1/models/{providerId}/enable` | `启用 Provider` |
| `DELETE` | `/api/v1/models/{providerId}/models` | `从 Provider 删除模型` |
| `POST` | `/api/v1/models/{providerId}/models` | `向 Provider 添加模型` |
| `POST` | `/api/v1/models/{providerId}/models/test` | `测试单个模型可用性` |
@ -375,7 +375,7 @@ curl -N -X POST http://localhost:18088/api/v1/chat/stream \
| 方法 | 路径 | 用途 / handler |
|---|---|---|
| `GET` | `/api/v1/skills` | `获取技能分页列表RFC-042 §2.1` |
| `GET` | `/api/v1/skills` | `获取技能分页列表` |
| `POST` | `/api/v1/skills` | `创建技能` |
| `GET` | `/api/v1/skills/counts` | `获取各类型技能计数tab 徽章用)` |
| `POST` | `/api/v1/skills/curator/activate` | `激活/取消激活 curator真正归档 vs 仅预览)` |
@ -398,19 +398,19 @@ curl -N -X POST http://localhost:18088/api/v1/chat/stream \
| `GET` | `/api/v1/skills/runtime/status` | `获取所有技能的运行时解析状态(管理页面使用)` |
| `GET` | `/api/v1/skills/summary` | `获取已启用技能摘要(按类型分组)` |
| `POST` | `/api/v1/skills/sync-files` | `Re-sync every skill's bundle files (admin)` |
| `POST` | `/api/v1/skills/synthesize-from-conversation` | `从对话历史合成 SkillRFC-023` |
| `POST` | `/api/v1/skills/synthesize-from-conversation` | `从对话历史合成 Skill` |
| `GET` | `/api/v1/skills/type/{skillType}` | `按类型获取技能列表` |
| `DELETE` | `/api/v1/skills/{id}` | `硬删除技能 (admin only — 物理删除 + 工作区清空)` |
| `GET` | `/api/v1/skills/{id}` | `获取技能详情` |
| `PUT` | `/api/v1/skills/{id}` | `更新技能` |
| `POST` | `/api/v1/skills/{id}/archive` | `手动归档技能` |
| `GET` | `/api/v1/skills/{id}/employees` | `List agents that can use this skill (RFC-090 §14.2)` |
| `GET` | `/api/v1/skills/{id}/employees` | `列出能使用该技能的员工` |
| `POST` | `/api/v1/skills/{id}/export-workspace` | `将 skill 导出到工作区目录` |
| `GET` | `/api/v1/skills/{id}/lessons` | `Read per-skill LESSONS.md (RFC-090 §11.4)` |
| `POST` | `/api/v1/skills/{id}/lessons/clear` | `Clear all lessons for a skill (RFC-090 §11.4)` |
| `GET` | `/api/v1/skills/{id}/lessons` | `读取该技能的 LESSONS.md` |
| `POST` | `/api/v1/skills/{id}/lessons/clear` | `清空该技能的所有 lessons` |
| `POST` | `/api/v1/skills/{id}/pin` | `钉住/取消钉住技能(钉住的技能不会被自动归档)` |
| `GET` | `/api/v1/skills/{id}/requirements` | `Pre-flight requirement statuses for a skill (RFC-090)` |
| `POST` | `/api/v1/skills/{id}/rescan` | `重新扫描单个技能RFC-042 §2.3.4` |
| `GET` | `/api/v1/skills/{id}/requirements` | `该技能的前置依赖检查状态` |
| `POST` | `/api/v1/skills/{id}/rescan` | `重新扫描单个技能` |
| `POST` | `/api/v1/skills/{id}/restore` | `恢复已归档的技能` |
| `POST` | `/api/v1/skills/{id}/sync-files` | `Re-sync this skill's bundle files from DB → local workspace cache` |
| `PUT` | `/api/v1/skills/{id}/toggle` | `启用/禁用技能` |
@ -423,7 +423,7 @@ curl -N -X POST http://localhost:18088/api/v1/chat/stream \
| 方法 | 路径 | 用途 / handler |
|---|---|---|
| `GET` | `/api/v1/skill-templates` | `List skill templates (RFC-091)` |
| `GET` | `/api/v1/skill-templates` | `获取技能模板列表` |
| `GET` | `/api/v1/skill-templates/{id}` | `Get a single skill template` |
| `POST` | `/api/v1/skill-templates/{id}/instantiate` | `Instantiate a template into a skill` |

View File

@ -258,10 +258,10 @@ curl -X POST http://localhost:18088/api/v1/channels \
"type": "feishu",
"agentId": 1,
"config": {
"appId": "cli_your_app_id",
"appSecret": "your-app-secret",
"verificationToken": "your-verification-token",
"encryptKey": "your-encrypt-key"
"app_id": "cli_your_app_id",
"app_secret": "your-app-secret",
"verification_token": "your-verification-token",
"encrypt_key": "your-encrypt-key"
},
"enabled": true
}'
@ -349,8 +349,8 @@ curl -X POST http://localhost:18088/api/v1/channels \
"type": "feishu",
"agentId": 1,
"config": {
"appId": "cli_your_app_id",
"appSecret": "your-app-secret",
"app_id": "cli_your_app_id",
"app_secret": "your-app-secret",
"card_format": "auto",
"card_header": "AI 助手",
"card_streaming_enabled": true,
@ -384,11 +384,8 @@ curl -X POST http://localhost:18088/api/v1/channels \
"type": "wecom",
"agentId": 1,
"config": {
"corpId": "your-corp-id",
"wecomAgentId": "1000002",
"secret": "your-secret",
"token": "your-token",
"encodingAesKey": "your-encoding-aes-key"
"bot_id": "your-bot-id",
"secret": "your-secret"
},
"enabled": true
}'
@ -396,8 +393,6 @@ curl -X POST http://localhost:18088/api/v1/channels \
![开始聊天](/images/channels/wecom/07-chat.png)
Webhook URL`https://your-domain/api/v1/channels/webhook/wecom`
::: tip 想把企业微信跑稳?
群聊多用户协作、引用消息、appmsg 解析、上传约束、aibot_respond_msg 路由、自循环检测、TLS 重试、平台级权限锁……所有非显然的优化点和踩坑,都在 [企业微信深度优化](./wecom-tuning) 单独整理了。
:::
@ -529,8 +524,8 @@ curl -X POST http://localhost:18088/api/v1/channels \
"type": "qq",
"agentId": 1,
"config": {
"appId": "your-app-id",
"appSecret": "your-app-secret"
"app_id": "your-app-id",
"client_secret": "your-app-secret"
},
"enabled": true
}'
@ -563,8 +558,7 @@ curl -X POST http://localhost:18088/api/v1/channels \
"agentId": 1,
"config": {
"bot_token": "xoxb-...",
"app_token": "xapp-...",
"mode": "socket"
"app_token": "xapp-..."
},
"enabled": true
}'
@ -603,7 +597,7 @@ curl -X POST http://localhost:18088/api/v1/channels \
"name": "微信",
"type": "weixin",
"agentId": 1,
"config": {"botToken": "your-bot-token"},
"config": {"bot_token": "your-bot-token"},
"enabled": true
}'
```

View File

@ -21,6 +21,8 @@ Segment 是**渐进到达**的。每个 segment 一落盘就立刻持久化到
这件事以前不成立。现在成立了。
回答正文里的引用也是活的:`[[slug]]` 形式的 wikilink、以及基于知识库作答时正文里的 `[1]` / `[2]` **来源引用标记**都可以**点击直接跳到对应的 wiki 页面**,末尾"来源:"清单的每一行也整行可点。详见 [LLM Wiki · Chat 里点引用直接跳](./wiki#chat-里点-wikilink-直接跳)。
---
## 持久化的任务清单:给需要时间的计划用

View File

@ -125,65 +125,56 @@ mate:
```yaml
mate:
wiki:
chunk-size: 1200
chunk-overlap: 200
digestion-concurrency: 2
llm-model-config-id: 1
min-concept-occurrences: 2
max-page-backlinks: 50
lock-on-manual-edit: true
rebuild-sources-on-update: true
enabled: true
max-chunk-size: 30000
max-context-chars: 10000
max-pages-per-raw: 15
max-parallel-raw-materials: 3
max-parallel-phase-b-pages: 3
auto-process-on-upload: true
upload-dir: ./data/wiki-uploads
```
八个旋钮。细节在 [LLM Wiki](./wiki)。
细节在 [LLM Wiki](./wiki)。
### Tool Guard基于规则
```yaml
mateclaw:
tool:
guard:
enabled: true
default-policy: require_approval # `allow` / `deny` / `require_approval`
approval-timeout-seconds: 600
rules:
- tool: ShellExecuteTool
arg-pattern: "^(ls|cat|grep|find)\\s"
action: allow
priority: 100
- tool: ShellExecuteTool
action: require_approval
priority: 50
```
Tool Guard 的全局开关、默认策略和规则**不在 application.yml 里配置**——它们存在数据库(`mate_tool_guard_config` / `mate_tool_guard_rule`),通过管理台的「安全」页或 REST 编辑:
细节在 [安全与审批](./security)。
| 方法 | 路径 | 作用 |
|---|---|---|
| `GET` / `PUT` | `/api/v1/security/guard/config` | 全局开关 + 默认策略(`allow` / `deny` / `require_approval` |
| `GET` | `/api/v1/security/guard/rules/builtin` | 内置规则 |
| `GET` / `POST` | `/api/v1/security/guard/rules` | 列出 / 新增自定义规则 |
| `PUT` | `/api/v1/security/guard/rules/{ruleId}` | 修改规则 |
| `PUT` | `/api/v1/security/guard/rules/{ruleId}/toggle` | 启停单条规则 |
每条规则按工具名 + 参数模式匹配,命中后给出 `allow` / `deny` / `require_approval` 动作,按优先级排序。细节在 [安全与审批](./security)。
### File Guard
File Guard 分两层:
1. **允许 / 禁止路径规则**——和 Tool Guard 一样存数据库、走管理台「安全」页REST 为 `GET` / `PUT /api/v1/security/guard/config/file-guard`**不在 application.yml 里**。
2. **全局兜底沙箱根**——唯一写在 application.yml 里的部分。当某个会话没有配置 per-workspace base path 时,文件 / Shell 工具被限制在这个根目录内fail-closed 默认):
```yaml
mateclaw:
security:
file-guard:
enabled: true
allowed-paths:
- "${user.dir}/workspace"
- "${java.io.tmpdir}/mateclaw"
denied-paths:
- "/etc"
- "/usr"
- "${user.home}/.ssh"
- "${user.home}/.config"
workspace:
sandbox:
enabled: true # 设 false 恢复旧的不受限行为
root: ${user.dir}/data/workspace # 兜底沙箱根,启动时自动创建
```
环境变量覆盖:`MATECLAW_WORKSPACE_SANDBOX_ENABLED` / `MATECLAW_WORKSPACE_SANDBOX_ROOT`
### JWT 认证
```yaml
mateclaw:
auth:
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-characters-long}
expiration: 86400000
sliding-window: true
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-characters-long}
expiration: 86400000
```
::: warning

View File

@ -7,7 +7,7 @@
1. Electron 启动并显示本地 Splash。
2. Electron 用内置 JRE 启动 `mateclaw-server.jar`
3. `mateclaw-ui` 已提前构建到 `mateclaw-server/src/main/resources/static`
4. `BrowserWindow` 最终加载 `http://localhost:18088`
4. `BrowserWindow` 最终加载 `http://localhost:{动态端口}`(由主进程在启动时随机选取)
这意味着:
@ -45,7 +45,7 @@
`mateclaw-desktop` 主窗口业务页当前加载:
- `http://localhost:18088`
- `http://localhost:{动态端口}`(端口由 Electron 主进程在启动时随机分配)
所以 UI 热更新不能只改 Electron `dist`,必须让后端在运行时能切换静态资源来源。
@ -106,7 +106,7 @@ Electron Shell
├── UI Update Manager新增
├── Bundled JRE
├── mateclaw-server.jar
└── BrowserWindow → http://localhost:18088
└── BrowserWindow → http://localhost:{动态端口}
├── 优先读取 userData/ui-bundles/current/
└── fallback 到 classpath:/static/
```
@ -202,7 +202,7 @@ Manifest 最好放在稳定的静态地址,不要依赖 GitHub API 动态查
- 对 `/assets/**`、`/icons/**`、`/logo/**`、`/favicon.ico`、`/index.html` 和 SPA 路由统一转发
- 当外部目录不存在时自动回退内置资源
这样 BrowserWindow 仍然访问 `http://localhost:18088`,但内容已经可由外置 UI 包覆盖。
这样 BrowserWindow 仍然访问 `http://localhost:{动态端口}`,但内容已经可由外置 UI 包覆盖。
### 4. Electron 侧 UI Update Manager

View File

@ -211,7 +211,7 @@ Electron 主进程通过 Node.js `child_process` 管理 Spring Boot 后端:
|----|------|
| macOS | `~/Library/Application Support/MateClaw/data/` |
| Windows | `%APPDATA%/MateClaw/data/` |
| Linux | `~/.local/share/MateClaw/data/` |
| Linux | `~/.config/MateClaw/data/` |
日志、工作空间文件、技能脚本、Wiki 内容都在同一个用户目录下。做重大变更前**备份**。
@ -257,9 +257,9 @@ Electron 主进程通过 Node.js `child_process` 管理 Spring Boot 后端:
1. 安装版自带 JRE——不需要装 Java。开发版确认 `java -version` 显示 21+。
2. 看日志:
- macOS`~/Library/Logs/MateClaw/`
- macOS`~/Library/Application Support/MateClaw/logs/`
- Windows`%APPDATA%/MateClaw/logs/`
- Linux`~/.local/share/MateClaw/logs/`
- Linux`~/.config/MateClaw/logs/`
3. 从终端启动看控制台输出
4. 确认后端选的端口没被防火墙挡

View File

@ -226,7 +226,7 @@ UI 里用 `工具 → MCP 服务`。三种传输模式stdio、streamable_http
### Pending 审批能放多久?
默认 10 分钟,之后过期变成 `rejected`。用 `mateclaw.tool.guard.approval-timeout-seconds` 配置
默认 30 分钟,过期后状态变成 `timeout`Agent 当成拒绝处理)。超时时长在管理台「安全」页的 Tool Guard 配置里调,不在 application.yml
---

View File

@ -119,6 +119,39 @@ POST /api/v1/goals
---
## 撞墙了怎么自己爬起来
::: tip 新增
长任务最怕的不是难,是**卡住**——撞到迭代上限就停、某一步失败就崩、在一个工具上空转到预算耗尽。这一组机制让员工在这些地方能自己续上、绕开、爬起来,而不是停在那等你再发消息。
:::
### 撞到迭代上限的硬延续
以前一轮 ReAct 跑满 `max_iterations`(结束原因 `MAX_ITERATIONS_REACHED`),目标子系统会**跳过**这一轮——不评估、不延续,任务就停在那。现在它走一条**硬延续**路径:把迭代计数清零、清掉"超限草稿",给员工**一段全新的满额迭代预算**接着干。
这和上面的"自动延续"不一样:自动延续是 evaluator 判"还没完成"后追加一句引导;硬延续是专门应对**撞上限**,重置的是迭代预算本身。每轮硬延续会吃掉一整段迭代配额,所以有上限——默认每轮最多 **1 次**(编译期硬顶 3 次),`0` = 关闭(回到旧行为:撞上限直接结束这轮)。
### 停滞检测与重新规划
Plan-Execute 模式下,单个步骤可能**抛异常**,也可能陷入**停滞**——在内部工具循环里反复用同样的调用失败、或拿到同样的无新信息的结果,一路烧到工具预算上限才以空结果"完成",然后污染依赖它的后续步骤。
运行时对每轮工具响应做签名检测,两级响应:
- **WARN**:同一调用重复失败几次后,注入一条系统提示让模型换个思路(同一个调用只提示一次)
- **HALT**:再撑下去就标记这一步 stuck结束内循环
一旦某步 HALT 或抛异常,运行时触发**重新规划**:清空当前计划,把"已完成步骤摘要 + 失败原因 + 绕开坏步骤"作为上下文带回规划节点,重新生成计划。每次运行最多重规划 **1 次**UI 会收到 `plan_replan` 事件(附失败步骤序号、原因)。
### 元工具回合不计迭代(迭代退款)
渐进式披露里的 `load_skill` / `enable_tool` 是**配置动作**,不是真正干活。如果某一轮 ReAct 里工具调用全是这类元工具,这一轮的迭代计数**不递增**(退款),免得"只顾加载技能"的模型白白耗光迭代预算。每次运行最多退款 3 次。
### 多步计划自动派生目标
一个**多步**≥2 步)的 Plan-Execute 计划,如果当前对话还没有活跃目标,规划节点会**自动建一个目标**,以计划的步骤作为验收准则,并广播 `goal_created` 事件刷新 UI 的目标面板。这样长计划天然就被目标系统的"跟到完成"语义托住。由 `mateclaw.goal.auto-goal-from-plan` 控制(默认开)。
---
## 目标是一份清单checklist1.5.0+
1.4.0 里 evaluator 每轮给一个完成度分数0~1和一句"还差什么"。问题是 **0.8 到底是什么意思**——哪几条做完了、哪几条没做,你看不清。
@ -275,6 +308,10 @@ mateclaw:
auto-followup-cooldown-seconds: 0
# 单次 graph 运行内自动延续的硬上限(每条消息的安全网;总预算仍由 turnBudget 管)
max-followups-per-run: 8
# 撞到迭代上限时每轮最多硬延续几次0 = 关闭;编译期硬顶 3
max-hard-continuations-per-run: 1
# 多步 Plan-Execute 计划在无活跃目标时自动派生一个目标
auto-goal-from-plan: true
# 评估器使用的模型;空字符串 = 沿用对话当前模型便宜的小模型推荐qwen-turbo / glm-4-flash
evaluator-model: ""
# 评估 prompt 携带的历史消息条数上限
@ -292,7 +329,7 @@ mateclaw:
| `mate_agent_goal` | 目标本体;含 status / budget / 双 LLM 计数器 / 自动延续配置 |
| `mate_agent_goal_event` | 目标的事件追加日志drawer 时间线读它 |
迁移由 Flyway 跑 `V120__agent_goal.sql`H2 + MySQL 双方言)。
迁移由 Flyway 跑 `V120__agent_goal.sql`H2 / MySQL / KingbaseES 三方言)。
---

View File

@ -275,10 +275,10 @@ v1.2.0 之前所有员工默认能用全部 MCP 工具——这是个全局开
### 三个解决的问题
**问题 1工具命名空间冲突**
两个 MCP server 都暴露 `read_file`——agent 调用时哪个赢v1.3.0 在内部使用**带 server 前缀的稳定 callback name**`{serverName}__{toolName}`),并把它持久化到 `mate_mcp_server.cached_tools`。两个 read_file 在 picker 里显示为 `serverA__read_file``serverB__read_file`agent 看到的 prompt 里映射回原始名以减少 token + 不让 LLM 困惑。
两个 MCP server 都暴露 `read_file`——agent 调用时哪个赢v1.3.0 在内部使用**带 server 前缀的稳定 callback name**`{serverName}__{toolName}`),并把它持久化到 `mate_mcp_server.tools_cache_json`。两个 read_file 在 picker 里显示为 `serverA__read_file``serverB__read_file`agent 看到的 prompt 里映射回原始名以减少 token + 不让 LLM 困惑。
**问题 2MCP server 改名 / 工具改名 → 员工绑定全部失效**
v1.2.0 时 server 一改名,绑这个 server 的员工全瞎了。v1.3.0 引入**持久化 tool cache**:每次成功 list-tools 后把工具元数据写到 `mate_mcp_server.cached_tools` JSON 列。agent binding 校验时如果 server 暂时连不上,就走 cache fallback——绑定保留为 `stale`,连接恢复后立即可用。
v1.2.0 时 server 一改名,绑这个 server 的员工全瞎了。v1.3.0 引入**持久化 tool cache**:每次成功 list-tools 后把工具元数据写到 `mate_mcp_server.tools_cache_json` JSON 列。agent binding 校验时如果 server 暂时连不上,就走 cache fallback——绑定保留为 `stale`,连接恢复后立即可用。
**问题 3员工保存时静默接受不存在的工具引用**
v1.2.0 时员工配置里写了一个 `nonexistent-server.weird-tool`保存成功运行时报错。v1.3.0 在保存时跑 `AgentBindingService.validate(...)`
@ -296,7 +296,7 @@ v1.2.0 时员工配置里写了一个 `nonexistent-server.weird-tool`,保存
### 数据契约
- `mate_mcp_server.cached_tools`v1.3.0 新列JSON 数组,每个元素 `{name, description, inputSchema, lastSeenAt}`
- `mate_mcp_server.tools_cache_json`v1.3.0 新列JSON 数组,每个元素 `{name, description, inputSchema, lastSeenAt}`
- `mate_agent_tool.tool_name`:存的是**带前缀的 callback name** `{serverName}__{toolName}` 而不是原始名,这样 server 改名时 join 失败立刻可观测
- `AgentBindingService.getEffectiveToolNames(agentId)` 是工具下发的唯一入口——agent 每个回合都跑一遍,确保运行时和编辑期看到的工具集一致

View File

@ -4,7 +4,7 @@ description: MateClaw 的四层记忆生命周期:即时上下文、对话后
head:
- - meta
- name: keywords
content: AI记忆,记忆系<EFBFBD><EFBFBD>,Dreaming,PROFILE.md,MEMORY.md,记忆生命周期,长期记忆,记忆提取,记忆整合
content: AI记忆,记忆系,Dreaming,PROFILE.md,MEMORY.md,记忆生命周期,长期记忆,记忆提取,记忆整合
---
# AI 记忆系统
@ -16,7 +16,7 @@ MateClaw 里其他所有东西在你配置完之后就静止了。Agent、工
::: tip 它在你睡着的时候做了一个关于你的梦
不是营销词。是 `memory/dreaming/` 包里真实跑的代码。
每天凌晨 2 点(默认时间,可改),系统跑一次调度任务,名字就叫 **Dreaming**:扫一遍今天和你聊天的每个 Agent 的对话痕迹,把零散的线索整合成对你的理解,过滤掉一次性的、矛盾的、过期的,把高频出现的提升进 `MEMORY.md`,整个"看见了什么、得出了什么、改写了什么"的过程追加进 `DREAMS.md`——一条人类可读的审计线。
每天凌晨 3 点(默认时间,可改),系统跑一次调度任务,名字就叫 **Dreaming**:扫一遍今天和你聊天的每个 Agent 的对话痕迹,把零散的线索整合成对你的理解,过滤掉一次性的、矛盾的、过期的,把高频出现的提升进 `MEMORY.md`,整个"看见了什么、得出了什么、改写了什么"的过程追加进 `DREAMS.md`——一条人类可读的审计线。
第二天早上你打开它,它**从昨天结束的地方继续**,不是从零开始。
@ -44,7 +44,7 @@ MateClaw 里其他所有东西在你配置完之后就静止了。Agent、工
│ 更新时机:每次有意义的对话结束后异步跑 │
└────────────────────────────────────────────────────────────┘
▼(默认每天凌晨 2 点,可调)
▼(默认每天凌晨 3 点,可调)
┌────────────────────────────────────────────────────────────┐
│ 3. 夜里整合Dreaming
│ 扫一遍最近的日常笔记,找出反复出现的模式, │
@ -196,7 +196,7 @@ v1.3.0 起,[工作流](./workflow) 的 `write_memory` step 可以在流程跑
三层防御:
**第一层:主动压缩。** 估算总 token 超过预算的 75%(默认窗口 12.8 万 token系统让 LLM 总结早期轮次。最近 2 轮4 条消息)保留原文。结果缓存 30 分钟。
**第一层:主动压缩。** 估算总 token 超过预算的 75%(默认窗口 12.8 万 token系统让 LLM 总结早期轮次。尾部基于 token 预算动态保留最近若干条(下限由 `preserve-recent-pairs``protect-last-min-messages` 两个参数取最大值决定,默认至少保留 10 条)。结果缓存 30 分钟。
**第二层:紧急恢复。** 如果 LLM 仍然返回上下文超限,系统不再调 LLM直接丢掉更早的消息、保留最后 2 轮、重试一次。
@ -280,7 +280,7 @@ mate:
### 触发方式
- **自动**——每个 Agent 在系统定时任务里有一行,每天凌晨 2 点跑一次
- **自动**——每个 Agent 在系统定时任务里有一行,每天凌晨 3 点跑一次
- **手动**——`POST /api/v1/memory/{agentId}/emergence`
### 为什么不会递归
@ -326,18 +326,70 @@ mate:
- **月度归档** —— 老报告滚进压缩的月度归档,时间线里能查
- **记忆浏览器** —— 时间线、事实、矛盾、变更对比、信任度面板
`application.yml` 启用:
`application.yml` 启用(这些开关都在 `mate.memory` 下,分三个 Phase
```yaml
mateclaw:
mate:
memory:
dream-v2:
enabled: true
fact-projection: true
contradictions: true
morning-card: true
# Phase 1逐轮生命周期总线
lifecycle-mediator-enabled: true
dream:
focused-enabled: true # 聚焦 dream 端点
archive-enabled: true # 月度归档轮转
archive-keep-days: 30
max-candidates-per-dream: 100
# Phase 2SOUL 自动演化
soul-update-interval: 20 # 每 20 次写入触发一次 SOUL.md 重写0 = 关)
# Phase 3事实投影
fact:
projection-enabled: true
projection-rebuild-cron: "0 */30 * * * ?"
contradiction-check-enabled: false # 矛盾检测(实验,默认关)
trust-half-life-days: 60
forget-enabled: true # UI 上的「遗忘」按钮
```
> 晨报卡片是一个端点(`GET /api/v1/memory/{agentId}/dream/morning-card`),不是单独的开关——只要事实投影 + dream 这套生命周期开着就有数据。
---
## always-on 记忆的尺寸控制
::: tip 新增
每一回合都注入 system prompt 的那些记忆(`user` / `feedback` 结构化条目、`PROFILE.md`、`MEMORY.md`)有个隐患——**只增不减**。条目越攒越多,每轮 token 一路膨胀。这一组机制给"常驻记忆"装上确定性的体积上限。
:::
三个层次各管一段:
### 注入预算(注入时截断,不动磁盘)
`user` / `feedback` 两类结构化条目注入 system prompt 时,按条目的 `Updated:` 日期排序LRU只保留最新的若干条超出部分**在注入时丢弃**——磁盘文件不动,并在块尾披露省略了多少条。
- `mate.memory.system-block-max-chars`(默认 `4000`):常驻结构化块的总字符上限,超了就按时间从最老的开始丢;`0` = 不限
- `mate.memory.system-block-max-entries-per-type`(默认 `40`每类user / feedback最多注入多少条`0` = 不限
### 夜间巩固(在存储层缩文件)
注入预算只在注入时截断,磁盘文件本身还在长。**巩固**是在存储层做合并:每晚定时(默认 03:30独立于 Dreaming 的开关和时间表)遍历每个员工的共享桶 + 各 per-owner 桶,条目数超过阈值时调 LLM 把近重复 / 过时的条目合并写回。
有一条**安全不变量**:巩固后的条目数**只能减不能增**——模型若幻觉出更多条目,这次写入直接跳过。
- `mate.memory.structured-consolidation-enabled`(默认 `true`):关掉就只剩注入截断、没有存储侧合并
- `mate.memory.structured-consolidation-min-entries`(默认 `8`):桶里条目少于此值跳过 LLM 调用省钱
- `mate.memory.structured-consolidation-cron`(默认 `"0 30 3 * * ?"`):独立调度,不碰 dreaming
- `mate.memory.structured-consolidation-max-owners-per-run`(默认 `50`):每个员工每次最多处理多少个 owner 桶,剩下的下次再来;`0` = 不限
手动触发:`POST /api/v1/memory/{agentId}/structured-consolidation`,返回 `ownersConsolidated` / `updated` / `entriesBefore` / `entriesAfter` 等统计。
> 别和 [Dreaming](#整合与-dreaming) 搞混Dreaming 把日常笔记整合进 `MEMORY.md`(写"重要的东西");巩固只负责把 `user` / `feedback` 结构化条目去重瘦身。两件事,两个调度。
### 文件上限(重写时的确定性兜底)
`PROFILE.md``MEMORY.md` 由 LLM 全量重写。prompt 里要求它简洁,但没有硬约束,仍可能越写越大。文件上限是写回时的**确定性兜底**:内容超预算就在最后一个能放下的 `##` 二级标题边界截断(保留文件头部的核心段),并追加一行截断标记。
- `mate.memory.profile-max-chars`(默认 `4000`PROFILE.md 硬上限;`0` = 不限
- `mate.memory.memory-md-max-chars`(默认 `8000`MEMORY.md 硬上限;`0` = 不限
---
## Agent 自己读写自己的记忆
@ -468,6 +520,19 @@ mate:
# 随发行版打包的默认值是 true对话抽取写入 owner 的 PERSONAL 记忆,召回按 owner_key 过滤。
# 设为 false 回到旧的共享行为(所有写入走 TEAM。Java 属性裸默认值为 false。
lifecycle-mediator-enabled: true
# --- always-on 记忆尺寸控制 ---
# 注入预算:常驻 user/feedback 结构化块(注入时 LRU 截断0 = 不限)
system-block-max-chars: 4000
system-block-max-entries-per-type: 40
# 夜间巩固:在存储层合并去重 user/feedback 条目(独立于 dreaming
structured-consolidation-enabled: true
structured-consolidation-min-entries: 8
structured-consolidation-cron: "0 30 3 * * ?"
structured-consolidation-max-owners-per-run: 50
# 文件上限PROFILE.md / MEMORY.md 重写时的硬截断节边界0 = 不限)
profile-max-chars: 4000
memory-md-max-chars: 8000
```
配置前缀:`mate.memory`。
@ -493,6 +558,7 @@ mate:
|------|------|------|
| POST | `/api/v1/memory/{agentId}/emergence` | 手动触发整合 |
| POST | `/api/v1/memory/{agentId}/summarize/{conversationId}` | 对某次对话手动触发提取 |
| POST | `/api/v1/memory/{agentId}/structured-consolidation` | 手动触发 user/feedback 结构化条目巩固 |
| GET | `/api/v1/memory/{agentId}/dreaming/status` | 查询上次运行、下次计划、最新 DREAMS.md 条目 |
---

View File

@ -23,8 +23,8 @@ MateClaw 不关心你用哪个 LLM。它通过五个协议适配器跟所有主
| **xAI / Grok** | Grok 3、Grok 4 | openai | OpenAI 兼容base URL + API KeyUI 带 xAI 品牌图标 |
| **DeepSeek** | deepseek-chat、deepseek-coder、**DeepSeek V4 flash + pro**(支持思考模式) | openai | OpenAI 兼容 |
| **KimiMoonshot** | moonshot-v1-8k/32k/128k | openai | OpenAI 兼容 |
| **智谱 AI** | GLM-5-Turbo、GLM-5V-Turbo、GLM-5、GLM-5.1 | openai | OpenAI 兼容 |
| **MiniMax** | abab6.5、abab5.5;扩展视频模型目录 + 国内端点 | openai | OpenAI 兼容 |
| **智谱 AI** | GLM-5-Turbo、GLM-5V-Turbo、GLM-5、GLM-5.1、**GLM-5.2** | openai | OpenAI 兼容;中国区 + 国际区各一个 standard 端点,外加两个 Coding Plan 订阅端点 |
| **MiniMax** | abab6.5、abab5.5;扩展视频模型目录 + 国内端点 | anthropic | Anthropic Messages API 兼容(端点 `/anthropic` |
| **SiliconFlow CN/INTL** | 托管路由推理 | openai | 双端点OpenAI 兼容 |
| **OpenCode** | 代码场景路由 | openai | OpenAI 兼容 |
| **OpenRouter** | 200+ 模型含免费档 | openai | 一个 key 路由到任何上游 |
@ -46,8 +46,8 @@ MateClaw 不关心你用哪个 LLM。它通过五个协议适配器跟所有主
| 协议 | 谁在用 |
|------|--------|
| **OpenAI** | OpenAI、Kimi、DeepSeek、MiniMax、智谱、OpenRouter、LM Studio、llama.cpp、MLX |
| **Anthropic** | Claude 家族 |
| **OpenAI** | OpenAI、Kimi、DeepSeek、智谱、OpenRouter、LM Studio、llama.cpp、MLX |
| **Anthropic** | Claude 家族、MiniMax |
| **DashScope** | Qwen 家族 |
| **Gemini** | Google Gemini 家族 |
| **Ollama** | 通过 Ollama 跑的本地模型 |
@ -388,7 +388,7 @@ MateClaw 用一个**活跃模型**作为全局默认。没有指定自己模型
- **自动 fallback** —— 主 provider 返回 `AUTH_ERROR` / `BILLING` / `MODEL_NOT_FOUND` / `NETWORK` / `5xx` 时,运行时滚到下一个 provider而不是把错误抛到 UI
- **每个 agent 自定义优先级** —— 在 `设置 → 模型` 的拖拽编辑器里把某个 agent 锁成 "OpenAI 优先 → Anthropic → DashScope"
- **池子状态实时可见** —— 每个 provider 用绿/琥珀/红徽章标健康状态
- **4 协议探活** —— DashScope、OpenAI 兼容、Anthropic、Ollama 风格
- **5 协议探活** —— DashScope、OpenAI 兼容、Anthropic、Gemini、Ollama 风格
- **手动重探 + 配置变更自动重探** —— 换 key 不用重启
- **出口 sanitizer** —— provider 专属选项(如 OpenAI 推理模型的 `reasoning_effort`)在 failover 到不支持的 provider 时被剥离,泄漏的选项不会让 fallback 报 400
- **UI 区分 401 与会话过期** —— provider 认证错误和用户会话过期现在显示不同消息、不同处置

View File

@ -100,7 +100,7 @@ Google 的图像生成走 **Nano Banana Pro**`gemini-3-pro-image-preview`
- **DashScope CosyVoice**——中英文,韵律自然
- **OpenAI TTS**——alloy、echo、fable、onyx、nova、shimmer 六种音色
- **MiniMax T2A**——中文音色,带情感标签
- **Edge TTS**——免费,无需 API Key音色丰富
任何 Assistant 消息上都有一个喇叭图标,点一下就朗读出来。用哪个声音取决于你在设置里激活的 TTS 供应商。

View File

@ -68,7 +68,7 @@ Docker 和源码启动在 [配置说明](./config) 和 [贡献指南](./contribu
第一次跑通本应该很顺。如果没跑通——
- **安装器打不开**——Windows 下右键 → 属性 → 解除锁定macOS 下去"系统设置 → 隐私与安全性"允许未签名应用。
- **后端起不来**——看 `~/.mateclaw/logs/app.log`Windows`%USERPROFILE%\.mateclaw\logs\`)。十有八九是 18088 端口被占了
- **后端起不来**——看日志文件macOS`~/Library/Application Support/MateClaw/logs/mateclaw.log`Windows`%APPDATA%\MateClaw\logs\mateclaw.log`)。桌面端后端使用动态端口,端口冲突会在日志里明确报出
- **模型调用报错**——API Key 填错了,或者网络不通。回设置里检查,或者换一家试试。
- **界面白屏**——Ctrl/Cmd + Shift + R 强刷。Electron 的缓存比较顽固。
- **还是不行**——去 [GitHub Issues](https://github.com/matevip/mateclaw/issues) 开一个 Issue`app.log` 的尾巴贴上。我们真的会看。

View File

@ -10,7 +10,7 @@
| 版本 | 日期 | 亮点 |
|------|------|------|
| [v1.6.0](./releases/1.6.0) | 2026-06-14 | 跑在国产数据库上 —— KingbaseES(人大金仓)+ PostgreSQL共用一套 PostgreSQL 家族迁移树 · 按需金仓驱动 · Docker 最小权限角色) · 新感官与双手(图片跨轮次留在上下文 + `image_analyze` · `execute_code` 运行员工编写的代码) · 你来塑造员工AGENTS.md 编辑器 + About You 身份 + 运行时模型身份 + KB 范围绑定 + 花名册标签) · Wiki Sources 标签(素材与监听合并、按 KB 自动同步、多路径/glob、pageType 表单编辑器) · 全局出站 HTTP/SOCKS 代理 · 确定性 Markdown 回答 · Claude Fable 5 |
| [v1.6.0](./releases/1.6.0) | 2026-06-22 | 跑在国产数据库上 —— KingbaseES(人大金仓)+ PostgreSQL共用一套 PostgreSQL 家族迁移树 · 按需金仓驱动 · Docker 最小权限角色) · 新感官与双手(图片跨轮次留在上下文 + `image_analyze` · `execute_code` 运行员工编写的代码) · 你来塑造员工AGENTS.md 编辑器 + About You 身份 + 运行时模型身份 + KB 范围绑定 + 花名册标签) · Wiki Sources 标签(素材与监听合并、按 KB 自动同步、多路径/glob、pageType 表单编辑器) · 全局出站 HTTP/SOCKS 代理 · 确定性 Markdown 回答 · Claude Fable 5 |
| [v1.5.0](./releases/1.5.0) | 2026-06-04 | 目标长出清单——从"打个分"到"逐条勾"checklist + Evaluator SPI + 确定性完成判定) · Wiki 学会自维护(`[[wikilink]]` 互联 + 改名/删页级联修链 + 坏链体检 · 事实/经验分层 + 失效传播 · pageType 档案与 per-agent 权限 · 处理流水线 · 本地目录知识源定时增量同步) · 记忆按主人隔离owner_key + 个人/团队/全局可见性 + 第三方 endUserId 透传) · 每个员工绑主知识库 · 偏好提供商决定主模型 + Claude Opus 4.8 |
| [v1.4.0](./releases/1.4.0) | 2026-05-23 | 持久化目标——员工锁住目标自己跟到完成 · 子员工委派变成一棵树(递归 3 层 + 异步 + 数字员工构建器) · 渐进式工具/技能披露(`enable_tool` + `load_skill` · 工作空间 RBAC四级角色 + 能力门禁) · 飞书做成一等公民(互动/审批/流式卡片 + 语音/文件音视频 + 渠道原生工具) |
| [v1.3.0](./releases/1.3.0) | 2026-05-13 | 工作流元年——7 种 step mode 把员工组装成业务流程 · 触发器 6 种 pattern 让事件自动启动流程 · Wiki 从搜索索引升级为处理流水线(用户模板 + 跨材料聚合 + reverse-citation · MCP per-agent 工具绑定 + 多模态旁路路由 · 4 个 JVM 原生文档生成工具 + 图像编辑 |

View File

@ -73,11 +73,10 @@ MateClaw 实现了滑动窗口 token 续签。当 token 剩余有效期低于 `r
```yaml
mateclaw:
auth:
jwt:
secret: your-secret-key-must-be-at-least-32-characters-long
expiration: 86400000 # 24 小时,毫秒
sliding-window: true
jwt:
secret: your-secret-key-must-be-at-least-32-characters-long
expiration: 86400000 # token 有效期(毫秒,默认 24 小时)
renewal-threshold: 7200000 # 剩余有效期低于此值(毫秒)触发滑动续期
```
::: warning
@ -267,7 +266,7 @@ POST /api/v1/chat/stream消息为 /approve 或 /deny
| `tool_name` | 要调的工具 |
| `tool_args` | 实际参数的 JSON |
| `rule_id` | 触发审批的规则 |
| `status` | `pending` / `approved` / `rejected` / `expired` |
| `status` | `pending` / `approved` / `denied` / `consumed` / `timeout` / `superseded` |
| `requested_at` | 审批被创建的时间 |
| `resolved_at` | 用户决定的时间 |
| `resolved_by` | 谁决定的 |
@ -279,7 +278,7 @@ POST /api/v1/chat/stream消息为 /approve 或 /deny
### 超时
Pending approval 在一个可配置的超时后过期(默认 10 分钟)。过期的审批变成 `rejected`Agent 把这个过期当作用户的拒绝一样对待。
Pending approval 在一个可配置的超时后过期(默认 30 分钟)。过期的审批变成 `timeout`Agent 把这个过期当作用户的拒绝一样对待。
### 通知
@ -348,20 +347,14 @@ File Guard 是文件系统级的访问控制。它坐在读写文件的任何工
### 配置
允许 / 禁止路径规则存在数据库,从管理台「安全」页或 `GET` / `PUT /api/v1/security/guard/config/file-guard` 管理——**不在 application.yml**。application.yml 里只有一项:会话没有 per-workspace base path 时,文件 / Shell 工具被限制其中的**全局兜底沙箱根**
```yaml
mateclaw:
security:
file-guard:
enabled: true
allowed-paths:
- "${user.dir}/workspace"
- "${java.io.tmpdir}/mateclaw"
denied-paths:
- "/etc"
- "/usr"
- "${user.home}/.ssh"
- "${user.home}/.config"
- "${user.home}/.env"
workspace:
sandbox:
enabled: true # 设 false 恢复旧的不受限行为
root: ${user.dir}/data/workspace # 兜底沙箱根,启动时自动创建
```
可视化编辑器在 `设置 → 安全与审批 → File Guard`
@ -534,42 +527,30 @@ server {
## 安全配置参考
application.yml 里**只有两块**安全相关配置——JWT 和文件沙箱:
```yaml
mateclaw:
auth:
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-chars}
expiration: 86400
sliding-window-ratio: 0.5
jwt:
secret: ${JWT_SECRET:your-secret-key-at-least-32-chars}
expiration: 86400000 # token 有效期(毫秒)
renewal-threshold: 7200000 # 剩余有效期低于此值时滑动续期(毫秒)
tool:
guard:
# 文件 / Shell 工具的全局兜底沙箱:会话没有 per-workspace base path 时,
# 所有文件 / Shell 操作被限制在这个根目录内fail-closed 默认)
workspace:
sandbox:
enabled: true
default-policy: require_approval
approval-timeout-seconds: 600
notifications:
email-enabled: false
dingtalk-enabled: false
security:
file-guard:
enabled: true
allowed-paths:
- "${user.dir}/workspace"
denied-paths:
- "/etc"
- "${user.home}/.ssh"
audit-log:
enabled: true
retention-days: 90
skill:
security-scan:
enabled: true
block-critical: true
root: ${user.dir}/data/workspace
```
**其余安全配置不走 application.yml而是存在数据库、从管理台「安全」页`/api/v1/security/guard/*`)管理**
- **Tool Guard** 的开关、默认策略、规则、审批超时(默认 30 分钟)、通知渠道 → `mate_tool_guard_config` / `mate_tool_guard_rule`
- **File Guard** 的允许 / 禁止路径规则 → `GET` / `PUT /api/v1/security/guard/config/file-guard`
- **审计日志**默认常开,逐条写入 `mate_tool_guard_audit_log`,可导出 CSV
- **技能安全扫描**的发现落在技能安装流程里CRITICAL 发现默认拦截
---
## 下一步

View File

@ -281,10 +281,8 @@ curl -X POST http://localhost:18088/api/v1/skills \
}'
# 启用 / 禁用
curl -X PUT http://localhost:18088/api/v1/skills/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"enabled": true}'
curl -X PUT "http://localhost:18088/api/v1/skills/1/toggle?enabled=true" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# 删除
curl -X DELETE http://localhost:18088/api/v1/skills/1 \

View File

@ -314,10 +314,8 @@ curl http://localhost:18088/api/v1/tools \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# 启用 / 禁用
curl -X PUT http://localhost:18088/api/v1/tools/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"enabled": false}'
curl -X PUT "http://localhost:18088/api/v1/tools/1/toggle?enabled=false" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# 设置内置或渠道工具的披露分级
curl -X PUT http://localhost:18088/api/v1/tools/1/disclosure-tier \

View File

@ -51,7 +51,7 @@ v0 = 6 种 pattern type + 2 种 dispatch targetagent / workflow。安全
| `cron` | 按 cron 表达式定时(**不进 ingest 管道**,由 scheduler 直跑) | `cronExpression`、`timezone` | 复用 `cron/` 模块的 ShedLock + Spring TaskScheduler**不写 mate_cron_job 实体、不调 CronJobService** |
| `webhook` | 通用事件入口透传(**v0 不做更细过滤**——secret 校验在 channel 层trigger 这边只看 `patternType=webhook` 命中) | v0 无字段) | 通过 `POST /api/v1/triggers/events` 入口 + envelope wrap |
| `channel_message` | 渠道收到消息 | `channelType`(可选,按 envelope `data.channelType` 比对)、`senderEquals`(可选,按 sender id 精确比对) | 旁路 `ChannelWebhookController`,原路由不变 |
| `agent_lifecycle` | 员工生命周期事件 | `agentId`(可选)、`phase`(可选,取值 `spawned` / `terminated` / `crashed` | 挂在 `ReActLifecycleListener` 上 |
| `agent_lifecycle` | 员工生命周期事件 | `agentId`(可选)、`phase`(可选,取值 `spawned` / `enabled` / `disabled` / `terminated``crashed` 保留给后续版本) | 挂在 `AgentLifecycleEventBridge` 上 |
| `content_match` | 内容包含 substring 才命中 | `substring`**必填**envelope 的 `data.content` 字段大小写不敏感包含匹配) | 通用过滤层,事件源由 envelope 决定 |
| `workflow_completion` | 工作流跑完进入终态 | `sourceWorkflowId`(可选)、`stateFilter`(可选,取值 `completed` / `failed` / `any` | 监听 `WorkflowEngine` 终态事件A→B→A 递归保护见下文 |
@ -118,7 +118,7 @@ v1.4.0 起,**定时任务**和**触发器**合并为单个**调度中心**页
- 选 `cron` → cron 表达式输入框 + 时区下拉 + 下一次触发时间预览。表达式可手输,也可点输入框旁的编辑按钮打开**可视化 cron 编辑器**(见下)
- 选 `channel_message` → 渠道类型可选 + (可选)按 sender id 精确匹配
- 选 `agent_lifecycle` → agent 可选 + phasespawned / terminated / crashed可选
- 选 `agent_lifecycle` → agent 可选 + phasespawned / enabled / disabled / terminated可选
- 选 `content_match` → substring 输入(**必填**),匹配 envelope 的 `data.content`
- 选 `workflow_completion` → 上游 workflow 可选 + state filtercompleted / failed / any可选
- 选 `webhook` → v0 没有额外字段(透传一切)
@ -272,7 +272,7 @@ v0 故意**不把 envelope 全文写进 `mate_trigger_event`**——大体量渠
- **没有可视化 trigger → workflow 串联图**——多 trigger 投递到同 workflow 在 UI 上看是两个独立列表
- **没有 trigger 间优先级 / 依赖**——同一事件命中多 trigger 时按数据库 id 升序串行 dispatch
- **Webhook 入口没鉴权 IP allowlist**——只有 secret header如果你需要更强的 IP 限制,前置 nginx / 网关
- **`agent_lifecycle` 不区分会话级和 step 级**——员工一次对话内多次 step 失败只会触发一次 `failed`
- **`agent_lifecycle` phase 只覆盖 CRUD 操作**——实际发出的 phase 为 `spawned` / `enabled` / `disabled` / `terminated``crashed`(运行时崩溃)保留给后续版本,目前不触发
- **没有事件回放**——`mate_trigger_event` 是只读历史,没有"重新派发这条事件"的按钮v1 加)
---
@ -284,7 +284,7 @@ v0 故意**不把 envelope 全文写进 `mate_trigger_event`**——大体量渠
| Cron trigger 没触发 | 1) `enabled=true` 2) cron 表达式 + 时区是否解析为下次时间UI 编辑器有预览; 3) ShedLock 锁是否被另一实例长持?查 `shedlock` 表 |
| 事件 `POST /events` 返回 200 但 dispatch 没发生 | 返回体里有 per-trigger fire / drop 汇总——看是否被 `BOT_SELF` / `RATE_LIMITED` / `DEDUPED` / `PATTERN_MISMATCH` 标了原因 |
| `channel_message` 触发不起来 | 1) envelope 的 `data.channelType` 拼写大小写是否和 trigger 的 `pattern_json.channelType` 匹配2) `bot_self_filter=true` 但有自定义 `BotSelfFilter` 实现把它过掉了3) `content_match``substring` 是否真的出现在 envelope 的 `data.content` 里 |
| `agent_lifecycle` 没触发 | 检查 `pattern_json.phase``spawned` / `terminated` / `crashed` 之一(不是 `started` / `completed` / `failed` |
| `agent_lifecycle` 没触发 | 检查 `pattern_json.phase``spawned` / `enabled` / `disabled` / `terminated` 之一(不是 `started` / `completed` / `failed``crashed` 保留给后续版本,目前不会被发出 |
| 重启后 cron trigger 不再触发 | 看启动日志 `syncFromDatabase()` 是否报错;常见是表损坏 / `pattern_json` 反序列化失败 |
| `mate_trigger.last_error``"rate-limited"` | 调高 `rate_limit_per_min` 或者把 trigger 拆成多条按 group 分流 |
| `bot_self_filter=true` 没起作用 | 确认 `BotSelfFilter` 是否真有非 noop 实现——默认 `NoopBotSelfFilter` 永远返回 false |

View File

@ -182,7 +182,7 @@ Wiki 不是全文搜索。它是**语义检索**——问「我们关于认证
| 症状 | 最可能的原因 |
|------|------------|
| 后端起不来 | 18088 端口被占。看 `~/.mateclaw/logs/app.log` |
| 后端起不来 | 18088 端口被占。看 `<用户数据目录>/logs/mateclaw.log`macOS: `~/Library/Application Support/MateClaw/logs/mateclaw.log` |
| 模型调用报错 | API Key 错了,或者网络不通。回设置里检查 |
| 界面白屏 | Ctrl+Shift+R 强刷 |
| Ollama 报 "does not support tools" | 换一个支持 function calling 的模型qwen3、llama3.1:8b+ |

View File

@ -111,7 +111,7 @@ WeCom 群里转发的文件经常**没有 filename 字段**。落地存成 `file
- 其他常见格式PNG / JPEG / MP4 / MP3 / WAV都能正确识别
- 实在认不出 → 保留 `.bin`,至少不假装是其他格式
实现在 `WeComChannelAdapter.sniffMagic()` + `refineZipKind()`
实现在 `MediaTypeSniffer.sniff()` + `MediaTypeSniffer.refineZipKind()`(被 `InboundMediaDownloader.download()` 调用)
---

View File

@ -91,7 +91,7 @@ MateClaw 的 LLM Wiki **是同一个想法长成的产品**
eager 模式分两阶段,速度提了一个数量级:
- **阶段 A路由**——抽取元信息和概念路由,决定每段原文会流向哪些页面。
- **阶段 B合并**——按页并行生成,60+ 页同时跑。每条原始素材有自己的**独立进度条**——不再盯着"处理中…"猜进度。
- **阶段 B合并**——按页并行生成,并发度由配置决定(可跨多条原始素材同时处理多页)。每条原始素材有自己的**独立进度条**——不再盯着"处理中…"猜进度。
**可恢复**:中途断了?点"重新处理",只重跑未完成的页面,已生成的不动。超过模型上下文限制的文档,系统自动做 mean-pool 子段切分——你不用管。
@ -285,7 +285,7 @@ UI 上能做:
- **最近变更**——上次重建以来新生成 / 重新编译的页面
- **悬而未决的话题**——开放问题和未结论的决策
重建在每次会话结束(`ConversationCompletedEvent`)异步触发,配合一个可配置的去抖窗口(默认约 30 秒),短轮次密集发生时不会把 LLM 打爆。Admin 也可以手动触发重建——手动路径会绕开去抖。
重建在每次会话结束(`ConversationCompletedEvent`)异步触发,配合一个可配置的去抖窗口(默认 5 分钟),短轮次密集发生时不会把 LLM 打爆。Admin 也可以手动触发重建——手动路径会绕开去抖。
注入受 `wiki.hot_cache.enabled` 特性开关控制(关闭 → 注入空字符串),并按 KB 优先级最多挑前两个,避免系统提示被撑爆。
@ -372,8 +372,8 @@ slug 必须是真实存在页面的 slug。LLM 生成内容时索引里给的就
进入任一 KB顶部 banner 会显示当前死链状态。按"扫描死链"启动一次全 KB job
| Method | Path | 说明 |
|---|---|---|
| 端点 | 说明 |
|---|---|
| `POST /api/v1/wiki/knowledge-bases/{kbId}/lint/broken-links` | 启动 jobjob-based 异步),返回 `{jobId, status, startedAt}`;同 KB 已有 running job 时幂等返回 |
| `GET .../lint/broken-links` | 拉最近一次 completed 扫描的聚合结果 |
| `GET .../lint/broken-links/jobs/{jobId}` | 查单次 job 状态 |
@ -410,6 +410,12 @@ Chat 渲染 agent 回复时content 里的 `[[slug]]` / `[[slug|alias]]` 会
不再需要先去 wiki 视图、再找 KB、再找页面——chat 里看到的引用直接跳。lookup 严格 case-insensitive exact不做 canonical 模糊,所以 LLM 写错 slug 会通过 toast 让你看到,而不是悄悄跳到一个"看起来像的"页面。
### Chat 里点 `[n]` 引用标记也能跳
员工基于 wiki 检索作答时,回答末尾会带一段"来源:"清单(`[1] 标题 - 章节 - page N`)。现在正文里的 `[1]`、`[2]` 这种**引用标记本身可点击**,来源清单里的每一行也整行可点——点哪个都跳到对应的 wiki 页面,跳转逻辑和上面的 wikilink 共用一套(按标题跨 KB lookup0 / 1 / 多命中分别 toast / 直达 / picker
后端会把来源行**规范化**成统一格式(必要时补上"来源:"标头、把旧格式原地替换),前端才能可靠地识别并把 `[n]` 接上链接。前提是该 KB 已启用 Wiki 并完成消化。
### Phase 路线图(每个 phase 都已 land
| Phase | 主要变更 |
@ -514,6 +520,40 @@ mate:
---
## 知识图谱:实体层
::: tip 新增
页面层回答"这件事写在哪一页"**实体层**回答"谁和谁有什么关系"。入库时除了切块、嵌入、写页面,还能再做一遍**实体抽取**:把人、组织、地点、事件、产品、概念这些**实体**和它们之间的**关系**抽出来,连成一张可点的知识图谱。
:::
### 抽什么、什么时候抽
抽两类东西:
- **实体(节点)**——每个实体有规范名、别名、描述、显著度salience、提及次数还有一个向量用于近义去重。内置六种类型`person` / `organization` / `location` / `event` / `product` / `concept`
- **关系(边)**——`主语 → 谓词 → 宾语` 三元组(谓词是 `works_for`、`located_in`、`founded` 这种 snake_case 短语),每条关系都附一段证据引文。
抽取在消化流水线里嵌入写完之后,作为一个**独立异步 pass** 触发,不阻塞页面生成。它是**增量**的——默认跳过已经抽过的 chunk。实体归一化走三级运行时缓存 → 数据库精确 key → 向量余弦相似度(阈值 0.92)合并近义实体,所以"阿里巴巴"和"Alibaba"会并到同一个节点。
只有在 KB 配置里**开启了实体抽取**才会跑。想立刻重抽:`POST /api/v1/wiki/kb/{kbId}/entities/extract?force=true`——force 模式会先拿到新结果再替换旧图谱,即使 LLM 整个失败,现有图谱也不会被清空。
### 配置实体类型
`Wiki → 配置 → 实体抽取` 卡片里:打开开关后出现一个标签编辑器(可多选、可搜索、可现场新建)。内置建议就是上面六种,你可以直接敲入自定义类型(比如 `technology`、`law`)按回车加进去。留空则回退到内置六种。类型列表存在 KB 的 `configContent` JSON 的 `entityTypes` 字段里。
### 在图上看关系
Wiki 图谱视图工具栏上多了**页面图 / 实体图**切换。切到实体图后:
- 整图加载(`GET /api/v1/wiki/kb/{kbId}/entity-graph`),节点按类型上色,标签**始终显示**
- 顶部**图例type legend**列出图里出现的所有实体类型;点某个类型标签可以把该类型的节点过滤掉 / 恢复,图大的时候很有用
- 点一个节点 → 加载它的**自我图ego-graph**,右侧面板列出这个实体的别名、关系、以及**提及它的 wiki 页面**(可点击跳过去)
- 配色用一套统一的大地色板,和页面类型图共用一套视觉语言;由于图谱用 canvas 渲染读不到 CSS 变量,配色在 JS 层读取当前主题的计算样式,亮 / 暗模式下标签颜色都正确
底层三张表见下方[底层数据](#底层数据-如果你好奇)。
---
## 视觉管线:图片也能被读出来
读不了图的 wiki 是半瞎的。PDF 尤其严重——一半的真信息往往就在那些图里。
@ -534,7 +574,7 @@ mate:
|---|---|---|
| `dashscope-vision` | `qwen-vl-max` | DashScope 兼容模式,复用 UI 里配好的 DashScope provider |
| `zhipu-vision` | `glm-5v-turbo` | 智谱 BigModelOpenAI 兼容 |
| `volcano-doubao-vision` | 可配置 | 字节跳动火山豆包视觉 |
| `doubao-vision` | 可配置 | 字节跳动火山豆包视觉 |
Provider 按 order 自动选用。Key / base URL 都在 `Settings → 模型` 里像普通 provider 那样配,视觉管线会从那里取凭证。
@ -582,11 +622,11 @@ stepModels[step] → wikiDefaultModelId → 系统默认模型
## 底层数据(如果你好奇)
九张表
核心表(完整列表见各功能章节)
| 表名 | 用途 |
|------|------|
| `mate_wiki_knowledge_base` | 每个 KB 一行。owner、名字、描述、配置 JSON`ingestMode` / `wikiDefaultModelId` / `stepModels` 等)。 |
| `mate_wiki_knowledge_base` | 每个 KB 一行。owner、名字、描述、配置 JSON`ingestMode` / `wikiDefaultModelId` / `stepModels` / `entityExtractionEnabled` / `entityTypes` 等)。 |
| `mate_wiki_raw_material` | 每份上传一行。状态、byte hash、来源路径、上次成功处理时的 hash。 |
| `mate_wiki_page` | 每个生成页面一行。标题、摘要、正文、`source_raw_ids`(回指原文)、`page_type`、`locked`、版本号,外加 `embedding` / `embedding_model` / `embedding_text_version` 让 synthesis 页直接进语义搜索。 |
| `mate_wiki_chunk` | 每个 chunk 一行。content + hash + 偏移 + embedding外加 `page_number` / `header_breadcrumb` / `source_section` / `token_count`。 |
@ -595,6 +635,9 @@ stepModels[step] → wikiDefaultModelId → 系统默认模型
| `mate_wiki_image_caption_cache` | 视觉管线提取出的 caption 缓存,按 SHA-256 索引。`caption` / `visible_text` / `mime_type` / `capture_model` / `provider_id` / `duration_ms` / `hit_count`。 |
| `mate_wiki_transformation` | 每个加工器模板一行。`name` / `title` / `description` / `prompt_template` / `model_id` / `apply_default` / `output_target` / `output_format` / `output_schema`。`kb_id=NULL` = 工作区全局可用。 |
| `mate_wiki_transformation_run` | 每次模板运行一行。`status` / `output` / `error` / `duration_ms` / `model_id` / `triggered_by` / `input_tokens` / `output_tokens` / `total_tokens` / `output_page_id`。 |
| `mate_wiki_entity`V148 | 每个实体一行。规范名、类型、别名 JSON、`salience`、`mention_count`、`embedding`(近义去重用)。 |
| `mate_wiki_entity_mention`V149 | 实体在某个 chunk 的一次出现。`entity_id` / `chunk_id` / `page_id`(反指 wiki 页面)/ `surface_form` / `evidence`。 |
| `mate_wiki_entity_relation`V150 | 实体间关系三元组。`subject_entity_id` / `predicate` / `object_entity_id` / `evidence` / `evidence_chunk_id`。 |
`mate_wiki_page` 还带两个保护字段:

View File

@ -295,7 +295,6 @@ curl -X PUT http://localhost:18088/api/v1/workspaces/1/members/42 \
| `workspace_id` | 外键到 `mate_workspace` |
| `user_id` | 外键到 `mate_user` |
| `role` | `owner` / `admin` / `member` / `viewer` |
| `joined_at` | 用户加入这个工作空间的时间 |
| `create_time` / `update_time` | 时间戳 |
---

View File

@ -1,6 +1,6 @@
{
"name": "mateclaw-ui",
"version": "1.6.0-SNAPSHOT",
"version": "1.6.0",
"private": true,
"type": "module",
"description": "MateClaw - Personal AI Assistant Web Console",

View File

@ -21,7 +21,7 @@
<properties>
<!-- MateClaw release version shared by all Maven modules. -->
<revision>1.6.0-SNAPSHOT</revision>
<revision>1.6.0</revision>
<!-- Java -->
<java.version>21</java.version>