From aa4b88b169849645038eeb6d305b533767dad77d Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 28 Sep 2026 14:46:49 -0500 Subject: [PATCH] fix(runner): include live completion contract in model instructions Co-Authored-By: Paperclip --- .../src/live/live-session.test.ts | 8 +++++++- .../paperclip-runner/src/live/live-session.ts | 15 +++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/paperclip-runner/src/live/live-session.test.ts b/packages/paperclip-runner/src/live/live-session.test.ts index f565840c151..f1d7a1d37ce 100644 --- a/packages/paperclip-runner/src/live/live-session.test.ts +++ b/packages/paperclip-runner/src/live/live-session.test.ts @@ -907,6 +907,8 @@ describe("Capability live runnerd and Codex session", () => { expect(names.filter((name) => name === "paperclip_finish")).toHaveLength(1); expect(names.filter((name) => name === "paperclip_block")).toHaveLength(1); expect(names).not.toContain("create_task"); + expect(opened.params.baseInstructions).toContain('"revision":"paperclip-capability-live-v1"'); + expect(opened.params.baseInstructions).toContain('"criterionIds":["objective"]'); await first.suspend(); const restoredService = new CapabilityLiveSessionService({ store, transportFactory: factory }); const restored = await restoredService.restore(first.id); @@ -914,6 +916,8 @@ describe("Capability live runnerd and Codex session", () => { expect(authority[1]?.resumeDynamicTools?.map((tool) => tool.name)).toEqual( names.filter((name) => name !== "paperclip_finish" && name !== "paperclip_block"), ); + const resumed = state.transports[1]!.requests.find((request) => request.method === "thread/resume")!; + expect(resumed.params.baseInstructions).toBe(opened.params.baseInstructions); await restoredService.shutdown(restored.id); await firstService.shutdown(first.id); }); @@ -972,9 +976,11 @@ describe("Capability live runnerd and Codex session", () => { state.transports[0]?.requests.find( (request) => request.method === "thread/start", )?.params.baseInstructions, - ).toBe( + ).toContain( "Native instructions\n\nRead-only instruction sibling root: /runtime/instructions", ); + expect(state.transports[0]?.requests.find((request) => request.method === "thread/start")?.params.baseInstructions) + .toContain('Native completion report contract: {"revision":"paperclip-capability-live-v1","criterionIds":["objective"]}'); await service.shutdown(session.id); }); diff --git a/packages/paperclip-runner/src/live/live-session.ts b/packages/paperclip-runner/src/live/live-session.ts index 35339216162..ecf16f24320 100644 --- a/packages/paperclip-runner/src/live/live-session.ts +++ b/packages/paperclip-runner/src/live/live-session.ts @@ -2346,6 +2346,15 @@ export class CapabilityLiveSession { itemId: `item_lab_${identityDigest}`, }; this.#providerRunBinding = providerRunBinding; + // PRP run-attach metadata binds server validation, but ACP providers receive + // only these system instructions. Make the same immutable contract visible + // without expanding the model's separately governed mock task authority. + const baseInstructions = [ + this.#transportOptions.baseInstructions ?? LIVE_BASE_INSTRUCTIONS, + "", + "Native completion report contract: " + JSON.stringify(LIVE_COMPLETION_CONTRACT), + "For paperclip_finish or paperclip_block, use that exact contract revision and criterion IDs. These tools report the current provider run result; they do not change mock task state. Mock task mutations still require separately authorized semantic tools and an explicit user request.", + ].join("\n"); const authorizedTools = this.#dispatcher.listTools(this.#authority.runId); const tools = (this.#config.toolExposure ?? "eager") === "lazy" ? authorizedTools.filter((tool) => tool.annotations.exposure === "always") @@ -2464,8 +2473,7 @@ export class CapabilityLiveSession { config: createSkilllessCodexThreadConfig(this.#config.workingDirectory), permissions: CODEX_PERMISSION_PROFILE, runtimeWorkspaceRoots: [this.#config.workingDirectory], - baseInstructions: - this.#transportOptions.baseInstructions ?? LIVE_BASE_INSTRUCTIONS, + baseInstructions, persistExtendedHistory: true, }); const resumedThread = record(resumed.thread); @@ -2490,8 +2498,7 @@ export class CapabilityLiveSession { permissions: CODEX_PERMISSION_PROFILE, runtimeWorkspaceRoots: [this.#config.workingDirectory], approvalPolicy: "never", - baseInstructions: - this.#transportOptions.baseInstructions ?? LIVE_BASE_INSTRUCTIONS, + baseInstructions, completionContract: LIVE_COMPLETION_CONTRACT, dynamicTools: [...semanticTools, ...codexSemanticToolSpecs()], experimentalRawEvents: true,