From cd88b2dfe5a8b3b47cc1f366f5bb41f34c28d159 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 13 Sep 2026 15:41:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(resident):=20=E5=AD=90=E7=9A=84=E3=80=8C?= =?UTF-8?q?=E8=BD=AE=E6=AC=A1=E3=80=8D=E4=B8=80=E7=9B=B4=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=200=20=E2=80=94=E2=80=94=20info()=20=E6=A0=B9=E6=9C=AC?= =?UTF-8?q?=E6=B2=A1=E5=A1=AB=20Rounds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象(用户线上联调实录 + 我复验):父侧 `resident_agents` 列出 `输入ch=[timer] 轮次=0 处理表=2` —— **处理表已有两条记录,轮次却是 0**,自相矛盾,容易被读成"子没干活"。 根因:`residentChild.info()` 构造 `ResidentInfo` 时**从来没有填过 Rounds 字段** (结构体里有这个字段,于是永远输出零值),不是计数漏加。 改法:`Rounds = 已执行轮次数`(调度器执行计数,单调不减)。新增 `Agent.roundsExecuted()` 并写明为什么**不能**用 inputch 处理表条数当轮次:那张表记的是"当前上下文窗口内"的轮次, 压缩会清空(设计 §8.3)—— 用它会让父看到轮次倒退。 判据:inputch 路由测试里补一条断言 —— 子处理完输入后 `info().Rounds > 0`。 --- internal/agent/core/inputroute_test.go | 5 +++++ internal/agent/core/resident.go | 7 +++++++ internal/agent/core/scheduler.go | 12 ++++++++++++ 3 files changed, 24 insertions(+) diff --git a/internal/agent/core/inputroute_test.go b/internal/agent/core/inputroute_test.go index a2c378b..e79d5cc 100644 --- a/internal/agent/core/inputroute_test.go +++ b/internal/agent/core/inputroute_test.go @@ -34,6 +34,11 @@ func TestResident_InputchRoutingIsExclusive(t *testing.T) { if got := parent.DumpScheduler().Stats.Enqueued; got != before { t.Fatalf("划给子的 inputch,父不应再入队(before=%d after=%d)", before, got) } + // 轮次必须真的涨:此前 info() 根本没填 Rounds ⇒ 父永远读到 0 + // (现场:子处理表已有 2 条,轮次却显示 0,被误判成"子没干活")。 + if got := parent.residents["r-route"].info().Rounds; got <= 0 { + t.Fatalf("子处理的轮次应 > 0,实际 %d", got) + } } // 归属到一个不存在(或已销毁)的 agent 时**不吞输入**:父兜底处理。 diff --git a/internal/agent/core/resident.go b/internal/agent/core/resident.go index caaabf0..dcb0a55 100644 --- a/internal/agent/core/resident.go +++ b/internal/agent/core/resident.go @@ -504,6 +504,13 @@ func (rc *residentChild) info() ResidentInfo { ID: rc.id, State: state, InputChs: append([]string(nil), rc.inputChs...), AllowedOutputs: append([]string(nil), rc.allowed...), ContextFull: full, CreatedAt: rc.createdAt, TableSize: len(table), + // Rounds = 子**已执行的轮次数**(调度器的执行计数,单调不减)。 + // + // 此前这里根本没填这个字段 ⇒ 父看到的永远是 `轮次=0`,与"处理表已有 N 条" + // 自相矛盾(现场:子明明处理了两轮,父读到 rounds=0,误判成"子没干活")。 + // 注意它**不等于** len(table):处理表记的是"当前上下文窗口内"的轮次, + // 压缩会清空(§8.3),所以窗口内的条数会被重置,而轮次总数不会。 + Rounds: rc.agent.roundsExecuted(), } if len(table) > 0 { info.Table = table diff --git a/internal/agent/core/scheduler.go b/internal/agent/core/scheduler.go index 97dc2b1..e899636 100644 --- a/internal/agent/core/scheduler.go +++ b/internal/agent/core/scheduler.go @@ -745,6 +745,18 @@ func newKernelInterruptTask(evt *agentIO.InputEvent) *Task { } // DumpScheduler 返回调度器的原子快照(供状态页/测试断言)。 +// roundsExecuted 返回本 agent 已执行的轮次数(供驻留子状态面展示)。 +// +// 一轮 = 一次被执行的输入(排队与中断都算)。为什么不用 inputch 处理表的条数: +// 那张表记的是"当前上下文窗口内"的轮次,压缩会清空(设计 §8.3)—— +// 拿它当轮次会让父看到轮次倒退。 +func (a *Agent) roundsExecuted() int { + if a.sched == nil { + return 0 + } + return int(a.DumpScheduler().Stats.Executed) +} + func (a *Agent) DumpScheduler() SchedulerSnapshot { if a.sched == nil { return SchedulerSnapshot{}