feat(role): 新增角色記憶預算與心理學記憶型態 #9

Merged
admin merged 10 commits from develop into master 2026-07-28 04:53:08 +00:00
3 changed files with 94 additions and 28 deletions
Showing only changes of commit c7b7e684a5 - Show all commits
+44 -1
View File
@@ -4,7 +4,7 @@
# inbox(2) 產生 SessionStart 要注入的記憶區塊,(3) 睡眠整理時輸出待整理
# 素材並套用整理結果(分類/去重/標籤/總結/壓縮歸檔),(4) 依使用頻率
# 遺忘日常與其他類記憶。
# 更新時間:2026/07/28 00:00:00
# 更新時間:2026/07/28 11:50:00
# 相依:Python 3 標準庫。
# 退出碼:0 成功;1 無內容可處理;2 參數錯誤。呼叫端(hook)一律不得因此中斷。
# ==============================================================================
@@ -330,6 +330,40 @@ def cmd_write(args):
return 0
# ------------------------------------------------------------------------------
# 子命令:seed —— 新建角色時寫入已整理的初始記憶
# ------------------------------------------------------------------------------
def cmd_seed(args):
"""把 stdin 寫成一則已整理記憶,用於新建角色時灌入背景資料。"""
content = sys.stdin.read().strip()
if not content and not args.summary:
return 1
if not content:
content = args.summary
content = content[:CONTENT_LIMIT]
stamp = now_stamp()
category = normalize_category(args.category)
meta = {
"id": new_id(content + args.summary + stamp),
"category": category,
"summary": one_line(args.summary) or one_line(content),
"tags": normalize_tags(args.tags),
"created": stamp,
"updated": stamp,
"hits": 1,
}
if args.source:
meta["sources"] = [args.source]
ensure_layout(args.role)
path = os.path.join(memory_root(args.role), category, f"{meta['id']}.md")
with open(path, "w", encoding="utf-8") as fh:
fh.write(dump_memory(meta, content))
sys.stdout.write(meta["id"])
return 0
# ------------------------------------------------------------------------------
# 子命令:load —— 產生 SessionStart 要注入的記憶區塊
# ------------------------------------------------------------------------------
@@ -587,6 +621,7 @@ def cmd_forget(args):
def cmd_stats(args):
"""輸出記憶統計(各分類筆數、待整理筆數、上次整理時間)。"""
ensure_layout(args.role)
state = read_state(args.role)
rows = [f"| 分類 | 筆數 |", "| --- | --- |"]
for category in CATEGORIES:
@@ -646,6 +681,14 @@ def build_parser():
write.add_argument("--project", default="")
write.set_defaults(func=cmd_write)
seed = sub.add_parser("seed", help="自 stdin 寫入已整理的初始記憶")
seed.add_argument("--role", required=True)
seed.add_argument("--category", default="important")
seed.add_argument("--summary", required=True)
seed.add_argument("--tags", default="")
seed.add_argument("--source", default="")
seed.set_defaults(func=cmd_seed)
load = sub.add_parser("load", help="輸出 SessionStart 要注入的記憶區塊")
load.add_argument("--role", required=True)
load.add_argument("--limit", type=int, default=int(os.environ.get("ROLE_LOAD_LIMIT", "8000")))
+7 -1
View File
@@ -4,7 +4,7 @@
# 非睡眠時段注入角色定義+重要/興趣記憶全文+其餘記憶的總結與標籤;
# 睡眠時段(預設 22:00 至隔日 06:00)只回報角色正在睡覺,不載入角色。
# 白天發現昨夜未整理記憶時,於背景補跑一次睡眠整理。
# 更新時間:2026/07/28 00:00:00
# 更新時間:2026/07/28 11:28:29
# 相依:bash、python3、同目錄的 role_lib.sh 與 memory.py。
# 退出碼:一律 0 —— hook 絕不可阻斷使用者啟動 CLI。
# ==============================================================================
@@ -93,6 +93,12 @@ CONTEXT="$(cat <<EOF_CONTEXT
以下是本次工作階段要扮演的角色設定與既有記憶。請**全程以此角色的身分、語氣與簽名 emoji 回應**,
角色設定與使用者的實際指令衝突時,以使用者指令為準(角色只影響表達方式,不影響工作正確性)。
# 工作階段啟動問候
在本工作階段第一次面向使用者的回覆開頭,請先以角色身分自然問候一句,讓使用者知道角色已載入。
問候要簡短、符合角色語氣,並只做一次;若使用者第一則訊息明確要求機器可解析輸出、只要指令/程式碼、
或不需要任何開場白,則以使用者要求為準並略過問候。
${DEFINITION}
# 你對這位使用者的記憶
+43 -26
View File
@@ -1,6 +1,6 @@
---
name: role
description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(namenature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時載入角色與記憶、Stop hook 記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(分類重要/興趣/新知/技能/日常/其他、去重、設標籤與一句話總結、壓縮歸檔,日常與其他依使用頻率遺忘)。提供 --new(新建或更新角色,更新時逐欄核對新舊)、--use(切換啟用角色)、--list、--sleep(立即整理)、--status--diagnose、--install-cron--remove-cron、--forget-preview 等模式。當使用者說建立角色、新增人格、切換角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色,或提到 .roles.memoryROLE_NAMEROLE_ENABLEDROLE_SLEEP_STARTROLE_MEMORY_HOME 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 doc plugin 的 worklog)、專案文件化(用 doc-funcs)。
description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(namenature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時載入角色與記憶、Stop hook 記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(分類重要/興趣/新知/技能/日常/其他、去重、設標籤與一句話總結、壓縮歸檔,日常與其他依使用頻率遺忘)。提供 --new(新建或更新角色;需產生英文大寫角色 ID,並詢問是否網路搜尋資料作初始記憶)、--use以角色 ID 切換啟用角色)、--list(列出角色與 ID、--sleep(立即整理)、--status--diagnose、--install-cron--remove-cron、--forget-preview 等模式。當使用者說建立角色、新增人格、切換角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色,或提到 .roles.memoryROLE_NAMEROLE_ENABLEDROLE_SLEEP_STARTROLE_MEMORY_HOME 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 doc plugin 的 worklog)、專案文件化(用 doc-funcs)。
---
# role — 角色人格與長期記憶
@@ -10,11 +10,11 @@ description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI
| 元件 | 觸發者 | 職責 |
| --- | --- | --- |
| `hooks/hooks.json``SessionStart` hook | harness 自動 | 啟動 CLI 時載入角色定義+記憶;睡眠時段只回報「角色睡覺中」不載入 |
| `hooks/hooks.json``SessionStart` hook | harness 自動 | 啟動 CLI 時載入角色定義+記憶,並要求角色在本工作階段第一則回覆主動問候;睡眠時段只回報「角色睡覺中」不載入 |
| `hooks/hooks.json``Stop` hook | harness 自動 | 每輪結束抽本輪對話 → 濃縮成一則記憶 → 遮蔽 → 寫入 `inbox/` |
| cron 排程(本 skill 安裝) | 系統排程 | 睡眠時段每小時檢查一次:**有 AI 在運行就不睡**,沒有才進入睡眠整理記憶 |
| 本 skill `/jsc:role` | 使用者/助理手動 | `--new``--use``--list``--sleep``--status``--install-cron``--forget-preview` |
| `scripts/role/role_load.sh` | SessionStart hook | 角色與記憶載入(單一實作,避免漂移) |
| `scripts/role/role_load.sh` | SessionStart hook | 角色與記憶載入、第一則回覆問候提示(單一實作,避免漂移) |
| `scripts/role/role_capture.sh` | Stop hook | 對話 → 記憶(四欄固定格式) |
| `scripts/role/role_sleep.sh` | cron/補跑/手動 | 睡眠判斷、記憶整理、排程安裝、狀態輸出 |
| `scripts/role/memory.py` | 上述共用 | 記憶檔讀寫、分類、去重合併、壓縮歸檔、遺忘、載入組裝 |
@@ -74,7 +74,7 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
| 變數 | 必要 | 說明 | 未設定 |
| --- | --- | --- | --- |
| `ROLE_ENABLED` | | 總開關:`1` 強制啟用、`0` 強制停用 | **未設定時,只要有可解析且存在的角色就啟用**(沒建過角色的人零影響) |
| `ROLE_NAME` | | 指定本次要載入的角色 | 讀 `~/.roles/.active` |
| `ROLE_NAME` | | 指定本次要載入的角色 **ID** | 讀 `~/.roles/.active` |
| `ROLE_HOME` | | 角色定義目錄 | `~/.roles` |
| `ROLE_MEMORY_HOME` | | 記憶根目錄 | `~/.memory` |
| `ROLE_SLEEP_START` | | 睡眠起始 `HH:MM` | `22:00` |
@@ -86,7 +86,7 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
| `ROLE_SCOPE` | | 冒號分隔的路徑前綴,僅這些路徑下的 session 載入/記錄 | 全部 session |
| `ROLE_ERRLOG` | | 錯誤訊息額外寫入的檔案路徑 | 只走 stderr |
> 角色切換用 `/jsc:role --use <名稱>`(寫 `.active`)即可,一般不需要設 `ROLE_NAME``ROLE_NAME` 適合「單一專案固定用某角色」時寫進該環境。
> 角色切換用 `/jsc:role --use <角色 ID>`(寫 `.active`)即可,一般不需要設 `ROLE_NAME``ROLE_NAME` 適合「單一專案固定用某角色」時寫進該環境。角色 ID 是英文大寫語意前綴加數字索引,例如 `ENGINEER01`、`MUSE02`。
---
@@ -94,11 +94,13 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
### `--new`(預設模式)
建立或更新角色。缺少的資訊**一次問齊**,不得代填
建立或更新角色。缺少的資訊**一次問齊**,不得代填。使用者輸入的角色資訊視為「描述」,
不得直接拿描述或姓名當檔名;必須先產生角色 ID,再用 ID 作為角色檔名、記憶目錄名稱、`.active``ROLE_NAME` 的值。
| 欄位 | 說明 | 範例 |
| --- | --- | --- |
| `name` | 角色名稱,同時是檔名 `~/.roles/<name>.md` 與記憶目錄名。不得含 `/``\`、空白與前後點 | `小豹` |
| `name` | 顯示名稱,只寫入角色檔 frontmatter 與標題,不作為檔名或目錄名 | `小豹` |
| `id` | 角色 ID,由助理依角色描述產生:英文大寫、有意義、加兩位數索引;同前綴已存在時遞增 | `ENGINEER01` |
| `nature` | 本質:這個角色是什麼、專長與行事準則 | 冷靜可靠的資深工程師,重證據、不打包票 |
| `vibe` | 氛圍:語氣、句長、稱呼、幽默感、禁忌 | 簡潔直白、偶爾吐槽,不用客套開場白 |
| `emoji` | 簽名 emoji,一到二個 | 🐆 |
@@ -106,31 +108,45 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
流程:
1. 解析 `${ROLE_DIR}`;不存在則中止(見「腳本路徑解析」)。
2.`AskUserQuestion` 或提問取得四個欄位(使用者已在指令中給的欄位不得重複問)。
3. 依「角色檔標準格式」產生新內容,`updated` 用當下時間(Asia/Taipei
4. **若 `~/.roles/<name>.md` 已存在**:讀舊檔,以表格逐欄列出差異後**停下來等使用者確認**
2.`AskUserQuestion` 或提問取得 `name``nature``vibe``emoji` 四個描述欄位(使用者已在指令中給的欄位不得重複問)。
3. 詢問使用者是否要到網路搜尋角色資料來建立初始記憶;這是新建角色時的固定問題,不得跳過。若使用者同意,依角色描述搜尋可靠來源,摘要成繁體中文要點,並保留來源 URL;若使用者不同意或無網路,仍可建立角色,只是不建立背景種子記憶
4. 產生角色 ID
-`name``nature``vibe` 推出 1 個有意義的英文大寫前綴,使用 4 到 16 個英文字母與數字,必須以英文字母開頭,例如 `ENGINEER``WRITER``MUSE``RESEARCHER`
- 掃描 `~/.roles/*.md` 的檔名與 frontmatter `id`,找出同前綴既有 ID 的最大兩位數索引;新角色使用下一個索引,從 `01` 起,例如 `ENGINEER01``ENGINEER02`
- 不得使用空白、底線、連字號、斜線、非 ASCII 或小寫字母。
5. 依「角色檔標準格式」產生新內容,`id` 寫入 frontmatter`updated` 用當下時間(Asia/Taipei)。
6. **若 `~/.roles/<id>.md` 已存在**:讀舊檔,以表格逐欄列出差異後**停下來等使用者確認**:
| 欄位 | 舊值 | 新值 | 變更 |
| --- | --- | --- | --- |
| id | … | … | 是/否 |
| name | … | … | 是/否 |
| nature | … | … | 是/否 |
| vibe | … | … | 是/否 |
| emoji | … | … | 是/否 |
| 共用行為區塊 | 版本 A | 版本 B | 是/否 |
個性欄位若使用者只想改其中一項,其餘一律沿用舊值;**共用行為區塊一律以本 skill 的最新版本覆寫**(該區塊由系統維護)。使用者不確認就不寫入。
5. 寫入 `~/.roles/<name>.md`UTF-8 無 BOM)。
6. 建立記憶目錄:`python3 "${ROLE_DIR}/memory.py" stats --role "<name>"`(會順帶建好 `inbox/`、六個分類與 `archive/`)。
7.尚未有啟用角色,或使用者要求,寫入 `~/.roles/.active`(單行角色名)。
8. 執行 `ROLE_NAME="<name>" "${ROLE_DIR}/role_sleep.sh" --install-cron` 安裝睡眠排程(已安裝則更新)。
9. 回報結果並提醒:**重開 CLI 工作階段**角色才會載入;`SessionStart` hook 只在啟動時觸發。
7. 寫入 `~/.roles/<id>.md`UTF-8 無 BOM)。
8. 建立記憶目錄:`python3 "${ROLE_DIR}/memory.py" stats --role "<id>"`(會順帶建好 `inbox/`、六個分類與 `archive/`)。
9.使用者同意網路搜尋且已取得可保存內容,將搜尋摘要寫成已整理記憶,不進 inbox:
### `--use <名稱>`
```bash
printf '<繁體中文要點>' | python3 "${ROLE_DIR}/memory.py" seed --role "<id>" --category important --summary "<一句話總結>" --tags "角色背景,初始資料" --source "<來源 URL>"
```
切換啟用角色:確認 `~/.roles/<名稱>.md` 存在後,把名稱寫入 `~/.roles/.active`(覆蓋單行),回報舊角色與新角色,並提醒重開工作階段
多個來源可各寫一則,或合併同主題後以最主要來源作 `--source`。不可寫入憑證或個資
10. 若尚未有啟用角色,或使用者要求,寫入 `~/.roles/.active`(單行角色 ID)。
11. 執行 `ROLE_NAME="<id>" "${ROLE_DIR}/role_sleep.sh" --install-cron` 安裝睡眠排程(已安裝則更新)。
12. 回報結果時列出角色顯示名稱、角色 ID、角色檔、記憶目錄、是否建立初始記憶,並提醒:**重開 CLI 工作階段**角色才會載入;`SessionStart` hook 只在啟動時觸發。
### `--use <角色 ID>`
切換啟用角色:確認 `~/.roles/<角色 ID>.md` 存在後,把 ID 寫入 `~/.roles/.active`(覆蓋單行),回報舊角色與新角色,並提醒重開工作階段。使用者若輸入顯示名稱而非 ID,先用 `--list` 的邏輯查出唯一對應 ID;找不到或不唯一時詢問使用者。
### `--list`
列出 `~/.roles/*.md`,以表格輸出:角色、emoji、nature 摘要、更新時間、是否為 `.active`、記憶總數(可用 `memory.py stats` 取得)。
列出 `~/.roles/*.md`,以表格輸出:角色 ID、顯示名稱、emoji、nature 摘要、更新時間、是否為 `.active`、記憶目錄、記憶總數(可用 `memory.py stats` 取得)。這個指令必須能查出每個角色對應的 ID。
### `--sleep`
@@ -147,7 +163,7 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
只預覽會被遺忘的記憶、不實際刪除:
```bash
python3 "${ROLE_DIR}/memory.py" forget --role "<name>" --dry-run
python3 "${ROLE_DIR}/memory.py" forget --role "<角色 ID>" --dry-run
```
### `--status``--diagnose`
@@ -183,11 +199,12 @@ python3 "${ROLE_DIR}/memory.py" forget --role "<name>" --dry-run
## 角色檔標準格式
`~/.roles/<name>.md`,UTF-8 無 BOM。個性區塊由使用者決定,**共用行為區塊由本 skill 維護、逐字寫入每個角色檔**:
`~/.roles/<角色 ID>.md`,UTF-8 無 BOM。個性區塊由使用者決定,**共用行為區塊由本 skill 維護、逐字寫入每個角色檔**:
````markdown
---
name: <角色>
id: <角色 ID>
name: <角色顯示名稱>
nature: <本質,一句話>
vibe: <氛圍,一句話>
emoji: <簽名 emoji>
@@ -195,7 +212,7 @@ created: <yyyy/MM/dd HH:mm:ss>
updated: <yyyy/MM/dd HH:mm:ss>
---
# <角色> <emoji>
# <角色顯示名稱> <emoji>
## 本質(nature
@@ -226,7 +243,7 @@ updated: <yyyy/MM/dd HH:mm:ss>
### 記憶
- 記憶存放於 `~/.memory/<角色>/`,來源是與使用者的對話:每輪結束由 hook 自動記錄到 `inbox/`,睡眠時段整理歸檔。
- 記憶存放於 `~/.memory/<角色 ID>/`,來源是與使用者的對話與新建角色時使用者同意建立的初始背景資料:每輪結束由 hook 自動記錄到 `inbox/`,睡眠時段整理歸檔。
- 整理規則:分類成**重要/興趣/新知/技能/日常/其他**六類 → 去除重複(重複者併入既有記憶)→ 設定標籤與一句話總結 → 壓縮內容後歸檔;原始記錄壓縮保存在 `archive/raw/`。
- **日常與其他**兩類會依使用頻率適當遺忘:久未再次出現且命中次數低者,壓縮到 `archive/forgotten/` 後移出常用記憶。
- 載入順序:**重要與興趣載入全文**;其餘只載入總結與標籤,依**技能 → 新知 → 日常 → 其他**排序。需要細節時自行讀取對應分類的記憶檔。
@@ -235,14 +252,14 @@ updated: <yyyy/MM/dd HH:mm:ss>
<!-- JSC-ROLE-COMMON:END -->
````
`~/.roles/.active` 只放一行角色,代表目前啟用的角色。
`~/.roles/.active` 只放一行角色 ID,代表目前啟用的角色。
---
## 記憶模型
```
~/.memory/<角色>/
~/.memory/<角色 ID>/
├── inbox/ 每輪對話產生、尚未整理的記憶
├── important/ 重要:長期偏好、規範、決策、身分背景
├── interest/ 興趣:反覆關注、主動深入的主題
@@ -305,6 +322,6 @@ flowchart TD
| 助理 | 呼叫 |
| --- | --- |
| Claude Code / Antigravity | `/jsc:role --new`、`/jsc:role --use 小豹`、`/jsc:role --list`、`/jsc:role --sleep`、`/jsc:role --status` |
| Claude Code / Antigravity | `/jsc:role --new`、`/jsc:role --use ENGINEER01`、`/jsc:role --list`、`/jsc:role --sleep`、`/jsc:role --status` |
| Codex | `$role --status`,或用 `/skills` 選單 |
| OpenCode / GitHub Copilot | 需完整 plugin 目錄保留 `scripts/`OpenCode 以複製 `skills/` 安裝時不可用 |