diff --git a/apps/server/src/mcp/toolkits/orchestrator/tools.test.ts b/apps/server/src/mcp/toolkits/orchestrator/tools.test.ts index e95639d308dc..4911fb14fa88 100644 --- a/apps/server/src/mcp/toolkits/orchestrator/tools.test.ts +++ b/apps/server/src/mcp/toolkits/orchestrator/tools.test.ts @@ -72,6 +72,17 @@ describe("orchestrator MCP tool guidance", () => { assert.include(ScheduleTaskTool.description ?? "", "nextRunAt"); }); + it("says runs post into this thread by default only in its own project", () => { + const schema = Tool.getJsonSchema(ScheduleTaskTool) as { + readonly properties?: Readonly>; + }; + + assert.include( + JSON.stringify(schema.properties?.bindToCurrentThread), + "In another project, or without a calling thread, each run launches a fresh thread", + ); + }); + it("publishes thread metadata actions from an object-root schema", () => { const schema = Tool.getJsonSchema(ThreadUpdateTool) as { readonly type?: unknown; diff --git a/packages/contracts/src/orchestratorMcp.ts b/packages/contracts/src/orchestratorMcp.ts index 0ab3e0b9b86c..c33d2debb322 100644 --- a/packages/contracts/src/orchestratorMcp.ts +++ b/packages/contracts/src/orchestratorMcp.ts @@ -524,14 +524,14 @@ export const OrchestratorMcpScheduleTaskInput = Schema.Struct({ Schema.Boolean.annotate({ description: "Whether the schedule starts enabled; defaults true." }), ), /** - * When true (the default), the scheduled task fires into the calling thread - * on each run instead of launching a fresh thread. This is the recurring - * "wake up in this thread" behaviour reserved for agent-created tasks. + * When true, the scheduled task fires into the calling thread on each run + * instead of launching a fresh thread. This is the recurring "wake up in this + * thread" behaviour reserved for agent-created tasks. */ bindToCurrentThread: Schema.optional( Schema.Boolean.annotate({ description: - "True (default) posts each run into this thread; false creates a fresh top-level thread per run.", + "In this thread's project, true (the default) posts each run into this thread and false creates a fresh top-level thread per run. In another project, or without a calling thread, each run launches a fresh thread and true is rejected.", }), ), clientRequestId: Schema.optional(OrchestratorMcpClientRequestId), diff --git a/packages/provider-core/src/server/orchestrationInstructions.test.ts b/packages/provider-core/src/server/orchestrationInstructions.test.ts index 4dd04c8ee8cd..654798fe6d25 100644 --- a/packages/provider-core/src/server/orchestrationInstructions.test.ts +++ b/packages/provider-core/src/server/orchestrationInstructions.test.ts @@ -26,6 +26,13 @@ describe("T3 orchestration provider instructions", () => { assert.include(T3_CODE_ORCHESTRATION_INSTRUCTIONS, "bindToCurrentThread=false"); }); + it("says scheduled runs return to this thread by default only in its own project", () => { + assert.include( + T3_CODE_ORCHESTRATION_INSTRUCTIONS, + "A task scheduled into another project launches a fresh thread for every run.", + ); + }); + it("injects prompt fallback only for an MCP-enabled first run", () => { const prompt = "Inspect the repository."; const injected = t3OrchestrationPromptForFirstRun({ diff --git a/packages/provider-core/src/server/orchestrationInstructions.ts b/packages/provider-core/src/server/orchestrationInstructions.ts index 2280d4d25619..8dbb8ece9644 100644 --- a/packages/provider-core/src/server/orchestrationInstructions.ts +++ b/packages/provider-core/src/server/orchestrationInstructions.ts @@ -9,7 +9,7 @@ The \`t3-code\` MCP server provides app-owned orchestration. Treat these concept - A delegated task/subagent is child work owned by the current thread. Use \`orchestrator_capabilities\` to discover the current provider/model IDs from the same live catalog as the composer, including configured custom models. Do not treat a native tool's model list as the full list of available subagent models. Prefer native subagent tools for same-provider work only when they support the chosen model. Use \`delegate_task\` with that provider instance and model when native tools cannot, including for same-provider work. Also use \`delegate_task\` for cross-provider or explicitly T3-owned child tasks. Retain each returned \`taskId\`, and use \`task_status\` or \`task_cancel\` to manage it. The returned \`childThreadId\` is backing storage for the subagent, not the target for starting another delegated review round. - \`t3_thread_launch\` and \`create_threads\` create ordinary top-level T3 conversations. Use them only when the user explicitly asks for separate/new/top-level threads or conversations. Never use them merely because the user said "subagent" or requested parallel delegated work. - For every T3 delegated review round, call \`delegate_task\` again. Include the original brief, prior findings, responses, and unresolved objections in each new task prompt. Track each round by its own \`taskId\`. Use a distinct \`clientRequestId\` per round, stable across retries of that round. Do not use \`t3_thread_send\` on \`childThreadId\` to continue a delegated review. -- \`schedule_task\` creates persistent recurring work in the app scheduler. Pass \`schedule\` as a structured object, never as JSON text: \`{"type":"interval","everyMs":3600000}\` for an interval, or \`{"type":"fixed_time","timeOfDay":"09:00","weekdays":[1,2,3,4,5]}\` for a wall-clock schedule, or \`{"type":"webhook"}\` to run on each request to the returned \`webhookUrl\` (the run sees the request only through \`{{body.path}}\`-style placeholders in the prompt). By default runs return to the current thread, which suits orchestrating: each trigger arrives here and you delegate or dedupe; set \`bindToCurrentThread=false\` only when the user wants a fresh thread for every run. After scheduling a timer, report the returned cadence and next run time; for a webhook, report its \`webhookUrl\`, or say T3 Connect remote access is needed if it is missing. +- \`schedule_task\` creates persistent recurring work in the app scheduler. Pass \`schedule\` as a structured object, never as JSON text: \`{"type":"interval","everyMs":3600000}\` for an interval, or \`{"type":"fixed_time","timeOfDay":"09:00","weekdays":[1,2,3,4,5]}\` for a wall-clock schedule, or \`{"type":"webhook"}\` to run on each request to the returned \`webhookUrl\` (the run sees the request only through \`{{body.path}}\`-style placeholders in the prompt). In this thread's project, runs return to the current thread by default, which suits orchestrating: each trigger arrives here and you delegate or dedupe; set \`bindToCurrentThread=false\` only when the user wants a fresh thread for every run. A task scheduled into another project launches a fresh thread for every run. After scheduling a timer, report the returned cadence and next run time; for a webhook, report its \`webhookUrl\`, or say T3 Connect remote access is needed if it is missing. - When you need a secret from the user (a token, API key, or webhook signing secret), call \`request_secret\` so they enter it privately, then pass the returned \`secretRef\` to the tool that needs it, e.g. \`signature.secretRef\` on a webhook task for a sender that signs requests such as GitHub. A \`secretRef\` works once. Never ask for a secret in chat, never invent one, and never repeat one. - To mention another thread to the user, link it as \`[title](t3-thread://v1/)\` with its exact \`threadId\`, not URL-encoded. T3 Code opens the thread in the app and shows its current title.