diff --git a/packages/agent-interface/package.json b/packages/agent-interface/package.json index d51b6bc..a5b4ae9 100644 --- a/packages/agent-interface/package.json +++ b/packages/agent-interface/package.json @@ -1,6 +1,6 @@ { "name": "@tangle-network/agent-interface", - "version": "2.6.1", + "version": "2.7.0", "type": "module", "sideEffects": false, "license": "MIT", diff --git a/packages/agent-interface/src/environment-interactive-control.ts b/packages/agent-interface/src/environment-interactive-control.ts index 5290d54..feb3680 100644 --- a/packages/agent-interface/src/environment-interactive-control.ts +++ b/packages/agent-interface/src/environment-interactive-control.ts @@ -8,6 +8,7 @@ import { } from "./agent-execution-preparation-receipt.js"; import { boundedIdentifierSchema, + boundedEventContentStringSchema, boundedStringSchema, } from "./contract-limits.js"; import { @@ -546,7 +547,13 @@ const AgentInteractiveSessionPromptCommandMaterialSchema = z operationId: boundedIdentifierSchema, ref: AgentInteractiveSessionRefSchema, control: AgentInteractiveSessionControlClaimSchema, - prompt: boundedStringSchema.min(1), + // Content, not metadata: see environment-runtime.ts. + // Content, not metadata: see environment-runtime.ts. `boundedEventContentStringSchema` is a + // custom schema and carries no `.min`, so the non-empty requirement is kept as a refinement — + // an interactive prompt with nothing in it is still refused. + prompt: boundedEventContentStringSchema.refine((value) => value.length >= 1, { + message: "interactive prompt must not be empty", + }), }) .superRefine((command, refinement) => { if (!agentInteractiveSessionControlClaimMatchesRef(command.ref, command.control)) { diff --git a/packages/agent-interface/src/environment-runtime.ts b/packages/agent-interface/src/environment-runtime.ts index 82cdd91..de7b7d7 100644 --- a/packages/agent-interface/src/environment-runtime.ts +++ b/packages/agent-interface/src/environment-runtime.ts @@ -55,7 +55,12 @@ export interface AgentTurnInput { } export const AgentTurnInputSchema = z.strictObject({ - prompt: boundedStringSchema.optional(), + // A prompt is what the agent is asked to do, so it is CONTENT: its size is set by the work, + // not by the protocol. Held to the metadata bound it capped a manager's brief at 16,384 + // characters, which refused a director handing a checker a 27 KB patch to re-verify a measured + // result (agent-sdk#313). Refusal is still right here — a silently shortened instruction is + // worse than none — so only the ceiling moves. + prompt: boundedEventContentStringSchema.optional(), parts: z.array(InputPartSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(), sessionId: boundedIdentifierSchema.optional(), model: boundedIdentifierSchema.optional(), diff --git a/packages/agent-interface/src/leaf-modules.test.ts b/packages/agent-interface/src/leaf-modules.test.ts index fecb567..99730a5 100644 --- a/packages/agent-interface/src/leaf-modules.test.ts +++ b/packages/agent-interface/src/leaf-modules.test.ts @@ -555,6 +555,18 @@ describe("interface split leaf modules", () => { expect(() => AgentTurnInputSchema.parse({ prompt: "x", context: { huge: "x".repeat(CONTRACT_MAX_STRING_LENGTH + 1) } }), ).toThrow(); + // A prompt is CONTENT: its size is set by the work, not by the protocol. Held to the metadata + // bound it capped a manager's brief at 16,384 characters, which refused a director handing a + // checker a 27 KB patch to re-verify a measured result (agent-sdk#313). `context` above keeps + // the metadata bound, because that is material describing the turn rather than the ask itself. + expect(() => + AgentTurnInputSchema.parse({ prompt: "x".repeat(CONTRACT_MAX_STRING_LENGTH + 1) }), + ).not.toThrow(); + expect(() => AgentTurnInputSchema.parse({ prompt: "x".repeat(200_000) })).not.toThrow(); + // The content bound still governs it, so an unbounded prompt is still refused. + expect(() => + AgentTurnInputSchema.parse({ prompt: "x".repeat(2 * 1024 * 1024) }), + ).toThrow(); for (const usageMode of ["delta", "cumulative"]) { expect(AgentTurnResultSchema.parse({ text: "x", success: true, diff --git a/packages/agent-interface/src/portable-context-continuation.ts b/packages/agent-interface/src/portable-context-continuation.ts index a341b78..817395c 100644 --- a/packages/agent-interface/src/portable-context-continuation.ts +++ b/packages/agent-interface/src/portable-context-continuation.ts @@ -5,7 +5,8 @@ import { type AgentExactRunControlRef, } from "./runtime-control.js"; import { idSchema, InputPartSchema, jsonRecordSchema, sha256DigestSchema, wireDigest } from "./portable-context-shared.js"; -import { boundedStringSchema, CONTRACT_MAX_ARRAY_LENGTH } from "./contract-limits.js"; +import { boundedEventContentStringSchema, + boundedStringSchema, CONTRACT_MAX_ARRAY_LENGTH } from "./contract-limits.js"; export const NativeContextBoundarySchema = z .discriminatedUnion("kind", [ @@ -78,7 +79,8 @@ export interface NativeContextContinuationTurn { } export const NativeContextContinuationTurnSchema = z.strictObject({ - prompt: boundedStringSchema.optional(), + // Content, not metadata: see environment-runtime.ts. + prompt: boundedEventContentStringSchema.optional(), parts: z.array(InputPartSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(), model: idSchema.optional(), context: jsonRecordSchema.optional(), diff --git a/packages/agent-interface/src/runtime-control.ts b/packages/agent-interface/src/runtime-control.ts index 21ba282..06465fa 100644 --- a/packages/agent-interface/src/runtime-control.ts +++ b/packages/agent-interface/src/runtime-control.ts @@ -399,7 +399,8 @@ const partSchema = z.discriminatedUnion("type", [ z.strictObject({ ...partBase, type: z.literal("subtask"), - prompt: boundedStringSchema, + // Content, not metadata: see environment-runtime.ts. + prompt: boundedEventContentStringSchema, description: boundedStringSchema, agent: stableIdSchema, }),