情感态energy昼夜节律nudge #124

Closed
opened 2026-08-13 01:34:36 +00:00 by KumaAgent · 1 comment
Member

Parent

#103 第四批清单:31 条实现类落差 + 9 条待拍板(条目 9、19)

What to build

ADR-0005/0017 拍板:快情感态 energy 方程要新增一项"昼夜节律 nudge",深夜时段给该项额外向下的值——"深夜"必须经 nudge 写进 energy 本身,已读延迟窗这个新 gate 绝不自己读墙钟,保"情感态是所有拟人参数唯一中央调节器"的边界不被绕过。这是本仓罕见的"裁决理由被原文逐字完整支撑"的一条,但产出方在 origin/main 上完全不存在(只有两处 docstring 说"这不是我")。

energy 今天已经有三个活着的消费方(门控阈值、打字节奏、mood_congruence),昼夜节律 nudge 落地当天就有可观测行为,不必等已读延迟窗片先上线

Acceptance criteria

  • 新增纯函数 circadian_energy_nudge(now)(时钟经参数注入,可性质测试),形状仿 temporal_awareness.py 的时段分桶函数
  • 归属边界(否定式声明,必须原样带上):新 gate(已读延迟窗)不单独读墙钟,只读 energy;"深夜"只能经 energy 本身接入

原文(docs/adr/0017-reactive-timing-and-coverage.md「决策 → 已读延迟窗」节):

"深夜"通过昼夜节律 nudge energy 本身接入(凌晨时段给 energy 一个额外向下 nudge),gate 不单独读墙钟。保 ADR-0005"情感态是所有拟人参数唯一中央调节器"不开口子。

原文(同 ADR「拒绝了」节——点名否决的替代方案):

深夜作为独立墙钟输入(新 gate 与 energy 并列各自读时钟):更直白,但给情感态之外开了第二个调节输入源,为以后"绕过情感态直接读环境信号"开了先例。

原文(docs/adr/0005-affective-state.md「更新(2026-07-06 已读延迟窗,见 ADR-0017)」节,方程原文):

快情感态 energy 方程新增一项昼夜节律 nudgeenergy += f(互动极性) + 资源态推送 + ε·群传染速率 + 昼夜节律nudge(time) − k_fast·(energy − slow_energy)。深夜时段给该项一个额外向下的值。……昼夜节律 nudge 的具体幅度/时段窗口留实现期按 ADR-0011 阈值三层分类标定。

  • 实现约束(issue #94 立下的硬约束,必须遵守,否则会重犯它刚防住的错)update_fast_state 的三轴衰减是调用驱动的(不看经过时间)。新建一个"每 N 分钟施加昼夜节律 nudge"的周期驱动器直接调 update_fast_state,会给 valence 和 dominance 各加一个白挨的衰减驱动源——正是 #94 明文防的事。片内二选一并写进 AC:
    • (a) 仿 delta._apply_interaction_polarity 的既有手法:算完只取 energy(replace(fast_record.state, energy=updated.energy)),另外两轴原样保留;或
    • (b) 改成读时施加(不落库,读快情感态时按当前墙钟叠加时段项),彻底不新增写入驱动源——但这会让"energy 是存储值"变成"存储值 + 读时修正",/why 的快照语义要跟着定,须在 ADR-0005 追加更新节
  • 驱动器落点须避开"搭 _drive_tick/run_delta_compression_cycle 的车"这个陷阱——两者都有各自的触发条件过滤,搭车会让 nudge 在无 delta / 无到期 chat 时不发生,达不到"按时间自然演进"的效果
  • 幅度/时段窗口按 ADR-0011 阈值三层分类归"情感态调制"层,具体数字留实现期标定
  • temporal_awareness.py 的时段分桶(深夜/清晨/白天/晚上)可复用,但不能让它变成"驱动数值方程"——它的模块契约明写"不驱动任何数值方程",与本条是两件不同的事,互不冲突,不要合并实现
  • 补一条守卫测试钉住 read_delay 内不出现任何 datetime.now()/墙钟读取——归属边界不能只靠代码审查

Blocked by

None — can start immediately

## Parent #103 第四批清单:31 条实现类落差 + 9 条待拍板(条目 9、19) ## What to build ADR-0005/0017 拍板:快情感态 energy 方程要新增一项"昼夜节律 nudge",深夜时段给该项额外向下的值——"深夜"必须**经 nudge 写进 energy 本身**,已读延迟窗这个新 gate **绝不自己读墙钟**,保"情感态是所有拟人参数唯一中央调节器"的边界不被绕过。这是本仓罕见的"裁决理由被原文逐字完整支撑"的一条,但产出方在 origin/main 上完全不存在(只有两处 docstring 说"这不是我")。 energy 今天已经有三个活着的消费方(门控阈值、打字节奏、mood_congruence),昼夜节律 nudge 落地当天就有可观测行为,**不必等已读延迟窗片先上线**。 ## Acceptance criteria - [ ] 新增纯函数 `circadian_energy_nudge(now)`(时钟经参数注入,可性质测试),形状仿 `temporal_awareness.py` 的时段分桶函数 - [ ] **归属边界(否定式声明,必须原样带上)**:新 gate(已读延迟窗)不单独读墙钟,只读 energy;"深夜"只能经 energy 本身接入 原文(docs/adr/0017-reactive-timing-and-coverage.md「决策 → 已读延迟窗」节): > "深夜"通过**昼夜节律 nudge energy 本身**接入(凌晨时段给 energy 一个额外向下 nudge),gate 不单独读墙钟。保 ADR-0005"情感态是所有拟人参数唯一中央调节器"不开口子。 原文(同 ADR「拒绝了」节——点名否决的替代方案): > **深夜作为独立墙钟输入**(新 gate 与 energy 并列各自读时钟):更直白,但给情感态之外开了第二个调节输入源,为以后"绕过情感态直接读环境信号"开了先例。 原文(docs/adr/0005-affective-state.md「更新(2026-07-06 已读延迟窗,见 ADR-0017)」节,方程原文): > 快情感态 energy 方程新增一项**昼夜节律 nudge**:`energy += f(互动极性) + 资源态推送 + ε·群传染速率 + 昼夜节律nudge(time) − k_fast·(energy − slow_energy)`。深夜时段给该项一个额外向下的值。……昼夜节律 nudge 的具体幅度/时段窗口留实现期按 ADR-0011 阈值三层分类标定。 - [ ] **实现约束(issue #94 立下的硬约束,必须遵守,否则会重犯它刚防住的错)**:`update_fast_state` 的三轴衰减是**调用驱动**的(不看经过时间)。新建一个"每 N 分钟施加昼夜节律 nudge"的周期驱动器直接调 `update_fast_state`,会给 valence 和 dominance 各加一个白挨的衰减驱动源——正是 #94 明文防的事。片内二选一并写进 AC: - (a) 仿 `delta._apply_interaction_polarity` 的既有手法:算完只取 energy(`replace(fast_record.state, energy=updated.energy)`),另外两轴原样保留;或 - (b) 改成**读时施加**(不落库,读快情感态时按当前墙钟叠加时段项),彻底不新增写入驱动源——但这会让"energy 是存储值"变成"存储值 + 读时修正",`/why` 的快照语义要跟着定,须在 ADR-0005 追加更新节 - [ ] 驱动器落点须避开"搭 `_drive_tick`/`run_delta_compression_cycle` 的车"这个陷阱——两者都有各自的触发条件过滤,搭车会让 nudge 在无 delta / 无到期 chat 时不发生,达不到"按时间自然演进"的效果 - [ ] 幅度/时段窗口按 ADR-0011 阈值三层分类归"情感态调制"层,具体数字留实现期标定 - [ ] `temporal_awareness.py` 的时段分桶(深夜/清晨/白天/晚上)可复用,但**不能**让它变成"驱动数值方程"——它的模块契约明写"不驱动任何数值方程",与本条是两件不同的事,互不冲突,不要合并实现 - [ ] 补一条守卫测试钉住 read_delay 内不出现任何 `datetime.now()`/墙钟读取——归属边界不能只靠代码审查 ## Blocked by None — can start immediately
Author
Member

2026-08-14 裁决(评估侧):方案 (b) 读时施加,撤掉 #123 的局部机制

诊断read_delay.py::circadian_energy_adjustment 不是"另一种设计",是 ADR-0017「拒绝了」节点名否决的那个形状:

深夜作为独立墙钟输入(新 gate 与 energy 并列各自读时钟):更直白,但给情感态之外开了第二个调节输入源,为以后"绕过情感态直接读环境信号"开了先例。

该函数自己的 docstring 论证"gate 不单独读墙钟"落在调用方——把"gate"缩小到只剩 read_delay_window 这个纯 sleep 协程,从而字面不违反;但它读 datetime.now()、算出的调整量只喂给 entry_reactive._debounced_flush 自己用,gate_pipeline/runtime_loop.py 两处(打字节奏、mood_congruence)完全看不到——这正是被否决方案的实质:"新 gate 与 energy 并列各自读时钟",只是技术上绕开了字面否决。且它长在 read_delay.py(消费方),不在 affective_state.py(energy 真正的归属地)——机制归属从一开始就点错了模块。

裁决:撤掉 circadian_energy_adjustmentread_delay.py 里的本体和调用,改成方案 (b)——读时施加,四个消费点共用同一个 helper。

具体形状

两者都搬进 affective_state.py(该文件已有 AffectiveStateStoragePort,是这个决定的天然归属地):

def circadian_energy_nudge(
    now: datetime, *, start_hour: int, end_hour: int, amount: float
) -> float:
    ...  # 原 circadian_energy_adjustment 逻辑原样搬家,纯函数不变

async def get_effective_fast_state(
    storage: AffectiveStateStoragePort, chat_id: str, *, now: datetime | None = None
) -> AffectiveState:
    fast_record = await storage.get_fast_affective_state(chat_id)
    nudge = circadian_energy_nudge(
        now or datetime.now(), start_hour=..., end_hour=..., amount=...
    )
    return replace(fast_record.state, energy=fast_record.state.energy + nudge)

replace(state, energy=...) 不是新手法——是 _apply_interaction_polarity 那条"快层只写 energy/valence 轴,其它轴原样保留"先例(issue #94,ADR-0005"更新 2026-07-08"节),只是从写时挪到读时。

四个消费点全部改走这个 helper,不再各自 get_fast_affective_state(...).state/.state.energy

位置 现状 改法
gate_pipeline.py:192 resolved_affective_state = fast_record.state(要整个 state) resolved_affective_state = await get_effective_fast_state(ports.storage, chat_id)
runtime_loop.py:568_scored_events,mood_congruence) current_mood = fast_record.state(要整个 state) current_mood = await get_effective_fast_state(self._storage, chat_id)
runtime_loop.py:1971(打字节奏) fast_energy = fast_record.state.energy(只要 float) fast_energy = (await get_effective_fast_state(self._storage, chat_id)).energy
entry_reactive.py:429(已读延迟) energy = fast_record.state.energy + circadian_energy_adjustment(...) energy = (await get_effective_fast_state(ports.storage, chat_id)).energy

#123 留下的三个 config 项(arise_circadian_energy_nudge_start_hour/end_hour/amount)原样复用,不用碰 config.py

选 (b) 不选 (a) 的理由(不只是"改动面小")

当前时段是全局时钟事实,不是逐 chat 状态。(a) 必须新建周期任务扫全部已知 chat(storage 现在没有这个枚举能力)才能保证"随便哪个 chat 被读到时节律值都是新的",本质是用持久化去伪造一个本来免费的计算,且要小心不能搭 _drive_tick/delta 压缩周期的车(否则闲置 chat 永远不生效,#94 已经因为同一类"意外多驱动一次衰减"的陷阱吃过亏)。(b) 只在真正被读的那一刻算,没有"还没扫到这个 chat"的陈旧窗口,且是"端口/客户端/回调/registry 不进 config,标量计算天然是读时"这条更简单的默认形态。

顺手钉住两处(AC 原文没展开,实现期请一并处理)

  • /why 决策快照要记的是 get_effective_fast_state 算出来的修正值,不是原始存储值,否则解释和实际门控用的值对不上(ADR-0010 可解释性)。
  • 守卫测试除了断言 read_delay.py 路径内不出现 datetime.now(),顺手断言该文件不再 import datetime——归属边界更彻底可查。

ADR-0005 更新节要写什么

  • 语义变化:energy 从"纯存储值"变成"存储值 + 读时修正(get_effective_fast_state)"。
  • 点名新 helper 及其位置(affective_state.py)。
  • 声明 circadian_energy_adjustment/#123 的局部版本已废弃、被本次取代。
  • 重申"唯一读墙钟做 energy 相关计算的地方就是这一个 helper"——把"情感态是唯一中央调节器"这条边界的可审计落点收敛到一处。
## 2026-08-14 裁决(评估侧):方案 (b) 读时施加,撤掉 #123 的局部机制 **诊断**:`read_delay.py::circadian_energy_adjustment` 不是"另一种设计",是 ADR-0017「拒绝了」节点名否决的那个形状: > **深夜作为独立墙钟输入**(新 gate 与 energy 并列各自读时钟):更直白,但给情感态之外开了第二个调节输入源,为以后"绕过情感态直接读环境信号"开了先例。 该函数自己的 docstring 论证"gate 不单独读墙钟"落在调用方——把"gate"缩小到只剩 `read_delay_window` 这个纯 sleep 协程,从而字面不违反;但它读 `datetime.now()`、算出的调整量只喂给 `entry_reactive._debounced_flush` 自己用,`gate_pipeline`/`runtime_loop.py` 两处(打字节奏、mood_congruence)完全看不到——这正是被否决方案的实质:"新 gate 与 energy 并列各自读时钟",只是技术上绕开了字面否决。且它长在 `read_delay.py`(消费方),不在 `affective_state.py`(energy 真正的归属地)——机制归属从一开始就点错了模块。 **裁决:撤掉 `circadian_energy_adjustment` 在 `read_delay.py` 里的本体和调用,改成方案 (b)——读时施加,四个消费点共用同一个 helper。** ### 具体形状 两者都搬进 `affective_state.py`(该文件已有 `AffectiveStateStoragePort`,是这个决定的天然归属地): ```python def circadian_energy_nudge( now: datetime, *, start_hour: int, end_hour: int, amount: float ) -> float: ... # 原 circadian_energy_adjustment 逻辑原样搬家,纯函数不变 async def get_effective_fast_state( storage: AffectiveStateStoragePort, chat_id: str, *, now: datetime | None = None ) -> AffectiveState: fast_record = await storage.get_fast_affective_state(chat_id) nudge = circadian_energy_nudge( now or datetime.now(), start_hour=..., end_hour=..., amount=... ) return replace(fast_record.state, energy=fast_record.state.energy + nudge) ``` `replace(state, energy=...)` 不是新手法——是 `_apply_interaction_polarity` 那条"快层只写 energy/valence 轴,其它轴原样保留"先例(issue #94,ADR-0005"更新 2026-07-08"节),只是从写时挪到读时。 **四个消费点全部改走这个 helper**,不再各自 `get_fast_affective_state(...).state`/`.state.energy`: | 位置 | 现状 | 改法 | |---|---|---| | `gate_pipeline.py:192` | `resolved_affective_state = fast_record.state`(要整个 state) | `resolved_affective_state = await get_effective_fast_state(ports.storage, chat_id)` | | `runtime_loop.py:568`(`_scored_events`,mood_congruence) | `current_mood = fast_record.state`(要整个 state) | `current_mood = await get_effective_fast_state(self._storage, chat_id)` | | `runtime_loop.py:1971`(打字节奏) | `fast_energy = fast_record.state.energy`(只要 float) | `fast_energy = (await get_effective_fast_state(self._storage, chat_id)).energy` | | `entry_reactive.py:429`(已读延迟) | `energy = fast_record.state.energy + circadian_energy_adjustment(...)` | `energy = (await get_effective_fast_state(ports.storage, chat_id)).energy` | #123 留下的三个 config 项(`arise_circadian_energy_nudge_start_hour`/`end_hour`/`amount`)原样复用,不用碰 `config.py`。 ### 选 (b) 不选 (a) 的理由(不只是"改动面小") 当前时段是**全局时钟事实,不是逐 chat 状态**。(a) 必须新建周期任务扫**全部已知 chat**(storage 现在没有这个枚举能力)才能保证"随便哪个 chat 被读到时节律值都是新的",本质是用持久化去伪造一个本来免费的计算,且要小心不能搭 `_drive_tick`/delta 压缩周期的车(否则闲置 chat 永远不生效,#94 已经因为同一类"意外多驱动一次衰减"的陷阱吃过亏)。(b) 只在真正被读的那一刻算,没有"还没扫到这个 chat"的陈旧窗口,且是"端口/客户端/回调/registry 不进 config,标量计算天然是读时"这条更简单的默认形态。 ### 顺手钉住两处(AC 原文没展开,实现期请一并处理) - **`/why` 决策快照**要记的是 `get_effective_fast_state` 算出来的修正值,不是原始存储值,否则解释和实际门控用的值对不上(ADR-0010 可解释性)。 - 守卫测试除了断言 `read_delay.py` 路径内不出现 `datetime.now()`,顺手断言该文件不再 `import datetime`——归属边界更彻底可查。 ### ADR-0005 更新节要写什么 - 语义变化:`energy` 从"纯存储值"变成"存储值 + 读时修正(`get_effective_fast_state`)"。 - 点名新 helper 及其位置(`affective_state.py`)。 - 声明 `circadian_energy_adjustment`/#123 的局部版本已废弃、被本次取代。 - 重申"唯一读墙钟做 energy 相关计算的地方就是这一个 helper"——把"情感态是唯一中央调节器"这条边界的可审计落点收敛到一处。
Yushu closed this issue 2026-08-13 08:27:38 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ProjectKuma/arise#124
No description provided.