跨平台资料拉取:自主触发候选 + 复用深挖任务委托执行核(可抢占) #76
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/71-cross-platform-pull"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #71
What
用户明确同意后,机器人自主、周期性地了解对方在其它平台上的动态记入画像,不必然触发一条打扰用户的消息。ADR-0027 落地,长尾记忆/感知增强包(issue #66)最后一片。
实现要点
spontaneous_goal.familiar_reconnect_hint精确形状:纯函数cross_platform_pull_hint达标才产出提示,注入 Drive Tick 既有 light context 同一挂载位置,不新增独立评估通道。产出的只是「多一层理由」,主模型自己决定要不要真去查。run_delegated_task本体、不加来源标记:它是纯函数式执行核(只吃 description/llm_client/get_tools/max_rounds、零状态、不认识调用方),治理差异全在调用方。subagent.py一行未改。delegate_task(AC 把这个选择留实现期按成本定):那条路径连着登记表 + Callback 送回 + 履约不可抢占治理,而本机制要的恰恰是不同的治理。走同一工具就得在_run_delegated_task里按来源分叉、改既有 schema/登记表,既污染既有契约又更贵。工具无参 —— 查谁由 core 按当前 Drive Tick 对象定,模型无法指定别人。arise_cross_platform_pull_preemptible(默认真)控制。竞速用asyncio.wait而非在拉取内部埋检查点,就是为了不给被复用的既有执行核插桩。last_pulled_at,免得「撤销再同意」变成绕过间隔节流的手段。sensitive硬编码为真 + 落库前过人设漂移守护否决闸)。Review
两轴都抓到实质缺陷,四条必修:
def,handler 里却await它的返回值 →TypeError,模型一调这个工具整轮就炸、拉取永不发生。根因是这条路零测试覆盖(我测了候选/治理/命令,唯独没测「模型真的调这个工具」)。已改 async 并补三条覆盖该路径的测试。event.get_user_id()(原始适配器 id),读取用chat_id.removesuffix(".p")(host 经 ingress port 映射后)—— 在做 id 归一化的 host 上会写进一个永远查不到的键,或命中另一个人的 id。已收敛到纯函数consent_subject_of,两侧同源。_known_platform_facts把该用户全部画像事实(含住址/健康/财务)拼进任务描述交给 host 抓取工具 —— 与本机制立意直接相反。已改为只带非敏感事实。另外修了:竞速的
try/finally收口(原先「外层任务被 cancel」时两个子任务都悬空);同意命令加to_me()(COMMAND_START含空串时群里有人引用那句话就会被当成授权);删死代码;两个阈值进 config。关于可抢占开关(已定论)
Spec 轴认为
arise_cross_platform_pull_preemptible属 scope creep:AC4「不复用履约不可抢占治理」是无条件的,而这个开关设False时执行中确实变成不可抢占。已与维护者确认:保留这个开关,并已在 ADR-0027 里注明它是「部署侧成本取舍」而非「允许退回 ADR-0020 的治理复用」。 区分两者的判据是起点那道闸有没有恒生效——「真人正活跃就不起这个任务」不可配、恒生效,而
delegate_task连起点都不看活跃度(用户已经在等,本来就该立刻开始)。所以关掉开关只意味着「已经开始的这一次不半途丢弃已付的 LLM 成本」,不意味着本机制获得了「履约」地位。ADR-0027 三处已更新(决策节新增该条、「拒绝了」节点明拒的是治理复用而非这个开关、「取代/关系」节加限定)。文档改动在
docs分支本地 commitd76e8c4,按既定规则不推送。已知范围边界
sensitive过滤是粗筛:可能漏掉一个被delta判成敏感的小众平台账号,那条就查不了。刻意选的方向 —— 漏查一次的成本远低于把一个人的住址送给外部抓取工具。Tests
全仓库 1071 个测试通过(新增 44)。变异复核五处守卫:同意闸挂 5 条、起点闸挂 2 条、in-flight 闸挂 1 条、敏感过滤挂 3 条、可抢占竞速关掉后测试挂死(据此给那条测试加了
wait_for超时)。两轴各抓到实质缺陷,四条必修: **1. 真 bug:工具一被调用整轮就炸(Standards 轴)**。注入的回调是同步 `def`, handler 里却 `await` 它的返回值(`None`)→ TypeError,拉取永远不会发生。根因是 `_handle_pull_cross_platform` 这条路**零测试覆盖**——我测了候选/治理/命令,唯独 没测"模型真的调这个工具"。改回调为 async,并补三条覆盖该路径的测试(含工具可 见/不可见两向)。 **2. 同意键错配(Spec 轴)**。写入用 `event.get_user_id()`(原始适配器 id), 读取用 `chat_id.removesuffix(".p")`(host 经 ingress port 映射后)。两者不保证 相同——在做 id 归一化的 host 上,同意会写进一个永远查不到的键(静默失效), 或更糟,命中另一个人的 id。这是隐私机制,键错配后果比"功能不生效"严重。收敛到 纯函数 `consent_subject_of`,两侧都走 ingress port + 它。群聊无唯一主体,返回 `None` 并提示私聊说。 **3. 隐私外送(Standards 轴)**。`_known_platform_facts` 把该用户**全部**画像 事实(含住址/健康/财务这类 `sensitive=True`)拼进任务描述,交给 host 注册的抓取 工具(可能对外发请求)——与本机制"隐私分量最重、边界最紧"的立意直接相反,也与 `pull_task_description` 自己的文档不符。改为 `render_platform_facts` 只带非敏感 事实,并补测试。代价是可能漏查一个被判敏感的平台账号,这个方向是刻意选的。 **4. 同一轮多次调用起并发子智能体(Spec 轴)**。候选每轮只算一次,但模型在同一 轮工具循环里可以调多次,此前每次都起一个子智能体各付一份成本。加 per-user in-flight 闸(`mark_pull_attempted` 在任务内部,拦不住同胞任务,所以闸必须在派发 这层)。 另外:竞速改 try/finally 收口 —— 原先只在抢占分支 cancel,漏了"外层任务被 cancel"这条路(`asyncio.wait` 被取消时不会连带取消它等的子任务,两个都悬空); 同意命令加 `to_me()` —— `COMMAND_START` 含空串时(本仓 .env.test 就是)群里有人 引用那句话就会被当成授权;删死代码 `PULL_INTERVAL_SECONDS`;活跃度窗口与轮询 间隔进 config(原先硬编码,测试只能按字面量 monkeypatch,改常量就静默退化成真 睡);`human_is_active` 提成纯函数移出 wiring 层。 新增守卫均已变异复核:in-flight 闸挂 1 条、敏感过滤挂 3 条。