diff --git a/apps/server/src/jobs/document-summary.ts b/apps/server/src/jobs/document-summary.ts index 0b907a80b..7e6a25260 100644 --- a/apps/server/src/jobs/document-summary.ts +++ b/apps/server/src/jobs/document-summary.ts @@ -5,7 +5,7 @@ import { serialize } from "@chopin/dialect/serialize"; import { assert } from "@chopin/dialect/validate"; import { summaryAgent } from "../harness/agents"; -import { openWorkerSession } from "./worker-session"; +import { openWorkerSession, workerModel } from "./worker-session"; import type { Config } from "../config"; import type { DocumentTarget } from "../plan/service"; import type { JsonValue } from "../storage/model"; @@ -295,7 +295,7 @@ class HarnessSummaryEngine { } partials = reduced; } - return { description: partials[0]!, model: this.#config.model }; + return { description: partials[0]!, model: workerModel(this.#config.model) }; } finally { await worker.close(); } @@ -339,7 +339,7 @@ export function documentSummaryDefinition(options: DocumentSummaryOptions): JobD if (serialize(tree) !== target.source) throw new Error("Document source is not canonical."); let result = target.source.trim() ? await engine(execution, target.source) - : { description: "Empty document", model: options.config.model }; + : { description: "Empty document", model: workerModel(options.config.model) }; return { revision: execution.input.revision, sourceHash: execution.input.sourceHash, diff --git a/apps/server/src/jobs/research-brief.ts b/apps/server/src/jobs/research-brief.ts index 728af6caf..f3cebcf21 100644 --- a/apps/server/src/jobs/research-brief.ts +++ b/apps/server/src/jobs/research-brief.ts @@ -5,7 +5,7 @@ import { assertResearchContext, researchSources } from "../conversation-plan/res import { id, knownKeys, record, text, version } from "../conversation-plan/validation-fields"; import type { JsonValue } from "../storage/model"; import { type JobDefinition, type JobExecution, JobExecutionError } from "./registry"; -import { openWorkerSession } from "./worker-session"; +import { openWorkerSession, workerModel } from "./worker-session"; // Copilot requires a session ceiling of at least 30, even for a single short turn. const MAX_AI_CREDITS = 30; @@ -123,7 +123,10 @@ async function generate( if (!await credential.authorize() || signal.aborted) { throw new JobExecutionError("owner-unavailable"); } - return { ...(result.output as { brief: string; sourceIds: string[] }), model: config.model }; + return { + ...(result.output as { brief: string; sourceIds: string[] }), + model: workerModel(config.model), + }; } finally { signal.removeEventListener("abort", rejectAbort); await close?.(); diff --git a/apps/server/src/jobs/research-workspace.test.ts b/apps/server/src/jobs/research-workspace.test.ts index 495e2e048..0a7a65e76 100644 --- a/apps/server/src/jobs/research-workspace.test.ts +++ b/apps/server/src/jobs/research-workspace.test.ts @@ -784,3 +784,19 @@ describe("research workspace execution", () => { .rejects.toMatchObject({ progressReason: "source-validation-failed" }); }); }); + +it("research artifacts record the attached client when the operator configured no model", async () => { + let definition = researchEvidenceDefinition({ + config: { agent: true, model: "" }, + engine: async () => ({ findings: [], sources: [] }), + }); + let input = { workspaceId: "workspace", turnId: "turn", query: "Rate limiting a login endpoint" }; + let artifact = await definition.execute({ + job: { channelId: "channel" }, + input, + progress: async () => {}, + } as unknown as JobExecution); + + expect(artifact.model).toBe("attached-client"); + expect(parseResearchEvidenceArtifact(artifact as unknown as JsonValue)).toEqual(artifact); +}); diff --git a/apps/server/src/jobs/research-workspace.ts b/apps/server/src/jobs/research-workspace.ts index dba6416c3..54583b6d3 100644 --- a/apps/server/src/jobs/research-workspace.ts +++ b/apps/server/src/jobs/research-workspace.ts @@ -3,7 +3,7 @@ import * as limits from "@chopin/dialect/limits"; import { WEB_SEARCH_MAX_CALLS, WebSearchTimeoutError, webSearchTool } from "../harness/web-search"; import { JobExecutionError } from "./registry"; -import { openWorkerSession, type WorkerSession } from "./worker-session"; +import { openWorkerSession, workerModel, type WorkerSession } from "./worker-session"; import type { WebSearchFailureDiagnostic } from "../harness/web-search"; import type { researchAgent } from "../harness/agents"; import type { Config } from "../config"; @@ -1158,7 +1158,7 @@ export function researchEvidenceDefinition(options: ResearchEvidenceOptions): Jo return parseResearchEvidenceArtifact({ ...input, ...evidence, - model: options.config.model, + model: workerModel(options.config.model), }); }, }; @@ -1232,7 +1232,7 @@ export function researchAnswerDefinition(options: ResearchAnswerOptions): JobDef privateFindings: privateValue.findings, documentRevision: input.document.revision, documentSourceHash: input.document.sourceHash, - model: options.config.model, + model: workerModel(options.config.model), }); } @@ -1261,7 +1261,7 @@ export function researchAnswerDefinition(options: ResearchAnswerOptions): JobDef sources: evidence.sources, documentRevision: input.document.revision, documentSourceHash: input.document.sourceHash, - model: options.config.model, + model: workerModel(options.config.model), }); }, }; diff --git a/apps/server/src/jobs/version-summary.ts b/apps/server/src/jobs/version-summary.ts index c95be2df1..278020c68 100644 --- a/apps/server/src/jobs/version-summary.ts +++ b/apps/server/src/jobs/version-summary.ts @@ -3,7 +3,7 @@ import { parse } from "@chopin/dialect/parse"; import { serialize } from "@chopin/dialect/serialize"; import { historySummaryAgent } from "../harness/agents"; -import { openWorkerSession } from "./worker-session"; +import { openWorkerSession, workerModel } from "./worker-session"; import type { Config } from "../config"; import type { JsonValue, StoredDocumentVersion, SummarizeDocumentVersion } from "../storage/model"; import type { JobDefinition, JobExecution } from "./registry"; @@ -228,7 +228,7 @@ class HarnessVersionSummaryEngine { aborted, ]); let output = result.output as { summaries: { id: string; summary: string }[] }; - return { summaries: output.summaries, model: this.#config.historyModel }; + return { summaries: output.summaries, model: workerModel(this.#config.historyModel) }; } finally { await worker.close(); } @@ -274,7 +274,11 @@ export function versionSummaryDefinition(options: VersionSummaryOptions): JobDef wanted.set(version.id, version.sourceHash); } if (changes.length === 0) { - return { generatorVersion: 1, model: options.config.historyModel, summaries: [] }; + return { + generatorVersion: 1, + model: workerModel(options.config.historyModel), + summaries: [], + }; } let result = await engine(execution, changes); let summaries: VersionSummaryArtifact["summaries"] = []; diff --git a/apps/server/src/jobs/worker-session.ts b/apps/server/src/jobs/worker-session.ts index 73b48c510..7f5f1f7e6 100644 --- a/apps/server/src/jobs/worker-session.ts +++ b/apps/server/src/jobs/worker-session.ts @@ -12,6 +12,14 @@ export type WorkerAgent = { export type WorkerSession = { session: Session; close(): Promise }; +/** + * The model a worker's artifact records. Under `HARNESS=remote` the operator may configure none, and the + * attached client then chooses its own. + */ +export function workerModel(configured: string): string { + return configured || "attached-client"; +} + async function openAbortable( opening: Promise, aborted: Promise,