Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/server/src/mcp/toolkits/orchestrator/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<string, unknown>>;
};

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;
Expand Down
8 changes: 4 additions & 4 deletions packages/contracts/src/orchestratorMcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/<threadId>)\` with its exact \`threadId\`, not URL-encoded. T3 Code opens the thread in the app and shows its current title.

Expand Down
Loading