Files
Cyrene_v2/.env.example
T
AskaEth 91a3209c9b
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(memory): 记忆整理 v1(design/23)——低峰定期扫描 + 事实包含判定 + 规则否决 + 只产建议队列(不自动应用)
2026-10-05 11:29:36 +08:00

443 lines
31 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ============================================
# Cyrene v2 配置文件 (.env)
# 复制为 .env 后修改。所有配置均从环境变量注入,代码内禁硬编码。
# 注意:.env 仅用于本地开发;生产用 systemd / env_file。
# 敏感信息(API Key / JWT_SECRET)一律不要写进日志。
# ============================================
# ---- 运行环境 ----
ENV=development
LOG_LEVEL=debug
TZ=Asia/Shanghai
# ---- 数据目录(裁决 12.4.2/15.4.1,design/13 §2) ----
# 两种取法都实现:默认 ./data;显式写 platform(保留字哨兵)则走平台标准数据目录
# (Windows %APPDATA%\Cyrene、macOS ~/Library/Application Support/Cyrene、Linux ~/.local/share/cyrene)。
DATA_DIR=./data
# ---- 服务器(网关,唯一对外端口) ----
# HOST 默认 0.0.0.0,暴露面由 .env 决定(本机自托管要收紧就改 127.0.0.1)。
HOST=0.0.0.0
PORT=8080
# 管理面板的安全入口路径(裁决 1.6/16.4.7,design/07 §0.2):
# 管理面板前端挂在这个路径下,默认 admin(即 /admin);想藏起来就自定义,例如
# ADMIN_PATH=my-secret/admin → 面板在 /my-secret/admin,猜 /admin 直接 404。
# 它只影响面板前端路由;REST 仍是 /api/v1/admin/*(API 不藏,安全靠 JWT + 角色门),
# 且该路径串不会出现在用户端页面里。
ADMIN_PATH=admin
# chat.image 的【解码后图像字节】上限(协同文档 G3),默认 5242880(5 MiB,与 POST /api/v1/upload 同口径)。
# 图片有「上传」与「base64 内联」两条入口,两边必须给出同一个「一张图能有多大」的答案,否则内联可绕开上传限制。
# 调大请谨慎:整段 base64 会被落库,并随每页历史(GET /sessions/{id}/messages)与每次 WS history 回放重传一遍。
# ≤0(写 0 或负数)= 用默认值 5 MiB,不是「禁用上限」——一张图能有多大只能有一个答案。
CHAT_IMAGE_MAX_BYTES=5242880
# POST /api/v1/documents 的单文件字节上限,默认 20971520(20 MiB)。
# 它与上面的图片上限【刻意解耦】:图片上限被 vision 解码层钳住,文档上限由本项目的解析与上下文预算决定;
# 合用一个数会让「调文档上限」静默改掉图片行为。
# 文档读侧(GET /api/v1/documents/{name})要 Bearer owner/admin,且一律 attachment + nosniff(永不可内联渲染)。
# ≤0(写 0 或负数)= 用默认值 20 MiB,不是「禁用上限」。
CHAT_DOCUMENT_MAX_BYTES=20971520
# ---- 文档回收(GC:引用计数 + 保留期,design/24 §3.2(配额与清理);切片 S7) ----
# 判据:**未被任何消息引用**(ref_count = 0)且最后一次被引用(从未被引用则看上传时间)
# 早于「现在 − 保留期」。动作:原件(<DATA_DIR>/uploads/documents/<存储名>)+ 抽取正文
# sidecar(<DATA_DIR>/extracted/<存储名>.txt)+ documents 表里的元数据行**三样一起删**。
# 引用计数由消息落库/编辑/删除时维护(都在同一个事务里),因此**仍被引用的文件绝不删**。
# 启动时清扫一次 + 此后按间隔定时清扫;每轮只记一条汇总(删了几件/释放多少字节,不含正文)。
# DOCUMENT_GC_INTERVAL:定时清扫间隔(Go duration,如 30m / 1h / 6h),默认 1h。
# DOCUMENT_RETENTION:保留期(Go duration),默认 168h(7 天)。
# ⚠️ 任一键写 0 或负数 = **关闭 GC**(同时关闭启动期那一次清扫,日志会写明原因)。
# `DOCUMENT_RETENTION=0` 不是「立即回收」——那种解释会让一次笔误清空未引用的附件。
DOCUMENT_GC_INTERVAL=1h
DOCUMENT_RETENTION=168h
# ---- LLM 用量样本保留期(llm_usage 表;切片 U1,主人裁决「2.4 落」) ----
# 每次成功模型调用都会把一条**非敏感**用量样本(token 计数 / 缓存两档 / 模型名 /
# 用途 / 耗时)异步落进 llm_usage 表,供 GET /api/v1/admin/llm/usage 与面板的
# 用量小卡聚合。**凭据与消息正文绝不入库、绝不进日志**。
# 写入是有界队列 + 单写者 + 批量事务:队列满就丢弃并限流 WARN,**绝不阻塞模型调用**。
# LLM_USAGE_RETENTION:保留期(Go duration),默认 720h(30 天)。
# 启动时清理一次 + 此后每小时清理一次(清扫间隔无 env 键:保留期才是旋钮)。
# ⚠️ 写 0 或负数 = **关闭清理**(样本全留,日志会写明「llm usage retention disabled」)。
# `LLM_USAGE_RETENTION=0` 不是「立即清空」——那种解释会让一次笔误把成本视图清空。
LLM_USAGE_RETENTION=720h
# ---- 认证 / 管理员 ----
# JWT_SECRET 必填,长度 >= 32;缺失或过短会在启动时 panic。
JWT_SECRET=change-me-to-a-random-string-at-least-32-characters
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin12345
REGISTRATION_ENABLED=true
# 不透明 refresh token 有效期(Go duration,如 720h = 30 天);access token 固定 24h。
AUTH_REFRESH_TTL=720h
# refresh token 单次使用轮换默认开启(用旧换新、防重放);仅当客户端无法保存轮换后的
# 新 token 时才设为 true 关闭轮换。
AUTH_REFRESH_ROTATE_DISABLED=false
# ---- 数据库 ----
# SQLite 为默认(单二进制、无需外部服务)。
DB_SQLITE_PATH=./data/cyrene.db
# PostgreSQL + pgvector 为可选增强;未配置时走 SQLite。相关向量能力在 M4 接入。
# DB_PG_URL=postgres://cyrene:cyrene_pass@localhost:5432/cyrene_ai?sslmode=disable
# ---- LLM 多供应商(LLM_PROVIDER 显式声明协议:openai / anthropic,默认 openai) ----
# ⚠️ 本节是【兜底】:管理面板「LLM 模型」页保存会写入本地 models.json(默认 <DATA_DIR>/models.json,已 gitignore),
# 只要 models.json 存在且有效,运行中的 LLM 就优先用它的多厂商多模型组,并支持面板实时保存→热更新(无需重启)。
# 仅当 models.json 不存在/无效时才回退到下面的 .env。
# Provider = openai:OpenAI 兼容 /chat/completions(含 OpenAI 兼容网关,也可经它承载 Anthropic 模型,但失去原生 tool input 对象优势)
LLM_PROVIDER=openai
LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=sk-xxxxx
LLM_MODEL=gpt-4o
LLM_FALLBACK_MODEL=gpt-4o-mini
# 最后一道回退模型(fallback 链末端)的 token 预算上限。仅当链确有回退模型、
# 且该模型组未在 models.json 设 maxTokens、请求也未显式给 MaxTokens 时生效;
# 0 = 关闭该兜底(改用适配器默认,如 anthropic 8192)。每模型组预算见 models.json 的 maxTokens。
LLM_FALLBACK_MAX_TOKENS=8192
# 推理开关的【提供商形状】声明(裁决 03.4.2 / 08.4.1):auto(默认,= 留空)/ deepseek /
# openai / vllm / dashscope。形状决定把思考开关下发改成哪个字段(如 vllm =
# chat_template_kwargs.enable_thinking、dashscope = 顶层 enable_thinking),必须按端点真实形状
# 声明——发错形状会被上游拒。留空/auto = 按模型名与 base URL 推断厂商,形状有歧义时一个字段都不发。
# ⚠️ 本键只作用于 .env 合成的模型组:存在有效 models.json 时,以该组的 reasoning.kind 为准
# (面板可编辑,见「LLM 模型」页)。
LLM_REASONING_KIND=
# ---- LLM 视觉模型(视觉/OCR 工具用的模型;与「主模型能否收图」无关) ----
# 语义(v0.53 修正):主模型自己能不能收图,看 models.json 生效组的 multimodal 字段(面板可勾选),
# 不是看这里;本节的模型【只供 vision / ocr 工具】调用。都不配置时工具回落用主模型(LLM_MODEL)。
# .env 侧的等价键是下面的 LLM_MULTIMODAL——它同样只作用于 .env 合成的模型组。
# LLM_VISION_MODEL=gpt-4o-mini
# OCR 工具单独用另一个模型(可选):留空 = 复用 LLM_VISION_MODEL。
# LLM_OCR_MODEL=qwen-vl-ocr
# 声明【主模型自身】能否直接接收图片(= models.json 组 multimodal 的 .env 等价物):
# true = 图片直接进主模型上下文(省掉工具那一轮模型调用、不做有损文本摘要);
# 留空 / false(默认)= 图片交给 vision / ocr 工具,由工具返回文本描述。
# 默认 false 与引入本键之前【逐字节一致】——扁平 env 块只有这一个键能声明收图能力,
# 而声称端点可能不具备的能力,比把图交给视觉/OCR 工具更糟(design/06 §0 首条);
# 绝不凭 base URL / 模型名猜测端点是否收图。
# ⚠️ 只作用于 .env 合成的模型组:存在有效 models.json 时以该组的 multimodal 为准(面板可勾选)。
# ⚠️ 非法值(非 bool,如 maybe)= 启动即报错,不静默回退。
# LLM_MULTIMODAL=false
# 声明该端点的【模型上下文窗口】(token)。窗口是上下文压缩预算的推导依据:
# 未设置 CONTEXT_MAX_TOKENS 时 budget = min(窗口 × CONTEXT_BUDGET_RATIO, 窗口 − 输出保留)。
# ⚠️ 本仓没有任何 provider 侧的窗口发现代码(GET /models 的自动发现另立设计),窗口只能声明:
# 不声明就回落 131072(128 Ki)并记一条 WARN,默认模型的 1M 窗口因此吃不到。
# 它必须与 models.json 组的 contextWindow 一致(那一处是面板一等来源,本键只给 .env 合成的组播种)。
# 负数 = 启动即报错。
# LLM_CONTEXT_WINDOW=1048576
# Provider = anthropic:Anthropic Messages /v1/messages(tool_use.input 为结构化对象,工具循环更稳)
# ANTHROPIC_BASE_URL=https://api.anthropic.com
# ANTHROPIC_API_KEY=sk-ant-xxxxx
# ANTHROPIC_MODEL=claude-sonnet-4-20250514
# ANTHROPIC_FALLBACK_MODEL=claude-haiku-3-5
# ⚠️ 若 ANTHROPIC_BASE_URL 指向 DeepSeek(https://api.deepseek.com/anthropic),模型名须用 DeepSeek 当前名称
# (deepseek-flash / deepseek-v4-pro):不认识的名称(如 claude-*)会被服务端【静默映射成 deepseek-flash】,
# 配置错了不会报错。旧的 deepseek-chat / deepseek-reasoner 已于 2026-07-24 停用。
# ---- LLM 本地第三级(降级链末端;默认关,裁决 03.4.3) ----
# 降级链为「主 → 备 → 本地」;本地这一级默认关闭(当前设备跑本地模型困难),链保持两级。
# 开启需要三个条件同时满足:LLM_LOCAL_ENABLED=true + LLM_LOCAL_BASE_URL + LLM_LOCAL_MODEL;
# 只开开关不配端点/模型会被记 WARN 并跳过(不会半配成一个只会失败的链尾步)。
LLM_LOCAL_ENABLED=false
# 本地端点协议:openai(llama.cpp / ollama / vLLM 兼容)或 anthropic;留空 = openai。
# LLM_LOCAL_PROVIDER=openai
# LLM_LOCAL_BASE_URL=http://127.0.0.1:8080/v1
# 本地服务通常不需要 key;留空即可。
# LLM_LOCAL_API_KEY=
# LLM_LOCAL_MODEL=qwen3-4b-instruct
# ---- 记忆嵌入(可选,真语义) ----
# 不配置时使用内置 n-gram / FTS 向量(dim=256)。配置后必须真实走语义路径。
# EMBEDDING_API_URL 是【完整 /embeddings 端点】(不是 base)——例:Ollama / llama.cpp = http://localhost:11434/v1/embeddings
# EMBEDDING_API_URL=http://localhost:11434/v1/embeddings
# EMBEDDING_API_KEY=
# EMBEDDING_MODEL=Qwen3-Embedding-0.6B # 推荐(Q8_0 ~639MB、dim=1024、ModelScope 国内可下)
# ---- IoT 设备提供者 ----
# mock = 模拟智能家居设备(开发测试用);none = 禁用 IoT。
IOT_PROVIDER=mock
# ---- 语音服务 ----
TTS_PROVIDER=edge-tts
STT_PROVIDER=dashscope
# DASHSCOPE_API_KEY=sk-xxxxx # ⚠️ 必填才能接通阿里百炼语音;未填时走降级链 (silent)。绝不打日志。
# --- 阿里百炼 DashScope 语音(STT + TTS 主链路)---
# DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/api/v1
# VOICE_STT_MODEL=qwen-audio-3.0-asr-flash
# VOICE_STT_LANGUAGE=zh
# IM 入站语音转写的超时(秒,默认 20):桥接器会为这次调用举着一个入站并发槽,
# 因此上游卡住不能拖死整条流水线。≤0 或未配置时用桥接器默认值 20s。
# 只影响 IM 渠道的入站语音;Web 客户端的语音输入不受它约束。
# VOICE_STT_TIMEOUT_SEC=20
# 已实测:cosyvoice-v3.5-plus / cosyvoice-v2 走 HTTP SpeechSynthesizer 会回 418(引擎拒绝);
# 可用组合为 cosyvoice-v3-flash + 对应 v3 音色(longxiaochun_v3 龙小淳·女助手等)。
# 各模型音色不可混用(v2 名如 longxiaochun 用于 v3 模型会 418)。
# VOICE_TTS_MODEL=cosyvoice-v3-flash
# VOICE_TTS_VOICE_ID=longxiaochun_v3
# VOICE_TTS_FORMAT=wav
# VOICE_TTS_SAMPLE_RATE=24000
# 情绪/语气指令(TTS instruction):只对「复刻/设计音色」生效(可任意指令,如
# "语气开朗俏皮,像女朋友撒娇"),系统音色 longxiaochun_v3 必须用固定格式故不传。
# 长度≤100 字符(汉字记 2)。也可在 POST /api/v1/audio/speech 用 instruction 按请求覆盖。
# VOICE_TTS_INSTRUCTION=语气开朗俏皮,像女朋友撒娇
# 声音复刻前缀(create_voice 的 prefix,≤10 字母/数字,空则用 cyrene)。target_model
# 固定为 VOICE_TTS_MODEL(须与合成模型一致)。
# VOICE_CLONE_PREFIX=cyrene
# 声音复刻(响昔涟音色):先用参考音频(公网 URL)调 POST /api/v1/audio/voice-clone
# 建自定义音色,再把返回的 voice_id 填到 VOICE_TTS_VOICE_ID。
# ⚠️ 建音色必须公网可访问的参考音频 URL(create_voice 只收 input.url,不收 base64);
# 待主人提供参考音频公网 URL 方能真建音色。
# ---- 后台思考 ----
ENABLE_BACKGROUND_THINKING=true
# 自主思考心跳周期(秒)。默认 900(15 分钟一次);数值越小越频繁打扰。
THINK_INTERVAL_SEC=900
THINK_SILENCE_TIMEOUT_SEC=120
# 主动推送频率(design/12 §6):窗口内最多 PushLimit 次;推后冷却 Cooldown 秒;
# 同一风味(问候/提醒/分享/关心)在 KindWindow 秒内最多一次;主人最近消息在
# RecentChatCooldown 秒内则冷避不推。收敛默认值:1 次 / 60 分钟 + 推后 30 分钟冷却。
THINK_PUSH_LIMIT=1
THINK_PUSH_WINDOW_SEC=3600
THINK_PUSH_COOLDOWN_SEC=1800
THINK_RECENT_CHAT_COOLDOWN_SEC=600
THINK_KIND_WINDOW_SEC=7200
# 安静时段(本地时钟小时,默认 23:00-07:00;两端相等且非 0 则关闭)。
# 同时用于 backthink 后台思考与 channelbridge 公共平台安静时段轻思考(design/12 §6 策略五)。
QUIET_START_HOUR=23
QUIET_END_HOUR=7
# ---- 记忆整理与去重(design/23;主题 N · 长期记忆维护) ----
# **低峰定期后台任务**(不是会话内派 subagent):逐分区扫描 → 相似度**召回**候选对 →
# 「事实集合包含关系」判定 → 规则否决 → 落 `memory_tidy_candidates` 建议队列。
# 第一版**只产建议**:绝不自动合并/删除/改写任何一条记忆,也**不推送**、不进 chat、
# 不碰主会话上下文(跑在专属隔离会话 sess-memorytidy,与 backthink 同一范式)。
# 每轮汇总量(扫描/候选/否决原因分布)进日志与 `memory_tidy_runs` 表,绝不静默。
# 关闭开关:写 false 即完全不跑(日志会写明)。
MEMORY_TIDY_ENABLED=true
# 整理周期(秒),默认 86400 = 每天 1 次。它是**下限**:实际在低峰窗口起点跑,
# 因此真实间隔取「低峰窗口每天一次」与本值中较长者。
MEMORY_TIDY_INTERVAL_SEC=86400
# 低峰窗口(本地钟点),默认与 backthink 一致的 23:00-07:00(窗口可跨午夜;
# 两端相等且非 0 = 关闭窗口,改为按上面周期跑)。整理只在窗口内开跑。
MEMORY_TIDY_QUIET_START_HOUR=23
MEMORY_TIDY_QUIET_END_HOUR=7
# 候选召回的余弦**召回门**,默认 0.80。⚠️ 它只决定「哪些对值得看一眼」,
# **不是**合并判据 —— 能否合并由「事实集合包含关系(A ⊆ B)+ 规则否决」决定
# (实据:必合对 0.8330 与必不合对 0.7100 只差 0.12,单阈值双向失效)。
MEMORY_TIDY_RECALL_THRESHOLD=0.80
# 每分区每轮的扫描上限(默认 5000)与候选上限(默认 20)。
MEMORY_TIDY_SCAN_LIMIT=5000
MEMORY_TIDY_CANDIDATE_LIMIT=20
# ---- 唤醒式能力渐进(capacity 意图判定,design/14 §2.2) ----
# 总开关(默认 false):关闭时 loop 向模型暴露【全部】已注册工具,行为与接线前逐字节一致;
# 置 true 才按判定出的状态裁剪可用工具集(判定只在 loop 外围,不阻塞 Invoke)。
CAPACITY_ENABLED=false
# 唤醒阈值(默认 0.6):判定置信度低于它的状态强制回落到角色默认态(保守、不打扰);
# 角色 YAML 的 awakening.minConfidence 为正数时覆盖本全局值。
# 写 0 = 用代码内默认 0.6,不是「阈值归零」。
CAPACITY_MIN_CONFIDENCE=0.6
# 状态 → 允许工具名的 JSON 映射,例如
# {"quiet":["terminal","file"],"awake":["terminal","file","web-search"],"full":null}
# 缺失的状态用内置默认集;null(如 "full")= 不限制。解析失败记 WARN 并回落到内置默认。
# CAPACITY_TOOL_SETS=
# L3 = 模型辅助的意图分类:仅当 L1 启发式与 L2 上下文仍拿不准时介入一次轻量模型调用。
# 默认关闭(纯启发式够日常,且该层要多花一次模型调用);关闭时该层从不运行,
# L1/L2/L4 的行为与接线前逐字节一致。
CAPACITY_L3_ENABLED=false
# L3 判定结果的缓存 TTL(Go duration,如 5m):同一段回合文本在此窗口内复用判定,避免每轮都问模型;
# 留空 = 5m;0 或解析不出 = 不缓存。
CAPACITY_L3_CACHE_TTL=5m
# ---- 子代理(orchestration,design/05 §7) ----
# 单个子代理整次运行的时限(秒)。默认 600 = 10 分钟(裁决 02.4.4:agent_timeout
# 可配置);模型派发时可用 subagent_spawn 的 timeout_seconds 参数按调用覆盖。
SUBAGENT_TIMEOUT_SEC=600
# ---- 护栏与上下文预算(design/04 §1.3 / §5、附录 A.3) ----
# 上下文溢出压缩预算(token,离线估算值)。会话估算上下文超过它时,更早的轮次被摘要成
# 一条前导摘要消息,近期尾部(含尾部工具结果)原样保留。三种取值:
# · 未设置(默认,即把本行注释掉)= 按模型窗口推导:
# budget = min(窗口 × CONTEXT_BUDGET_RATIO, 窗口 − 输出保留)
# 输出保留 = max(32768, 本组 maxTokens);窗口未声明 ⇒ 回落 131072 并记一条 WARN。
# · > 0 = 显式覆盖(原样使用)
# · 0 / 负数 = 关闭压缩(行为与本条接线前逐字节一致)
# ⚠️ 旧模板里的 CONTEXT_MAX_TOKENS=32768 取自「gpt-4o 128K 窗口的四分之一」,那个前提已失效
# (默认模型窗口 1M)。若你的 .env 里还留着它,它就是【显式覆盖 32768】,会盖掉按窗口推导
# (1M 窗口 ⇒ 699050 token)——要吃到推导,把那一行注释掉。固定低阈值会既丢上下文、又因反复
# 压缩作废 KV 缓存前缀而反复烧钱(附录 A.2)。
# CONTEXT_MAX_TOKENS=
# 推导比例:预算占窗口的比例,主人 2026-10-04 裁决取 1/2–3/4 之间,默认 2/3。
# 写法:小数(0.5)或分数(1/2)都接受;非法值 = 记一条 WARN 并回落 2/3。
# CONTEXT_BUDGET_RATIO=2/3
# 单份附件注入模型可见上下文的字符上限(sidecar 仍存全文,只受 8 MiB 安全上限约束)。
# 留空 = 按推导出的预算算:推导预算的 1/8(按一个回合最多 5 个附件留余量),下限 64K 字符。
# 它决定「每回合往上下文里放多少」,也因此决定清单行给的续读游标从哪里开始。
# ⚠️ 它必须**不超过**下面 GUARDRAIL_MAX_INPUT_CHARS 生效的输入上限 —— 严格说是
# 「单份注入 × 单回合附件数(最多 5)」不超过上限,否则满载附件的回合会在装配完之后
# 被输入护栏整回合拦死(input_length_exceeded)。改了本键就顺手核对那一项。
# DOCUMENT_MAX_INJECT_CHARS=
# 请求侧字符上限(输入护栏跑在**组装完成的请求**上:系统提示词 + 记忆块 + 整段对话 + 工具结果)。
# 三种取值:
# · 未设置 / 0(默认)= 由推导出的上下文预算派生:
# 上限 = max(262144, 推导预算 token 数 × 4)
# 那个 4 与附件注入预算用的是**同一个换算常量**(loop.CharsPerToken = 4 字符 ≈ 1 token)。
# 上限量的是整段装配请求,而注入预算是单份的,两边必须是同一套换算,否则**多附件**回合
# 会被拦死(G2b 修的正是这个:旧口径按 1 字符 ≈ 1 token 兑,2 份满额注入就顶到上限):
# 1M 窗口 ⇒ 预算 699050 token ⇒ 上限 2796200 字符;
# 单份附件注入预算 349525 字符 ⇒ 5 份(单回合附件上限)满额注入合计 1747625 字符,
# 关系不变量「单回合全部附件的注入预算合计 ≤ 输入上限」成立,「分享大文件」才走得通。
# 预算关闭(CONTEXT_MAX_TOKENS=0)或预算很小时,上限落回 262144。
# · > 0 = 显式覆盖(原样使用;压到「单份注入 × 单回合附件数」之下会让多附件回合被拦,
# 届时启动日志里有 WARN)
# · < 0 = 非法:记一条 WARN 并回落派生值(绝不静默生效)
# 它只动**输入**侧;输出上限(65536 字符)防的是模型输出失控,与上下文预算无关,不开放配置。
# 敏感信息(API key / bearer token / 密码 / 私钥)为阻断档,同样是代码内定的安全默认值。
# (取值与关系不变量的落点在 cmd/cyrene/wire_guardrail.go)
# GUARDRAIL_MAX_INPUT_CHARS=
# 内容策略关键词表(逗号分隔,大小写不敏感的子串匹配)。留空 = 不挂关键词护栏
# (项目不内置任何词表,只有你显式声明过的词才会被拦)。
# GUARDRAIL_KEYWORDS=
# ---- 主人身份说明书注入范围(design/11 §0 红线) ----
# 主人身份说明书(owner.yaml identityFile)注入范围:owner_only(默认,仅主人会话) | always(所有会话,含群聊/陌生人,仅自测) | off。
# owner_only 按渠道层强类型 actor 元数据判定;非法值回退 owner_only 并记 WARN(绝不静默放行)。
# 它只决定那段知识进不进该会话的上下文,不参与任何身份/权限判定(身份始终看 actor 元数据 + 白名单)。
OWNER_IDENTITY_INJECTION=owner_only
# ---- 推送出口(D1,主动推送的两级投递)----
# 主动消息在 WS/IM 投递之外,异步镜像到「主人自建统一推送服务」(主通道)。
# 该服务不可达/未配时,日志注明「个推(Getui)回退待接入」——当前无 Getui SDK,不伪造投递。
# 填写自建服务的 webhook 地址并置 ENABLE=true 即启用;token 只走 Authorization 头,不落日志。
PUSH_EXPORTER_ENABLED=false
# PUSH_EXPORTER_URL=https://push.example.com/webhook
# PUSH_EXPORTER_TOKEN=change-me
# PUSH_EXPORTER_TIMEOUT_SEC=5
# 个推(Getui)回退字段:预留占位,SDK 未接入,当前不生效。
# GETUI_APP_ID=
# GETUI_APP_KEY=
# GETUI_MASTER_SECRET=
# GETUI_BASE_URL=
# ---- WebSocket ----
WS_MAX_CONNECTIONS=1000
SESSION_IDLE_TIMEOUT_MIN=30
# ---- MCP 服务器(可选,JSON 数组字符串;每台 server 含 name/command/args/url/enabled) ----
# stdio 子进程示例:
# MCP_SERVERS_JSON=[{"name":"calc","command":"mcp-calc","args":["--stdio"],"enabled":true}]
# SSE 端点示例:
# MCP_SERVERS_JSON=[{"name":"weather","url":"http://localhost:3001/sse","enabled":true}]
# ⚠️ 旧键(未接线,配了不生效,勿用):`MCP_NAME` / `MCP_COMMAND` / `MCP_ARGS` / `MCP_URL` /
# `MCP_TRANSPORT` / `MCP_ENABLED` 这类「单 server 扁平 env 模式」(含带编号的
# `MCP_0_*`、`MCP_1_*` …)只在 internal/capability/mcp 的单测里被解析;生产装配
# (cmd/cyrene/mcp.go)只读 `MCP_SERVERS_JSON`。`MCP_API_KEY` / `MCP_ENV` 更连那套
# 解析都不读(结构体上的残留 tag)。要接 MCP server 请只用上面的 MCP_SERVERS_JSON。
# ---- 本地执行沙箱(exec/terminal 工具;默认关闭,授权放权) ----
# 只有 Enabled=true 时 agent 才可执行命令;仍需命令白名单与根目录边界。
# EXEC_SANDBOX_ENABLED=false
# EXEC_SANDBOX_COMMANDS=git,ls,cat,pwd
# EXEC_SANDBOX_ROOTS=./data
# EXEC_SANDBOX_ENV=PATH
# ---- 跨域白名单 ----
ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000
# ---- KOOK(KaiHeiLa)渠道 ----
# 单账号走环境变量;多账号走下方 CHANNELS_JSON。
# .env 仅提供【必要连接兜底】;接收模式 / webhook 回调路由 / verify_token / encrypt_key 等
# 业务·高级项一律在管理面板「渠道管理」配置(写入 channels.json,热更新),不落 .env。
# CHANNEL_KOOK_TOKEN 为 KOOK 开发者中心机器人 token(敏感,勿写日志/提交)。
# CHANNEL_KOOK_BASE_URL 可选,默认 https://www.kookapp.cn/api/v3。
# CHANNEL_KOOK_TOKEN=
# CHANNEL_KOOK_BASE_URL=
# ---- Telegram 渠道 ----
# 同 KOOK 口径:单账号走环境变量、多账号走下方 CHANNELS_JSON,业务·高级项一律在面板配置。
# bot_token 为空时该账号【完全不存在】(空占位符绝不占用裸平台名),所以不用的平台留空即可。
# CHANNEL_TELEGRAM_BOT_TOKEN 为 BotFather 签发的 token(敏感,勿写日志/提交)。
# CHANNEL_TELEGRAM_WEBHOOK_URL 可选:填公网 HTTPS 回调地址走 webhook 投递;
# 留空则适配器改用 getUpdates 长轮询(本地开发无需公网地址)。
# CHANNEL_TELEGRAM_BOT_TOKEN=
# CHANNEL_TELEGRAM_WEBHOOK_URL=
# ---- OneBot v11(QQ)渠道 ----
# 四端由 transport(ws|http) × mode(client|server) 组合决定(裁决 1.4 / design/08 §4):
# ws+client Cyrene 拨号 OneBot,收发同一 WS
# ws+server OneBot 反连 Cyrene(需 LISTEN_ADDR),收发同一 WS
# http+client Cyrene → OneBot(纯出站,POST {API_BASE_URL}/{action})
# http+server OneBot → Cyrene(纯入站,POST 到 LISTEN_ADDR + REPORT_PATH)
# 该平台【无 token 门槛】:一个键都不配也会合成账号,是否真拨号由渠道桥的 configured 闸门
# 决定(server 模式要 LISTEN_ADDR 非空,client 模式要 REMOTE_URL 非空)。
# transport 留空或未知值一律回落 ws;mode 默认 server(即不给任何新配置时行为与改造前一致)。
# CHANNEL_QQ_OBV11_TRANSPORT=ws
# CHANNEL_QQ_OBV11_MODE=server
# ⚠️ LISTEN_ADDR 落在 AGENTS §9 登记的唯一「可开监听端口」豁免(OneBot 反连):
# 必须由配置显式给出,为空则 server 模式 Connect 直接失败、绝不开端口。
# CHANNEL_QQ_OBV11_LISTEN_ADDR=127.0.0.1:5700
# ws+client 的拨号端点(ws:// 或 wss://)。
# CHANNEL_QQ_OBV11_REMOTE_URL=
# OneBot HTTP API 基址(如 http://127.0.0.1:3000):http+client 必填;http+server 可选,
# 给了才有出站(回消息)能力。必须显式给出,不从 REMOTE_URL 推导。
# CHANNEL_QQ_OBV11_API_BASE_URL=
# http+server 接收上报的路径,默认 /(缺前导 / 会自动补齐)。
# CHANNEL_QQ_OBV11_REPORT_PATH=/
# 可选 OneBot access_token,四端通用:出站走 Authorization: Bearer;http+server 入站侧
# fail-closed——配了则 Bearer 或 ?access_token= 不匹配回 403,为空则放行(勿写日志/提交)。
# CHANNEL_QQ_OBV11_ACCESS_TOKEN=
# 机器人自己的 QQ 号(已知时填写,用于识别自消息)。
# CHANNEL_QQ_OBV11_SELF_ID=
# ---- 通用 Webhook 渠道(入站专用) ----
# 回调由网关挂在上面的唯一端口上,不新开监听。全部字段可选且都有可用默认值,
# 空配置也是一个能接请求的端点;该平台无 token 门槛,configured 闸门对 webhook 恒放行。
# ENDPOINT_PATH 是该适配器在网关上的路由,默认 /webhook。
# CHANNEL_WEBHOOK_ENDPOINT_PATH=/webhook
# CHANNEL_ID / CHANNEL_NAME 只用于向 ListChannels 报告该渠道(留空 = 不预注册渠道)。
# CHANNEL_WEBHOOK_CHANNEL_ID=
# CHANNEL_WEBHOOK_CHANNEL_NAME=
# ⚠️ SECRET 非空才对请求体做 HMAC-SHA256 验签;留空 = 【关闭验签】,任何能访问该路径的请求
# 都会被当消息收下。对公网暴露时务必填写(敏感,勿写日志/提交)。
# CHANNEL_WEBHOOK_SECRET=
# 承载签名的请求头名,默认 X-Webhook-Signature。
# CHANNEL_WEBHOOK_SIGNATURE_HEADER=X-Webhook-Signature
# ---- 微信 iLink 渠道 ----
# bot_token 为空时该账号【不存在】(同 Telegram),configured 闸门要求它非空才连接。
# CHANNEL_WEIXIN_ILINK_BOT_TOKEN 是扫码登录得到的长期 bearer token:非空即跳过二维码流程
# (敏感,勿写日志/提交)。
# CHANNEL_WEIXIN_ILINK_BOT_TOKEN=
# BOT_ID = 登录确认返回的 ilink_bot_id;OWNER_ID = 已绑定主人的 ilink_user_id
# (用于把该渠道的主人身份对上:身份判定仍只看渠道层 actor 元数据,不看提示词)。
# CHANNEL_WEIXIN_ILINK_BOT_ID=
# CHANNEL_WEIXIN_ILINK_OWNER_ID=
# API 根地址与媒体 CDN 根地址;留空用协议默认值:
# https://ilinkai.weixin.qq.com 与 https://novac2c.cdn.weixin.qq.com/c2c。
# CHANNEL_WEIXIN_ILINK_BASE_URL=
# CHANNEL_WEIXIN_ILINK_CDN_BASE_URL=
# ---- IM 多账号 / 黑白名单(可选,design/16 §4.6 §5-6) ----
# ⚠️ 本节是【兜底】:管理面板「渠道管理」页保存会写入本地 channels.json(默认 <DATA_DIR>/channels.json,已 gitignore),
# 只要 channels.json 存在且有效,运行中的渠道 adapter 就优先用它的多平台账号配置,并支持面板实时保存→热更新(无需重启)。
# 仅当 channels.json 不存在/无效时才回退到下面的 CHANNELS_JSON 与 CHANNEL_<NAME>_*。
# 业务·高级项(接收模式 receive_mode / webhook 回调路由与 verify_token、encrypt_key / 黑白名单 / 限流)
# 一律在管理面板「渠道管理」配置:写 channels.json、热更新;.env 仅作无文件时的必要连接回退(token/base_url 等)。
# 单账号仍走上面 CHANNEL_<NAME>_* 环境变量;这里提供独立的 JSON 注册表,
# 支持同一平台配多个接入配置(多账号),并配群聊/私聊黑白名单(代码层拦截,
# 被拉黑/不在白名单的群或用户消息不进 LLM,且有审计)。形如:
# CHANNELS_JSON={"telegram":[{"id":"conf0","config":{"bot_token":"..."},"denyUsers":["12345"],"denyGroups":["-100"]}]}
# 每个 account 字段:id(平台内唯一)、config(平台特有凭据 map)、
# allowGroups/denyGroups(群黑白名单,allowGroups 非空则成为白名单)、
# allowUsers/denyUsers(私聊用户黑白名单)。未配置时按单账号 env 路径处理。
# 群/总限流与总上限(可选,design/12 §6 策略三/四,公共平台防刷屏/最小化打扰):
# groupMsgPerMin 群聊每分钟上限(0=用默认6,-1=不限,正整数=该值)
# msgPerMin 非主人消息每分钟总上限(>0 生效,0/-1=不限)
# maxMessages 待处理上限,接近上限时由审查模型剔除低价值消息(0=用默认50,-1=不限,正整数=该值)
# 旧账号不配这些字段默认走「保护性默认/不限」,保持向后兼容。
# CHANNELS_JSON=