Plan: wiki 每日沉思 — 週期性統整層 (#479)¶
「wiki 需要每日沉思、重新整理 wiki 內的東西、把 general concepts/terms 取出 (follow llm wiki)」。這是加在 #50 增量維護(fold)之上的一個週期性統整 (consolidation/synthesis)層 —— 不取代增量。增量負責保鮮(每次 ingest 局部 更新);沉思負責整理提煉(退一步讀整份 wiki → 去重/合併/拆分/提煉概念)。
心智模型的直接先例 = topic-hub 的
→consolidate(「讀現有記憶 → 改寫:去重/ 合併/摘要/丟棄」,且「週期性是呼叫者的事」)。這裡把同一模型套到 KB prose wiki。
0 · 鎖定的決策(/grill-me Q1–Q9)¶
| # | 決策 | 結論 |
|---|---|---|
| Q1 定位 | 加層 vs 取代 | 加上去的統整層,不動 #50 增量維護;職責分離(保鮮 vs 整理提煉) |
| Q2 觸發 | 節奏 | 固定每日、可設定時間(照 CodeRepoSweeper/kb.git.daily_sync)+ 手動 Reflect now。變動閘 v1 不做(每天無條件跑;「只在有變動才跑」留旋鈕) |
| Q3 範圍 | 哪種 wiki | 只 prose wiki(use_wiki 且 git_url is None);code wiki(#281,確定性重生)跳過 |
| Q4 產物 | 概念去哪 | 留在 wiki 內:/concepts/*.md 概念頁 + wiki 內術語索引;外部 ContextCard glossary 不碰(#414 獨立供),避免兩 producer 打架 |
| Q5 動作 | 做什麼 | ①提煉概念 ②術語索引 ③合併重複頁 ④修 [[wikilink]]+index ⑤統整矛盾 ⑥拆過肥頁 全做;⑦孤兒頁只標記不刪。刪除政策:只有 content-preserving 合併才刪頁 |
| Q6 模型 | 怎麼跑 | survey → plan → apply(仿 #281 code_wiki:程式管控制流、LLM 只做有界逐單元合成)。1 plan + N apply 次 LLM;survey 0 LLM;列舉每頁強制覆蓋率 |
| Q7 安全 | 冪等 | 保守 planner + 確定性 write-suppression(新內容與現有 diff,一樣就不寫);wiki AI-owned(只 /clarifications、/corrections 免動,pin marker 延後);拆併門檻+遲滯;不做快照回滾 |
| Q8 可觀測 | 看得到 | 動作紀錄寫 /reflections/<date>.md 專區(reflect 自有命名空間,fold 免動、survey 自跳、reader 可讀);重用 WikiBuildState.phase;全 collect streaming;不做 canned check(改手動 dogfood 當 live 驗證) |
| Q9 FE | 露出 | Reflect now 按鈕(擺 Rebuild 旁)+ 上次沉思時間;概念/日誌靠現有唯讀 wiki 樹瀏覽;進度加 reflect phase 標籤 |
1 · 架構¶
┌─ fold (既有 #50,每次 ingest 局部增量) ── 保鮮
prose ───┤
wiki └─ reflect (新;每日 or 手動,整份 wiki 統整) ── 整理提煉
│
└─ WikiReflector.reflect(cid):
survey() 程式、0 LLM:列每頁(跳過保留區/reflections/WIKI.md/log.md)
→ 每頁確定性抽一行(heading + Sources: + [[links]]) → digest
plan() 1 次 streaming collect:digest → ReflectPlan(結構化 JSON)
空計畫 = no-op(天然冪等);拆併有 threshold+hysteresis
apply() 程式 iterate 計畫,每動作有界 collect + 程式寫檔:
- concept:提煉/刷新 /concepts/<slug>.md
- merge:內容併進 target + 修 inbound [[link]] + 刪空頁(content-preserving)
- split:過肥頁拆多頁
- 修 index + 術語索引
- orphan:只寫進 /reflections 日誌(不刪)
全部經 write-suppression(diff 一樣不寫)
1.1 儲存與命名空間(P1)¶
/reflections/加進store._RESERVED_DIRS(與/clarifications/、/corrections/同級)。 效果:fold/unfold/correct maintainer(走MaintainerWikiStore)動不了/reflections/; 反思日誌用 rawWikiFileStore寫(比照 corrections/clarifications landing path 用 raw store 的既有模式)。- 頁面重整走
MaintainerWikiStore(guarded)→ 自動保住/clarifications、/corrections(Q7)。 Collection.last_reflected_at: str = ""(非索引,無 migration;比照auto_digest等既有欄位)。 reflect 完成時 stamp ISO 時間 → FE 顯示「上次沉思」。
1.2 WikiReflector(P2–P4,新模組 kb/wiki/reflect.py)¶
仿 CodeWikiBuilder:__init__(spec, llm, *, wiki_store=None);prompt 用 inline 常數(仿 kb/quality.py/code_wiki,非 .md);LLM = ILlm.collect()(底層永遠 stream)。
- survey:
WikiFileStore._paths(metadata-only,#411)列頁,skip_is_reserved+/reflections/+/WIKI.md+/log.md;每頁讀一次,_first_paragraph_after_h1(重用 code_wiki)抽一行 + parseSources:+[[links]]→ digest(每頁一行,有界)。 - plan:
collect(plan_prompt)→_unfence→msgspec.json.decode(ReflectPlan);parse 失敗 → 空計畫(no-op,安全)。保守 prompt(只在有具體缺陷才提動作)。拆/併門檻(page size)+ hysteresis(兩門檻留 gap)。 - apply:程式 iterate;每動作聚焦 collect(輸入只有牽涉頁);寫檔前 write-suppression(讀舊內容,byte-equal 就 skip);merge 後修 inbound
[[link]](程式,deterministic)。
1.3 Coordinator 接線(P5)¶
WikiJobPayload.op增reflect(jobs.pydocstring 補一行)。_handledispatch:elif payload.op == "reflect": self._handle_reflect(payload, triggered_by=actor)。- producer
enqueue_reflect(cid, *, requested_by=None):僅 proseuse_wiki(且非 git_url);coalesce(已有 active reflect 就跳);partition_key=cid(免費序列化,且與 fold 序列化 → 不同時跑)。 _handle_reflect:seed phase=surveying→WikiReflector.reflect(cid, on_phase=…)(phase 依序 surveying/planning/applying,current帶 N/M)→ 記錯不 crash partition(仿_handle_fold);頁寫acting_as(actor)(#83)。- reflector 用既有
code_wiki_llm(= kb.wiki.llm,就是 wiki LLM):self._reflector = WikiReflector(...) if code_wiki_llm is not None else None(免動build_coordinatorsLLM 接線)。
1.4 Route(P6)¶
POST /kb/collections/{id}/wiki/reflect→coordinator.enqueue_reflect(cid, requested_by=user)+start_consuming;回 pydantic(照既有 rebuild route)。進度沿用GET .../wiki/status。
1.5 排程(P7)¶
reflect_sweeper(api/lifecycle.py,仿code_sync_sweeper/CodeRepoSweeper):每日牆鐘於設定時間,對每個 proseuse_wikicollectionenqueue_reflect。- config
settings.kb.wiki.reflect(enabled: bool、daily_time/interval);沿用kb.git.daily_sync的形狀。非 queue sweeper 永遠留 API(不受run_consumersgate)。
1.6 FE(P8)¶
- Reflect now 按鈕擺 wiki 分頁 Rebuild 旁 →
POST .../wiki/reflect;顯示last_reflected_at。 - reflect phase 標籤(
surveying/planning/applying)進現有wiki/status進度 UI(仿 code wiki phase 標籤)。 - 概念(
/concepts/)、日誌(/reflections/)自動出現在現有唯讀 wiki 樹,v1 無專屬視圖。
2 · 扁平階段(flat integer;每 phase 一 commit)¶
- P1
/reflections/命名空間(reserved dir,raw-store 可寫、maintainer 免動、survey 跳過)+Collection.last_reflected_at。 - P2
WikiReflector.survey— 確定性 digest builder(純函式)。 - P3
ReflectPlanschema +WikiReflector.plan(streaming collect,保守 prompt,拆併 threshold+hysteresis,tolerant parse → 空計畫)。 - P4
WikiReflector.apply— 各動作 executor(concept / merge+link-fix+content-preserving delete / split / index+術語索引 / orphan→journal)+ write-suppression;reflect()串起 survey→plan→apply +on_phase。 - P5 Coordinator:
op="reflect"+_handle_reflect+enqueue_reflect+ reflector 注入。 - P6
POST .../wiki/reflectroute(手動)。 - P7
reflect_sweeper(每日)+kb.wiki.reflectconfig。 - P8 FE:Reflect now 按鈕 + 上次沉思時間 + reflect phase 標籤(vitest TDD)。
- P9 live dogfood(手動 Reflect now 對真 collection 跑真 LLM)+ docs(
development.mdhow-to)。
DoD:每 phase ruff check && ruff format --check、ty check、targeted pytest 綠、commit;最後全套 coverage … --fail-under=100 + FE typecheck+build。
3 · 不在範圍(本次)¶
- 變動閘(「只在 wiki 有變動才跑」的排程優化)—— Q2 明確延後為旋鈕。
- pin marker(保護人工手改的 prose wiki 頁)—— Q7 延後;v1 wiki 視為 AI-owned。
- 概念 → 外部
ContextCardglossary 橋接 —— Q4 留 follow-up(重用classify_against_existing去重)。 - 超大 wiki 的 plan fan-out 分批(digest 上千頁才需要)—— 同 code_wiki 當初,延後。
- 快照 / 回滾 —— Q7 延後;靠 content-preserving 合併 + write-suppression + 日誌。
- canned capability check —— Q8 user 明確 waive;改手動 dogfood 當 live 驗證。
4 · 風險與對策¶
- 每日無條件跑 → thrash:write-suppression(byte-diff)保證穩定 wiki 實體不動;保守 planner 回空計畫;拆併 hysteresis 防震盪。代價 = 穩定 wiki 每日仍白跑
1 plan + N applyLLM(變動閘補上即免)。 - #50 narrate 雷:apply 由程式寫檔(非 agent loop),繞開。
- context 爆:survey digest 每頁一行(有界),plan 只讀 digest,apply 每動作只讀牽涉頁。
- 誤刪:唯一刪來自 content-preserving 合併(內容已併入 target);孤兒只標記;guarded store 擋掉 ground-truth 保留區。