From f62ac0ba76eb6c6db9b2ea8ba2dd9c1839d1a6cd Mon Sep 17 00:00:00 2001 From: "Nick T." <73005126+tiliakoos@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:55:33 -0400 Subject: [PATCH] fix(server): schedule_task no longer tells agents every run returns to their thread Since #15219 an omitted bindToCurrentThread binds only in the calling thread's own project; the field description and the orchestration instructions now say so. --- .../src/mcp/toolkits/orchestrator/tools.test.ts | 11 +++++++++++ .../src/provider/T3OrchestrationInstructions.test.ts | 7 +++++++ .../src/provider/T3OrchestrationInstructions.ts | 2 +- packages/contracts/src/orchestratorMcp.ts | 8 ++++---- 4 files changed, 23 insertions(+), 5 deletions(-) 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/apps/server/src/provider/T3OrchestrationInstructions.test.ts b/apps/server/src/provider/T3OrchestrationInstructions.test.ts index e625e40c1125..27a902068ded 100644 --- a/apps/server/src/provider/T3OrchestrationInstructions.test.ts +++ b/apps/server/src/provider/T3OrchestrationInstructions.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/apps/server/src/provider/T3OrchestrationInstructions.ts b/apps/server/src/provider/T3OrchestrationInstructions.ts index fe700c0e2084..5611a361cd30 100644 --- a/apps/server/src/provider/T3OrchestrationInstructions.ts +++ b/apps/server/src/provider/T3OrchestrationInstructions.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. ### Choose the workspace before starting a new thread diff --git a/packages/contracts/src/orchestratorMcp.ts b/packages/contracts/src/orchestratorMcp.ts index d13cf419f19f..33eab78b3cf5 100644 --- a/packages/contracts/src/orchestratorMcp.ts +++ b/packages/contracts/src/orchestratorMcp.ts @@ -528,14 +528,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),