arcrun — AI workflow execution engine (clean history)
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
# SDD: arcrun Recipe System(容器 + Recipe 模式)
|
||||
|
||||
> 2026-05-07 建立。吃狗糧寫 wiki 合成 workflow 時撞牆發現的平台缺口。
|
||||
> 核心原則:**一個 WASM 零件 = 容器,內容(recipe)存資料庫**。
|
||||
> n8n 為每種 API 寫獨立 node,arcrun 走「容器 + recipe」減少零件數量。
|
||||
|
||||
---
|
||||
|
||||
## 1. 問題
|
||||
|
||||
### 1.1 撞牆現場
|
||||
|
||||
寫 mira wiki 合成 workflow(7-B)時:
|
||||
- 流程:`kbdb_get(stale)` → foreach → `kbdb_get(drafts)` → `claude_api(合成 prompt)` → `kbdb_ingest`
|
||||
- 第三步要組 prompt:`schema 內容 + skill 模板 + drafts array + existing_entities`
|
||||
- cypher binding 內建 `{{var}}` 模板太弱(只支援 top-level,不支援嵌套 / array → string)
|
||||
- 沒有 `string_template` 零件、沒有 `array_to_markdown` 零件
|
||||
- 寫專用 `wiki_prompt_builder` 零件 = 走 n8n 老路,每個 AI workflow 都要寫一個
|
||||
|
||||
### 1.2 根因
|
||||
|
||||
**arcrun recipe 系統只覆蓋 HTTP / auth 兩層**:
|
||||
|
||||
| Recipe 種類 | 存哪 | 容器 | 狀態 |
|
||||
|---|---|---|---|
|
||||
| auth_recipe | RECIPES KV (`auth_recipe:{service}`) | auth_static_key / auth_oauth2 / ... | ✅ 已有 |
|
||||
| api_recipe | RECIPES KV (`rec_{hash}`) | http_request | ✅ 已有(hard-code 在 cypher-executor 待清,Phase 1-3 處理)|
|
||||
| **prompt_recipe** | ❌ 不存在 | claude_api(容器) | **缺** |
|
||||
|
||||
`claude_api` 零件目前吃 `prompt: string`(已組好的字串),沒有「recipe 模式」可以讓 AI 用「組合配方」的方式呼叫。
|
||||
|
||||
### 1.3 影響
|
||||
|
||||
- **致命**:寫不出第一個 wiki 合成 workflow(7-B 卡關)
|
||||
- **推廣破功**:arcrun 對外 prop 是「容器 + recipe,AI 不用寫 code」,但 prompt 這層做不到
|
||||
- **未來所有 AI workflow 都會撞同樣問題**:rss-tech-news 評語、河道 AI 副駕、ai-comment、文章摘要⋯ 全部需要組 prompt
|
||||
|
||||
---
|
||||
|
||||
## 2. 設計
|
||||
|
||||
### 2.1 核心:prompt_recipe 平行於 auth_recipe / api_recipe
|
||||
|
||||
**儲存**:`RECIPES` KV,key 格式 `prompt_recipe:{name}`
|
||||
|
||||
**結構**:
|
||||
```yaml
|
||||
id: prompt_recipe:wiki_synthesis
|
||||
version: v1
|
||||
description: "Mira wiki 合成(抽 triplet + 寫 wiki paragraph)"
|
||||
model: sonnet # haiku / sonnet / opus(claude_api 沿用既有 routing)
|
||||
|
||||
# 從 KBDB / 其他來源取的 fragment(在 prompt 組合時抓並插入)
|
||||
fragments:
|
||||
- var: schema
|
||||
source: kbdb_block
|
||||
block_id: "7a4e456e-1b0f-406a-8842-5e01d1cf1eef" # mira-wiki-schema
|
||||
field: content
|
||||
- var: skill_template
|
||||
source: kbdb_block
|
||||
block_id: "85e3b81e-dca8-4131-bcdc-990bd0d3a16f" # source-skill-wiki-synthesis
|
||||
field: content
|
||||
|
||||
# 從 workflow context 取(input/前置節點輸出)
|
||||
inputs:
|
||||
- var: drafts # 草稿 array
|
||||
from: "ctx.read_drafts.blocks"
|
||||
transform: "json_array" # 轉成 JSON array string
|
||||
- var: existing_entities
|
||||
from: "ctx.read_entities.blocks"
|
||||
transform: "extract_field:page_name" # 抽 array 的 page_name 欄位 join 成 list
|
||||
- var: entity_name
|
||||
from: "ctx.loop.item" # foreach 迴圈當前元素
|
||||
|
||||
# 最終 prompt 由 fragments + inputs 套進 skill_template 組成
|
||||
prompt_assembly:
|
||||
system: "{{schema}}" # 直接用 schema 當 system prompt
|
||||
user: "{{skill_template}}" # skill template 內含 {{drafts}} {{existing_entities}} {{entity_name}} 變數
|
||||
|
||||
# 期待輸出
|
||||
output:
|
||||
format: json # claude_api 自動 parse 為 object
|
||||
schema: # zod-style,parse 失敗回 success:false
|
||||
type: object
|
||||
required: [triplets, entities, paragraphs, source_summary]
|
||||
```
|
||||
|
||||
### 2.2 Recipe 解析在 cypher-executor(架構選擇 B)
|
||||
|
||||
**設計決策**(2026-05-07):recipe 解析跟 prompt 組裝**在 cypher-executor TS**,不改既有 claude_api WASM。
|
||||
|
||||
理由:
|
||||
1. recipe 解析是 cypher-executor 既有 `api_recipe / auth_recipe` 同性質工作
|
||||
2. 既有 claude_api 已部署 + 已測試,不動影響面最小
|
||||
3. transform 邏輯(json_array / extract_field 等)TS 寫起來比 TinyGo 簡單 10 倍
|
||||
4. 不違反 §1.6 — skill 還是 KBDB block,cypher-executor 只是組合者,不寫死 prompt
|
||||
|
||||
**流程:**
|
||||
|
||||
```
|
||||
workflow YAML 節點 config 出現 `recipe: prompt_recipe:xxx`
|
||||
│
|
||||
▼
|
||||
cypher-executor graph-executor.ts
|
||||
在執行該節點前 → 偵測 recipe 欄位 → 走 recipe expander
|
||||
│
|
||||
▼
|
||||
recipe expander(新 module)
|
||||
1. 從 RECIPES KV 抓 `prompt_recipe:xxx` 定義
|
||||
2. 按 fragments 規則 → 用既有 KBDB client 抓 block content
|
||||
3. 按 inputs 規則 → 從 context 取值 + 跑 transform
|
||||
4. 組 system prompt + user prompt
|
||||
5. 把 {prompt, model, mira_token, ...} 當作節點實際 input
|
||||
│
|
||||
▼
|
||||
loader 呼叫 claude_api 容器(不知道 recipe 存在,仍吃舊介面)
|
||||
│
|
||||
▼
|
||||
claude_api 容器 → Mira daemon → 回 LLM 結果
|
||||
│
|
||||
▼
|
||||
graph-executor 取結果 → 按 recipe.output 規則 parse JSON / 驗 schema
|
||||
```
|
||||
|
||||
**對 claude_api 容器的影響**:完全沒有。它仍吃 `{mira_token, prompt, model}`。
|
||||
|
||||
**對 workflow 作者的體驗**:
|
||||
```yaml
|
||||
config:
|
||||
synthesize:
|
||||
component: claude_api
|
||||
recipe: "prompt_recipe:wiki_synthesis" # ← cypher-executor 偵測到這欄位,自動解析
|
||||
mira_token: "{{secret.mira_token}}"
|
||||
```
|
||||
|
||||
不寫 recipe 走舊路:
|
||||
```yaml
|
||||
config:
|
||||
reply:
|
||||
component: claude_api
|
||||
prompt: "{{ctx.user_message}}" # ← 沒 recipe,cypher-executor 直接透傳
|
||||
mira_token: "{{secret.mira_token}}"
|
||||
```
|
||||
|
||||
### 2.3 Workflow YAML 體驗
|
||||
|
||||
```yaml
|
||||
name: wiki_synthesis
|
||||
flow:
|
||||
- "input >> 完成後 >> read_stale"
|
||||
- "read_stale >> 對每個 >> read_drafts"
|
||||
- "read_drafts >> 完成後 >> synthesize"
|
||||
- "synthesize >> 完成後 >> write_wiki"
|
||||
config:
|
||||
read_stale:
|
||||
component: kbdb_get
|
||||
page_name: "mira-wiki-index-stale"
|
||||
read_drafts:
|
||||
component: kbdb_get
|
||||
page_name: "{{loop.item}}" # entity name
|
||||
synthesize:
|
||||
component: claude_api
|
||||
recipe: "prompt_recipe:wiki_synthesis" # ← 重點:指 recipe,不寫 prompt
|
||||
mira_token: "{{secret.mira_token}}"
|
||||
write_wiki:
|
||||
component: kbdb_ingest
|
||||
text: "{{prev.paragraphs}}"
|
||||
```
|
||||
|
||||
**AI 寫這 workflow 只需要:**
|
||||
1. 知道有 `kbdb_get / claude_api / kbdb_ingest` 三個容器(MCP search 找得到)
|
||||
2. 知道有 `prompt_recipe:wiki_synthesis` 這個配方(MCP search 找得到)
|
||||
3. 不需要懂 prompt 怎麼組、不需要看 wiki schema 文字
|
||||
|
||||
### 2.4 Recipe 是 KBDB block 還是 KV?
|
||||
|
||||
**選 KV**(`RECIPES` namespace),跟既有 auth_recipe / api_recipe 一致:
|
||||
- key: `prompt_recipe:{name}`
|
||||
- value: YAML/JSON
|
||||
- CLI 跟 MCP 用既有 `recipe push` / `recipe list` 工具管理(不需新工具)
|
||||
|
||||
**不選 KBDB block**:
|
||||
- 雖然 polaris/mira/CLAUDE.md §1.6 說「source-skill 存 KBDB block」
|
||||
- 但 §1.6 講的是 mira 業務的 skill template(schema / skill 模板)
|
||||
- recipe 是「組合配方」(指向哪些 block + 怎麼組),是 platform 層
|
||||
- recipe **裡面** 引用 KBDB block id(fragments.source: kbdb_block)— 兩層關係清楚
|
||||
|
||||
---
|
||||
|
||||
## 3. 範圍邊界
|
||||
|
||||
**在本 SDD 範圍內:**
|
||||
- ✅ Phase 1: prompt_recipe schema + RECIPES KV 規範
|
||||
- ✅ Phase 2: claude_api 改吃 recipe(向後相容舊 prompt 參數)
|
||||
- ✅ Phase 3: 寫第一個 recipe `prompt_recipe:wiki_synthesis`
|
||||
- ✅ Phase 4: 用此 recipe 完成 mira 7-B workflow
|
||||
- ✅ Phase 5: MCP 加 recipe 管理 tool(list / get / push / delete prompt_recipe)
|
||||
|
||||
**不在範圍內:**
|
||||
- HTTP api_recipe / auth_recipe 改造(已有,不動)
|
||||
- 多模態 prompt(image input)— 等 P2
|
||||
- recipe 沙盒驗收(recipe 是資料不是 code,不需要)
|
||||
|
||||
**前置依賴(已完成):**
|
||||
- ✅ kbdb_get 零件(5.3)
|
||||
- ✅ component-registry MCP backfill(component-registry-canon Phase 1)
|
||||
|
||||
---
|
||||
|
||||
## 4. 為什麼這個設計重要
|
||||
|
||||
| n8n | arcrun |
|
||||
|---|---|
|
||||
| Gmail node、Slack node、OpenAI node、Anthropic node、各 LLM node ⋯(每種 API 一個 node)| `http_request` 容器 + 各 service 的 api_recipe |
|
||||
| 每個 LLM 用法新 node(chat / completion / embedding)| `claude_api` 容器 + 各用途的 prompt_recipe |
|
||||
| AI 要學「Gmail node 怎麼用」「Slack node 怎麼用」⋯ | AI 要學「容器 + 配方」一次學會 |
|
||||
| 零件數爆炸(500+) | 容器固定(< 30),配方無限擴充 |
|
||||
| 配方藏在程式碼 | 配方在 KV,AI 直接 CRUD |
|
||||
|
||||
**對 AI 推廣**:第三方 AI 看到「30 個容器 + 100 個配方」遠比「500 個 node」好理解,且配方是文字資料不是 code,AI 寫配方比寫 node 簡單。
|
||||
|
||||
---
|
||||
|
||||
## 5. 風險與緩解
|
||||
|
||||
| 風險 | 緩解 |
|
||||
|---|---|
|
||||
| recipe 結構過度複雜,AI 寫不出來 | Phase 3 寫第一個 recipe(wiki_synthesis)作為範本,未來 AI 抄 |
|
||||
| 向後相容讓 claude_api 變兩條路 | 內部統一用 recipe path,舊 prompt 參數 → 自動轉成 inline recipe |
|
||||
| recipe 引用 KBDB block id 寫死,block 改 id 就壞 | KBDB block 用 `page_name` 識別比 id 穩定,recipe 支援 `block_page_name` 欄位 |
|
||||
| KV 寫入頻繁的 transform 邏輯(json_array, extract_field:x)→ 變 mini DSL | 限制 transform 種類(10 個內),列白名單,超過就請寫零件 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 變更紀錄
|
||||
|
||||
| 版本 | 日期 | 內容 |
|
||||
|---|---|---|
|
||||
| v1.0 | 2026-05-07 | 初版。吃狗糧寫 wiki 合成 workflow 撞到「prompt 組裝缺口」,補 prompt_recipe 層平行於既有 auth_recipe / api_recipe。 |
|
||||
| v1.1 | 2026-05-07 | 架構選擇 B:recipe 解析在 cypher-executor TS(不改 claude_api WASM)。減少改動面、可單元測試、跟既有 api_recipe 同層次。 |
|
||||
@@ -0,0 +1,110 @@
|
||||
# Tasks — Recipe System (容器 + Recipe 模式)
|
||||
|
||||
> 對應 SDD:[design.md](design.md)
|
||||
> 上次更新:2026-05-07
|
||||
|
||||
**狀態 legend**:`[ ]` 待辦 / `[🔄]` 進行中 / `[x]` 完成
|
||||
|
||||
---
|
||||
|
||||
## Phase 1:prompt_recipe Schema + KV 規範
|
||||
|
||||
- [x] 1.1 寫 `cypher-executor/src/lib/prompt-recipe-schema.ts`(85 行 Zod schema:fragments / inputs / prompt_assembly / output + transform 白名單 7 個)
|
||||
- [x] 1.2 確認 cypher-executor wrangler.toml 已有 RECIPES KV binding
|
||||
- [x] 1.3 寫 recipe loader (`recipe-loader.ts` 50 行) + transforms (`recipe-transforms.ts` 58 行) + expander (`recipe-expander.ts` 127 行)
|
||||
- transform 7 個:json_array / to_string / join / markdown_list / extract_field / first / pluck_content
|
||||
- expander:fragments(KBDB) + inputs(context+transform) → 套 {{var}} 模板 → {prompt, model, output_*}
|
||||
- type-check 全通過
|
||||
|
||||
## Phase 2:cypher-executor recipe expander(架構選擇 B,不改 claude_api)
|
||||
|
||||
- [x] 2.1 寫 `recipe-expander.ts`(127 行:load → fragments → inputs+transform → 套模板 → 回傳 prompt+model+output_*)
|
||||
- [x] 2.2 寫 `recipe-transforms.ts`(58 行:7 個 transform)
|
||||
- [x] 2.3 改 `graph-executor.ts` Component case:偵測 `node.data.recipe` → 呼叫 expandPromptRecipe → merge 進 mergedContext
|
||||
- [x] 2.4 output parser hook:執行完若 `_recipe_output_format === 'json'` 自動 parse + required_fields 驗證
|
||||
- [x] 2.5 部署 cypher-executor v426b099e
|
||||
- [x] 2.6 端對端驗證:用 curl 打 `/cypher/execute` 帶 recipe,trace 顯示 recipe 展開正確 + claude_api 拿到組好 prompt(Mira daemon 端 522 timeout 是 daemon 問題,不是 recipe 系統)
|
||||
- [x] 2.7 [紅利修復] cypher-executor `WASM_HTTP_RUNNER_IDS` 加 5 個 mira 零件(claude_api / kbdb_*)— 短期解,根本修法見 KI-13
|
||||
|
||||
## Phase 3:第一個 recipe — wiki_synthesis
|
||||
|
||||
- [x] 3.1 寫 `polaris/mira/recipes/wiki_synthesis.json`(4 fragments + 4 inputs + system/user template + json output)
|
||||
- [x] 3.2 用 `wrangler kv key put --remote` 推進 RECIPES KV (key: `prompt_recipe:wiki_synthesis`)
|
||||
- [x] 3.3 確認 KV 寫入成功(wrangler kv get 驗證)
|
||||
- [ ] 3.4 不適用(架構選擇 B 不改 claude_api,recipe 在 cypher-executor 解析)
|
||||
- [x] 3.5 端對端測試:用 MCP `u6u_execute_workflow` 跑 wiki_synthesis 成功
|
||||
- input:1 句草稿(黃仁勳 GTC 2026 物理 AI)
|
||||
- output:3 triplets + 3 entities + 1 wiki paragraph + source_summary
|
||||
- 過程修了 KI-14 (service binding 指錯)、KI-15 (token 沒轉發)、KI-16 (Claude markdown fence 沒剝)
|
||||
|
||||
## Phase 4:mira 7-B 用 recipe 完成 wiki workflow
|
||||
|
||||
- [🔄] 4.1 寫 `polaris/mira/workflows/wiki_synthesis.yaml`(cypher binding YAML)
|
||||
- 用 `recipe: prompt_recipe:wiki_synthesis` 指 recipe
|
||||
- 4-5 個節點:read_stale → foreach → read_drafts → synthesize → write_wiki + log
|
||||
- [ ] 4.2 用 MCP `u6u_execute_workflow` sandbox 跑(試一個 entity 不真寫 KBDB)
|
||||
- [ ] 4.3 用 MCP `u6u_deploy_workflow` 部署到 cypher-executor
|
||||
- [ ] 4.4 手動觸發 cron,驗 wiki page 真的出現
|
||||
- [ ] 4.5 在 mira/wiki/ 前端看到第一張 AI 合成 wiki page
|
||||
|
||||
## Phase 5:MCP recipe 管理 tools
|
||||
|
||||
- [ ] 5.1 加 MCP tool `arcrun_list_recipes(prefix?)`:列所有 prompt_recipe
|
||||
- [ ] 5.2 加 MCP tool `arcrun_get_recipe(name)`:取單一 recipe 內容
|
||||
- [ ] 5.3 加 MCP tool `arcrun_push_recipe(name, yaml_content)`:upsert recipe
|
||||
- [ ] 5.4 加 MCP tool `arcrun_delete_recipe(name)`
|
||||
- [ ] 5.5 既有 auth_recipe / api_recipe 也通用同套 tool(不只 prompt_recipe)
|
||||
|
||||
---
|
||||
|
||||
## 風險追蹤
|
||||
|
||||
- 風險 1:claude_api 改造跟 mira-app 同時動,可能影響河道 AI 副駕
|
||||
- 緩解:向後相容,舊 input 仍可用,mira 河道先不切 recipe
|
||||
- 風險 2:recipe transform 白名單漏了某種需求
|
||||
- 緩解:發現缺什麼再加,第一版優先支援 wiki 用到的(json_array, extract_field, join)
|
||||
- 風險 3:KV 跟 KBDB 都存配置,AI 困惑「該存哪邊」
|
||||
- 緩解:清楚分層 — recipe(容器組合方式) KV,data(schema 文字、skill 模板) KBDB
|
||||
|
||||
---
|
||||
|
||||
## Known Issues(吃狗糧發現,記錄)
|
||||
|
||||
### KI-11:MCP `u6u_execute_workflow` 不暴露 config 欄位 ✅ 修復(2026-05-07)
|
||||
- 已修:tool schema 加 optional `config: Record<string, Record<string, any>>`
|
||||
- 部署:u6u-mcp v11d7e366
|
||||
- 用戶要重啟 client session 才能看到新 schema
|
||||
|
||||
### KI-12:MCP execute 路由打 `/execute` 而非 `/cypher/execute` ✅ 修復(2026-05-07)
|
||||
- 已修:service binding fetch URL 改成 `http://cypher-executor/cypher/execute`
|
||||
- 部署:u6u-mcp v11d7e366
|
||||
|
||||
### KI-14:u6u-mcp service binding 指向已廢棄的 inkstone-cypher-executor ✅ 修復
|
||||
- 現象:MCP 路徑跑 workflow trace 顯示 synth 變 Output、config 被忽略
|
||||
- 根因:`u6u-mcp/wrangler.toml` services binding 是舊 worker `inkstone-cypher-executor`,不是現役 `arcrun-cypher-executor`
|
||||
- 解法:改 service name + redeploy
|
||||
|
||||
### KI-15:u6u-mcp 沒把 partner token 轉發給 cypher-executor ✅ 修復
|
||||
- 現象:recipe expander 抓 KBDB block 401(沒 auth)
|
||||
- 根因:partnerAuthMiddleware 驗完 token 但只 set org_namespace,沒留 token;execute_workflow tool fetch 沒帶 X-Arcrun-API-Key
|
||||
- 解法:middleware 也 set partner_token、handleMcpRequest + registerAllTools + execute_workflow 多一個 partnerToken 參數、fetch header 加 X-Arcrun-API-Key
|
||||
|
||||
### KI-16:Recipe JSON output 被 Claude 包在 ```json``` markdown fence ✅ 修復
|
||||
- 現象:JSON.parse 失敗 "Unexpected token \`"
|
||||
- 根因:Claude 預設輸出 ```json\n{...}\n``` 包裝
|
||||
- 解法:cypher-executor 解析前 regex 剝 fence
|
||||
|
||||
### KI-13:cypher-executor `WASM_HTTP_RUNNER_IDS` 寫死白名單
|
||||
- 現象:每加新零件要回 cypher-executor 改白名單 + 重部署
|
||||
- 影響:違反 arcrun「容器+ recipe,新零件無需改 platform」承諾
|
||||
- 短期解:手動加進白名單(claude_api / kbdb_* 已加)
|
||||
- 根本解:改成從 component-registry KV 動態查 canonical_id
|
||||
- 優先級:P1(推廣破口),需新 SDD `cypher-executor-dynamic-component-discovery`
|
||||
|
||||
---
|
||||
|
||||
## 對外推廣(Phase 6+,本 SDD 不執行,記錄)
|
||||
|
||||
- README 示範「容器 + recipe = 一個 service」(Gmail / Slack / Claude)
|
||||
- onboarding kit GitHub template 內含 5 個經典 recipe 當範例
|
||||
- 「recipe market」想法:用戶分享 recipe 幫他人少寫 prompt
|
||||
Reference in New Issue
Block a user