forked from ChaiHao/BPOS-MVP
调优:并发推进的真根因 + loop 的三处行为修正
E2E 跑 loop 时 A3 一口气发了八张一模一样的「请处理」卡。逐层查下去,四处: ① **并发推进的真根因**:`pg_try_advisory_lock` 是**会话级**的,而我们经连接池取连接 —— 加锁与解锁多半落在不同连接上,解锁解不掉自己那把;同一连接被复用时又因可重入而直接放行。 两头都不对,于是同一任务被并发推进(现象:同一步跑两遍、同一张卡发两张、 同一行草稿写出两条互相打架的版本)。前一轮「共用同一把锁」的修法方向对、器械错。 改用**进程内按目标互斥**(MVP 单进程,确定且够用),推进器与派发器共用一把; 多进程部署时再叠事务级 `pg_try_advisory_xact_lock`(随事务释放,不依赖连接归属)。 实测:同一单从 8 张卡降到 1 张,2 回合后落到等待。 ② **非法行动会把回合打死**:AI 提了不合法的生命周期迁移(Available → Disabled), `applyAction` 抛出、运行时没落笔、推进器一遍遍重来。引擎拒绝它是对的 ——「语义忠实:引擎不得私改迁移拓扑」——但不该因此崩掉。 改为:拒绝 + 留痕 + 把理由喂回下一回合的上下文。AI 提出非法行动是预期内的事。 ③ **副作用抢在校验之前**:卡在迁移合法性判定**之前**就发出去了,于是每个被拒的回合 都白发一张卡 —— 卡收不回,负责人工作台上堆起一摞没人知道怎么答的「请处理」。 human / tool 两个分支改为**先验迁移再动手**。 ④ **裸调用**:模型只回一个「请处理」。按「调用必附上下文,裸调用不得发出」(AM 06) 把卡面结构写进出参 schema(title / ask / basis / fields 必填),提示里讲明要求, 归一时用当下局面兜底;卡片装配也修了 —— 结构化 basis 优先,嵌套对象转 JSON, 不再出现一行 `[object Object]`。 实测卡面:「请确认 NE555P 历史报价并判定采用策略」+ 三个回填字段。 gates: tsc / eslint / depcruise(336 模块)/ api-report / 429 测试 全绿 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -22,7 +22,21 @@ const ACTION_SCHEMA = {
|
||||
tool: { type: 'string' },
|
||||
input: { type: 'object' },
|
||||
workOrder: { enum: ['execution', 'acceptance'] },
|
||||
card: { type: 'object' },
|
||||
/*
|
||||
* 卡面结构由 schema 框住 —— 「调用必附上下文,**裸调用不得发出**」(AM 06)。
|
||||
* 一张只写「请处理」的卡等于没说话:收卡的人不知道要答什么、凭什么答。
|
||||
*/
|
||||
card: {
|
||||
type: 'object',
|
||||
required: ['title', 'ask'],
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
ask: { type: 'string' },
|
||||
reason: { type: 'string' },
|
||||
basis: { type: 'array' },
|
||||
fields: { type: 'array' },
|
||||
},
|
||||
},
|
||||
eventType: { enum: ['data', 'time'] },
|
||||
keyFilter: { type: 'object' },
|
||||
deadline: { type: 'string' },
|
||||
@@ -51,6 +65,9 @@ export function createAgentRunner(deps: AgentRunnerDeps): {
|
||||
'你是这一单任务的案例工作者。下面是语义引擎发布的**当下可为**与**目标缺口**。',
|
||||
'你只能选一个行动,且必须落在三型行动之内:调工具 / 调人 / 立监听(外加 start、plan、disable、judge、submit)。',
|
||||
'规矩:①触达世界的动作只能 tool(写类会被登记为动作意向,放行时才落地)',
|
||||
'④调人(human)必须把卡填实:title 一句话说清要什么、ask 写明具体问题、',
|
||||
' basis 给出判断依据(键值对数组)、fields 列出要对方回填的字段名。',
|
||||
' 只写「请处理」的卡收卡人无从下手,等于白发一张。',
|
||||
'②缺口只有人知道 → human;缺口在世界侧 → listen(必须给 deadline,ISO-8601)',
|
||||
'③成果已齐才 submit。只输出 JSON。',
|
||||
'',
|
||||
@@ -109,6 +126,32 @@ export function createAgentRunner(deps: AgentRunnerDeps): {
|
||||
}
|
||||
|
||||
/** 模型出参归一为 AgentAction;不合规一律退化为「立监听等人」而不是乱动世界 */
|
||||
/**
|
||||
* 卡面兜底:模型没填全就用局面补。
|
||||
* 「调用必附上下文」是硬规矩 —— 与其发一张「请处理」,不如把当下可为与缺口摆上去,
|
||||
* 收卡的人至少知道自己站在哪儿。
|
||||
*/
|
||||
function fillCard(
|
||||
card: Record<string, unknown>,
|
||||
enabled: EnabledSet,
|
||||
itemId: string,
|
||||
): Record<string, unknown> {
|
||||
const item = enabled.entries.find((e) => e.id === itemId);
|
||||
const title = String(card['title'] ?? '').trim();
|
||||
const ask = String(card['ask'] ?? '').trim();
|
||||
return {
|
||||
...card,
|
||||
title: title === '' ? `请处理:${item?.name ?? itemId}` : title,
|
||||
ask: ask === '' ? '模型未给出具体问题,请按当下可为与缺口自行判断下一步。' : ask,
|
||||
basis: Array.isArray(card['basis'])
|
||||
? card['basis']
|
||||
: [
|
||||
{ k: '当下可为', v: enabled.entries.map((e) => e.name).join('、') || '(空)' },
|
||||
{ k: '目标缺口', v: enabled.gaps.join('、') || '(无)' },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function normalize(object: unknown, enabled: EnabledSet): AgentAction {
|
||||
const raw = (object ?? {}) as Record<string, unknown>;
|
||||
const kind = String(raw['kind'] ?? '');
|
||||
@@ -138,7 +181,8 @@ function normalize(object: unknown, enabled: EnabledSet): AgentAction {
|
||||
return {
|
||||
kind: 'human',
|
||||
itemId,
|
||||
card: (raw['card'] ?? {}) as Record<string, unknown>,
|
||||
// 卡面缺项用**当下局面**补全 —— 宁可信息略多,不发一张空卡
|
||||
card: fillCard((raw['card'] ?? {}) as Record<string, unknown>, enabled, itemId),
|
||||
workOrder: raw['workOrder'] === 'acceptance' ? 'acceptance' : 'execution',
|
||||
};
|
||||
case 'listen':
|
||||
|
||||
Reference in New Issue
Block a user