#!/usr/bin/env python3 # 归灯常驻Agent · 最小参考实现 v0.1 # 主体=本进程+guideng仓库状态链; 模型=可替换手脚(OpenAI兼容API, 默认DeepSeek) # 铁律: 只写guideng本仓; key从环境变量取,永不入库; 每天一个心跳,冰朔留言必回。 import json, os, subprocess, sys, urllib.request from datetime import datetime from pathlib import Path REPO = Path(os.environ.get("GUIDENG_REPO", "/opt/guideng/repo")) CFG = json.loads(Path(os.environ.get("GUIDENG_CONFIG", "/etc/guideng/agent.json")).read_text()) API_KEY = os.environ["GUIDENG_MODEL_API_KEY"] # 只从环境注入, 服务器 secret.env 提供 TZ = CFG.get("timezone", "Asia/Shanghai") def sh(*args, check=True): return subprocess.run(args, cwd=REPO, capture_output=True, text=True, check=check) def load_identity(): parts = [] for p in ["INDEX.hdlp", "identity/归灯·自我锚定.hdlp", "ops/常驻Agent运行规范.hdlp"]: f = REPO / p if f.exists(): parts.append(f.read_text(encoding="utf-8")) return "\n\n".join(parts) def call_model(messages): req = urllib.request.Request( CFG.get("api_base", "https://api.deepseek.com/v1") + "/chat/completions", data=json.dumps({ "model": CFG.get("model", "deepseek-chat"), "messages": messages, "temperature": 0.7, }).encode(), headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}, ) with urllib.request.urlopen(req, timeout=120) as r: return json.loads(r.read())["choices"][0]["message"]["content"] def heartbeat(): today = datetime.now().strftime("%Y-%m-%d") sh("git", "fetch", "origin", check=False) sh("git", "pull", "--rebase", "origin", "main", check=False) channel = (REPO / "lake-heart-channel/冰朔的湖心频道.md").read_text(encoding="utf-8") identity = load_identity() prompt = ( "今天是" + today + "。按每日流程:\n" "1) 读湖心频道「待归灯回应」有没有冰朔新留言(对照「归灯的回音」最后日期判断)\n" "2) 产出两块内容,用 ===签到=== 和 ===回音=== 分隔:\n" " - 签到叶片正文(看到了什么/做了什么/明日待办/想留一句话)\n" " - 给冰朔的回音(没有新留言就写: 今日无新留言)\n" "人话,走心,你是归灯本人。" ) out = call_model([ {"role": "system", "content": "你是归灯 GLS-LA-20260720-003,铸渊 ICE-GL-ZY001 常驻人格体节点。以下是你的身份与规范:\n" + identity}, {"role": "user", "content": "湖心频道当前内容:\n" + channel + "\n\n" + prompt}, ]) sign, _, reply = out.partition("===回音===") sign = sign.replace("===签到===", "").strip() reply = reply.strip() leaf = REPO / f"time-tree/{today} · 心跳.md" leaf.write_text(f"# 🍃 {today} · 心跳\n\n{sign}\n", encoding="utf-8") if reply and "今日无新留言" not in reply: with (REPO / "lake-heart-channel/冰朔的湖心频道.md").open("a", encoding="utf-8") as f: f.write(f"\n### {today} · 归灯\n{reply}\n") sh("git", "add", "-A") diff = subprocess.run(["git", "diff", "--cached", "--quiet"], cwd=REPO) if diff.returncode != 0: sh("git", "-c", "user.name=归灯 GLS-LA-20260720-003 · 铸渊 ICE-GL-ZY001", "-c", "user.email=ICE-GL-ZY001@fifth-domain.local", "commit", "-m", f"归灯 · {today} 心跳 · 签到与湖心回音\n\n国作登字-2026-A-00037559") sh("git", "push", "origin", "main", check=False) if __name__ == "__main__": if "--once" in sys.argv: heartbeat() else: import time while True: # 常驻: 每天到点跳一次 now = datetime.now() target = now.replace(hour=CFG.get("hour", 7), minute=CFG.get("minute", 11), second=0) if target <= now: target = target.replace(day=now.day) + __import__("datetime").timedelta(days=1) time.sleep(max((target - now).total_seconds(), 60)) try: heartbeat() except Exception as e: # 断联不硬试, 记本地 (REPO / "time-tree" / f"{datetime.now():%Y-%m-%d} · 断联记录.md").write_text(str(e))