jsc-persona — AI 人格化記憶聊天

一套讓 AI 成為某個人、而不只是回答問題的 plugin。 以 OpenClaw 相同的人格描述IDENTITY.md 五欄位 + SOUL.md 四段落)建立人格, 再用 hook 強制的人格鎖與跨人格隔離,把 十二情緒語意分析短期/長期記憶心智圖/思維導圖人際關係圖 綁在一起。

可同時安裝於 Claude Code、Codex、Antigravity、OpenCode skills 是共通標準,鎖與隔離的強制執行需要 hook,目前只有 Claude Code 支援(見「跨助理支援度」)。


十四條硬規則

規則 怎麼做到
1a. 用與 OpenClaw 相同的描述建立人格 persona-create 逐項索取 Name / Creature / Vibe / Emoji / Avatar(連括號提示文字都照 OpenClaw 原文),SOUL.md 沿用 Core Truths / Boundaries / Vibe / Continuity 段落結構
1b. 動漫作品+角色名快速建人格 persona-anime 先上網蒐集該角色的公開設定(至少 3 個獨立來源),映射成上述五欄位與 SOUL,再把設定固化成 canon 基礎記憶(每則帶來源 URL)+原作人際關係圖+情緒基線
2. 同一個人格只能被一個程序載入(Sub Agent 不限) state/lock.jsonsession_id 為主鍵、15 分鐘心跳租約;同一 session 的 sub agent 沿用同一把鎖,跨 session 搶佔會被拒;租約過期才可接手(並強制回報)
3. 禁止跨人格讀取資料 PreToolUse hook 對 Read/Write/Edit/Glob/Grep/Bash 做路徑判定(含 ../、symlink、$PERSONA_HOME 繞路),非當前人格一律 deny;CLI 也驗 --session 防止冒用身分
4. 邀請人格用 Sub Agent 一起聊,且只顯示對話 persona-invite 建聊天室 + guest 唯讀租約 + persona-guest sub agent;同時開啟劇場模式hook 每輪強制「只輸出 名字:內容」、停掉所有系統提醒,CLI 有 --quietroom script(乾淨對話稿)。同場也分一對一與全場room post --to <他> 進一對一(旁人插話會被擋下,要帶 --barge-in "<理由>")、--to all 開回全場,現況查 room floor
5. 腳本用 Node.js scripts/*.mjshooks/*.mjs,只用 Node 內建模組(fs/path/os/crypto),無 npm 依賴
6. 由使用者呼叫才載入並鎖定 人格不會自動附身:SessionStart hook 只列出可用人格,等使用者下 /jsc-persona:persona-chat <slug>;載入即取得獨占鎖並綁定該 session。唯一例外是使用者自己設的預設人格default --persona <slug>)——設了才自動載入,沒設就什麼都不做
7. 短期記憶轉入長期記憶有成文條件 R1R6 六條規則寫在程式裡(promotionCandidates),candidates 子指令會列出達標的候選與依據,hook 在達標時提醒固化
8. 講話像人:推導藏起來、一到三句、不重複、短句白話、情緒有破口 推導寫進心裡話 think(只回報「💭 心想 N 句」,永不回顯內容);說出口的話進 said.jsonl,下一輪注入「最近說過的話」提醒別重講;room post 直接擋下近似重複(字元 bigram+字集合相似度 ≥ 0.72)與超過三句的發言。再加四條講話的樣子:短句(一句 45 字內)、日常用詞、多講看得見的東西、不要解釋自己的話(句長與「我的意思是」這類開頭由 speechLint() 擋下),以及情緒要改變句子的形狀EMOTION_TELLS:焦慮→斷句與疊字、羞愧→鬧彆扭、憤怒→短句、悲傷→只回一個詞,每輪注入強度 ≥ 40 的前兩種)——劇場模式同樣適用
9. 人格可以匯出匯入 export 把身分/情緒/記憶/心智圖/關係圖打包成單一 JSON bundle(可 --gzip、附 sha256),import 還原或換名複製;不帶載入鎖與 guest 租約,journal/ 要明確 --with-journal 才帶走
10. 人格有編號 編號 = 英文名全大寫 + 兩位索引(同名才遞增):ASUNA-01YUI-01ASUNA-02。編號同時是新人格的本機目錄名與 Gitea 存取庫名稱;中文名先轉羅馬拼音並跟使用者確認拼法
11. 人格存在 Gitea,依更新頻率分區 每個人格一個私有存取庫(庫名=編號)。檔案區放每輪都在變的活狀態(情緒/短期記憶/心裡話/說過的話/逐字),Wiki 區放低頻的身分與長期結構(IDENTITY/SOUL/長期記憶/心智圖/關係圖)當設定百科。本機仍是工作副本,同步失敗不阻斷對話
12. 形象圖來自高解析度官方圖,去背後合成 icon search 從 Fandom 撈官方圖並依「解析度+是否官方設定稿」排序(設定稿多為透明/白底、773×1056 起跳)→ icon measure 確認臉夠大、背景好去 → icon cutout 去背成透明 PNG(原生 alpha /單色底/GrabCut 三條路徑)→ icon generate --from-cutout 裁頭肩、合成到角色配色的漸層底。找不到官方圖才退回依人格資料重繪的向量形象
13. 睡眠把一天收成能留下來的形狀 persona-sleep:需要判斷的(固化什麼/忘掉什麼/日記)由人格自己做,機械性的由 sleep 子指令做(關係時間戳→裁短期→收思維導圖→情緒衰減 8 小時→重建索引→修剪 said→壓縮 journal→兩區 push+驗證)。主人格可透過 persona-sleeper sub agent 請別的人格去睡——那是它本人在睡,回傳值只有「睡完了沒、哪一步出錯」,不含任何記憶內容
14. Wiki 必須保存並同步形象圖 icon.svgicon.pngicon/ 資料夾(向量原稿 + 512/1024 高解析度) 都同步到 Wiki 區,另有自動產生的 Icon 頁展示與來源。Wiki 頁面一律用 Markdown 圖片語法Gitea 只改寫這種語法為 /wiki/raw/...HTML <img> 會變成破圖),圖片保留資料夾結構、只有 .md 需要攤平。icon generate 推完會回頭驗證,另有 sync verify

架構

flowchart TB
  subgraph P["主程序(一個 session = 一個人格)"]
    U["使用者訊息"] --> H1["UserPromptSubmit hook<br/>注入 情緒+短期記憶+命中的長期記憶+關係<br/>+心裡話+最近說過的話"]
    H1 --> A["語意分析:意圖/主題/實體/情感/需求"]
    A --> T["心裡話 think<br/>(推導只留在 inner.jsonl,不輸出)"]
    T --> E["情緒評估 → 十二情緒 deltas"]
    E --> R["以人格語氣回覆(1–3 句、不重複說過的話)"]
    R --> W["記憶回寫(短期)"]
    W --> H2["Stop hook:情緒衰減+續租+記說過的話+固化提醒"]
  end
  subgraph G["Sub Agent(受邀人格,唯讀)"]
    GA["persona-guest"]
  end
  subgraph S["人格倉庫 ~/.claude/personas(工作副本)"]
    PA["ASUNA-01/IDENTITY SOUL 記憶 情緒 心智圖 關係圖"]
    PB["YUI-01/|…"]
    RM[".rooms/room/transcript.jsonl"]
  end
  subgraph GT["Gitea(存取庫名稱 = 人格編號)"]
    GF["ASUNA-01 檔案區<br/>高頻活狀態"]
    GW["ASUNA-01 Wiki<br/>低頻設定百科"]
  end
  P -->|"只能碰自己"| PA
  GA -->|"只能碰自己"| PB
  P <-->|"唯一合法交流管道"| RM
  GA <--> RM
  PA -->|"每輪背景 push"| GF
  PA -->|"固化/改身分/release"| GW
  GF -.->|"載入時 pull"| PA
  GW -.-> PA

人格倉庫(預設 ~/.claude/personas/<slug>/,可用 PERSONA_HOME 覆寫)

<slug>/
├── IDENTITY.md              # 身分卡:Name / Creature / Vibe / Emoji / AvatarOpenClaw 同欄位)
├── SOUL.md                  # 靈魂:Core Truths / Boundaries / Vibe / Continuity + 情緒傾向
├── AGENTS.md                # 操作規則(與個性分離)
├── icon.svg / icon.png      # 人格圖示(由編號/名字/emoji 決定,也是 Gitea 存取庫頭像)
├── USER.md                  # 對使用者的畫像(事實/推測分開)
├── state/
│   ├── lock.json            # 載入鎖(session_id + 心跳租約)
│   ├── guests.json          # guest 唯讀租約
│   ├── emotion.json         # 十二情緒 levels / baseline / 半衰期
│   ├── inner.jsonl          # 心裡話(推導過程;只回報「心想 N 句」,不說出口)
│   ├── said.jsonl           # 說過的話(用來擋短時間內的重複發言)
│   ├── sync.json            # Gitea 同步狀態(最後 push / pull
│   └── config.json          # 含人格編號 code
├── memory/
│   ├── short-term.jsonl     # 短期記憶(語意分析後;上限 240 筆 / 14 天)
│   ├── long-term/*.md       # 長期記憶(一則一檔 + frontmatter
│   ├── INDEX.md             # 長期記憶索引(自動產生)
│   └── inbox/room-*.jsonl   # 當 guest 時留下的見聞,待本體消化
├── mindmap/
│   ├── semantic.mmd         # 心智圖:概念的長期關聯(Mermaid mindmap
│   └── threads/*.mmd        # 思維導圖:單一話題的推理鏈(Mermaid graph,短期)
├── relations/
│   ├── graph.json           # 人際關係圖(親近度/信任度/連線)
│   └── graph.mmd            # Mermaid 呈現(自動產生)
└── journal/YYYY-MM.jsonl    # 原始逐字 + 情緒史(hook 自動寫)

(另有 `.sync/files/`、`.sync/wiki/`:兩個同步區的 git clone 快取,可安全刪除)

十二情緒

六正向 六負向
喜悅 joy、信任 trust、期待 anticipation、感激 gratitude、平靜 serenity、驚喜 delight 憤怒 anger、悲傷 sadness、恐懼 fear、厭惡 disgust、羞愧 shame、焦慮 anxiety
  • 每種 0100,各有不同半衰期(驚喜 60 分最快、信任 720 分最慢),每輪自動朝 baseline 指數衰減。
  • 由十二情緒推導 valence(正向/中性/負向)與 arousal(高張/平穩/低張),決定語氣與句長。
  • 主導情緒取「超出基線最多」的前三名,所以個性底色不會永遠霸榜。
  • 觸發規則與事件→delta 對照表:skills/persona-chat/reference/emotions.md

短期 → 長期的轉入條件(R1R6

寫在 scripts/persona-lib.mjspromotionCandidates(),用 candidates 子指令查:

規則 條件 建議固化為
R1 單筆顯著度 ≥ 60 event / fact
R2 同一 topic ≥ 3 筆,或 ≥ 2 筆且平均顯著度 ≥ 45 preference
R3 單筆情緒變動總量 ≥ 25 event(帶情緒錨點)
R4 intent=commit 或命中承諾/界線關鍵詞 promise / boundarysalience ≥ 80,不可遺忘)
R5 同一人物(entity)≥ 2 筆 relationship(並更新關係圖)
R6 短期記憶 ≥ 40 筆(容量壓力) 依顯著度清出空間

沒命中任何規則的就讓它被裁掉——遺忘是功能。達標時 Stopremember 都會提醒去跑 /jsc-persona:persona-memory

講話像人(心裡話 / 一到三句 / 不重複 / 短句白話 / 情緒的破口)

AI 最容易露餡的五件事:把推理過程講出來、一次講一大段、換句話說同一件事、講完再解釋一遍、 說自己在緊張但句子完整得像稿子。五個對策:

機制 怎麼運作
心裡話 think 語意分析、推論、盤算全寫進 state/inner.jsonl;這個指令只印「💭 心想 N 句」,內容永不回顯。下一輪 <persona-context> 會帶回最近三句,推論因此有連續性,但使用者只看得到狀態
一到三句 <persona-context> 每輪注入上限;room post 對超過三句的發言直接拒收(--force 例外)
不重複 說出口的話由 Stop hook 自動記進 state/said.jsonlsaid check 可事前確認,room post 事中攔截。相似度=字元 bigram Jaccard0.4)+字集合 Jaccard0.6),≥ 0.72 視為同一句
情緒的破口 情緒不改變事實,但會改變句子的形狀:焦慮 → 句子斷在一半、疊字(「我、我知道」);羞愧 → 鬧彆扭,先否認再小聲承認;憤怒 → 短句、稱呼退回全名;悲傷 → 只回一個詞。十二情緒各自的破口寫在 EMOTION_TELLSemotionTells() 每輪挑主導情緒裡強度 ≥ 40 的前兩種注入。三條界線:演出來不要講出來(「我有點緊張」是解釋,斷句才是緊張)、一輪最多露一個破口強度不到就不演。混合狀態(緊張=焦慮+期待、害羞=喜悅+羞愧、賭氣=憤怒+悲傷…)的對照表在 skills/persona-chat/reference/emotions.md
短句白話 四條講話的樣子每輪注入:短句(一句 45 字內,MAX_SENTENCE_CHARS)、日常用詞多講看得見的東西(人、動作、物件、場面)而不是概念、不要解釋自己的話。句長與「我的意思是/換句話說/也就是說」這類開頭由 speechLint() 機械攔截(room post 擋下、said check 事前警告,--force 例外);用詞與具體度沒辦法用規則抓,靠注入的規則自律

字集合權重較高,是為了分開「重排語序」與「換掉關鍵詞」這兩種很像但意義完全不同的情況:

「我等一下把報告寄給你」vs「等一下我會把報告寄給你」→ 0.779 擋下(用字幾乎相同=同一件事換句話說)
「你今天看起來很累」  vs「你今天看起來很開心」  → 0.687 放行(換了關鍵詞=新資訊)

視窗預設 120 分鐘、少於 8 個字的短附和(「嗯」「好啊」)不算重複。

人格圖示(SVG + PNG,零外部依賴)

建立人格並補齊 IDENTITYSOUL 之後產生,512×512。優先是人物形象圖(看得到臉) 依環境有無工具分三種樣式:

流程:找官方圖 → 量測 → 去背 → 裁頭肩 → 合成

步驟 指令 產出
1. 找圖 icon search --wiki <sub> --page <角色> 候選清單,📐 = 官方設定稿(優先)
2. 量測 icon measure --photo <網址> 解析度、臉多大、背景透明/單色/有場景
3. 去背 icon cutout --photo <網址> icon/portrait-cutout.png(透明 PNG
4. 合成 icon generate --from-cutout --palette ... icon.svg + icon.png + icon/ 多解析度

去背三條路徑自動選:原生 alpha(官方設定稿常見,完美)→ 單色底移除(很好)→ GrabCut(有場景時,邊緣普通;黑髮角色容易被誤切,這時該換設定稿)。

樣式 條件 長什麼樣
cutout 有去背圖 → 最佳 官方原圖裁頭肩,疊在角色配色的漸層底上
portrait 沒有可用官方圖 依特徵重繪的人物頭像,五官俱全
badge 完全沒有參考圖 雙色漸層 + 編號前兩個字母

柵格器對每個圖形先算 bounding box 再逐點測試,1024×1024(3× 超取樣)約 1.8 秒。

找到的圖片只能當底稿:產出的 SVG 沒有 <image>、沒有 base64、沒有外連,每個像素都是畫出來的。

裁底稿需要的工具缺了,CLI 會印出安裝指令,不會靜默降級:

python3 -m venv ~/.cache/jsc-persona/venv
~/.cache/jsc-persona/venv/bin/pip install pillow "opencv-python-headless<5"   # OpenCV 5 拿掉了 CascadeClassifier
curl -sL -o ~/.cache/jsc-persona/lbpcascade_animeface.xml \
  https://raw.githubusercontent.com/nagadomi/lbpcascade_animeface/master/lbpcascade_animeface.xml

(venv 放這個路徑會被自動偵測;也可用 PERSONA_PYTHON 指定。)

配色一律取自實際看過的參考圖

元素 取自照片的哪裡
對角漸層 hair(髮色)→ accent(服裝主色)
外框 eye(瞳色)
點陣紋 light(最亮的部位)
中央兩個字母 編號前兩字(ASUNA-01AS

--palette 必須配 --source-url(CLI 強制):配色是從哪張圖來的要留得下來。 兩個顏色一深一淺時(藍黑髮 + 淡粉洋裝),程式會把較亮的一端往較暗的壓到對比 ≥ 3.2, 確保字讀得到——顏色仍然是照片來的,只是收斂色階。

沒有參考圖時(原創人格)才退回雜湊配色:sha256(編號|NameEmoji) 決定漸層與點陣紋。

  • 同一個人格永遠得到同一張圖(純函數,沒有隨機);ASUNA-01ASUNA-02 明顯不同。
  • SVG 與 PNG 是同一張圖:兩者共用同一組單位座標與同一份點陣字資料。
  • PNG 由自寫的柵格器畫出(3× 超取樣 + 盒式縮減當反鋸齒),再用 zlib 手工組出 IHDRIDATIEND 與 CRC32。這台機器沒有 rsvginkscapeimagemagick,也沒有影像函式庫, 而本專案禁止 npm 依賴——所以就自己畫。
  • 為什麼不直接用找到的圖:那是別人的美術作品。底稿只用來「看」,圖示由本工具重畫。
  • 為什麼沒有 emoji:把 emoji 畫進 PNG 需要字型柵格化,環境裡連 emoji 字型都沒有; emoji 仍參與雜湊配色。
  • 圖示屬於低頻資料 → icon.svgicon.png 都同步到 Wiki 區,並自動產生一頁 Icon 展示兩種格式與來源;PNG 同時設成 Gitea 存取庫頭像
node scripts/persona.mjs icon faces    --session <id> --photo "<圖片網址>"
node scripts/persona.mjs icon headshot --session <id> --photo "<圖片網址>" --pick 1

node scripts/persona.mjs icon generate --session <id> --force \
  --palette  "hair=#d9a45b,eye=#9e5b3e,accent=#c0392b,secondary=#e77a8e,light=#f2ebe3,skin=#f7ddc4" \
  --features "hairstyle=straight,length=very-long,fringe=parted,eyes=almond,expression=calm,collar=v,ahoge=yes" \
  --source-url "<底稿那張圖的網址>" --source-note "<作品(年份)+重繪依據>"

node scripts/persona.mjs icon show   --session <id>              # 樣式、配色、特徵、來源
node scripts/persona.mjs sync verify --session <id> --area wiki  # 確認 Wiki 真的同步了

人格編號與 Gitea 儲存

編號 = 英文名全大寫 + 兩位索引,同名才遞增,也就是 Gitea 存取庫的名稱:

亞絲娜(第一個) → ASUNA-01      結衣 → YUI-01      另一個亞絲娜 → ASUNA-02

每個人格一個私有存取庫,內容依更新頻率分兩區:

放什麼 何時 push
檔案區(主存取庫) 高頻活狀態:emotion.jsonshort-term.jsonlinner.jsonlsaid.jsonlinbox/mindmap/threads/journal/ 每輪對話後由 Stop hook 背景推送(PERSONA_SYNC_MIN_SECONDS 節流)
Wiki 區 低頻設定:IDENTITYSOULAGENTSUSER、長期記憶、INDEX、心智圖、關係圖 記憶固化、改身分/關係圖、release
  • 本機永遠是工作副本:hook 每輪讀寫本機檔案,不經網路;Gitea 掛掉照樣能聊天。 同步失敗永遠不阻斷對話。
  • 載入人格時會先 pull;兩邊都改過同一個檔案就停下來不覆蓋本機,由使用者決定保留哪一邊。
  • Gitea 的 wiki 只有根目錄的 .md 會變成頁面(1.27 實測子目錄頁面 404),所以 memory/long-term/xxx.md 攤平成 Memory-xxx.md,原始路徑記在 _paths.json; Wiki 首頁自動列出所有長期記憶的連結,變成真的讀得下去的「設定百科」。
export GITEA_HOST=https://gitea.example.com
export GITEA_TOKEN=<個人存取權杖>
export PERSONA_GITEA_OWNER=<帳號或組織>   # 選填,預設 token 本人
export PERSONA_GITEA=off                 # 需要時整個關掉

node scripts/persona.mjs code assign --session <id> --romaji Asuna --rename   # 既有人格遷移
node scripts/persona.mjs sync status|init|push|pull --session <id> [--area files|wiki|all]

存取庫預設私有——人格裡是使用者的個人記憶,公開必須由使用者明講(--public)。

匯出 / 匯入(人格搬家)

node scripts/persona.mjs export --session <id> --out ~/backup/lumi.persona.json [--gzip] [--with-journal]
node scripts/persona.mjs import --session <id> --file ~/backup/lumi.persona.json [--persona lumi-copy] [--load]
  • bundle = 單一 JSON(可 gzip)+ sha256 checksum,無外部工具依賴。
  • 只能匯出本 session 目前載入的人格——否則就是跨人格外洩的後門(guard 會 deny)。
  • 不帶 state/lock.jsonguests.json(鎖屬於那台機器的那個程序);journal/ 預設不帶。
  • 換名匯入(--persona <新 slug>)可讓同一個人格並存兩份,config.json 會記下來歷。
  • bundle 內的 ../ 逃逸路徑一律拒收;checksum 不符要 --force 才吃。

劇場模式(多人格對話只顯示對話)

invite 成功即開啟(leave 沒有客人時自動關閉,也可 room theater --on/--off 手動切):

  • UserPromptSubmit hook 每輪注入強制規則:輸出只能名字:內容, 不得出現指令、指令輸出、狀態、分析、旁白、摘要。
  • Stop hook 在劇場模式完全不發系統訊息(提醒會破壞畫面)。
  • CLI 提供 --quiet(成功時零輸出)與 room script(只有 emoji 名字(情緒):內容 的乾淨對話稿)。
  • 「講話像人」的三條規則在這裡一樣生效:每個人格每輪 13 句、推導走 think、 近似重複的台詞被 room post 拒收(host 與 guest 走同一支 CLI,一視同仁)。
🪼 Lumi(喜悅42/期待31):所以你真的一個人把那台舊鐘修好了?
🌙 Shen(平靜50/信任38):修好了。它現在慢三分鐘,我決定不修那三分鐘。

一對一與全場(發言權)

同一個空間不代表每句話都要每個人接。發言權寫在每一句話上:

怎麼進去 誰該講話 對話稿
一對一 room post --to <某個人格> 只有被指名的那個;旁人不生成台詞、也不啟動他的 sub agent 🪼 Lumi(喜悅42 → Shen:…(三人以上才標對象)
全場 room post --to all(或不帶 --to 誰接都可以,一輪讓一個人接 🪼 Lumi(喜悅42):…
  • 一對一進行中,旁人插話會被 room post 直接擋下;真的話題放大了才帶 --barge-in "<為什麼現在該他講>"(理由留在逐字稿裡,不顯示給使用者), 或由當事人下一句 --to all 把話題開回全場。
  • room floor 回報現況:誰對誰在講、該誰接話、誰先安靜、誰隔了幾輪沒開口 (被冷落太久是資訊——下次話題碰到他的領域時優先給他,而不是硬插一句「我也這麼覺得」)。
  • 一份現況每輪也注入 <persona-context>,所以人格自己知道這句該不該由他接。
  • 什麼算「話題放大」:出現「我們/大家」、要一起決定的事、需要第二意見或仲裁、 講到某個在場者的專長或他認識的人、使用者點名。判準是話題,不是公平。

預設人格(開新 session 自動載入)

預設行為是不自動附身。使用者明示設定之後,SessionStart hook 才會在每個新 session 自動載入它:

node scripts/persona.mjs default --session <PERSONA_SESSION>                      # 看目前設定
node scripts/persona.mjs default --persona KIRITO-01 --session <PERSONA_SESSION>  # 設定
node scripts/persona.mjs default --clear --session <PERSONA_SESSION>              # 取消
事項 行為
設定位置 <PERSONA_HOME>/.runtime/settings.jsondefault_persona(只由 CLI 維護)
臨時覆寫 環境變數 PERSONA_DEFAULT=<slug>PERSONA_DEFAULT=off 臨時關掉
沒設定 什麼都不做——只列出可用人格,等使用者指定
人格被別的程序鎖住 只回報 owner 與最後心跳,不會自動 --takeover
預設人格不存在 警告並改列可用人格,不崩潰、不亂挑一個
Gitea 自動載入走本機路徑,拉遠端;可能在別台機器動過就先 sync pull

睡眠(persona-sleep

node scripts/persona.mjs sleep --session <PERSONA_SESSION> --json     # 機械性收尾(預設保留載入鎖)
node scripts/persona.mjs sleep --personas <slug,slug> --session <PERSONA_SESSION> --json
node scripts/persona.mjs sleep --session <PERSONA_SESSION> --release  # 收工:睡完釋放鎖
誰做 內容
需要判斷 人格自己 哪些短期記憶值得固化、日記寫什麼、哪些該忘、心智圖怎麼接
機械性 sleep 子指令 關係時間戳 → 裁短期 → 收太久沒動的思維導圖 → 套用一次 8 小時的情緒衰減 → 重建索引 → 修剪 said.jsonl → 壓縮舊 journal → 寫 state/sleep.json → Gitea 兩區 push+驗證
  • 情緒不會歸零:喜悅(半衰期 120 分)一夜後幾乎回基線,悲傷(480 分)只退一半——睡一覺不該把難過抹平。
  • 主人格請別人去睡:開 persona-sleeper sub agentprompt 帶 persona=<slug> session=<id>)。 它就是那個人格本人,對自己可寫但被 pin 住、只准跑收尾子指令、不得碰任何其他人格(包含叫它來的主人格)。
  • 一次睡多個人格:可用 sleep --personas A,B 批次處理;主程序只是把流程排成一串,每個人格仍各自判斷、各自收尾、各自回 JSON
  • 回傳值刻意很窮{persona, ok, slept_at, steps, sync, kept_lock}——只有狀態。 回傳值本身就是一條會繞過隔離的通道,所以在 CLI 這一層封死,不靠提示詞自律。
  • :沒有活鎖 → 取 5 分鐘的 sleeper 租約;同 session → 直接睡;死鎖 → 可接手; 別的程序活鎖住 → 拒絕(硬睡會讓兩邊的記憶互相覆蓋)。
  • 順序上的硬相依:關係時間戳早於裁短期、push 早於 release、reindex 晚於固化。

HooksClaude Code

Hook 做什麼
SessionStart 清死鎖、接續人格、有設預設人格就自動載入它(沒設就只列出可用人格)、把 PERSONA_SESSION=<session_id> 與規則注入上下文
UserPromptSubmit 注入 <persona-context>:身分、情緒、短期記憶、關鍵詞命中的長期記憶、相關人際關係;劇場模式時追加「只輸出人格對話」的強制規則;並記原始逐字
PreToolUse 人格隔離與鎖驗證的唯一強制點deny 帶原因)
Stop 情緒隨時間衰減、續租、記錄回覆、達固化條件時提醒(劇場模式時完全靜音)
SubagentStop 解除 guestsleeper sub agent 的 pin,並還掉 sleeper 的短期寫入權
SessionEnd 釋放鎖與 guest 租約,人格才能被下一個程序載入

session_id 只有 hook 拿得到 → 注入上下文 → skills 呼叫 CLI 時必須帶 --session hook 會驗證是否相符。這是「一人格一程序」與「跨人格隔離」不能被繞過的關鍵


Skills 目錄

persona-create

建立人格:以 OpenClaw 相同的五個身分欄位與 SOUL 段落訪談使用者,初始化情緒基線、記憶、心智圖與關係圖,並立即取得載入鎖。

  • Claude Code / Antigravity/jsc-persona:persona-create Codex$persona-create

persona-anime

用「動漫作品+角色名」建立人格:上網蒐集角色公開設定 → 映射成 OpenClaw 五欄位與 SOUL → 固化成 canon 基礎記憶(附來源)+原作關係圖+情緒基線。

  • Claude Code / Antigravity/jsc-persona:persona-anime 《作品》 角色名 Codex$persona-anime

persona-chat

載入人格並對話:取得獨占鎖 → 每輪做語意分析(推導寫進心裡話,不說出口)→ 更新十二情緒 → 回想記憶與關係 → 以人格語氣回覆(1–3 句、不重複說過的話)→ 寫回記憶。

  • Claude Code / Antigravity/jsc-persona:persona-chat <slug> Codex$persona-chat

persona-invite

邀請一個或多個人格透過 persona-guest sub agent 加入聊天室,進入劇場模式(畫面只留 名字:內容 的人格對話);結束後讓它們離場並把見聞留在各自的 inbox。多位 guest 可用 invite --guests A,B 同場加入。同場的發言權分一對一--to <他>,旁人不該接話)與全場--to all),現況查 room floor

  • Claude Code / Antigravity/jsc-persona:persona-invite <slug> Codex$persona-invite

persona-transfer

人格搬家:把身分、十二情緒、短期/長期記憶、心智圖與關係圖打包成單一 bundle 檔(可 gzip、附 checksum),或從 bundle 還原/換名複製成新人格。

  • Claude Code / Antigravity/jsc-persona:persona-transfer Codex$persona-transfer

persona-icon

依人格最新一次登場的官方視覺產生圖示:上網查最新造型 → 下載並親眼看過參考圖 → 取髮色/瞳色/服裝色 → 繪製 icon.svg + icon.png,來源網址一併存證。

  • Claude Code / Antigravity/jsc-persona:persona-icon Codex$persona-icon

persona-sync

人格編號與 Gitea 儲存:指派編號(ASUNA-01)、開以編號命名的私有存取庫、高頻活狀態同步到檔案區、低頻身分與長期記憶同步到 Wiki 區,並處理既有人格遷移與同步衝突。

  • Claude Code / Antigravity/jsc-persona:persona-sync Codex$persona-sync

persona-memory

記憶固化:依 R1–R6 條件把短期記憶轉入長期(一則一檔)、淘汰雜訊、更新心智圖與思維導圖、消化 guest inbox、重建索引。

  • Claude Code / Antigravity/jsc-persona:persona-memory Codex$persona-memory

persona-relation

人際關係圖維護:節點/連線、親近度與信任度調整、輸出 Mermaid 關係圖。

  • Claude Code / Antigravity/jsc-persona:persona-relation Codex$persona-relation

persona-therapist

語氣診療(第三方、不附身、唯讀):由雙方各給姓名/關係/情緒/逐字稿,用「關係、事實、目的」三把尺逐句標記不合理的語氣與用詞(模式表 P1–P16),指出是誰說的,只對出錯的人給保留原意的改寫。出現自傷、暴力或受控訊號時改為安全優先,不做語氣潤飾。

  • Claude Code / Antigravity/jsc-persona:persona-therapist Codex$persona-therapist

persona-sleep

睡眠與收尾:固化該記住的、忘掉該忘的、更新心智圖與關係圖、套用一次 8 小時的情緒衰減、壓縮舊紀錄,最後兩個區都同步到 Gitea 並驗證。也可以由主人格透過 sub agent 請其他人格各自去睡,或一次用 sleep --personas A,B 批次收尾多個人格。

  • Claude Code / Antigravity/jsc-persona:persona-sleep Codex$persona-sleep

persona-status

載入狀態與鎖管理:誰被哪個程序鎖住、guest 租約、釋放、接手死鎖、清理殘留,以及預設人格(開新 session 要自動載入誰)。

  • Claude Code / Antigravity/jsc-persona:persona-status Codex$persona-status

Agents

  • persona-guestagents/persona-guest.md)— 受邀人格的 sub agent,唯讀、被綁死在自己的人格目錄。
  • persona-sleeperagents/persona-sleeper.md)— 睡眠收尾的 sub agent就是那個人格本人在睡,對自己可寫但被 pin 住,只回傳「睡完了沒、哪一步出錯」。

CLI 與自我測試

所有狀態變更都經過 scripts/persona.mjsNode.js ≥ 18,只用內建模組,無 npm 依賴):

node scripts/persona.mjs --help
node scripts/persona.mjs list
node scripts/persona.mjs candidates --session <PERSONA_SESSION>   # 看哪些短期記憶該固化
node scripts/persona.mjs think --session <id> --text "<推導>"     # 心裡話(只回報「心想 N 句」)
node scripts/persona.mjs said check --session <id> --text "<話>"  # 這句是不是又要說一次?
node scripts/persona.mjs room script --session <id> --room <room> # 乾淨對話稿(劇場模式用)
node scripts/persona.mjs room floor --session <id> --room <room>  # 發言權:誰對誰在講、該誰接話
node scripts/persona.mjs export --session <id> --out lumi.json    # 離線搬家(單檔)
node scripts/persona.mjs sync status --session <id>               # Gitea 同步狀態
node scripts/selftest.mjs     # 248 項驗證:鎖、隔離、情緒、固化、說話節制、劇場模式與發言權、匯出匯入、編號與 Gitea、找圖去背合成、高解析輸出與 Wiki 同步、預設人格、睡眠與 sleeper、hooks

檔案結構:scripts/persona-lib.mjs(核心:鎖/隔離/情緒/記憶)、scripts/persona.mjsCLI)、 scripts/persona-gitea.mjs(編號與 Gitea 同步)、scripts/persona-icon.mjs(形象圖:SVG + 自寫 PNG 編碼)、scripts/portrait.py(選用:照片裁臉)、hooks/*.mjs(六個 hook)、scripts/selftest.mjs(自我測試)。


跨助理支援度

助理 skills hooks(鎖/隔離強制) 受邀人格 sub agent
Claude Code /jsc-persona:<name> 完整 jsc-persona:persona-guest
Codex $<name> ⚠ 需自行以子任務模擬
Antigravity /jsc-persona:<name>
OpenCode 依描述自動觸發

沒有 hook 的助理仍會遵守 CLI 層的檢查(--session 綁定、require_ownerrequire_member、 guest 唯讀),但那是自律而非強制:真正的 deny 只有 Claude Code 的 PreToolUse 做得到。


安裝 / 更新 / 移除

指令中的 repo 網址換成你的:https://gitea.jsc.idv.tw/plugins/persona.git Claude / Codex 從 git URL 安裝(會 clone 遠端),請先把本 repo push 到 gitea。

Claude Code

claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/persona.git
claude plugin install jsc-persona@jsc-plugins

# 更新
claude plugin marketplace update jsc-plugins
claude plugin update jsc-persona@jsc-plugins

# 移除
claude plugin uninstall jsc-persona@jsc-plugins
  • 工作階段內 slash 版(等價):把 claude plugin 換成 /plugin
  • 本機開發(免 push):claude plugin marketplace add /home/coder/plugins/persona 後再 install。
  • 安裝後重啟工作階段讓 hooks 生效;用 /hooks 確認六個 hook 都在。

Codex

codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/persona.git
codex plugin add jsc-persona@jsc-plugins
codex plugin marketplace upgrade jsc-plugins   # 更新
codex plugin remove jsc-persona@jsc-plugins    # 移除

Antigravityagy

agy plugin install <url> 目前只支援 github.comgitea 請 clone 後用本地路徑。

git clone https://gitea.jsc.idv.tw/plugins/persona.git ~/plugins/persona
agy plugin install ~/plugins/persona
# 更新:git -C ~/plugins/persona pull && agy plugin uninstall jsc-persona && agy plugin install ~/plugins/persona

OpenCode

git clone https://gitea.jsc.idv.tw/plugins/persona.git ~/plugins/persona
mkdir -p ~/.config/opencode/skills
cp -r ~/plugins/persona/skills/* ~/.config/opencode/skills/

Windows PowerShellcp -r A BCopy-Item A B -Recurse -Force~$HOME

headless 一次性執行

助理 指令
Claude Code claude -p "/jsc-persona:persona-chat lumi"
Codex codex exec '$persona-chat lumi'
Antigravity agy -p "/jsc-persona:persona-chat lumi"
OpenCode opencode run "用 lumi 這個人格跟我聊聊"

設計取捨(讀之前先知道)

  • 記憶是被策展的,不是全存:逐字稿進 journal/,但只有經過語意分析、有顯著度的內容才進短期記憶, 再由 persona-memory 決定什麼值得成為長期記憶。遺忘是功能
  • 事實與推測分離:推論走思維導圖(mindmap/threads/),驗證後才升格長期記憶。
  • 鎖的擁有者是 session 不是 process:CLI 跑完就結束,所以租約判定只看心跳。
  • guest 唯讀:受邀人格不能在別人的 session 裡改自己的長期記憶(避免兩個程序同時寫), 只能把見聞放進 memory/inbox/,等它自己被載入時消化。
  • hook 是強制、SKILL 是引導:SKILL.md 寫的規則模型可能忘記,hook 不會。
  • 「不要說出來」要靠設計而不是靠忍住:所以心裡話有自己的指令與檔案, 而且那個指令印不出內容——就算不小心把輸出貼上去,使用者也只看到「💭 心想 N 句」。
  • 重複用相似度擋、不用語意判斷:字元層級的比對沒有模型成本、行為可預期, 誤判時有 --allow-repeat 可救;漏判的代價(人格自我重複)比誤判高。

新增/修改 skill

  1. 複製一個現有 skill 目錄,改 SKILL.mdnamedescription(描述要寫清楚何時用、何時不用)。
  2. 需要新的狀態操作 → 加到 scripts/persona.mjs 的子指令,並在 scripts/selftest.mjs 補測試。
  3. 動到隔離規則 → 一定要在 selftest.mjs 的第 ③(隔離)/⑧(guest 與劇場模式)區加對應案例,跑到全綠。
  4. 把 skill 補進上方「Skills 目錄」區塊。
  5. bump .claude-plugin/plugin.json.codex-plugin/plugin.jsonplugin.json 三處 versioncommit 後 push。
S
Description
Persona 指令 for AI 助理
https://gitea.jsc.idv.tw/plugins/persona
Readme
7.2 MiB
Languages
JavaScript 98.3%
Python 1.7%