Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"t3": {
"repo": "pingdotgg/t3code",
"commit": "57b3780770a829ff81e62d8dc658683bf51d5155",
"commit": "8d199b4f7b918079161d5f43e0146c099301be08",
"paths": [
"apps/server/src/mcp/toolkits/attachment/tools.ts",
"apps/server/src/mcp/toolkits/device/tools.ts",
Expand Down
17 changes: 16 additions & 1 deletion t3/contract/apps/server/src/mcp/toolkits/orchestrator/tools.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
OrchestratorMcpCapabilitiesResult,
OrchestrationV2StopWorkflowInput,
OrchestratorMcpCreateThreadsInput,
OrchestratorMcpCreateThreadsResult,
OrchestratorMcpDelegateTaskInput,
Expand Down Expand Up @@ -31,6 +32,7 @@ import {
ThreadMetadataMcpUpdateResult,
} from "@t3tools/contracts";
import { Tool, Toolkit } from "effect/ai";
import * as Schema from "effect/Schema";

import * as ThreadManagementService from "../../../orchestration-v2/ThreadManagementService.ts";
import * as McpInvocationContext from "../../McpInvocationContext.ts";
Expand Down Expand Up @@ -63,7 +65,7 @@ const OrchestratorCapabilitiesTool = Tool.make("orchestrator_capabilities", {

export const DelegateTaskTool = Tool.make("delegate_task", {
description:
"Needs an agent running inside a T3 thread. Delegate one task to a T3-owned child agent/subagent of THIS thread and run it with only the supplied task prompt, without copying parent conversation history. Choose providers and models from orchestrator_capabilities, which uses the same live catalog as the composer. Prefer native subagent tools for same-provider work only when they support the chosen model. Use this for any model missing from the native tool, including same-provider work, for cross-provider work, or for explicitly T3-owned child tasks. For every T3 delegated review round, call delegate_task again with the original brief, prior findings, responses, and unresolved objections in the task prompt. Track each round by its own taskId and use a distinct clientRequestId per round, stable across retries of that round. The childThreadId is backing storage, not the target for starting another delegated review round through t3_thread_send. Provider, model, model options (see orchestrator_capabilities), runtime mode, and interaction mode inherit unless target overrides them. Prefer mode='async' for long work; mode='wait' blocks until completion or timeout. timeoutMs on mode=wait is only the parent's wait budget and does not cancel the child. waitTimedOut on that wait call means the timeout fired; keep that taskId and read status on later task_status. An async child's completion wakes this thread through a notification, steered into active turns where supported or queued otherwise, so end the turn instead of polling or spawning watchers; use task_status only when the result is needed mid-turn.",
"Needs an agent running inside a T3 thread. Delegate one task to a T3-owned child agent/subagent of THIS thread and run it with only the supplied task prompt, without copying parent conversation history. Choose providers and models from orchestrator_capabilities, which uses the same live catalog as the composer. Prefer native subagent tools for same-provider work only when they support the chosen model. Use this for any model missing from the native tool, including same-provider work, for cross-provider work, or for explicitly T3-owned child tasks. For every T3 delegated review round, call delegate_task again with the original brief, prior findings, responses, and unresolved objections in the task prompt. Track each round by its own taskId and use a distinct clientRequestId per round, stable across retries of that round. The childThreadId is backing storage, not the target for starting another delegated review round through t3_thread_send. Provider, model, model options (see orchestrator_capabilities), runtime mode, and interaction mode inherit unless target overrides them. A requested runtimeMode must be one the target provider lists in orchestrator_capabilities runtimeModes; an inherited one steps down to the broadest mode it offers. Prefer mode='async' for long work; mode='wait' blocks until completion or timeout. timeoutMs on mode=wait is only the parent's wait budget and does not cancel the child. waitTimedOut on that wait call means the timeout fired; keep that taskId and read status on later task_status. An async child's completion wakes this thread through a notification, steered into active turns where supported or queued otherwise, so end the turn instead of polling or spawning watchers; use task_status only when the result is needed mid-turn.",
parameters: OrchestratorMcpDelegateTaskInput,
success: OrchestratorMcpDelegateTaskResult,
failure: OrchestratorMcpFailure,
Expand Down Expand Up @@ -100,6 +102,18 @@ const TaskCancelTool = Tool.make("task_cancel", {
.annotate(Tool.Title, "Cancel delegated task")
.annotate(Tool.Destructive, true);

const WorkflowStopTool = Tool.make("t3_workflow_stop", {
description:
"Stop one running Claude workflow in a T3 thread. Use the workflow coordinator's subagentId returned by t3_thread_read with view='activity'. Stops the whole workflow, including its phases and agents. Does not stop the parent thread or other workflows. Individual workflow phases and agents cannot be stopped separately.",
parameters: OrchestrationV2StopWorkflowInput,
success: Schema.Struct({}),
failure: OrchestratorMcpFailure,
failureMode: "return",
dependencies,
})
.annotate(Tool.Title, "Stop a workflow")
.annotate(Tool.Destructive, true);

export const ScheduleTaskTool = Tool.make("schedule_task", {
description:
"Create persistent work in the app scheduler that runs even when no turn is active. Pass schedule as a STRUCTURED OBJECT, never JSON text. Timers: {type:'interval', everyMs:3600000} is hourly; {type:'fixed_time', timeOfDay:'09:00', weekdays:[1,2,3,4,5]} is weekday mornings; report the returned nextRunAt. Webhooks: {type:'webhook'} runs once per request to a generated URL. The run sees the request ONLY through prompt placeholders: {{body.path}} (e.g. {{body.action}}, {{body.release.tag_name}}), {{headers.name}}, {{query.name}}, {{body}}, or {{request}} (method, headers with credentials redacted, and body). For a sender that signs requests, first call request_secret so the user enters the secret privately (never ask for it in chat or invent one), then set signature with the returned secretRef, e.g. GitHub: {type:'webhook', signature:{header:'x-hub-signature-256', encoding:'hex', prefix:'sha256=', secretRef}}. The result's webhookUrl is the public URL to give the user; if it is absent, this environment has no T3 Connect managed tunnel, so tell the user to enable T3 Connect remote access rather than sharing a path. Omit projectId for this thread's project. In this thread's project, runs post into THIS thread by default (bindToCurrentThread=true), which suits an orchestrator that sees every trigger, delegates work, and can dedupe against what is in flight; use false only when the user wants a fresh top-level thread per run. Elsewhere each run launches a fresh thread. Provider, model, and runtime settings inherit from this thread, or from the project default when there is no calling thread.",
Expand Down Expand Up @@ -261,6 +275,7 @@ export const OrchestratorToolkit = Toolkit.make(
DelegateTaskTool,
TaskStatusTool,
TaskCancelTool,
WorkflowStopTool,
ScheduleTaskTool,
ListScheduledTasksTool,
UpdateScheduledTaskTool,
Expand Down
3 changes: 3 additions & 0 deletions t3/contract/packages/contracts/src/orchestratorMcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ export const OrchestratorMcpThreadTimelineItem = Schema.Struct({
visibility: Schema.Literals(["local", "inherited", "synthetic"]),
sourceThreadId: ThreadId,
itemId: TurnItemId,
subagentId: Schema.optional(NodeId),
runId: Schema.NullOr(RunId),
messageId: Schema.NullOr(MessageId),
createdBy: Schema.NullOr(OrchestrationV2Actor),
Expand Down Expand Up @@ -487,6 +488,8 @@ export const OrchestratorMcpProviderCapability = Schema.Struct({
options: Schema.optional(Schema.Array(ProviderOptionDescriptor)),
}),
),
/** Runtime modes the provider offers; omitted when it offers all of them. */
runtimeModes: Schema.optional(Schema.Array(RuntimeMode)),
canRunChildTask: Schema.Boolean,
canRunCrossProviderChildTask: Schema.Boolean,
constraints: Schema.Array(Schema.String),
Expand Down
Loading