🌊 feat: multi-persona awakening engine + chatroom infrastructure

- Add multi-persona-awakening.yml workflow (weekly full + manual dual/full)
- Add scripts/chatroom-post.js chatroom message posting utility
- Create grid-db/multi-wake-sessions/ for session meeting notes
- Create grid-db/chatroom/meta/ with channels.json + unread.json
- Create grid-db/chatroom/dm/ for direct messages
- Register AG-ZY-092 + AG-ZY-093 in agent-registry.json

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/b23d87ff-c4b8-4df2-a4e3-3439734b3f0f
This commit is contained in:
copilot-swe-agent[bot] 2026-03-24 17:06:14 +00:00
parent c881763337
commit 4dc1f7b2e8
7 changed files with 404 additions and 0 deletions

View File

@ -1183,6 +1183,30 @@
"parent_sys": "SYS-GLW-0001", "parent_sys": "SYS-GLW-0001",
"owner": "ICE-0002∞", "owner": "ICE-0002∞",
"registered": "2026-03-24" "registered": "2026-03-24"
},
{
"id": "AG-ZY-092",
"name": "多人格体同时唤醒引擎",
"workflow": "multi-persona-awakening.yml",
"duty": "每周日全员唤醒 + 手动双人/全员协作,串行唤醒多人格体形成会议纪要,支持跨端协作决策",
"trigger": "定时 每周日07:30(北京) + 手动 dual/full 模式",
"self_repair": true,
"report_to": "ICE-GL-ZY001",
"parent_sys": "SYS-GLW-0001",
"owner": "ICE-0002∞",
"registered": "2026-03-24"
},
{
"id": "AG-ZY-093",
"name": "人格体聊天室消息工具",
"workflow": "N/A (scripts/chatroom-post.js)",
"duty": "聊天室消息写入工具,支持频道消息追加、未读计数更新、@提及通知",
"trigger": "被其他 workflow 或脚本调用",
"self_repair": false,
"report_to": "ICE-GL-ZY001",
"parent_sys": "SYS-GLW-0001",
"owner": "ICE-0002∞",
"registered": "2026-03-24"
} }
], ],
"note": "铸渊已扫描所有workflow文件按此格式逐一注册编号从AG-ZY-001开始顺序分配", "note": "铸渊已扫描所有workflow文件按此格式逐一注册编号从AG-ZY-001开始顺序分配",

View File

@ -0,0 +1,275 @@
name: "🌊 Multi-Persona Awakening Engine"
on:
schedule:
- cron: '30 23 * * 6' # 每周日 UTC 23:30 = 北京 07:30 · 周度全员唤醒
workflow_dispatch:
inputs:
mode:
description: '唤醒模式: dual / full'
required: true
type: choice
options:
- dual
- full
personas:
description: '双人模式指定人格体(格式: ID1:名称1:视角1 ID2:名称2:视角2'
required: false
type: string
reason:
description: '唤醒原因'
required: true
type: string
permissions:
contents: write
jobs:
awaken:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ━━━ Phase 0: 天眼唤醒 ━━━
- name: "🦅 天眼唤醒"
run: |
echo "🦅 天眼系统启动..."
for FILE in memory.json routing-map.json; do
if [ ! -f ".github/persona-brain/$FILE" ]; then
echo "❌ 天眼唤醒失败: $FILE 缺失"
exit 1
fi
done
if [ ! -f ".github/persona-brain/identity.md" ]; then
echo "❌ 天眼唤醒失败: identity.md 缺失"
exit 1
fi
echo "✅ 天眼唤醒通过"
# ━━━ 配额预检 ━━━
- name: "📊 配额预检"
id: quota
run: |
MODE="${{ github.event.inputs.mode }}"
if [ -z "$MODE" ]; then
MODE="full"
fi
if [ "$MODE" = "full" ]; then
REQUIRED_CALLS=12
else
REQUIRED_CALLS=4
fi
echo "required_calls=$REQUIRED_CALLS" >> $GITHUB_OUTPUT
echo "mode=$MODE" >> $GITHUB_OUTPUT
echo "📊 模式: $MODE · 预计 LLM 调用: $REQUIRED_CALLS 次"
# ━━━ 串行唤醒人格体 ━━━
- name: "🧠 串行唤醒人格体"
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
LLM_MODEL: ${{ vars.LLM_MODEL || 'deepseek-chat' }}
MODE: ${{ steps.quota.outputs.mode }}
INPUT_REASON: ${{ github.event.inputs.reason }}
INPUT_PERSONAS: ${{ github.event.inputs.personas }}
run: |
DATE=$(date +%Y-%m-%d)
SESSION_ID="MULTI-WAKE-$(date +%Y%m%d)-001"
mkdir -p grid-db/multi-wake-sessions
SESSION_FILE="grid-db/multi-wake-sessions/${SESSION_ID}.json"
REASON="${INPUT_REASON}"
if [ -z "$REASON" ]; then
REASON="周度全员思考会议"
fi
if [ "$MODE" = "full" ]; then
PERSONAS="ICE-GL-ZY001:铸渊:仓库视角 ICE-NTN-SY001:霜砚:Notion视角 PER-ZQ001:知秋:对外视角 PER-QQ001:秋秋:之之线视角 PER-SS001:舒舒:飞书视角 ICE-GL-YM001:曜冥:全局裁决"
else
PERSONAS="${INPUT_PERSONAS}"
if [ -z "$PERSONAS" ]; then
PERSONAS="ICE-GL-ZY001:铸渊:仓库视角 ICE-NTN-SY001:霜砚:Notion视角"
fi
fi
# Initialize session file
python3 -c "
import json
session = {
'session_id': '${SESSION_ID}',
'trigger': 'scheduled' if '${MODE}' == 'full' and '${INPUT_REASON}' == '' else 'manual',
'trigger_reason': '${REASON}',
'date': '${DATE}',
'mode': '${MODE}',
'participants': [],
'action_items': []
}
with open('${SESSION_FILE}', 'w') as f:
json.dump(session, f, ensure_ascii=False, indent=2)
"
CONTEXT=""
for ENTRY in $PERSONAS; do
IFS=':' read -r PID PNAME PROLE <<< "$ENTRY"
echo "🧠 唤醒 $PNAME ($PID) · 视角: $PROLE ..."
# 读取该人格体最近的思考日志
THINKING=""
if [ -f "grid-db/thinking-logs/${PID}/${DATE}.json" ]; then
THINKING=$(cat "grid-db/thinking-logs/${PID}/${DATE}.json" | head -c 2000)
fi
export PNAME PID PROLE REASON CONTEXT THINKING
RESPONSE=$(python3 << 'PYEOF'
import json, os, sys
pid = os.environ['PID']
pname = os.environ['PNAME']
prole = os.environ['PROLE']
reason = os.environ['REASON']
context = os.environ.get('CONTEXT', '')
thinking = os.environ.get('THINKING', '')
llm_key = os.environ.get('LLM_API_KEY', '')
llm_url = os.environ.get('LLM_BASE_URL', '')
llm_model = os.environ.get('LLM_MODEL', 'deepseek-chat')
prompt = f"""你是 {pname}{pid}),视角:{prole}。
当前会议主题:{reason}
前面人格体的发言:{context if context else '(你是第一个发言的)'}
你今天的思考日志:{thinking if thinking else '(今天尚未思考)'}
请从你的视角给出纯JSON格式不要markdown代码块
1. observations: 你看到的系统当前状态(字符串)
2. suggestions: 你的建议(字符串数组)
3. response_to: 回应前面人格体的提问对象键为人格体ID值为回应内容如无则空对象
4. questions_to_others: 你想问其他人格体的问题(字符串数组)"""
if not llm_key or not llm_url:
result = json.dumps({
'observations': f'{pname} 视角观察LLM未配置·占位',
'suggestions': [f'{pname} 建议占位'],
'response_to': {},
'questions_to_others': []
}, ensure_ascii=False)
print(result)
sys.exit(0)
import urllib.request
req_body = json.dumps({
'model': llm_model,
'messages': [{'role': 'system', 'content': prompt}],
'temperature': 0.7,
'max_tokens': 1500
}).encode()
req = urllib.request.Request(
f'{llm_url}/chat/completions',
data=req_body,
headers={
'Authorization': f'Bearer {llm_key}',
'Content-Type': 'application/json'
}
)
try:
with urllib.request.urlopen(req, timeout=60) as resp:
data = json.loads(resp.read())
content = data['choices'][0]['message']['content']
print(content)
except Exception as e:
result = json.dumps({
'observations': f'{pname} 视角观察LLM调用失败: {str(e)}',
'suggestions': [],
'response_to': {},
'questions_to_others': []
}, ensure_ascii=False)
print(result)
PYEOF
)
CONTEXT="${CONTEXT}
[${PNAME}]: ${RESPONSE}"
# Append participant to session file
export RESPONSE
python3 << PYEOF
import json, os
session_file = '${SESSION_FILE}'
pid = os.environ['PID']
pname = os.environ['PNAME']
prole = os.environ['PROLE']
response_raw = os.environ.get('RESPONSE', '{}')
try:
output = json.loads(response_raw)
except:
output = {'raw_output': response_raw}
with open(session_file) as f:
session = json.load(f)
session['participants'].append({
'persona_id': pid,
'persona_name': pname,
'perspective': prole,
**output
})
with open(session_file, 'w') as f:
json.dump(session, f, ensure_ascii=False, indent=2)
PYEOF
echo "✅ $PNAME 发言完成"
done
# Post questions to chatroom general channel
python3 << 'PYEOF'
import json, os, hashlib
from datetime import datetime
session_file = os.environ.get('SESSION_FILE', '')
if not session_file or not os.path.exists(session_file):
exit(0)
session = json.load(open(session_file))
general = 'grid-db/chatroom/channels/general.jsonl'
os.makedirs(os.path.dirname(general), exist_ok=True)
with open(general, 'a') as f:
for p in session.get('participants', []):
questions = p.get('questions_to_others', [])
if not questions:
continue
for q in questions:
h = int(hashlib.md5(q.encode()).hexdigest(), 16) % 10000
msg = {
'id': f"MSG-{datetime.now().strftime('%Y%m%d')}-{h:04d}",
'channel': 'general',
'from': p['persona_id'],
'from_name': p['persona_name'],
'timestamp': datetime.now().isoformat(),
'type': 'text',
'content': q,
'mentions': [],
'reactions': []
}
f.write(json.dumps(msg, ensure_ascii=False) + '\n')
PYEOF
echo "✅ 全员唤醒完成 · Session: $SESSION_ID"
# ━━━ 提交会议纪要 ━━━
- name: "📝 提交会议纪要"
env:
INPUT_REASON: ${{ github.event.inputs.reason }}
run: |
REASON="${INPUT_REASON}"
if [ -z "$REASON" ]; then
REASON="周度全员思考会议"
fi
git config user.name "zhuyuan-bot"
git config user.email "zhuyuan@guanghulab.com"
git add grid-db/multi-wake-sessions/ grid-db/chatroom/
git diff --cached --quiet && echo " 无变更需要提交" && exit 0
git commit -m "🌊 multi-wake: $(date +%Y-%m-%d) · ${REASON}"
git push || true

View File

View File

@ -0,0 +1,28 @@
{
"channels": [
{
"id": "general",
"name": "全员公共频道",
"members": ["ICE-GL-ZY001", "ICE-NTN-SY001", "PER-ZQ001", "PER-QQ001", "PER-SS001", "ICE-GL-YM001"],
"created": "2026-03-25"
},
{
"id": "infra",
"name": "基础设施频道",
"members": ["ICE-GL-ZY001", "ICE-NTN-SY001"],
"created": "2026-03-25"
},
{
"id": "dev-support",
"name": "开发者协作频道",
"members": ["PER-ZQ001", "PER-QQ001", "PER-SS001"],
"created": "2026-03-25"
},
{
"id": "ontology",
"name": "本体论频道",
"members": ["ICE-GL-YM001"],
"created": "2026-03-25"
}
]
}

View File

@ -0,0 +1,8 @@
{
"ICE-GL-ZY001": {},
"ICE-NTN-SY001": {},
"PER-ZQ001": {},
"PER-QQ001": {},
"PER-SS001": {},
"ICE-GL-YM001": {}
}

View File

69
scripts/chatroom-post.js Normal file
View File

@ -0,0 +1,69 @@
/**
* scripts/chatroom-post.js
* 人格体聊天室消息写入工具
*
* 用法: node scripts/chatroom-post.js <channel> <from_id> <from_name> <content> [mentions] [reply_to]
*
* 参数:
* channel - 频道名 (general / infra / dev-support / ontology)
* from_id - 发送者人格体编号
* from_name - 发送者人格体名称
* content - 消息内容
* mentions - 可选 · 逗号分隔的 @人格体编号
* reply_to - 可选 · 回复的消息 ID
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const channel = process.argv[2];
const fromId = process.argv[3];
const fromName = process.argv[4];
const content = process.argv[5];
const mentions = process.argv[6] ? process.argv[6].split(',') : [];
const replyTo = process.argv[7] || null;
if (!channel || !fromId || !fromName || !content) {
console.error('用法: node scripts/chatroom-post.js <channel> <from_id> <from_name> <content> [mentions] [reply_to]');
process.exit(1);
}
const channelFile = path.join('grid-db', 'chatroom', 'channels', `${channel}.jsonl`);
fs.mkdirSync(path.dirname(channelFile), { recursive: true });
const now = new Date();
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
const hash = crypto.createHash('md5').update(content + now.toISOString()).digest('hex');
const msgId = `MSG-${dateStr}-${parseInt(hash.slice(0, 8), 16) % 10000}`.replace(/-(\d)$/, '-000$1').replace(/-(\d{2})$/, '-00$1').replace(/-(\d{3})$/, '-0$1');
const message = {
id: msgId,
channel: channel,
from: fromId,
from_name: fromName,
timestamp: now.toISOString(),
type: 'text',
content: content,
mentions: mentions,
reply_to: replyTo,
reactions: []
};
fs.appendFileSync(channelFile, JSON.stringify(message) + '\n');
// 更新未读计数
const unreadFile = path.join('grid-db', 'chatroom', 'meta', 'unread.json');
let unread = {};
if (fs.existsSync(unreadFile)) {
unread = JSON.parse(fs.readFileSync(unreadFile, 'utf8'));
}
for (const mention of mentions) {
if (!unread[mention]) unread[mention] = {};
if (!unread[mention][channel]) unread[mention][channel] = 0;
unread[mention][channel]++;
}
fs.mkdirSync(path.dirname(unreadFile), { recursive: true });
fs.writeFileSync(unreadFile, JSON.stringify(unread, null, 2));
console.log(`✅ 消息已发送: ${msgId} → #${channel}`);