fix: honor explicit strict-agentic retry contract

Honor explicit strict-agentic execution contracts for incomplete-turn retry guards across providers, including local/compatible models that opt in without relying on OpenAI model inference.

Validation:
- pnpm test src/agents/pi-embedded-runner/run.incomplete-turn.test.ts
- pnpm check:changed
- GitHub CI + parity gate green

Thanks @ziomancer.
This commit is contained in:
Devin Matthews
2026-04-22 14:03:03 -06:00
committed by GitHub
parent c0cafb6bbe
commit 5528793adf
4 changed files with 17 additions and 3 deletions

View File

@@ -87,6 +87,7 @@ Docs: https://docs.openclaw.ai
- Security/dotenv: block workspace `.env` overrides for Matrix, Mattermost, IRC, and Synology endpoint settings so cloned workspaces cannot redirect bundled connector traffic through local endpoint config. (#70240) Thanks @drobison00.
- Telegram: require the same `/models` authorization for group model-picker callbacks, so unauthorized participants can no longer browse or change the session model through inline buttons. (#70235) Thanks @drobison00.
- Agents/Pi: keep the filtered tool-name allowlist active for embedded OpenAI/OpenAI Codex GPT-5 runs and compaction sessions, so bundled and client tools still execute after the Pi `0.68.1` session-tool allowlist change instead of stopping at plan-only replies with no tool call. (#70281) Thanks @jalehman.
- Agents/Pi: honor explicit `strict-agentic` execution contracts for incomplete-turn retry guards across providers, so manually opted-in local or compatible models get the same retry behavior without relying on OpenAI model inference. (#66750) Thanks @ziomancer.
## 2026.4.21

View File

@@ -360,7 +360,7 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
expect(result.payloads?.[0]?.text).toContain("Please try again");
});
it("does not retry reasoning-only turns for non-openai assistant metadata", async () => {
it("does not retry reasoning-only turns for non-strict-agentic providers", async () => {
mockedClassifyFailoverReason.mockReturnValue(null);
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
@@ -386,8 +386,8 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
const result = await runEmbeddedPiAgent({
...overflowBaseRunParams,
provider: "openai",
model: "gpt-5.4",
provider: "anthropic",
model: "sonnet-4.6",
runId: "run-reasoning-only-provider-mismatch",
});

View File

@@ -1709,6 +1709,7 @@ export async function runEmbeddedPiAgent(
const nextPlanningOnlyRetryInstruction = resolvePlanningOnlyRetryInstruction({
provider,
modelId,
executionContract,
prompt: params.prompt,
aborted,
timedOut,
@@ -1717,6 +1718,7 @@ export async function runEmbeddedPiAgent(
const nextReasoningOnlyRetryInstruction = resolveReasoningOnlyRetryInstruction({
provider: activeErrorContext.provider,
modelId: activeErrorContext.model,
executionContract,
aborted,
timedOut,
attempt,
@@ -1724,6 +1726,7 @@ export async function runEmbeddedPiAgent(
const nextEmptyResponseRetryInstruction = resolveEmptyResponseRetryInstruction({
provider: activeErrorContext.provider,
modelId: activeErrorContext.model,
executionContract,
payloadCount,
aborted,
timedOut,

View File

@@ -307,6 +307,7 @@ function shouldSkipPlanningOnlyRetry(params: {
export function resolveReasoningOnlyRetryInstruction(params: {
provider?: string;
modelId?: string;
executionContract?: string;
aborted: boolean;
timedOut: boolean;
attempt: IncompleteTurnAttempt;
@@ -319,6 +320,7 @@ export function resolveReasoningOnlyRetryInstruction(params: {
!shouldApplyPlanningOnlyRetryGuard({
provider: params.provider,
modelId: params.modelId,
executionContract: params.executionContract,
})
) {
return null;
@@ -341,6 +343,7 @@ export function resolveReasoningOnlyRetryInstruction(params: {
export function resolveEmptyResponseRetryInstruction(params: {
provider?: string;
modelId?: string;
executionContract?: string;
payloadCount: number;
aborted: boolean;
timedOut: boolean;
@@ -354,6 +357,7 @@ export function resolveEmptyResponseRetryInstruction(params: {
!shouldApplyPlanningOnlyRetryGuard({
provider: params.provider,
modelId: params.modelId,
executionContract: params.executionContract,
})
) {
return null;
@@ -374,7 +378,11 @@ export function resolveEmptyResponseRetryInstruction(params: {
function shouldApplyPlanningOnlyRetryGuard(params: {
provider?: string;
modelId?: string;
executionContract?: string;
}): boolean {
if (params.executionContract === "strict-agentic") {
return true;
}
return isStrictAgenticSupportedProviderModel({
provider: params.provider,
modelId: params.modelId,
@@ -531,6 +539,7 @@ export function resolvePlanningOnlyRetryLimit(
export function resolvePlanningOnlyRetryInstruction(params: {
provider?: string;
modelId?: string;
executionContract?: string;
prompt?: string;
aborted: boolean;
timedOut: boolean;
@@ -547,6 +556,7 @@ export function resolvePlanningOnlyRetryInstruction(params: {
!shouldApplyPlanningOnlyRetryGuard({
provider: params.provider,
modelId: params.modelId,
executionContract: params.executionContract,
}) ||
(typeof params.prompt === "string" && !isLikelyActionableUserPrompt(params.prompt)) ||
params.aborted ||