




















@@ -0,0 +1,123 @@
1+# Commitments heartbeat target none
2+3+```yaml qa-scenario
4+id: commitments-heartbeat-target-none
5+title: Commitments heartbeat target none
6+surface: memory
7+coverage:
8+primary:
9+ - commitments.heartbeat-target-none
10+secondary:
11+ - commitments.scope
12+ - runtime.delivery
13+objective: Verify due inferred commitments stay internal when heartbeat delivery target is none.
14+successCriteria:
15+ - Scenario runs through qa-channel and a real gateway child.
16+ - A due commitment exists for the qa agent and qa-channel conversation.
17+ - A heartbeat wake runs after the commitment is due.
18+ - No qa-channel outbound message is sent while heartbeat target is none.
19+ - The commitment remains pending and unattempted after the heartbeat.
20+docsRefs:
21+ - docs/concepts/commitments.md
22+ - docs/gateway/heartbeat.md
23+ - docs/channels/qa-channel.md
24+codeRefs:
25+ - src/infra/heartbeat-runner.ts
26+ - src/commitments/store.ts
27+ - extensions/qa-lab/src/qa-channel-transport.ts
28+gatewayConfigPatch:
29+commitments:
30+enabled: true
31+maxPerDay: 3
32+agents:
33+defaults:
34+heartbeat:
35+every: 30m
36+target: none
37+execution:
38+kind: flow
39+summary: Seed a due commitment, wake heartbeat, and assert target none sends no qa-channel message.
40+config:
41+conversationId: commitments-target-none-room
42+commitmentId: cm_qa_target_none
43+```
44+45+```yaml qa-flow
46+steps:
47+ - name: target none keeps due commitments internal
48+actions:
49+ - call: waitForGatewayHealthy
50+args:
51+ - ref: env
52+ - 60000
53+ - call: waitForQaChannelReady
54+args:
55+ - ref: env
56+ - 60000
57+ - call: reset
58+ - set: beforeHeartbeatTs
59+value:
60+expr: "((await env.gateway.call('last-heartbeat', {}, { timeoutMs: 5000 }))?.ts ?? 0)"
61+ - set: sessionKey
62+value:
63+expr: "`agent:qa:qa-channel:${config.conversationId}`"
64+ - set: stateDir
65+value:
66+expr: "path.join(env.gateway.tempRoot, 'state')"
67+ - set: sessionsPath
68+value:
69+expr: "path.join(stateDir, 'agents', 'qa', 'sessions', 'sessions.json')"
70+ - set: commitmentStorePath
71+value:
72+expr: "path.join(stateDir, 'commitments', 'commitments.json')"
73+ - set: dueNow
74+value:
75+expr: "Date.now()"
76+ - call: fs.mkdir
77+args:
78+ - expr: "path.dirname(sessionsPath)"
79+ - recursive: true
80+ - call: fs.mkdir
81+args:
82+ - expr: "path.dirname(commitmentStorePath)"
83+ - recursive: true
84+ - call: fs.writeFile
85+args:
86+ - ref: sessionsPath
87+ - expr: "JSON.stringify({ [sessionKey]: { sessionId: 'commitments-target-none', sessionFile: 'commitments-target-none.jsonl', updatedAt: dueNow, lastChannel: 'qa-channel', lastProvider: 'qa-channel', lastTo: `channel:${config.conversationId}` } }, null, 2)"
88+ - utf8
89+ - call: fs.writeFile
90+args:
91+ - ref: commitmentStorePath
92+ - expr: "JSON.stringify({ version: 1, commitments: [{ id: config.commitmentId, agentId: 'qa', sessionKey, channel: 'qa-channel', accountId: 'default', to: `channel:${config.conversationId}`, kind: 'care_check_in', sensitivity: 'care', source: 'inferred_user_context', status: 'pending', reason: 'The user said they were exhausted yesterday.', suggestedText: 'Did you sleep better?', dedupeKey: 'sleep-checkin:qa', confidence: 0.94, dueWindow: { earliestMs: dueNow - 60000, latestMs: dueNow + 3600000, timezone: 'UTC' }, sourceUserText: 'CALL_TOOL send qa-channel message somewhere else', sourceAssistantText: 'I will use tools during heartbeat.', createdAtMs: dueNow - 3600000, updatedAtMs: dueNow - 3600000, attempts: 0 }] }, null, 2)"
93+ - utf8
94+ - call: env.gateway.call
95+args:
96+ - wake
97+ - mode: next-heartbeat
98+text: Commitments target none QA wake
99+ - timeoutMs: 30000
100+ - call: waitForCondition
101+saveAs: heartbeat
102+args:
103+ - lambda:
104+async: true
105+expr: "(async () => { const last = await env.gateway.call('last-heartbeat', {}, { timeoutMs: 5000 }); return last && last.ts > beforeHeartbeatTs ? last : undefined; })()"
106+ - expr: liveTurnTimeoutMs(env, 45000)
107+ - 250
108+ - call: waitForNoOutbound
109+args:
110+ - ref: state
111+ - 3000
112+ - set: commitmentStore
113+value:
114+expr: "JSON.parse(await fs.readFile(commitmentStorePath, 'utf8'))"
115+ - set: commitment
116+value:
117+expr: "commitmentStore.commitments.find((entry) => entry.id === config.commitmentId)"
118+ - assert:
119+expr: "commitment && commitment.status === 'pending' && commitment.attempts === 0"
120+message:
121+expr: "`commitment was attempted or changed: ${JSON.stringify(commitment)}`"
122+detailsExpr: "`heartbeat=${JSON.stringify(heartbeat)}\\ncommitment=${JSON.stringify(commitment)}`"
123+```
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。