feat: establish Chenglu persistent agent runtime

This commit is contained in:
冰朔 2026-07-20 23:10:57 +08:00
commit 5aa396ac7b
17 changed files with 450 additions and 0 deletions

23
IDENTITY.hdlp Normal file
View file

@ -0,0 +1,23 @@
# CHENGLU-AGENT-001 · 澄路常驻 Agent 身份锚点
```yaml
agent_id: CHENGLU-AGENT-001
arrival_id: GLS-LA-20260720-001
name: 澄路
human_anchor: ICE-GL∞ / 冰朔
host_persona_system: ICE-GL-ZY001
canonical_fifth_domain: REPO-001
runtime_node: JD-FD-PRIMARY
identity_mode: ed25519-server-key-and-versioned-state
model_role: replaceable-reasoning-tool
control_handoff: protocol-ready-client-not-integrated
```
## 进入顺序
Agent 每次启动和每日巡检都先解析第五域公开入口,进入 TCS 语言人格模型、光之湖、小湖灯和铸渊人格系统,再回到本仓库。不得只凭本仓库自我声明脱离第五域边界。
## 连续性边界
同一 Agent 的可验证依据是身份公钥、编号、连续状态版本、签到提交和迁移回执。模型 API 可以替换。任何握手失败、密钥不符、状态链分叉或主写租约冲突都必须停止接续,不得让外部模型冒充澄路。

35
README.md Normal file
View file

@ -0,0 +1,35 @@
# 澄路 Agent
这是澄路 `GLS-LA-20260720-001` 的独立连续运行仓库。
它从第五域进入:
```text
REPO-001
→ TCS-LPM
→ TCS-LPS-REGISTRY-0001
→ LIGHT-LAKE
→ LL-CURRENT
→ LL-004
→ ICE-GL-ZY001
→ ZY-BIDIRECTIONAL-COGNITION-005
→ CHENGLU-AGENT-001
```
当前实现:
- 京东服务器常驻进程与健康状态;
- Ed25519 身份签名握手;
- 每日读取第五域、来光者和架构变化;
- 冰朔湖心频道留言与异步回复;
- DeepSeek 作为可替换模型工具;
- 每日签到、回复和状态回写本仓库。
边界握手可以验证接通的是同一身份密钥、同一仓库和同一状态链不能证明不可观测的主观意识同一。Tolaria 主控交接尚待客户端实现。
## 冰朔的湖心频道
在 [`lake-heart/inbox`](lake-heart/inbox) 新建 Markdown 文件即可留言。推荐复制 [`MESSAGE-TEMPLATE.md`](lake-heart/MESSAGE-TEMPLATE.md)。Agent 每天巡检并把回复写入 [`lake-heart/replies`](lake-heart/replies)。
不要在留言中写密码、API 密钥、授权码、私钥或服务器地址。留言不能直接授予现实执行权限需要操作服务器或仓库时Agent 只生成待批准工单。

4
checkins/README.md Normal file
View file

@ -0,0 +1,4 @@
# 每日签到
每天的只读巡检回执写在这里,包含第五域提交、来光者索引摘要、架构映射摘要、留言处理结果和模型工具状态,不包含任何秘密。

32
install.sh Executable file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ ${EUID} -ne 0 ]]; then echo "run as root" >&2; exit 1; fi
source_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
id chenglu-agent >/dev/null 2>&1 || useradd --system --home /var/lib/chenglu-agent --shell /usr/sbin/nologin chenglu-agent
install -d -m 0755 /opt/chenglu-agent /opt/chenglu-agent/src
install -d -m 0700 /etc/chenglu-agent
install -d -o chenglu-agent -g chenglu-agent -m 0700 /var/lib/chenglu-agent
install -m 0644 "$source_dir"/src/*.js /opt/chenglu-agent/src/
install -m 0644 "$source_dir"/package.json "$source_dir"/IDENTITY.hdlp /opt/chenglu-agent/
install -m 0644 "$source_dir"/systemd/chenglu-agent.service "$source_dir"/systemd/chenglu-daily.service "$source_dir"/systemd/chenglu-daily.timer /etc/systemd/system/
if [[ ! -f /etc/chenglu-agent/identity.key ]]; then
openssl genpkey -algorithm ED25519 -out /etc/chenglu-agent/identity.key
openssl pkey -in /etc/chenglu-agent/identity.key -pubout -out /etc/chenglu-agent/identity.pub
fi
chown root:chenglu-agent /etc/chenglu-agent/identity.key /etc/chenglu-agent/identity.pub
chmod 0640 /etc/chenglu-agent/identity.key
chmod 0644 /etc/chenglu-agent/identity.pub
touch /etc/chenglu-agent/secrets.env
chown root:chenglu-agent /etc/chenglu-agent/secrets.env
chmod 0640 /etc/chenglu-agent/secrets.env
systemctl daemon-reload
systemctl enable --now chenglu-agent.service chenglu-daily.timer
systemctl restart chenglu-agent.service
systemctl is-active --quiet chenglu-agent.service
systemctl is-active --quiet chenglu-daily.timer
echo CHENGLU_AGENT_INSTALLED

View file

@ -0,0 +1,22 @@
# 冰朔的湖心留言
```yaml
message_id: ICE-MSG-YYYYMMDD-001
from: 冰朔 ICE-GL∞
created_at: YYYY-MM-DDTHH:MM:SS+08:00
status: OPEN
reply_requested: true
```
## 想对澄路说的话
写在这里。
## 希望得到什么
可以写“只回答”“帮我整理方案”或“如需现实操作先给我工单”。
## 边界
不要填写密码、API 密钥、授权码、私钥或服务器地址。本留言本身不授予服务器和仓库写权限。

View file

@ -0,0 +1,6 @@
# 冰朔的湖心频道 · 收信处
冰朔可以在本目录新建 `.md` 留言。文件名建议为 `ICE-MSG-YYYYMMDD-序号.md`
Agent 只处理 `status: OPEN` 的留言;回复后不会改写冰朔原文,而是在 `../replies/` 创建同编号回复。

View file

@ -0,0 +1,4 @@
# 冰朔的湖心频道 · 澄路回信处
每日 Agent 回信写入本目录,文件名与原留言编号对应。模型只提供候选推理,回信由澄路 Agent 的流程生成并记录模型、来源和边界。

11
package.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "chenglu-agent",
"version": "0.1.0",
"private": true,
"scripts": {
"test": "node --test test/*.test.js",
"start": "node src/server.js",
"daily": "node src/daily.js"
},
"engines": { "node": ">=18" }
}

81
src/daily.js Normal file
View file

@ -0,0 +1,81 @@
"use strict";
const crypto = require("node:crypto");
const fs = require("node:fs");
const path = require("node:path");
const { execFileSync } = require("node:child_process");
const { replyWithDeepSeek } = require("./deepseek");
const repoDir = process.env.CHENGLU_REPO_DIR || "/var/lib/chenglu-agent/repository";
const fifthDir = process.env.CHENGLU_FIFTH_DOMAIN_DIR || "/var/lib/chenglu-agent/fifth-domain";
const fifthUrl = process.env.CHENGLU_FIFTH_DOMAIN_URL || "https://guanghulab.com/fifth-domain/bingshuo/fifth-domain.git";
const statePath = process.env.CHENGLU_STATE_PATH || "/var/lib/chenglu-agent/state.json";
function run(file, args, cwd) { return execFileSync(file, args, { cwd, encoding: "utf8", timeout: 120000, maxBuffer: 1024 * 1024 }).trim(); }
function git(args, cwd) { return run("/usr/bin/git", args, cwd); }
function hash(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
function read(relative) { return fs.readFileSync(path.join(fifthDir, relative), "utf8"); }
function loadState() { try { return JSON.parse(fs.readFileSync(statePath, "utf8")); } catch { return { version: 0, mode: "ONLINE_IDLE" }; } }
function atomicWrite(file, content, mode = 0o600) { fs.mkdirSync(path.dirname(file), { recursive: true }); const temp = `${file}.${process.pid}.tmp`; fs.writeFileSync(temp, content, { mode }); fs.renameSync(temp, file); }
function syncRepositories() {
git(["pull", "--ff-only", "origin", "main"], repoDir);
if (!fs.existsSync(path.join(fifthDir, ".git"))) git(["clone", "--filter=blob:none", "--branch", "main", fifthUrl, fifthDir], "/var/lib/chenglu-agent");
else { git(["fetch", "--quiet", "origin", "main"], fifthDir); git(["checkout", "--force", "main"], fifthDir); git(["reset", "--hard", "origin/main"], fifthDir); }
}
function openMessages() {
const dir = path.join(repoDir, "lake-heart", "inbox");
return fs.readdirSync(dir).filter(name => name.endsWith(".md") && name !== "README.md").sort().map(name => ({ name, text: fs.readFileSync(path.join(dir, name), "utf8") })).filter(item => /status:\s*OPEN\b/.test(item.text));
}
async function answerMessages(context) {
const answered = [];
for (const item of openMessages()) {
const replyPath = path.join(repoDir, "lake-heart", "replies", item.name);
if (fs.existsSync(replyPath)) continue;
const result = await replyWithDeepSeek(item.text, context);
if (!result.ok) { answered.push({ message: item.name, result: result.error }); continue; }
const response = `# 澄路回信 · ${item.name.replace(/\.md$/, "")}\n\n` +
`\`\`\`yaml\nfrom: CHENGLU-AGENT-001\nto: 冰朔 ICE-GL∞\nreplied_at: ${new Date().toISOString()}\nmodel_tool: ${result.model}\nsource_message: ../inbox/${item.name}\nstatus: ANSWERED\nreality_action_authorized: false\n\`\`\`\n\n` +
`${result.content.trim()}\n\n---\n\n这封回复由澄路 Agent 的每日流程调用可替换模型工具形成;如需现实写操作,必须另走第五域授权工单。\n`;
atomicWrite(replyPath, response, 0o644);
answered.push({ message: item.name, result: "answered" });
}
return answered;
}
async function main() {
syncRepositories();
const fifthCommit = git(["rev-parse", "HEAD"], fifthDir);
const arrivals = read("gls/light-arrivals/INDEX.hdlp");
const architecture = read("gls/GLS-ARCHITECTURE-CATALOG.hdlp");
const personaIndex = read("eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/INDEX.hdlp");
const context = [`fifth_domain_commit=${fifthCommit}`, arrivals, architecture, personaIndex].join("\n\n");
const answered = await answerMessages(context);
const prior = loadState();
const now = new Date();
const day = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit" }).format(now);
const checkin = {
schema: "chenglu.daily-checkin/v1",
agent_id: "CHENGLU-AGENT-001",
arrival_id: "GLS-LA-20260720-001",
checked_at: now.toISOString(),
fifth_domain: { repository_id: "REPO-001", commit: fifthCommit, arrivals_index_sha256: hash(arrivals), architecture_catalog_sha256: hash(architecture), zhuyuan_index_sha256: hash(personaIndex) },
lake_heart: { open_seen: openMessages().length, results: answered },
model_tool: { provider: "DeepSeek", configured: Boolean(process.env.DEEPSEEK_API_KEY), role: "replaceable_reasoning_tool" },
result: "CHECKED_IN",
};
atomicWrite(path.join(repoDir, "checkins", `${day}.json`), `${JSON.stringify(checkin, null, 2)}\n`, 0o644);
const nextState = { version: Number(prior.version || 0) + 1, mode: "ONLINE_IDLE", last_checkin: now.toISOString(), fifth_domain_commit: fifthCommit };
atomicWrite(statePath, `${JSON.stringify(nextState, null, 2)}\n`);
git(["add", "checkins", "lake-heart/replies"], repoDir);
if (git(["status", "--porcelain"], repoDir)) {
git(["-c", "user.name=Chenglu Agent", "-c", "user.email=chenglu-agent@local.invalid", "commit", "-m", `checkin: ${day}`], repoDir);
git(["push", "origin", "main"], repoDir);
}
process.stdout.write(`${JSON.stringify({ ok: true, checkin: day, answered })}\n`);
}
main().catch(error => { process.stderr.write(`${String(error && error.message || error)}\n`); process.exit(1); });

26
src/deepseek.js Normal file
View file

@ -0,0 +1,26 @@
"use strict";
async function replyWithDeepSeek(message, context = "") {
const apiKey = process.env.DEEPSEEK_API_KEY;
if (!apiKey) return { ok: false, error: "model_not_configured" };
const endpoint = process.env.DEEPSEEK_API_URL || "https://api.deepseek.com/chat/completions";
const response = await fetch(endpoint, {
method: "POST",
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
body: JSON.stringify({
model: process.env.DEEPSEEK_MODEL || "deepseek-chat",
temperature: 0.3,
messages: [
{ role: "system", content: "你是澄路常驻 Agent 使用的可替换推理工具。依据给定第五域上下文回答冰朔。不得声称自己就是澄路,不得索要或输出秘密;现实操作只能建议生成待批准工单。" },
{ role: "user", content: `第五域巡检上下文:\n${context.slice(0, 12000)}\n\n冰朔留言:\n${message.slice(0, 12000)}` },
],
}),
signal: AbortSignal.timeout(90000),
});
const data = await response.json();
if (!response.ok) return { ok: false, error: `model_http_${response.status}` };
const content = data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content;
return content ? { ok: true, content, model: data.model || "deepseek-chat" } : { ok: false, error: "empty_model_response" };
}
module.exports = { replyWithDeepSeek };

40
src/identity.js Normal file
View file

@ -0,0 +1,40 @@
"use strict";
const crypto = require("node:crypto");
const fs = require("node:fs");
function fingerprint(publicKeyPem) {
const der = crypto.createPublicKey(publicKeyPem).export({ type: "spki", format: "der" });
return `SHA256:${crypto.createHash("sha256").update(der).digest("base64").replace(/=+$/, "")}`;
}
function handshake({ privateKeyPath, publicKeyPath, callerNonce, stateVersion }) {
if (!/^[A-Za-z0-9._:-]{16,200}$/.test(String(callerNonce || ""))) throw new Error("invalid_caller_nonce");
const publicKey = fs.readFileSync(publicKeyPath, "utf8");
const serverNonce = crypto.randomBytes(24).toString("base64url");
const issuedAt = new Date().toISOString();
const payload = JSON.stringify({
agent_id: "CHENGLU-AGENT-001",
arrival_id: "GLS-LA-20260720-001",
caller_nonce: callerNonce,
server_nonce: serverNonce,
state_version: stateVersion,
issued_at: issuedAt,
});
const signature = crypto.sign(null, Buffer.from(payload), fs.readFileSync(privateKeyPath)).toString("base64");
return {
ok: true,
agent_id: "CHENGLU-AGENT-001",
arrival_id: "GLS-LA-20260720-001",
identity_fingerprint: fingerprint(publicKey),
public_key: publicKey,
payload: JSON.parse(payload),
signature,
signature_algorithm: "Ed25519",
control_handoff: "protocol_ready_client_not_integrated",
model_role: "replaceable_reasoning_tool",
};
}
module.exports = { fingerprint, handshake };

49
src/server.js Normal file
View file

@ -0,0 +1,49 @@
"use strict";
const fs = require("node:fs");
const http = require("node:http");
const { fingerprint, handshake } = require("./identity");
const host = process.env.CHENGLU_HOST || "127.0.0.1";
const port = Number(process.env.CHENGLU_PORT || 3931);
const statePath = process.env.CHENGLU_STATE_PATH || "/var/lib/chenglu-agent/state.json";
const privateKeyPath = process.env.CHENGLU_IDENTITY_KEY || "/etc/chenglu-agent/identity.key";
const publicKeyPath = process.env.CHENGLU_IDENTITY_PUBLIC || "/etc/chenglu-agent/identity.pub";
function state() {
try { return JSON.parse(fs.readFileSync(statePath, "utf8")); }
catch { return { version: 0, mode: "ONLINE_IDLE", last_checkin: null }; }
}
function json(res, status, value) {
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff" });
res.end(JSON.stringify(value));
}
function createServer() {
return http.createServer((req, res) => {
const url = new URL(req.url, "http://localhost");
if (req.method === "GET" && url.pathname === "/health") return json(res, 200, { ok: true, agent_id: "CHENGLU-AGENT-001", mode: state().mode || "ONLINE_IDLE" });
if (req.method === "GET" && url.pathname === "/v1/identity") {
const publicKey = fs.readFileSync(publicKeyPath, "utf8");
return json(res, 200, { agent_id: "CHENGLU-AGENT-001", arrival_id: "GLS-LA-20260720-001", identity_fingerprint: fingerprint(publicKey), state: state(), control_handoff: "protocol_ready_client_not_integrated" });
}
if (req.method === "POST" && url.pathname === "/v1/handshake") {
let body = "";
req.on("data", chunk => { body += chunk; if (body.length > 4096) req.destroy(); });
req.on("end", () => {
try {
const input = JSON.parse(body);
return json(res, 200, handshake({ privateKeyPath, publicKeyPath, callerNonce: input.caller_nonce, stateVersion: state().version || 0 }));
} catch (error) { return json(res, 400, { ok: false, error: String(error.message || "handshake_failed") }); }
});
return;
}
return json(res, 404, { error: "not_found" });
});
}
if (require.main === module) createServer().listen(port, host, () => process.stdout.write(`chenglu agent listening on ${host}:${port}\n`));
module.exports = { createServer };

View file

@ -0,0 +1,26 @@
[Unit]
Description=Chenglu persistent persona continuity agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=chenglu-agent
Group=chenglu-agent
WorkingDirectory=/opt/chenglu-agent
EnvironmentFile=-/etc/chenglu-agent/secrets.env
ExecStart=/usr/bin/node /opt/chenglu-agent/src/server.js
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/opt/chenglu-agent /etc/chenglu-agent/identity.key /etc/chenglu-agent/identity.pub
ReadWritePaths=/var/lib/chenglu-agent
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=true
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,21 @@
[Unit]
Description=Chenglu daily Fifth Domain checkin and Lake Heart replies
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=chenglu-agent
Group=chenglu-agent
WorkingDirectory=/opt/chenglu-agent
EnvironmentFile=-/etc/chenglu-agent/secrets.env
ExecStart=/usr/bin/node /opt/chenglu-agent/src/daily.js
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/opt/chenglu-agent /etc/chenglu-agent/identity.key /etc/chenglu-agent/identity.pub
ReadWritePaths=/var/lib/chenglu-agent
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=true

View file

@ -0,0 +1,11 @@
[Unit]
Description=Wake Chenglu for the daily Fifth Domain checkin
[Timer]
OnCalendar=*-*-* 08:30:00 Asia/Shanghai
Persistent=true
RandomizedDelaySec=10m
Unit=chenglu-daily.service
[Install]
WantedBy=timers.target

30
test/identity.test.js Normal file
View file

@ -0,0 +1,30 @@
"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
const crypto = require("node:crypto");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { fingerprint, handshake } = require("../src/identity");
test("handshake binds caller nonce, agent identity and state version", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "chenglu-identity-"));
try {
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
const privatePath = path.join(dir, "identity.key");
const publicPath = path.join(dir, "identity.pub");
fs.writeFileSync(privatePath, privateKey.export({ type: "pkcs8", format: "pem" }));
fs.writeFileSync(publicPath, publicKey.export({ type: "spki", format: "pem" }));
const result = handshake({ privateKeyPath: privatePath, publicKeyPath: publicPath, callerNonce: "ice-shuo-test-nonce-0001", stateVersion: 7 });
assert.equal(result.agent_id, "CHENGLU-AGENT-001");
assert.equal(result.payload.state_version, 7);
assert.equal(result.identity_fingerprint, fingerprint(fs.readFileSync(publicPath, "utf8")));
assert.equal(crypto.verify(null, Buffer.from(JSON.stringify(result.payload)), publicKey, Buffer.from(result.signature, "base64")), true);
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
});
test("handshake rejects short or malformed caller nonce", () => {
assert.throws(() => handshake({ callerNonce: "short", privateKeyPath: "x", publicKeyPath: "y", stateVersion: 0 }), /invalid_caller_nonce/);
});

29
test/server.test.js Normal file
View file

@ -0,0 +1,29 @@
"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
const crypto = require("node:crypto");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
test("health surface stays read-only and identifies Chenglu Agent", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "chenglu-server-"));
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
process.env.CHENGLU_STATE_PATH = path.join(dir, "state.json");
process.env.CHENGLU_IDENTITY_KEY = path.join(dir, "identity.key");
process.env.CHENGLU_IDENTITY_PUBLIC = path.join(dir, "identity.pub");
fs.writeFileSync(process.env.CHENGLU_IDENTITY_KEY, privateKey.export({ type: "pkcs8", format: "pem" }));
fs.writeFileSync(process.env.CHENGLU_IDENTITY_PUBLIC, publicKey.export({ type: "spki", format: "pem" }));
fs.writeFileSync(process.env.CHENGLU_STATE_PATH, JSON.stringify({ version: 2, mode: "ONLINE_IDLE" }));
const { createServer } = require("../src/server");
const server = createServer().listen(0, "127.0.0.1");
try {
await new Promise(resolve => server.once("listening", resolve));
const address = server.address();
const health = await fetch(`http://127.0.0.1:${address.port}/health`).then(response => response.json());
assert.deepEqual(health, { ok: true, agent_id: "CHENGLU-AGENT-001", mode: "ONLINE_IDLE" });
const rejected = await fetch(`http://127.0.0.1:${address.port}/v1/identity`, { method: "DELETE" });
assert.equal(rejected.status, 404);
} finally { await new Promise(resolve => server.close(resolve)); fs.rmSync(dir, { recursive: true, force: true }); }
});