AI 一連上 MCP 就知道從哪開始(leo 追問揪出的最後一哩)
leo:「arcrun_get_skill 放在 MCP 裡,現在人類說『幫我用 arcrun 寫 xxx』, Haiku 會說『arcrun 是什麼?我看看』然後發現有個 arcrun mcp 就去執行? 如果不會,要寫什麼在外面讓它一聽到就知道要用這些資源?」 實測答案:**不會**。 ① 總管寫的指引沒有任何入口指向它(真實 AI 找不到) ② MCP 五個 skill 沒有「怎麼寫意圖工作流」這支 ③ AI 只看到 41 個工具名 → 自己猜 → 很可能直接 push_workflow 瞎編, 或去讀 registry/examples 那 8/13 引用不存在零件的壞範例 兩件修法: 1. registry/skills/write_intent_workflow.md — 把指引變成 AI 拿得到的 skill (範本全取自實跑 verdict=success 的四支 workflow;含「查詢現在回假 found」的已知限制警告) 2. mcp-handler.ts instructions 開場指路 — instructions 是唯一「AI 一連上就必看」的欄位 ⇒ 開場就寫明五步順序(先讀 write_intent_workflow → whoami → 查零件 → list_skills → 缺件怎麼補) +邊只有兩種、第一個節點是 input、不要因查不到就改寫 code ⚠️ 靜態常數:KBDB 掛了也必出現(守「不擋連線」鐵律) 驗:tsc 零錯誤。⏳ 待部署後由 leo 真的問 haiku「幫我用 arcrun 做 X」驗證它會不會自己讀 skill。
This commit is contained in:
+26
-1
@@ -15,10 +15,35 @@ export async function handleMcpRequest(
|
||||
//(選型理由見 lib/library-map.ts 檔頭);任何失敗回 null → 靜默略過,絕不擋 MCP 連線(鐵律)。
|
||||
const mapInstructions = await buildLibraryMapInstructions(env);
|
||||
|
||||
// 2026-07-30(leo 問「人類說『幫我用 arcrun 寫 xxx』,Haiku 會知道要用這些資源嗎?
|
||||
// 如果不會,要寫什麼在外面讓它一聽到就知道?」):
|
||||
// 實測答案是**不會**——AI 只看到 41 個工具名,會自己猜(很可能直接跳到 push_workflow
|
||||
// 瞎編,或去讀 registry/examples 那 8/13 引用不存在零件的壞範例)。
|
||||
// instructions 是唯一「AI 一連上就必看」的欄位 ⇒ 開場就指路,不依賴任何查詢成功。
|
||||
// ⚠️ 這段是靜態常數:即使 KBDB 掛了、藏書地圖抓不到,它也必須出現(鐵律:不擋連線)。
|
||||
const startHere = [
|
||||
"【先讀這裡】要在 Arcrun 上做任何事(用戶說「幫我用 Arcrun 做 X」),**照這個順序**:",
|
||||
"",
|
||||
"1. `arcrun_get_skill('write_intent_workflow')` — **必讀第一支**。",
|
||||
" 教你用 `>>` 寫「意圖工作流」。你**不需要先知道有哪些零件**,先寫意圖。",
|
||||
"2. `arcrun_whoami()` — 確認連到哪個帳號(勿自行 curl 猜帳號 URL)。",
|
||||
"3. 把意圖丟 `POST /cypher/search` 或 `arcrun_validate_yaml` — 系統告訴你哪些零件存在。",
|
||||
"4. `arcrun_list_skills()` — 看有沒有更貼近你這件事的 skill(watcher/RAG/debug…)。",
|
||||
"5. 缺零件時:缺 API → 寫 recipe(`arcrun_recipe_push`);缺能力 → 投稿零件 PR。",
|
||||
" 🔴 **不要因為查不到零件就改寫成 `code` 節點**——那叫「腹語術」(表面用 Arcrun、",
|
||||
" 實際全寫 JS)。`code` 只用於局部整形(例:剝掉 LLM 回應的雜訊)。",
|
||||
"",
|
||||
"邊只有兩種:`ON_SUCCESS` 與 `對每個 <變數>`(FOREACH)。",
|
||||
"**沒有** `ON_TRUE`/`ON_FALSE`/`ON_FAILURE`——引擎目前不支援條件分支。",
|
||||
"第一個節點固定是 `input`。",
|
||||
].join("\n");
|
||||
|
||||
const instructions = mapInstructions ? `${startHere}\n\n---\n\n${mapInstructions}` : startHere;
|
||||
|
||||
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
||||
const server = new McpServer(
|
||||
{ name: "arcrun-mcp-server", version: "1.0.0" },
|
||||
mapInstructions ? { instructions: mapInstructions } : undefined,
|
||||
{ instructions },
|
||||
);
|
||||
|
||||
registerAllTools(server, env, orgNamespace, partnerToken);
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# Skill: Write Intent Workflow(寫意圖工作流)
|
||||
|
||||
## 何時用這個 skill
|
||||
|
||||
**用戶說「幫我用 Arcrun 做 X」時,第一個讀這支。** 其他 skill 都是它的下游。
|
||||
|
||||
- 「幫我用 Arcrun 寫一個…」
|
||||
- 「用 Arcrun 做 X」
|
||||
- 你要在 Arcrun 上做任何事,但不知道有哪些零件可用
|
||||
|
||||
> **你不需要知道有哪些零件。** 先把意圖寫出來丟去查,系統會告訴你哪些存在、哪些不存在。
|
||||
|
||||
## 核心 pattern
|
||||
|
||||
```
|
||||
input >> ON_SUCCESS >> <第一步> >> ... → 丟 /cypher/search → 系統回哪些零件存在
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 意圖工作流的語法
|
||||
|
||||
一串「誰接誰」,每行一個關係:
|
||||
|
||||
```
|
||||
<節點A> >> <邊> >> <節點B>
|
||||
```
|
||||
|
||||
- **節點**=一個步驟。用你想得到的名字(中文可以),**不必是真實零件名**
|
||||
- **邊**=什麼情況下往下走
|
||||
|
||||
## 2. 邊只有兩種(真範本裡出現過的)
|
||||
|
||||
| 邊 | 意思 | 真例 |
|
||||
|---|---|---|
|
||||
| `ON_SUCCESS` | 上一步成功就往下 | `input >> ON_SUCCESS >> prep` |
|
||||
| `對每個 <變數>` | 上一步產出清單,逐項處理(FOREACH)| `parse_card >> 對每個 block >> post_block` |
|
||||
|
||||
⚠️ **不要寫 `ON_FAILURE`/`ON_TRUE`/`ON_FALSE`**——引擎目前**沒有條件分支**
|
||||
(實測 `grep ON_TRUE|ON_FALSE` 於 cypher-executor = 0;見 Gitea Arcrun#5)。
|
||||
需要判斷時:**寫成一個獨立節點**(例 `check_amount`)再接 `ON_SUCCESS`,
|
||||
讓查詢告訴你有沒有零件可用。
|
||||
|
||||
## 3. 第一個節點固定是 `input`
|
||||
|
||||
所有真範本都以 `input` 起頭——那是「觸發時帶進來的資料」。
|
||||
|
||||
---
|
||||
|
||||
## 4. 真範本(照抄結構、改內容)
|
||||
|
||||
> 以下四份**全部是實際部署且 `verdict=success` 的 workflow**,不是簡化示範。
|
||||
> 用 `arcrun_get_workflow(<name>)` 可以拿完整定義。
|
||||
|
||||
### A. 最短:取資料 → 處理 (`graph_neighbors`)
|
||||
```
|
||||
input >> ON_SUCCESS >> fetch_triplets
|
||||
fetch_triplets >> ON_SUCCESS >> bfs_neighbors
|
||||
```
|
||||
|
||||
### B. 長鏈:多次查詢 → 組裝 → 問 AI → 收尾 (`rag_chat`)
|
||||
```
|
||||
input >> ON_SUCCESS >> prep
|
||||
prep >> ON_SUCCESS >> kw_search
|
||||
kw_search >> ON_SUCCESS >> sem_search
|
||||
sem_search >> ON_SUCCESS >> fetch_triplets
|
||||
fetch_triplets >> ON_SUCCESS >> fetch_blocks_a
|
||||
fetch_blocks_a >> ON_SUCCESS >> assemble
|
||||
assemble >> ON_SUCCESS >> ask_llm
|
||||
ask_llm >> ON_SUCCESS >> finalize
|
||||
```
|
||||
`prep` 前處理/`assemble` 組 prompt/`finalize` 收拾回應——三個常見的整形節點。
|
||||
|
||||
### C. 一節點分岔兩條 FOREACH (`rag_ingest_card`)
|
||||
```
|
||||
input >> ON_SUCCESS >> parse_card
|
||||
parse_card >> 對每個 block >> post_block
|
||||
parse_card >> 對每個 rel >> post_triplet
|
||||
```
|
||||
同一節點可有多條出邊,各自處理不同清單。
|
||||
|
||||
### D. 混合:直線 + 兩段 FOREACH (`rag_takedown_direct`)
|
||||
```
|
||||
input >> ON_SUCCESS >> prep
|
||||
prep >> ON_SUCCESS >> list_dead_blocks
|
||||
list_dead_blocks >> ON_SUCCESS >> build_deprecations
|
||||
build_deprecations >> 對每個 dead_entry >> deprecate_entry
|
||||
build_deprecations >> ON_SUCCESS >> list_triplets
|
||||
list_triplets >> ON_SUCCESS >> pick_dead_triplets
|
||||
pick_dead_triplets >> 對每個 dead_record >> deprecate_triplet
|
||||
```
|
||||
`build_deprecations` 同時有 FOREACH 出邊與 `ON_SUCCESS` 出邊——
|
||||
前者處理清單、後者繼續主線。
|
||||
|
||||
---
|
||||
|
||||
## 5. 節點怎麼命名(照真範本的模式,查詢較容易媒合)
|
||||
|
||||
| 意圖 | 模式 | 真例 |
|
||||
|---|---|---|
|
||||
| 前處理/正規化 | `prep` | `rag_chat.prep` |
|
||||
| 取一批資料 | `fetch_*`/`list_*` | `fetch_triplets`/`list_dead_blocks` |
|
||||
| 搜尋 | `*_search` | `kw_search`/`sem_search` |
|
||||
| 解析/切塊 | `parse_*` | `parse_card` |
|
||||
| 寫入 | `post_*` | `post_block`/`post_triplet` |
|
||||
| 組裝 | `assemble`/`build_*` | `assemble`/`build_deprecations` |
|
||||
| 問 AI | `ask_llm` | `rag_chat.ask_llm` |
|
||||
| 收尾整形 | `finalize` | `rag_chat.finalize` |
|
||||
|
||||
---
|
||||
|
||||
## 6. 寫完一定要查(**不要直接部署**)
|
||||
|
||||
```bash
|
||||
curl -s -X POST https://arcrun-cypher-executor.<subdomain>.workers.dev/cypher/search \
|
||||
-H 'content-type: application/json' -H 'X-Arcrun-API-Key: <namespace>' \
|
||||
-d '{"triplets":["input >> ON_SUCCESS >> fetch_data","fetch_data >> ON_SUCCESS >> notify"]}'
|
||||
```
|
||||
|
||||
回應的每個節點會有:
|
||||
|
||||
| status | 意思 | 你該做什麼 |
|
||||
|---|---|---|
|
||||
| `found` | 有零件,附 `input_schema`(怎麼填 payload)與 `success_rate` | **只填 payload** |
|
||||
| `missing` | 沒有這個零件 | 缺 API → 寫 recipe;缺能力 → 投稿零件 PR |
|
||||
| `unknown` | 查不到 registry | **不代表不存在**,別據此改寫成 code |
|
||||
|
||||
⚠️ **2026-07-30 已知限制**:`/cypher/search` 目前對**任何**節點名都回 `found`
|
||||
(不查 registry)⇒ **這個 status 現在不可信**。修復中(CP `arcrun-usable` 步驟 3)。
|
||||
在它修好前:用 `arcrun_list_components` / `arcrun_search_components` 自己確認零件是否存在。
|
||||
|
||||
---
|
||||
|
||||
## 7. 常犯的錯
|
||||
|
||||
1. **用不存在的邊**(`ON_FAILURE`/`ON_TRUE`)→ 只有 `ON_SUCCESS` 與 `對每個 X`
|
||||
2. **第一個節點不是 `input`**
|
||||
3. **把 recipe 當零件寫**——`telegram_send`/`gmail`/`kbdb_get` 是 **recipe** 不是零件
|
||||
→ 寫成 `http_request` + 該 recipe
|
||||
4. 🔴 **查詢回 `missing` 就改寫成 `code` 節點**
|
||||
→ 那叫「腹語術」(表面用 Arcrun、實際全寫 JS)。正解:缺 API 寫 recipe、缺能力投稿零件。
|
||||
`code` 只用在**局部整形**(例:剝掉 LLM 回應的雜訊),不用來取代零件與流程控制。
|
||||
|
||||
---
|
||||
|
||||
## 8. 相關
|
||||
|
||||
- 完整版指引與十題考卷(含 haiku 實測 10/10):
|
||||
頂層 repo `system-dev/docs/3-specs/arcrun-usable/`
|
||||
- 下一步該讀哪支 skill:`arcrun_list_skills()`
|
||||
- 定期掃資料 → `build_watcher_workflow`
|
||||
- RAG 檢索問答 → `rag_with_arcrun`
|
||||
- workflow 卡住不動 → `debug_paused_workflow`
|
||||
Reference in New Issue
Block a user