instar 1.3.1007 → 1.3.1008
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scheduler/MentorOnboardingRunner.d.ts +2 -2
- package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
- package/dist/scheduler/MentorOnboardingRunner.js +1 -1
- package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
- package/dist/scheduler/MentorOnboardingTick.d.ts +8 -1
- package/dist/scheduler/MentorOnboardingTick.d.ts.map +1 -1
- package/dist/scheduler/MentorOnboardingTick.js +15 -7
- package/dist/scheduler/MentorOnboardingTick.js.map +1 -1
- package/dist/scheduler/OutstandingPromptTracker.d.ts +52 -0
- package/dist/scheduler/OutstandingPromptTracker.d.ts.map +1 -1
- package/dist/scheduler/OutstandingPromptTracker.js +135 -3
- package/dist/scheduler/OutstandingPromptTracker.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +107 -61
- package/dist/server/AgentServer.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +3 -3
- package/upgrades/1.3.1008.md +39 -0
- package/upgrades/eli16/bounded-mentor-onboarding-retries.md +53 -0
- package/upgrades/side-effects/bounded-mentor-onboarding-retries.md +142 -0
|
@@ -3920,6 +3920,40 @@ export class AgentServer {
|
|
|
3920
3920
|
const lastResultPath = options.config.stateDir
|
|
3921
3921
|
? path.join(options.config.stateDir, 'mentor-last-result.json')
|
|
3922
3922
|
: null;
|
|
3923
|
+
const sweepMentorOrphans = () => {
|
|
3924
|
+
const outstanding = self.getOrCreateMentorOutstanding();
|
|
3925
|
+
const orphans = outstanding.sweepExpired();
|
|
3926
|
+
for (const orphan of orphans) {
|
|
3927
|
+
if (!outstanding.recordOrphanNotified(orphan.corr))
|
|
3928
|
+
continue;
|
|
3929
|
+
console.warn(`[mentor] orphaned prompt — no reply within ${orphan.ageMs}ms (corr=${orphan.corr}, mentee=${orphan.mentee})`);
|
|
3930
|
+
try {
|
|
3931
|
+
DegradationReporter.getInstance().report({
|
|
3932
|
+
feature: 'mentor.reply-orphaned',
|
|
3933
|
+
primary: 'mentor receives a correlated mentee reply within replyTimeoutMs',
|
|
3934
|
+
fallback: 'the content-key retry brake accounts for the attempt before any later send',
|
|
3935
|
+
reason: `outstanding prompt corr=${orphan.corr} aged ${orphan.ageMs}ms without a mentor-reply`,
|
|
3936
|
+
impact: 'the attempt is classified as unconfirmed delivery; identical content remains subject to its bounded retry budget',
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
catch { /* best-effort */ }
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3942
|
+
const escalateMentorDeliveryExhaustion = (contentKey, attempts, reason) => {
|
|
3943
|
+
const outstanding = self.getOrCreateMentorOutstanding();
|
|
3944
|
+
if (!outstanding.recordRetryExhaustionEscalated(contentKey))
|
|
3945
|
+
return;
|
|
3946
|
+
try {
|
|
3947
|
+
DegradationReporter.getInstance().report({
|
|
3948
|
+
feature: 'mentor.delivery-unconfirmed-retry-exhausted',
|
|
3949
|
+
primary: 'a mentor prompt produces a correlated mentee reply',
|
|
3950
|
+
fallback: 'the durable content-key breaker suppresses further identical sends',
|
|
3951
|
+
reason,
|
|
3952
|
+
impact: `that agenda item is paused as a transport/delivery failure after ${attempts} attempts; a genuinely new agenda item remains eligible`,
|
|
3953
|
+
});
|
|
3954
|
+
}
|
|
3955
|
+
catch { /* best-effort */ }
|
|
3956
|
+
};
|
|
3923
3957
|
return new MentorOnboardingRunner({
|
|
3924
3958
|
capture: (input) => ledger.captureRun(input),
|
|
3925
3959
|
loadLastResult: lastResultPath
|
|
@@ -4102,6 +4136,11 @@ export class AgentServer {
|
|
|
4102
4136
|
isMenteeBusy: () => {
|
|
4103
4137
|
const cfg = getConfig();
|
|
4104
4138
|
const menteeAgent = cfg.menteeAgentName || `instar-${cfg.menteeFramework}`;
|
|
4139
|
+
// Sweep + classify expired correlations BEFORE asking the busy gate.
|
|
4140
|
+
// canSendTo also sweeps defensively, but doing it here preserves the
|
|
4141
|
+
// orphan evidence instead of deleting it before the distinct delivery
|
|
4142
|
+
// signal can be emitted.
|
|
4143
|
+
sweepMentorOrphans();
|
|
4105
4144
|
return !self.getOrCreateMentorOutstanding().canSendTo(menteeAgent).ok;
|
|
4106
4145
|
},
|
|
4107
4146
|
minIntervalElapsed: () => {
|
|
@@ -4145,30 +4184,24 @@ export class AgentServer {
|
|
|
4145
4184
|
// instar-codey — using the derived name silently broke peer lookup.
|
|
4146
4185
|
const menteeAgent = cfg.menteeAgentName || `instar-${framework}`;
|
|
4147
4186
|
const corr = `mp-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
4148
|
-
// Anti-ping-pong
|
|
4149
|
-
//
|
|
4150
|
-
//
|
|
4187
|
+
// Anti-ping-pong + content-key retry breaker. Reserve the attempt in
|
|
4188
|
+
// durable state BEFORE calling any transport. A failed reservation is
|
|
4189
|
+
// a hard refusal: acting without the restart-proof ledger would turn a
|
|
4190
|
+
// storage problem back into an unbounded self-action loop.
|
|
4151
4191
|
const outstanding = self.getOrCreateMentorOutstanding();
|
|
4152
|
-
const
|
|
4153
|
-
|
|
4154
|
-
if (
|
|
4155
|
-
console.warn(`[mentor]
|
|
4156
|
-
|
|
4157
|
-
DegradationReporter.getInstance().report({
|
|
4158
|
-
feature: 'mentor.reply-orphaned',
|
|
4159
|
-
primary: 'mentor receives Codey reply within replyTimeoutMs',
|
|
4160
|
-
fallback: 'tick continues; no auto-resend; Stage-B sees the routed-sent row + no matching reply row',
|
|
4161
|
-
reason: `outstanding prompt corr=${orphan.corr} aged ${orphan.ageMs}ms without a mentor-reply`,
|
|
4162
|
-
impact: 'mentor cycle silently lost a reply; next tick allowed to retry',
|
|
4163
|
-
});
|
|
4164
|
-
}
|
|
4165
|
-
catch { /* best-effort */ }
|
|
4192
|
+
const reservation = outstanding.reserveSend(corr, menteeAgent, message);
|
|
4193
|
+
if (!reservation.ok) {
|
|
4194
|
+
if (reservation.reason === 'prior-prompt-in-flight') {
|
|
4195
|
+
console.warn(`[mentor] deliverToMentee deferred — prior-prompt-in-flight (corr=${reservation.outstandingCorr}, sentAt=${reservation.sentAt})`);
|
|
4196
|
+
return { delivered: false, reason: reservation.reason };
|
|
4166
4197
|
}
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4198
|
+
if (reservation.reason === 'identical-content-retry-exhausted') {
|
|
4199
|
+
console.warn(`[mentor] deliverToMentee suppressed — identical-content-retry-exhausted (attempts=${reservation.attempts}, key=${reservation.contentKey.slice(0, 12)})`);
|
|
4200
|
+
escalateMentorDeliveryExhaustion(reservation.contentKey, reservation.attempts, `${reservation.attempts} attempts for the same normalized mentor content ended without a confirmed reply`);
|
|
4201
|
+
return { delivered: false, reason: reservation.reason };
|
|
4202
|
+
}
|
|
4203
|
+
console.warn(`[mentor] deliverToMentee refused — ${reservation.reason}`);
|
|
4204
|
+
return { delivered: false, reason: reservation.reason };
|
|
4172
4205
|
}
|
|
4173
4206
|
// Deliver via the unified a2a transport: same-machine /a2a/inbox
|
|
4174
4207
|
// when the mentee is a local peer (Telegram blocks bot-to-bot, so
|
|
@@ -4177,47 +4210,52 @@ export class AgentServer {
|
|
|
4177
4210
|
const telegramBot = cfg.botToken && cfg.menteeChatId
|
|
4178
4211
|
? self.getOrCreateMentorBot(cfg.botToken, cfg.menteeChatId) ?? undefined
|
|
4179
4212
|
: undefined;
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
// (knownMentors[echo].botId === senderBotId) passes.
|
|
4197
|
-
fromBotId: cfg.botToken ? cfg.botToken.split(':')[0] : undefined,
|
|
4198
|
-
toBotId: cfg.menteeBotId,
|
|
4199
|
-
telegramBot: telegramBot
|
|
4200
|
-
? { sendToTopic: (t, txt) => telegramBot.sendToTopic(t, txt) }
|
|
4201
|
-
: undefined,
|
|
4202
|
-
botToken: cfg.botToken,
|
|
4203
|
-
visibleEcho: {
|
|
4204
|
-
enabled: cfg.visibleEcho !== false,
|
|
4205
|
-
bot: telegramBot
|
|
4213
|
+
let delivered = false;
|
|
4214
|
+
try {
|
|
4215
|
+
delivered = await self.deliverA2aMessage({
|
|
4216
|
+
fromAgent: 'echo',
|
|
4217
|
+
toAgent: menteeAgent,
|
|
4218
|
+
role: 'mentor',
|
|
4219
|
+
corr,
|
|
4220
|
+
body: message,
|
|
4221
|
+
allowedRoles: new Set(['mentor']),
|
|
4222
|
+
// Route the mentor exchange to a DEDICATED mentor topic when one is
|
|
4223
|
+
// configured, so mentor a2a stays off the human conversation.
|
|
4224
|
+
telegramTopicId: resolveMentorDeliveryTopic(cfg),
|
|
4225
|
+
targetMachineId: cfg.menteeMachineId,
|
|
4226
|
+
fromBotId: cfg.botToken ? cfg.botToken.split(':')[0] : undefined,
|
|
4227
|
+
toBotId: cfg.menteeBotId,
|
|
4228
|
+
telegramBot: telegramBot
|
|
4206
4229
|
? { sendToTopic: (t, txt) => telegramBot.sendToTopic(t, txt) }
|
|
4207
4230
|
: undefined,
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4231
|
+
botToken: cfg.botToken,
|
|
4232
|
+
visibleEcho: {
|
|
4233
|
+
enabled: cfg.visibleEcho !== false,
|
|
4234
|
+
bot: telegramBot
|
|
4235
|
+
? { sendToTopic: (t, txt) => telegramBot.sendToTopic(t, txt) }
|
|
4236
|
+
: undefined,
|
|
4237
|
+
topicId: resolveMentorDeliveryTopic(cfg),
|
|
4238
|
+
roleTag: '[mentor]',
|
|
4239
|
+
reportFailure: (reason) => {
|
|
4240
|
+
DegradationReporter.getInstance().report({
|
|
4241
|
+
feature: 'mentor.visible-echo',
|
|
4242
|
+
primary: 'successful inbox-local mentor delivery is mirrored visibly in Telegram',
|
|
4243
|
+
fallback: 'canonical /a2a/inbox delivery remains successful; operator may see a phantom prompt',
|
|
4244
|
+
reason,
|
|
4245
|
+
impact: 'mentor exchange was delivered but its visible chat mirror is partial or absent',
|
|
4246
|
+
});
|
|
4247
|
+
},
|
|
4218
4248
|
},
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
catch (err) {
|
|
4252
|
+
outstanding.markDeliveryFailed(corr);
|
|
4253
|
+
console.warn(`[mentor] deliverToMentee transport failed (corr=${corr}, mentee=${menteeAgent}):`, err instanceof Error ? err.message : String(err));
|
|
4254
|
+
if (reservation.exhausted) {
|
|
4255
|
+
escalateMentorDeliveryExhaustion(reservation.contentKey, reservation.attempt, `${reservation.attempt} transport attempts for the same normalized mentor content threw before delivery confirmation`);
|
|
4256
|
+
}
|
|
4257
|
+
return { delivered: false, reason: 'transport-failed' };
|
|
4258
|
+
}
|
|
4221
4259
|
if (delivered) {
|
|
4222
4260
|
self.appendMentorSent(options.config.stateDir, {
|
|
4223
4261
|
ts: Date.now(),
|
|
@@ -4227,10 +4265,18 @@ export class AgentServer {
|
|
|
4227
4265
|
topicId: resolveMentorDeliveryTopic(cfg),
|
|
4228
4266
|
message,
|
|
4229
4267
|
});
|
|
4230
|
-
|
|
4268
|
+
return { delivered: true };
|
|
4231
4269
|
}
|
|
4232
4270
|
else {
|
|
4271
|
+
outstanding.markDeliveryFailed(corr);
|
|
4233
4272
|
console.warn(`[mentor] deliverToMentee did not deliver (corr=${corr}, mentee=${menteeAgent}) — no local peer + telegram fallback unavailable/blocked`);
|
|
4273
|
+
// The attempt remains in the content ledger. On the final allowed
|
|
4274
|
+
// attempt, emit the distinct delivery failure immediately instead
|
|
4275
|
+
// of waiting for another tick merely to discover the open breaker.
|
|
4276
|
+
if (reservation.exhausted) {
|
|
4277
|
+
escalateMentorDeliveryExhaustion(reservation.contentKey, reservation.attempt, `${reservation.attempt} transport attempts for the same normalized mentor content were refused`);
|
|
4278
|
+
}
|
|
4279
|
+
return { delivered: false, reason: 'transport-unavailable' };
|
|
4234
4280
|
}
|
|
4235
4281
|
},
|
|
4236
4282
|
onTickRan: () => {
|